add planned deploy

pull/330/head
vapao 2021-04-26 17:30:20 +08:00
parent a28300a641
commit 0e6e618cef
8 changed files with 55 additions and 12 deletions

View File

@ -32,6 +32,7 @@ class DeployRequest(models.Model, ModelMixin):
reason = models.CharField(max_length=255, null=True)
version = models.CharField(max_length=50, null=True)
spug_version = models.CharField(max_length=50, null=True)
plan = models.DateTimeField(null=True)
created_at = models.CharField(max_length=20, default=human_datetime)
created_by = models.ForeignKey(User, models.PROTECT, related_name='+')

View File

@ -201,6 +201,7 @@ def post_request_ext1(request):
Argument('repository_id', type=int, help='请选择发布版本'),
Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'),
Argument('type', default='1'),
Argument('plan', required=False),
Argument('desc', required=False),
).parse(request.body)
if error is None:
@ -232,7 +233,8 @@ def post_request_ext2(request):
Argument('name', help='请输申请标题'),
Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'),
Argument('extra', type=dict, required=False),
Argument('version', required=False),
Argument('version', default=''),
Argument('plan', required=False),
Argument('desc', required=False),
).parse(request.body)
if error is None:

View File

@ -6,8 +6,10 @@ from apps.account.models import History
from apps.alarm.models import Alarm
from apps.schedule.models import Task
from apps.deploy.models import DeployRequest
from libs.utils import parse_time
from apps.deploy.utils import dispatch
from libs.utils import parse_time, human_datetime
from datetime import datetime, timedelta
from threading import Thread
def auto_run_by_day():
@ -24,8 +26,15 @@ def auto_run_by_day():
def auto_run_by_minute():
close_old_connections()
now = datetime.now()
for req in DeployRequest.objects.filter(status='2'):
if (now - parse_time(req.do_at)).seconds > 3600:
req.status = '-3'
req.save()
for req in DeployRequest.objects.filter(status='1', plan__lte=now):
req.status = '2'
req.do_at = human_datetime()
req.do_by = req.created_by
req.save()
Thread(target=dispatch, args=(req,)).start()

View File

@ -78,7 +78,7 @@ class Scheduler:
def _init_builtin_jobs(self):
self.scheduler.add_job(auto_run_by_day, 'cron', hour=1, minute=20)
self.scheduler.add_job(auto_run_by_minute, 'interval', minutes=5)
self.scheduler.add_job(auto_run_by_minute, 'interval', minutes=1)
def _dispatch(self, task_id, command, targets):
close_old_connections()

View File

@ -5,7 +5,7 @@
*/
import React, { useState, useEffect } from 'react';
import { observer } from 'mobx-react';
import { Modal, Form, Input, Select, message, Button } from 'antd';
import { Modal, Form, Input, Select, DatePicker, message, Button } from 'antd';
import HostSelector from './HostSelector';
import hostStore from 'pages/host/store';
import http from 'libs/http';
@ -19,6 +19,7 @@ export default observer(function () {
const [loading, setLoading] = useState(false);
const [versions, setVersions] = useState([]);
const [host_ids, setHostIds] = useState([]);
const [plan, setPlan] = useState(store.record.plan);
useEffect(() => {
const {deploy_id, app_host_ids, host_ids} = store.record;
@ -38,6 +39,7 @@ export default observer(function () {
formData['host_ids'] = host_ids;
formData['type'] = store.record.type;
formData['deploy_id'] = store.record.deploy_id;
if (plan) formData.plan = plan.format('YYYY-MM-DD HH:mm:00');
http.post('/api/deploy/request/ext1/', formData)
.then(res => {
message.success('操作成功');
@ -46,7 +48,7 @@ export default observer(function () {
}, () => setLoading(false))
}
const {app_host_ids, type, rb_id,} = store.record;
const {app_host_ids, type, rb_id} = store.record;
return (
<Modal
visible
@ -56,7 +58,7 @@ export default observer(function () {
onCancel={() => store.ext1Visible = false}
confirmLoading={loading}
onOk={handleSubmit}>
<Form form={form} initialValues={store.record} labelCol={{span: 5}} wrapperCol={{span: 17}}>
<Form form={form} initialValues={store.record} labelCol={{span: 6}} wrapperCol={{span: 16}}>
<Form.Item required name="name" label="申请标题">
<Input placeholder="请输入申请标题"/>
</Form.Item>
@ -79,6 +81,18 @@ export default observer(function () {
<Form.Item name="desc" label="备注信息">
<Input placeholder="请输入备注信息"/>
</Form.Item>
{type !== '2' && (
<Form.Item label="定时发布" tooltip="在到达指定时间后自动发布会有最多1分钟的延迟。">
<DatePicker
showTime
value={plan}
style={{width: 180}}
format="YYYY-MM-DD HH:mm"
placeholder="请设置发布时间"
onChange={setPlan}/>
{plan ? <span style={{marginLeft: 24, fontSize: 12, color: '#888'}}>大约 {plan.fromNow()}</span> : null}
</Form.Item>
)}
</Form>
{visible && <HostSelector
host_ids={host_ids}

View File

@ -6,7 +6,7 @@
import React, { useState, useEffect } from 'react';
import { observer } from 'mobx-react';
import { UploadOutlined } from '@ant-design/icons';
import { Modal, Form, Input, Upload, message, Button } from 'antd';
import { Modal, Form, Input, Upload, DatePicker, message, Button } from 'antd';
import hostStore from 'pages/host/store';
import HostSelector from './HostSelector';
import { http, X_TOKEN } from 'libs';
@ -21,6 +21,7 @@ export default observer(function () {
const [uploading, setUploading] = useState(false);
const [fileList, setFileList] = useState([]);
const [host_ids, setHostIds] = useState([]);
const [plan, setPlan] = useState(store.record.plan);
useEffect(() => {
const {app_host_ids, host_ids, extra} = store.record;
@ -38,6 +39,7 @@ export default observer(function () {
formData['id'] = store.record.id;
formData['host_ids'] = host_ids;
formData['deploy_id'] = store.record.deploy_id;
if (plan) formData.plan = plan.format('YYYY-MM-DD HH:mm:00');
if (fileList.length > 0) formData['extra'] = lds.pick(fileList[0], ['path', 'name']);
http.post('/api/deploy/request/ext2/', formData)
.then(res => {
@ -67,17 +69,17 @@ export default observer(function () {
return false
}
const {app_host_ids, deploy_id} = store.record;
const {app_host_ids, deploy_id, type} = store.record;
return (
<Modal
visible
width={800}
width={600}
maskClosable={false}
title={`${store.record.id ? '编辑' : '新建'}发布申请`}
onCancel={() => store.ext2Visible = false}
confirmLoading={loading}
onOk={handleSubmit}>
<Form form={form} initialValues={store.record} labelCol={{span: 6}} wrapperCol={{span: 14}}>
<Form form={form} initialValues={store.record} labelCol={{span: 6}} wrapperCol={{span: 16}}>
<Form.Item required name="name" label="申请标题">
<Input placeholder="请输入申请标题"/>
</Form.Item>
@ -100,6 +102,18 @@ export default observer(function () {
<Form.Item name="desc" label="备注信息">
<Input placeholder="请输入备注信息"/>
</Form.Item>
{type !== '2' && (
<Form.Item label="定时发布" tooltip="在到达指定时间后自动发布会有最多1分钟的延迟。">
<DatePicker
showTime
value={plan}
style={{width: 180}}
format="YYYY-MM-DD HH:mm"
placeholder="请设置发布时间"
onChange={setPlan}/>
{plan ? <span style={{marginLeft: 24, fontSize: 12, color: '#888'}}>大约 {plan.fromNow()}</span> : null}
</Form.Item>
)}
</Form>
{visible && <HostSelector
host_ids={host_ids}

View File

@ -6,7 +6,7 @@
import React from 'react';
import { observer } from 'mobx-react';
import { BranchesOutlined, BuildOutlined, TagOutlined, PlusOutlined } from '@ant-design/icons';
import { Radio, Modal, Popover, Tag, Popconfirm, message } from 'antd';
import { Radio, Modal, Popover, Tag, Popconfirm, Tooltip, message } from 'antd';
import { http, hasPermission } from 'libs';
import { Action, AuthButton, TableCard } from 'components';
import styles from './index.module.less';
@ -17,7 +17,8 @@ function ComTable() {
title: '申请标题',
render: info => (
<div>
{info.type === '2' && <Tag color="#f50">R</Tag>}
{info.type === '2' && <Tooltip title="回滚发布"><Tag color="#f50">R</Tag></Tooltip>}
{info.plan && <Tooltip title={`定时发布(${info.plan}`}> <Tag color="#108ee9">P</Tag></Tooltip>}
{info.name}
</div>
)

View File

@ -5,6 +5,7 @@
*/
import { observable, computed } from "mobx";
import http from 'libs/http';
import moment from 'moment';
import lds from 'lodash';
class Store {
@ -106,6 +107,7 @@ class Store {
showForm = (info) => {
this.record = info;
if (info.plan) this.record.plan = moment(info.plan);
if (info['app_extend'] === '1') {
this.ext1Visible = true
} else {