mirror of https://github.com/openspug/spug
fix issue
parent
c078f9aa5d
commit
1897739512
|
@ -165,7 +165,7 @@ def _deploy_ext1_host(req, helper, h_id, env):
|
||||||
helper.send_error(host.id, f'检测到该主机的发布目录 {extend.dst_dir!r} 已存在,为了数据安全请自行备份后删除该目录,Spug 将会创建并接管该目录。')
|
helper.send_error(host.id, f'检测到该主机的发布目录 {extend.dst_dir!r} 已存在,为了数据安全请自行备份后删除该目录,Spug 将会创建并接管该目录。')
|
||||||
# clean
|
# clean
|
||||||
clean_command = f'ls -d {extend.deploy_id}_* 2> /dev/null | sort -t _ -rnk2 | tail -n +{extend.versions + 1} | xargs rm -rf'
|
clean_command = f'ls -d {extend.deploy_id}_* 2> /dev/null | sort -t _ -rnk2 | tail -n +{extend.versions + 1} | xargs rm -rf'
|
||||||
helper.remote_raw(host.id, ssh, f'cd {extend.dst_repo} && rm -rf {req.spug_version} && {clean_command}')
|
helper.remote_raw(host.id, ssh, f'cd {extend.dst_repo} && {clean_command}')
|
||||||
# transfer files
|
# transfer files
|
||||||
tar_gz_file = f'{req.spug_version}.tar.gz'
|
tar_gz_file = f'{req.spug_version}.tar.gz'
|
||||||
try:
|
try:
|
||||||
|
@ -173,7 +173,7 @@ def _deploy_ext1_host(req, helper, h_id, env):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
helper.send_error(host.id, f'exception: {e}')
|
helper.send_error(host.id, f'exception: {e}')
|
||||||
|
|
||||||
command = f'cd {extend.dst_repo} && tar xf {tar_gz_file} && rm -f {req.deploy_id}_*.tar.gz'
|
command = f'cd {extend.dst_repo} && rm -rf {req.spug_version} && tar xf {tar_gz_file} && rm -f {req.deploy_id}_*.tar.gz'
|
||||||
helper.remote_raw(host.id, ssh, command)
|
helper.remote_raw(host.id, ssh, command)
|
||||||
helper.send_step(h_id, 1, '完成\r\n')
|
helper.send_step(h_id, 1, '完成\r\n')
|
||||||
|
|
||||||
|
|
|
@ -7,5 +7,6 @@ from .views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', RepositoryView.as_view()),
|
path('', RepositoryView.as_view()),
|
||||||
|
path('<int:r_id>/', get_detail),
|
||||||
path('request/', get_requests),
|
path('request/', get_requests),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
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.conf import settings
|
||||||
from libs import json_response, JsonParser, Argument, AttrDict
|
from django_redis import get_redis_connection
|
||||||
|
from libs import json_response, JsonParser, Argument, human_time, AttrDict
|
||||||
from apps.repository.models import Repository
|
from apps.repository.models import Repository
|
||||||
from apps.deploy.models import DeployRequest
|
from apps.deploy.models import DeployRequest
|
||||||
from apps.repository.utils import dispatch
|
from apps.repository.utils import dispatch
|
||||||
|
@ -102,3 +103,32 @@ def get_requests(request):
|
||||||
data['status_alias'] = item.get_status_display()
|
data['status_alias'] = item.get_status_display()
|
||||||
requests.append(data)
|
requests.append(data)
|
||||||
return json_response(requests)
|
return json_response(requests)
|
||||||
|
|
||||||
|
|
||||||
|
def get_detail(request, r_id):
|
||||||
|
repository = Repository.objects.filter(pk=r_id).first()
|
||||||
|
if not repository:
|
||||||
|
return json_response(error='未找到指定构建记录')
|
||||||
|
rds, counter = get_redis_connection(), 0
|
||||||
|
key = f'{settings.BUILD_KEY}:{repository.spug_version}'
|
||||||
|
data = rds.lrange(key, counter, counter + 9)
|
||||||
|
response = AttrDict(data='', step=0, s_status='process', status=repository.status)
|
||||||
|
while data:
|
||||||
|
for item in data:
|
||||||
|
counter += 1
|
||||||
|
item = json.loads(item.decode())
|
||||||
|
if 'data' in item:
|
||||||
|
response.data += item['data']
|
||||||
|
if 'step' in item:
|
||||||
|
response.step = item['step']
|
||||||
|
if 'status' in item:
|
||||||
|
response.status = item['status']
|
||||||
|
data = rds.lrange(key, counter, counter + 9)
|
||||||
|
response.index = counter
|
||||||
|
if repository.status in ('0', '1'):
|
||||||
|
response.data = f'{human_time()} 建立连接... ' + response.data
|
||||||
|
elif not response.data:
|
||||||
|
response.data = f'{human_time()} 读取数据... \r\n\r\n未读取到数据,Spug 仅保存最近2周的构建日志。'
|
||||||
|
else:
|
||||||
|
response.data = f'{human_time()} 读取数据... ' + response.data
|
||||||
|
return json_response(response)
|
||||||
|
|
|
@ -79,7 +79,10 @@ class AttrDict(dict):
|
||||||
self.__setitem__(key, value)
|
self.__setitem__(key, value)
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
return self.__getitem__(item)
|
try:
|
||||||
|
return self.__getitem__(item)
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(item)
|
||||||
|
|
||||||
def __delattr__(self, item):
|
def __delattr__(self, item):
|
||||||
self.__delitem__(item)
|
self.__delitem__(item)
|
||||||
|
|
|
@ -8,21 +8,35 @@ import { observer } from 'mobx-react';
|
||||||
import { FullscreenOutlined, FullscreenExitOutlined, LoadingOutlined } from '@ant-design/icons';
|
import { FullscreenOutlined, FullscreenExitOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
import { Terminal } from 'xterm';
|
import { Terminal } from 'xterm';
|
||||||
import { Modal, Steps } from 'antd';
|
import { Modal, Steps, Spin } from 'antd';
|
||||||
import { X_TOKEN, human_time } from 'libs';
|
import { X_TOKEN, http } from 'libs';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
|
||||||
export default observer(function Console() {
|
export default observer(function Console() {
|
||||||
const el = useRef()
|
const el = useRef()
|
||||||
|
const [term] = useState(new Terminal({disableStdin: true}))
|
||||||
const [fullscreen, setFullscreen] = useState(false);
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
const [status, setStatus] = useState('process')
|
const [status, setStatus] = useState('process');
|
||||||
|
const [fetching, setFetching] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const term = initialTerm()
|
let socket;
|
||||||
term.write(`${human_time()} 建立连接... `)
|
initialTerm()
|
||||||
let index = 0;
|
http.get(`/api/repository/${store.record.id}/`)
|
||||||
|
.then(res => {
|
||||||
|
term.write(res.data)
|
||||||
|
setStep(res.step)
|
||||||
|
setStatus(res.status)
|
||||||
|
socket = _makeSocket(res.index)
|
||||||
|
})
|
||||||
|
.finally(() => setFetching(false))
|
||||||
|
return () => socket && socket.close()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
function _makeSocket(index = 0) {
|
||||||
const token = store.record.spug_version;
|
const token = store.record.spug_version;
|
||||||
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/build/${token}/?x-token=${X_TOKEN}`);
|
const socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/build/${token}/?x-token=${X_TOKEN}`);
|
||||||
|
@ -38,17 +52,21 @@ export default observer(function Console() {
|
||||||
if (status !== undefined) setStatus(status);
|
if (status !== undefined) setStatus(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return () => socket.close();
|
return socket
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
term.fit && term.fit()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [fullscreen])
|
||||||
|
|
||||||
function initialTerm() {
|
function initialTerm() {
|
||||||
const fitPlugin = new FitAddon()
|
const fitPlugin = new FitAddon()
|
||||||
const term = new Terminal({disableStdin: true})
|
|
||||||
term.loadAddon(fitPlugin)
|
term.loadAddon(fitPlugin)
|
||||||
term.setOption('theme', {background: '#fafafa', foreground: '#000', selection: '#999'})
|
term.setOption('theme', {background: '#fafafa', foreground: '#000', selection: '#999'})
|
||||||
term.open(el.current)
|
term.open(el.current)
|
||||||
|
term.fit = () => fitPlugin.fit()
|
||||||
fitPlugin.fit()
|
fitPlugin.fit()
|
||||||
return term
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
|
@ -85,9 +103,11 @@ export default observer(function Console() {
|
||||||
<StepItem title="检出后任务" step={3}/>
|
<StepItem title="检出后任务" step={3}/>
|
||||||
<StepItem title="执行打包" step={4}/>
|
<StepItem title="执行打包" step={4}/>
|
||||||
</Steps>
|
</Steps>
|
||||||
<div className={styles.out}>
|
<Spin spinning={fetching}>
|
||||||
<div ref={el}/>
|
<div className={styles.out}>
|
||||||
</div>
|
<div ref={el}/>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue