diff --git a/spug_api/apps/deploy/utils.py b/spug_api/apps/deploy/utils.py index 8999dbfc..012aa9ad 100644 --- a/spug_api/apps/deploy/utils.py +++ b/spug_api/apps/deploy/utils.py @@ -236,15 +236,15 @@ def parse_filter_rule(self, data: str): return files def send_info(self, key, message): - self.rds.rpush(self.token, json.dumps({'key': key, 'status': 'info', 'data': message})) + self.rds.lpush(self.token, json.dumps({'key': key, 'status': 'info', 'data': message})) def send_error(self, key, message): message = '\r\n' + message - self.rds.rpush(self.token, json.dumps({'key': key, 'status': 'error', 'data': message})) + self.rds.lpush(self.token, json.dumps({'key': key, 'status': 'error', 'data': message})) raise Exception(message) def send_step(self, key, step, data): - self.rds.rpush(self.token, json.dumps({'key': key, 'step': step, 'data': data})) + self.rds.lpush(self.token, json.dumps({'key': key, 'step': step, 'data': data})) def local(self, command, env=None): command = 'set -e\n' + command diff --git a/spug_api/apps/deploy/views.py b/spug_api/apps/deploy/views.py index 02164e43..01a38751 100644 --- a/spug_api/apps/deploy/views.py +++ b/spug_api/apps/deploy/views.py @@ -3,6 +3,8 @@ # Released under the MIT License. from django.views.generic import View from django.db.models import F +from django.conf import settings +from django_redis import get_redis_connection from libs import json_response, JsonParser, Argument, human_datetime, human_time from apps.deploy.models import DeployRequest from apps.app.models import Deploy @@ -112,10 +114,17 @@ def get(self, request, r_id): return json_response(error='未找到指定发布申请') hosts = Host.objects.filter(id__in=json.loads(req.host_ids)) targets = [{'id': x.id, 'title': f'{x.name}({x.hostname}:{x.port})'} for x in hosts] - server_actions, host_actions = [], [] + server_actions, host_actions, outputs = [], [], [] if req.deploy.extend == '2': server_actions = json.loads(req.deploy.extend_obj.server_actions) host_actions = json.loads(req.deploy.extend_obj.host_actions) + if request.GET.get('log'): + rds, key, counter = get_redis_connection(), f'{settings.REQUEST_KEY}:{r_id}', 0 + data = rds.lrange(key, counter, counter + 9) + while data: + counter += 10 + outputs.extend(x.decode() for x in data) + data = rds.lrange(key, counter, counter + 9) return json_response({ 'app_name': req.deploy.app.name, 'env_name': req.deploy.env.name, @@ -124,7 +133,8 @@ def get(self, request, r_id): 'status_alias': req.get_status_display(), 'targets': targets, 'server_actions': server_actions, - 'host_actions': host_actions + 'host_actions': host_actions, + 'outputs': outputs }) def post(self, request, r_id): diff --git a/spug_api/apps/monitor/scheduler.py b/spug_api/apps/monitor/scheduler.py index d9023009..3572074b 100644 --- a/spug_api/apps/monitor/scheduler.py +++ b/spug_api/apps/monitor/scheduler.py @@ -109,7 +109,7 @@ def run(self): rds_cli.delete(settings.MONITOR_KEY) logger.info('Running monitor') while True: - _, data = rds_cli.blpop(settings.MONITOR_KEY) + _, data = rds_cli.brpop(settings.MONITOR_KEY) task = AttrDict(json.loads(data)) if task.action in ('add', 'modify'): trigger = IntervalTrigger(minutes=int(task.rate), timezone=self.timezone) diff --git a/spug_api/apps/monitor/views.py b/spug_api/apps/monitor/views.py index f53ef824..f1196204 100644 --- a/spug_api/apps/monitor/views.py +++ b/spug_api/apps/monitor/views.py @@ -40,13 +40,13 @@ def post(self, request): if task and task.is_active: form.action = 'modify' rds_cli = get_redis_connection() - rds_cli.rpush(settings.MONITOR_KEY, json.dumps(form)) + rds_cli.lpush(settings.MONITOR_KEY, json.dumps(form)) else: dtt = Detection.objects.create(created_by=request.user, **form) form.action = 'add' form.id = dtt.id rds_cli = get_redis_connection() - rds_cli.rpush(settings.MONITOR_KEY, json.dumps(form)) + rds_cli.lpush(settings.MONITOR_KEY, json.dumps(form)) return json_response(error=error) def patch(self, request): @@ -64,7 +64,7 @@ def patch(self, request): else: message = {'id': form.id, 'action': 'remove'} rds_cli = get_redis_connection() - rds_cli.rpush(settings.MONITOR_KEY, json.dumps(message)) + rds_cli.lpush(settings.MONITOR_KEY, json.dumps(message)) return json_response(error=error) def delete(self, request): diff --git a/spug_api/apps/schedule/scheduler.py b/spug_api/apps/schedule/scheduler.py index 6e94487a..9ad9026f 100644 --- a/spug_api/apps/schedule/scheduler.py +++ b/spug_api/apps/schedule/scheduler.py @@ -87,7 +87,7 @@ def run(self): rds_cli.delete(settings.SCHEDULE_KEY) logger.info('Running scheduler') while True: - _, data = rds_cli.blpop(settings.SCHEDULE_KEY) + _, data = rds_cli.brpop(settings.SCHEDULE_KEY) task = AttrDict(json.loads(data)) if task.action in ('add', 'modify'): trigger = self.parse_trigger(task.trigger, task.trigger_args) diff --git a/spug_api/apps/schedule/views.py b/spug_api/apps/schedule/views.py index 05dd52df..03c802ce 100644 --- a/spug_api/apps/schedule/views.py +++ b/spug_api/apps/schedule/views.py @@ -40,7 +40,7 @@ def post(self, request): form.action = 'modify' form.targets = json.loads(form.targets) rds_cli = get_redis_connection() - rds_cli.rpush(settings.SCHEDULE_KEY, json.dumps(form)) + rds_cli.lpush(settings.SCHEDULE_KEY, json.dumps(form)) else: Task.objects.create(created_by=request.user, **form) return json_response(error=error) @@ -60,7 +60,7 @@ def patch(self, request): else: message = {'id': form.id, 'action': 'remove'} rds_cli = get_redis_connection() - rds_cli.rpush(settings.SCHEDULE_KEY, json.dumps(message)) + rds_cli.lpush(settings.SCHEDULE_KEY, json.dumps(message)) return json_response(error=error) def delete(self, request): diff --git a/spug_api/consumer/consumers.py b/spug_api/consumer/consumers.py index 5c68bc64..01c69c54 100644 --- a/spug_api/consumer/consumers.py +++ b/spug_api/consumer/consumers.py @@ -3,17 +3,24 @@ # Released under the MIT License. from channels.generic.websocket import WebsocketConsumer from django_redis import get_redis_connection +from django.conf import settings from apps.setting.utils import AppSetting from apps.host.models import Host from threading import Thread +from urllib.parse import parse_qs import json class ExecConsumer(WebsocketConsumer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + query = parse_qs(self.scope['query_string'].decode()) + e_id = query.get('id', [None])[0] self.token = self.scope['url_route']['kwargs']['token'] + self.log_key = f'{settings.REQUEST_KEY}:{e_id}' if e_id else None self.rds = get_redis_connection() + if self.log_key: + self.rds.delete(self.log_key) def connect(self): self.accept() @@ -21,11 +28,18 @@ def connect(self): def disconnect(self, code): self.rds.close() + def get_response(self): + if self.log_key: + return self.rds.brpoplpush(self.token, self.log_key, timeout=5) + else: + return self.rds.brpop(self.token, timeout=5)[1] + def receive(self, **kwargs): - response = self.rds.blpop(self.token, timeout=5) + response = self.get_response() while response: - self.send(text_data=response[1].decode()) - response = self.rds.blpop(self.token, timeout=5) + data = response.decode() + self.send(text_data=data) + response = self.get_response() self.send(text_data='pong') diff --git a/spug_api/consumer/executors.py b/spug_api/consumer/executors.py index 68b06071..6a52f6bc 100644 --- a/spug_api/consumer/executors.py +++ b/spug_api/consumer/executors.py @@ -28,7 +28,7 @@ def __init__(self, hostname, port, username, pkey, command, token=None, **kwargs def _send(self, message, with_expire=False): if self.rds_cli is None: self.rds_cli = get_redis_connection() - self.rds_cli.rpush(self.token, json.dumps(message)) + self.rds_cli.lpush(self.token, json.dumps(message)) if with_expire: self.rds_cli.expire(self.token, 300) diff --git a/spug_api/spug/settings.py b/spug_api/spug/settings.py index 0570ba37..5a139546 100644 --- a/spug_api/spug/settings.py +++ b/spug_api/spug/settings.py @@ -100,6 +100,7 @@ SCHEDULE_KEY = 'spug:schedule' MONITOR_KEY = 'spug:monitor' +REQUEST_KEY = 'spug:request' REPOS_DIR = os.path.join(BASE_DIR, 'repos') # Internationalization diff --git a/spug_web/src/pages/deploy/do/Ext1Index.js b/spug_web/src/pages/deploy/do/Ext1Index.js index c2fcb41b..9d7b246e 100644 --- a/spug_web/src/pages/deploy/do/Ext1Index.js +++ b/spug_web/src/pages/deploy/do/Ext1Index.js @@ -26,8 +26,19 @@ class Ext1Index extends React.Component { componentDidMount() { this.id = this.props.match.params.id; - http.get(`/api/deploy/request/${this.id}/`) - .then(res => store.request = res) + this.log = this.props.match.params.log; + http.get(`/api/deploy/request/${this.id}/`, {params: {log: this.log}}) + .then(res => { + store.request = res; + while (res.outputs.length) { + const msg = JSON.parse(res.outputs.pop()); + if (!store.outputs.hasOwnProperty(msg.key)) { + const data = msg.key === 'local' ? '读取数据... ' : ''; + store.outputs[msg.key] = {data} + } + this._parse_message(msg) + } + }) .finally(() => this.setState({fetching: false})) } @@ -37,6 +48,13 @@ class Ext1Index extends React.Component { store.outputs = {}; } + _parse_message = (message) => { + const {key, data, step, status} = message; + if (data !== undefined) store.outputs[key]['data'] += data; + if (step !== undefined) store.outputs[key]['step'] = step; + if (status !== undefined) store.outputs[key]['status'] = status; + }; + handleDeploy = () => { this.setState({loading: true}); http.post(`/api/deploy/request/${this.id}/`) @@ -44,7 +62,7 @@ class Ext1Index extends React.Component { store.request.status = '2'; store.outputs = outputs; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - this.socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${token}/`); + this.socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${token}/?id=${this.id}`); this.socket.onopen = () => { this.socket.send('ok'); }; @@ -52,10 +70,7 @@ class Ext1Index extends React.Component { if (e.data === 'pong') { this.socket.send('ping') } else { - const {key, data, step, status} = JSON.parse(e.data); - if (data !== undefined) store.outputs[key]['data'] += data; - if (step !== undefined) store.outputs[key]['step'] = step; - if (status !== undefined) store.outputs[key]['status'] = status; + this._parse_message(JSON.parse(e.data)) } } }) @@ -100,7 +115,8 @@ class Ext1Index extends React.Component { subTitle={`${app_name} - ${env_name}`} style={{padding: 0}} tags={this.getStatusAlias()} - extra={} onBack={() => history.goBack()}/> diff --git a/spug_web/src/pages/deploy/do/Ext2Index.js b/spug_web/src/pages/deploy/do/Ext2Index.js index db3068ef..dae5a9f3 100644 --- a/spug_web/src/pages/deploy/do/Ext2Index.js +++ b/spug_web/src/pages/deploy/do/Ext2Index.js @@ -26,8 +26,19 @@ class Ext1Index extends React.Component { componentDidMount() { this.id = this.props.match.params.id; - http.get(`/api/deploy/request/${this.id}/`) - .then(res => store.request = res) + this.log = this.props.match.params.log; + http.get(`/api/deploy/request/${this.id}/`, {params: {log: this.log}}) + .then(res => { + store.request = res; + while (res.outputs.length) { + const msg = JSON.parse(res.outputs.pop()); + if (!store.outputs.hasOwnProperty(msg.key)) { + const data = msg.key === 'local' ? '读取数据... ' : ''; + store.outputs[msg.key] = {data} + } + this._parse_message(msg) + } + }) .finally(() => this.setState({fetching: false})) } @@ -37,6 +48,13 @@ class Ext1Index extends React.Component { store.outputs = {}; } + _parse_message = (message) => { + const {key, data, step, status} = message; + if (data !== undefined) store.outputs[key]['data'] += data; + if (step !== undefined) store.outputs[key]['step'] = step; + if (status !== undefined) store.outputs[key]['status'] = status; + }; + handleDeploy = () => { this.setState({loading: true}); http.post(`/api/deploy/request/${this.id}/`) @@ -44,7 +62,7 @@ class Ext1Index extends React.Component { store.request.status = '2'; store.outputs = outputs; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - this.socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${token}/`); + this.socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${token}/?id=${this.id}`); this.socket.onopen = () => { this.socket.send('ok'); }; @@ -52,10 +70,7 @@ class Ext1Index extends React.Component { if (e.data === 'pong') { this.socket.send('ping') } else { - const {key, data, step, status} = JSON.parse(e.data); - if (data !== undefined) store.outputs[key]['data'] += data; - if (step !== undefined) store.outputs[key]['step'] = step; - if (status !== undefined) store.outputs[key]['status'] = status; + this._parse_message(JSON.parse(e.data)) } } }) @@ -100,7 +115,7 @@ class Ext1Index extends React.Component { subTitle={`${app_name} - ${env_name}`} style={{padding: 0}} tags={this.getStatusAlias()} - extra={} onBack={() => history.goBack()}/> diff --git a/spug_web/src/pages/deploy/request/Table.js b/spug_web/src/pages/deploy/request/Table.js index 34379bda..9e7430a4 100644 --- a/spug_web/src/pages/deploy/request/Table.js +++ b/spug_web/src/pages/deploy/request/Table.js @@ -85,6 +85,8 @@ class ComTable extends React.Component { switch (info.status) { case '-3': return + 查看 + 发布 this.handleRollback(info)}>回滚 ; case '3': - return this.handleRollback(info)}>回滚; + return + 查看 + + this.handleRollback(info)}>回滚 + ; case '-1': return store.showForm(info)}>编辑 diff --git a/spug_web/src/pages/deploy/routes.js b/spug_web/src/pages/deploy/routes.js index d1c8fadc..f9cb6172 100644 --- a/spug_web/src/pages/deploy/routes.js +++ b/spug_web/src/pages/deploy/routes.js @@ -15,4 +15,6 @@ export default [ makeRoute('/request', request), makeRoute('/do/ext1/:id', doExt1Index), makeRoute('/do/ext2/:id', doExt2Index), + makeRoute('/do/ext1/:id/:log', doExt1Index), + makeRoute('/do/ext2/:id/:log', doExt2Index), ]