mirror of https://github.com/openspug/spug
parent
17dfe2618d
commit
9e6d3b3010
|
@ -236,15 +236,15 @@ class Helper:
|
|||
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
|
||||
|
|
|
@ -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 @@ class RequestDetailView(View):
|
|||
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 @@ class RequestDetailView(View):
|
|||
'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):
|
||||
|
|
|
@ -109,7 +109,7 @@ class Scheduler:
|
|||
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)
|
||||
|
|
|
@ -40,13 +40,13 @@ class DetectionView(View):
|
|||
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 @@ class DetectionView(View):
|
|||
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):
|
||||
|
|
|
@ -87,7 +87,7 @@ class Scheduler:
|
|||
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)
|
||||
|
|
|
@ -40,7 +40,7 @@ class Schedule(View):
|
|||
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 @@ class Schedule(View):
|
|||
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):
|
||||
|
|
|
@ -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 @@ class ExecConsumer(WebsocketConsumer):
|
|||
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')
|
||||
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class Job:
|
|||
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)
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@ TEMPLATES = [
|
|||
|
||||
SCHEDULE_KEY = 'spug:schedule'
|
||||
MONITOR_KEY = 'spug:monitor'
|
||||
REQUEST_KEY = 'spug:request'
|
||||
REPOS_DIR = os.path.join(BASE_DIR, 'repos')
|
||||
|
||||
# Internationalization
|
||||
|
|
|
@ -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={<Button loading={this.state.loading} type="primary" disabled={!['1', '-3'].includes(status)}
|
||||
extra={<Button loading={this.state.loading} type="primary"
|
||||
disabled={this.log || !['1', '-3'].includes(status)}
|
||||
onClick={this.handleDeploy}>发布</Button>}
|
||||
onBack={() => history.goBack()}/>
|
||||
<Collapse defaultActiveKey={1} className={styles.collapse}>
|
||||
|
|
|
@ -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={<Button loading={this.state.loading} type="primary" disabled={!['1', '-3'].includes(status)}
|
||||
extra={<Button loading={this.state.loading} type="primary" disabled={this.log || !['1', '-3'].includes(status)}
|
||||
onClick={this.handleDeploy}>发布</Button>}
|
||||
onBack={() => history.goBack()}/>
|
||||
<Collapse defaultActiveKey={1} className={styles.collapse}>
|
||||
|
|
|
@ -85,6 +85,8 @@ class ComTable extends React.Component {
|
|||
switch (info.status) {
|
||||
case '-3':
|
||||
return <React.Fragment>
|
||||
<AuthLink auth="deploy.request.do" to={`/deploy/do/ext${info['app_extend']}/${info.id}/1`}>查看</AuthLink>
|
||||
<Divider type="vertical"/>
|
||||
<AuthLink auth="deploy.request.do" to={`/deploy/do/ext${info['app_extend']}/${info.id}`}>发布</AuthLink>
|
||||
<Divider type="vertical"/>
|
||||
<LinkButton
|
||||
|
@ -94,11 +96,15 @@ class ComTable extends React.Component {
|
|||
onClick={() => this.handleRollback(info)}>回滚</LinkButton>
|
||||
</React.Fragment>;
|
||||
case '3':
|
||||
return <LinkButton
|
||||
auth="deploy.request.do"
|
||||
disabled={info.type === '2'}
|
||||
loading={this.state.loading}
|
||||
onClick={() => this.handleRollback(info)}>回滚</LinkButton>;
|
||||
return <React.Fragment>
|
||||
<AuthLink auth="deploy.request.do" to={`/deploy/do/ext${info['app_extend']}/${info.id}/1`}>查看</AuthLink>
|
||||
<Divider type="vertical"/>
|
||||
<LinkButton
|
||||
auth="deploy.request.do"
|
||||
disabled={info.type === '2'}
|
||||
loading={this.state.loading}
|
||||
onClick={() => this.handleRollback(info)}>回滚</LinkButton>
|
||||
</React.Fragment>;
|
||||
case '-1':
|
||||
return <React.Fragment>
|
||||
<LinkButton auth="deploy.request.edit" onClick={() => store.showForm(info)}>编辑</LinkButton>
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue