mirror of https://github.com/openspug/spug
parent
17dfe2618d
commit
9e6d3b3010
|
@ -236,15 +236,15 @@ class Helper:
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def send_info(self, key, message):
|
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):
|
def send_error(self, key, message):
|
||||||
message = '\r\n' + 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)
|
raise Exception(message)
|
||||||
|
|
||||||
def send_step(self, key, step, data):
|
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):
|
def local(self, command, env=None):
|
||||||
command = 'set -e\n' + command
|
command = 'set -e\n' + command
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
# Released under the MIT License.
|
# Released under the MIT License.
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.db.models import F
|
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 libs import json_response, JsonParser, Argument, human_datetime, human_time
|
||||||
from apps.deploy.models import DeployRequest
|
from apps.deploy.models import DeployRequest
|
||||||
from apps.app.models import Deploy
|
from apps.app.models import Deploy
|
||||||
|
@ -112,10 +114,17 @@ class RequestDetailView(View):
|
||||||
return json_response(error='未找到指定发布申请')
|
return json_response(error='未找到指定发布申请')
|
||||||
hosts = Host.objects.filter(id__in=json.loads(req.host_ids))
|
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]
|
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':
|
if req.deploy.extend == '2':
|
||||||
server_actions = json.loads(req.deploy.extend_obj.server_actions)
|
server_actions = json.loads(req.deploy.extend_obj.server_actions)
|
||||||
host_actions = json.loads(req.deploy.extend_obj.host_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({
|
return json_response({
|
||||||
'app_name': req.deploy.app.name,
|
'app_name': req.deploy.app.name,
|
||||||
'env_name': req.deploy.env.name,
|
'env_name': req.deploy.env.name,
|
||||||
|
@ -124,7 +133,8 @@ class RequestDetailView(View):
|
||||||
'status_alias': req.get_status_display(),
|
'status_alias': req.get_status_display(),
|
||||||
'targets': targets,
|
'targets': targets,
|
||||||
'server_actions': server_actions,
|
'server_actions': server_actions,
|
||||||
'host_actions': host_actions
|
'host_actions': host_actions,
|
||||||
|
'outputs': outputs
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request, r_id):
|
def post(self, request, r_id):
|
||||||
|
|
|
@ -109,7 +109,7 @@ class Scheduler:
|
||||||
rds_cli.delete(settings.MONITOR_KEY)
|
rds_cli.delete(settings.MONITOR_KEY)
|
||||||
logger.info('Running monitor')
|
logger.info('Running monitor')
|
||||||
while True:
|
while True:
|
||||||
_, data = rds_cli.blpop(settings.MONITOR_KEY)
|
_, data = rds_cli.brpop(settings.MONITOR_KEY)
|
||||||
task = AttrDict(json.loads(data))
|
task = AttrDict(json.loads(data))
|
||||||
if task.action in ('add', 'modify'):
|
if task.action in ('add', 'modify'):
|
||||||
trigger = IntervalTrigger(minutes=int(task.rate), timezone=self.timezone)
|
trigger = IntervalTrigger(minutes=int(task.rate), timezone=self.timezone)
|
||||||
|
|
|
@ -40,13 +40,13 @@ class DetectionView(View):
|
||||||
if task and task.is_active:
|
if task and task.is_active:
|
||||||
form.action = 'modify'
|
form.action = 'modify'
|
||||||
rds_cli = get_redis_connection()
|
rds_cli = get_redis_connection()
|
||||||
rds_cli.rpush(settings.MONITOR_KEY, json.dumps(form))
|
rds_cli.lpush(settings.MONITOR_KEY, json.dumps(form))
|
||||||
else:
|
else:
|
||||||
dtt = Detection.objects.create(created_by=request.user, **form)
|
dtt = Detection.objects.create(created_by=request.user, **form)
|
||||||
form.action = 'add'
|
form.action = 'add'
|
||||||
form.id = dtt.id
|
form.id = dtt.id
|
||||||
rds_cli = get_redis_connection()
|
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)
|
return json_response(error=error)
|
||||||
|
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
|
@ -64,7 +64,7 @@ class DetectionView(View):
|
||||||
else:
|
else:
|
||||||
message = {'id': form.id, 'action': 'remove'}
|
message = {'id': form.id, 'action': 'remove'}
|
||||||
rds_cli = get_redis_connection()
|
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)
|
return json_response(error=error)
|
||||||
|
|
||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
|
|
|
@ -87,7 +87,7 @@ class Scheduler:
|
||||||
rds_cli.delete(settings.SCHEDULE_KEY)
|
rds_cli.delete(settings.SCHEDULE_KEY)
|
||||||
logger.info('Running scheduler')
|
logger.info('Running scheduler')
|
||||||
while True:
|
while True:
|
||||||
_, data = rds_cli.blpop(settings.SCHEDULE_KEY)
|
_, data = rds_cli.brpop(settings.SCHEDULE_KEY)
|
||||||
task = AttrDict(json.loads(data))
|
task = AttrDict(json.loads(data))
|
||||||
if task.action in ('add', 'modify'):
|
if task.action in ('add', 'modify'):
|
||||||
trigger = self.parse_trigger(task.trigger, task.trigger_args)
|
trigger = self.parse_trigger(task.trigger, task.trigger_args)
|
||||||
|
|
|
@ -40,7 +40,7 @@ class Schedule(View):
|
||||||
form.action = 'modify'
|
form.action = 'modify'
|
||||||
form.targets = json.loads(form.targets)
|
form.targets = json.loads(form.targets)
|
||||||
rds_cli = get_redis_connection()
|
rds_cli = get_redis_connection()
|
||||||
rds_cli.rpush(settings.SCHEDULE_KEY, json.dumps(form))
|
rds_cli.lpush(settings.SCHEDULE_KEY, json.dumps(form))
|
||||||
else:
|
else:
|
||||||
Task.objects.create(created_by=request.user, **form)
|
Task.objects.create(created_by=request.user, **form)
|
||||||
return json_response(error=error)
|
return json_response(error=error)
|
||||||
|
@ -60,7 +60,7 @@ class Schedule(View):
|
||||||
else:
|
else:
|
||||||
message = {'id': form.id, 'action': 'remove'}
|
message = {'id': form.id, 'action': 'remove'}
|
||||||
rds_cli = get_redis_connection()
|
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)
|
return json_response(error=error)
|
||||||
|
|
||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
|
|
|
@ -3,17 +3,24 @@
|
||||||
# Released under the MIT License.
|
# Released under the MIT License.
|
||||||
from channels.generic.websocket import WebsocketConsumer
|
from channels.generic.websocket import WebsocketConsumer
|
||||||
from django_redis import get_redis_connection
|
from django_redis import get_redis_connection
|
||||||
|
from django.conf import settings
|
||||||
from apps.setting.utils import AppSetting
|
from apps.setting.utils import AppSetting
|
||||||
from apps.host.models import Host
|
from apps.host.models import Host
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from urllib.parse import parse_qs
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class ExecConsumer(WebsocketConsumer):
|
class ExecConsumer(WebsocketConsumer):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*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.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()
|
self.rds = get_redis_connection()
|
||||||
|
if self.log_key:
|
||||||
|
self.rds.delete(self.log_key)
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
self.accept()
|
self.accept()
|
||||||
|
@ -21,11 +28,18 @@ class ExecConsumer(WebsocketConsumer):
|
||||||
def disconnect(self, code):
|
def disconnect(self, code):
|
||||||
self.rds.close()
|
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):
|
def receive(self, **kwargs):
|
||||||
response = self.rds.blpop(self.token, timeout=5)
|
response = self.get_response()
|
||||||
while response:
|
while response:
|
||||||
self.send(text_data=response[1].decode())
|
data = response.decode()
|
||||||
response = self.rds.blpop(self.token, timeout=5)
|
self.send(text_data=data)
|
||||||
|
response = self.get_response()
|
||||||
self.send(text_data='pong')
|
self.send(text_data='pong')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Job:
|
||||||
def _send(self, message, with_expire=False):
|
def _send(self, message, with_expire=False):
|
||||||
if self.rds_cli is None:
|
if self.rds_cli is None:
|
||||||
self.rds_cli = get_redis_connection()
|
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:
|
if with_expire:
|
||||||
self.rds_cli.expire(self.token, 300)
|
self.rds_cli.expire(self.token, 300)
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ TEMPLATES = [
|
||||||
|
|
||||||
SCHEDULE_KEY = 'spug:schedule'
|
SCHEDULE_KEY = 'spug:schedule'
|
||||||
MONITOR_KEY = 'spug:monitor'
|
MONITOR_KEY = 'spug:monitor'
|
||||||
|
REQUEST_KEY = 'spug:request'
|
||||||
REPOS_DIR = os.path.join(BASE_DIR, 'repos')
|
REPOS_DIR = os.path.join(BASE_DIR, 'repos')
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
|
|
|
@ -26,8 +26,19 @@ class Ext1Index extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.id = this.props.match.params.id;
|
this.id = this.props.match.params.id;
|
||||||
http.get(`/api/deploy/request/${this.id}/`)
|
this.log = this.props.match.params.log;
|
||||||
.then(res => store.request = res)
|
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}))
|
.finally(() => this.setState({fetching: false}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +48,13 @@ class Ext1Index extends React.Component {
|
||||||
store.outputs = {};
|
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 = () => {
|
handleDeploy = () => {
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
http.post(`/api/deploy/request/${this.id}/`)
|
http.post(`/api/deploy/request/${this.id}/`)
|
||||||
|
@ -44,7 +62,7 @@ class Ext1Index extends React.Component {
|
||||||
store.request.status = '2';
|
store.request.status = '2';
|
||||||
store.outputs = outputs;
|
store.outputs = outputs;
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
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.onopen = () => {
|
||||||
this.socket.send('ok');
|
this.socket.send('ok');
|
||||||
};
|
};
|
||||||
|
@ -52,10 +70,7 @@ class Ext1Index extends React.Component {
|
||||||
if (e.data === 'pong') {
|
if (e.data === 'pong') {
|
||||||
this.socket.send('ping')
|
this.socket.send('ping')
|
||||||
} else {
|
} else {
|
||||||
const {key, data, step, status} = JSON.parse(e.data);
|
this._parse_message(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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -100,7 +115,8 @@ class Ext1Index extends React.Component {
|
||||||
subTitle={`${app_name} - ${env_name}`}
|
subTitle={`${app_name} - ${env_name}`}
|
||||||
style={{padding: 0}}
|
style={{padding: 0}}
|
||||||
tags={this.getStatusAlias()}
|
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>}
|
onClick={this.handleDeploy}>发布</Button>}
|
||||||
onBack={() => history.goBack()}/>
|
onBack={() => history.goBack()}/>
|
||||||
<Collapse defaultActiveKey={1} className={styles.collapse}>
|
<Collapse defaultActiveKey={1} className={styles.collapse}>
|
||||||
|
|
|
@ -26,8 +26,19 @@ class Ext1Index extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.id = this.props.match.params.id;
|
this.id = this.props.match.params.id;
|
||||||
http.get(`/api/deploy/request/${this.id}/`)
|
this.log = this.props.match.params.log;
|
||||||
.then(res => store.request = res)
|
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}))
|
.finally(() => this.setState({fetching: false}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +48,13 @@ class Ext1Index extends React.Component {
|
||||||
store.outputs = {};
|
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 = () => {
|
handleDeploy = () => {
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
http.post(`/api/deploy/request/${this.id}/`)
|
http.post(`/api/deploy/request/${this.id}/`)
|
||||||
|
@ -44,7 +62,7 @@ class Ext1Index extends React.Component {
|
||||||
store.request.status = '2';
|
store.request.status = '2';
|
||||||
store.outputs = outputs;
|
store.outputs = outputs;
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
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.onopen = () => {
|
||||||
this.socket.send('ok');
|
this.socket.send('ok');
|
||||||
};
|
};
|
||||||
|
@ -52,10 +70,7 @@ class Ext1Index extends React.Component {
|
||||||
if (e.data === 'pong') {
|
if (e.data === 'pong') {
|
||||||
this.socket.send('ping')
|
this.socket.send('ping')
|
||||||
} else {
|
} else {
|
||||||
const {key, data, step, status} = JSON.parse(e.data);
|
this._parse_message(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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -100,7 +115,7 @@ class Ext1Index extends React.Component {
|
||||||
subTitle={`${app_name} - ${env_name}`}
|
subTitle={`${app_name} - ${env_name}`}
|
||||||
style={{padding: 0}}
|
style={{padding: 0}}
|
||||||
tags={this.getStatusAlias()}
|
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>}
|
onClick={this.handleDeploy}>发布</Button>}
|
||||||
onBack={() => history.goBack()}/>
|
onBack={() => history.goBack()}/>
|
||||||
<Collapse defaultActiveKey={1} className={styles.collapse}>
|
<Collapse defaultActiveKey={1} className={styles.collapse}>
|
||||||
|
|
|
@ -85,6 +85,8 @@ class ComTable extends React.Component {
|
||||||
switch (info.status) {
|
switch (info.status) {
|
||||||
case '-3':
|
case '-3':
|
||||||
return <React.Fragment>
|
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>
|
<AuthLink auth="deploy.request.do" to={`/deploy/do/ext${info['app_extend']}/${info.id}`}>发布</AuthLink>
|
||||||
<Divider type="vertical"/>
|
<Divider type="vertical"/>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
|
@ -94,11 +96,15 @@ class ComTable extends React.Component {
|
||||||
onClick={() => this.handleRollback(info)}>回滚</LinkButton>
|
onClick={() => this.handleRollback(info)}>回滚</LinkButton>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
case '3':
|
case '3':
|
||||||
return <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"
|
auth="deploy.request.do"
|
||||||
disabled={info.type === '2'}
|
disabled={info.type === '2'}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
onClick={() => this.handleRollback(info)}>回滚</LinkButton>;
|
onClick={() => this.handleRollback(info)}>回滚</LinkButton>
|
||||||
|
</React.Fragment>;
|
||||||
case '-1':
|
case '-1':
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<LinkButton auth="deploy.request.edit" onClick={() => store.showForm(info)}>编辑</LinkButton>
|
<LinkButton auth="deploy.request.edit" onClick={() => store.showForm(info)}>编辑</LinkButton>
|
||||||
|
|
|
@ -15,4 +15,6 @@ export default [
|
||||||
makeRoute('/request', request),
|
makeRoute('/request', request),
|
||||||
makeRoute('/do/ext1/:id', doExt1Index),
|
makeRoute('/do/ext1/:id', doExt1Index),
|
||||||
makeRoute('/do/ext2/:id', doExt2Index),
|
makeRoute('/do/ext2/:id', doExt2Index),
|
||||||
|
makeRoute('/do/ext1/:id/:log', doExt1Index),
|
||||||
|
makeRoute('/do/ext2/:id/:log', doExt2Index),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue