mirror of https://github.com/openspug/spug
U 优化批量执行UX
parent
38b77c77bb
commit
ddd1cb8cc1
|
@ -38,7 +38,7 @@ class Job:
|
||||||
|
|
||||||
def _handle_command(self, command, interpreter):
|
def _handle_command(self, command, interpreter):
|
||||||
if interpreter == 'python':
|
if interpreter == 'python':
|
||||||
return f'python << EOF\n{command}\nEOF'
|
return f'python << EOF\n# -*- coding: UTF-8 -*-\n{command}\nEOF'
|
||||||
return command
|
return command
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
|
@ -53,7 +53,7 @@ class Job:
|
||||||
if not self.token:
|
if not self.token:
|
||||||
with self.ssh:
|
with self.ssh:
|
||||||
return self.ssh.exec_command(self.command, self.env)
|
return self.ssh.exec_command(self.command, self.env)
|
||||||
self.send('\r\33[K\x1b[36m### Executing ...\x1b[0m\r\n')
|
self.send('\r\n\x1b[36m### Executing ...\x1b[0m\r\n')
|
||||||
code = -1
|
code = -1
|
||||||
try:
|
try:
|
||||||
with self.ssh:
|
with self.ssh:
|
||||||
|
|
|
@ -36,7 +36,7 @@ function OutView(props) {
|
||||||
term.loadAddon(fitPlugin)
|
term.loadAddon(fitPlugin)
|
||||||
term.open(el.current)
|
term.open(el.current)
|
||||||
fitPlugin.fit()
|
fitPlugin.fit()
|
||||||
term.write('WebSocket connecting ... ')
|
term.write('\x1b[36m### WebSocket connecting ...\x1b[0m')
|
||||||
const resize = () => fitPlugin.fit();
|
const resize = () => fitPlugin.fit();
|
||||||
window.addEventListener('resize', resize)
|
window.addEventListener('resize', resize)
|
||||||
setTerm(term)
|
setTerm(term)
|
||||||
|
@ -49,7 +49,11 @@ function OutView(props) {
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${store.token}/?x-token=${X_TOKEN}`);
|
const socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${store.token}/?x-token=${X_TOKEN}`);
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
term.write('\r\x1b[K\x1b[36m### Waiting for scheduling ...\x1b[0m')
|
const message = '\r\x1b[K\x1b[36m### Waiting for scheduling ...\x1b[0m'
|
||||||
|
for (let key of Object.keys(store.outputs)) {
|
||||||
|
store.outputs[key].data = message
|
||||||
|
}
|
||||||
|
term.write(message)
|
||||||
socket.send('ok');
|
socket.send('ok');
|
||||||
}
|
}
|
||||||
socket.onmessage = e => {
|
socket.onmessage = e => {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { PlusOutlined, ThunderboltOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
import { PlusOutlined, ThunderboltOutlined, BulbOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import { Form, Button, Alert, Radio, Tooltip } from 'antd';
|
import { Form, Button, Alert, Radio, Tooltip } from 'antd';
|
||||||
import { ACEditor, AuthDiv, Breadcrumb } from 'components';
|
import { ACEditor, AuthDiv, Breadcrumb } from 'components';
|
||||||
import Selector from 'pages/host/Selector';
|
import Selector from 'pages/host/Selector';
|
||||||
|
@ -68,14 +68,19 @@ function TaskIndex() {
|
||||||
<div className={style.index} hidden={store.showConsole}>
|
<div className={style.index} hidden={store.showConsole}>
|
||||||
<Form layout="vertical" className={style.left}>
|
<Form layout="vertical" className={style.left}>
|
||||||
<Form.Item required label="目标主机">
|
<Form.Item required label="目标主机">
|
||||||
{store.host_ids.length > 0 && (
|
{store.host_ids.length > 0 ? (
|
||||||
<Alert style={{width: 200}} type="info" message={`已选择 ${store.host_ids.length} 台主机`}/>
|
<Alert
|
||||||
|
type="info"
|
||||||
|
className={style.area}
|
||||||
|
message={<div>已选择 <b style={{fontSize: 18, color: '#1890ff'}}>{store.host_ids.length}</b> 台主机</div>}
|
||||||
|
onClick={() => store.showHost = true}/>
|
||||||
|
) : (
|
||||||
|
<Button icon={<PlusOutlined/>} onClick={() => store.showHost = true}>
|
||||||
|
从主机列表中选择
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button
|
|
||||||
style={{marginBottom: 24}}
|
|
||||||
icon={<PlusOutlined/>}
|
|
||||||
onClick={() => store.showHost = true}>从主机列表中选择</Button>
|
|
||||||
<Form.Item required label="执行命令" style={{position: 'relative'}}>
|
<Form.Item required label="执行命令" style={{position: 'relative'}}>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
buttonStyle="solid"
|
buttonStyle="solid"
|
||||||
|
@ -86,12 +91,10 @@ function TaskIndex() {
|
||||||
<Radio.Button value="python">Python</Radio.Button>
|
<Radio.Button value="python">Python</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
<a href="https://spug.cc/docs/batch-exec" target="_blank" rel="noopener noreferrer"
|
<a href="https://spug.cc/docs/batch-exec" target="_blank" rel="noopener noreferrer"
|
||||||
className={style.tips}>全局变量</a>
|
className={style.tips}><BulbOutlined/> 使用全局变量?</a>
|
||||||
|
<Button style={{float: 'right'}} icon={<PlusOutlined/>} onClick={store.switchTemplate}>从执行模版中选择</Button>
|
||||||
<ACEditor className={style.editor} mode={interpreter} value={command} width="100%" onChange={setCommand}/>
|
<ACEditor className={style.editor} mode={interpreter} value={command} width="100%" onChange={setCommand}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
|
||||||
<Button icon={<PlusOutlined/>} onClick={store.switchTemplate}>从执行模版中选择</Button>
|
|
||||||
</Form.Item>
|
|
||||||
<Button loading={loading} icon={<ThunderboltOutlined/>} type="primary" onClick={handleSubmit}>开始执行</Button>
|
<Button loading={loading} icon={<ThunderboltOutlined/>} type="primary" onClick={handleSubmit}>开始执行</Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,21 @@
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
|
|
||||||
|
.area {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 200px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
.tips {
|
.tips {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 180px;
|
left: 180px;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
height: calc(100vh - 588px) !important;
|
height: calc(100vh - 482px) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class Store {
|
||||||
const host = hostStore.idMap[id];
|
const host = hostStore.idMap[id];
|
||||||
this.outputs[host.id] = {
|
this.outputs[host.id] = {
|
||||||
title: `${host.name}(${host.hostname}:${host.port})`,
|
title: `${host.name}(${host.hostname}:${host.port})`,
|
||||||
data: '',
|
data: '\x1b[36m### WebSocket connecting ...\x1b[0m',
|
||||||
status: -2
|
status: -2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue