mirror of https://github.com/openspug/spug
fix issue
parent
ec90b820ba
commit
4e45fe9136
|
@ -2,10 +2,13 @@
|
|||
# Copyright: (c) <spug.dev@gmail.com>
|
||||
# Released under the AGPL-3.0 License.
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from libs import ModelMixin, human_datetime
|
||||
from apps.account.models import User
|
||||
from apps.config.models import Environment
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
class App(models.Model, ModelMixin):
|
||||
|
@ -62,8 +65,15 @@ class Deploy(models.Model, ModelMixin):
|
|||
deploy.update(self.extend_obj.to_dict())
|
||||
return deploy
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
deploy_id = self.id
|
||||
super().delete(using, keep_parents)
|
||||
repo_dir = os.path.join(settings.REPOS_DIR, str(deploy_id))
|
||||
build_dir = os.path.join(settings.BUILD_DIR, f'{deploy_id}_*')
|
||||
subprocess.Popen(f'rm -rf {repo_dir} {repo_dir + "_*"} {build_dir}', shell=True)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Deploy app_id=%r>' % self.app_id
|
||||
return '<Deploy app_id=%r env_id=%r>' % (self.app_id, self.env_id)
|
||||
|
||||
class Meta:
|
||||
db_table = 'deploys'
|
||||
|
|
|
@ -3,15 +3,12 @@
|
|||
# Released under the AGPL-3.0 License.
|
||||
from django.views.generic import View
|
||||
from django.db.models import F
|
||||
from django.conf import settings
|
||||
from libs import JsonParser, Argument, json_response
|
||||
from apps.app.models import App, Deploy, DeployExtend1, DeployExtend2
|
||||
from apps.config.models import Config
|
||||
from apps.app.utils import fetch_versions, remove_repo
|
||||
from apps.setting.utils import AppSetting
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
|
@ -164,11 +161,11 @@ class DeployView(View):
|
|||
).parse(request.GET)
|
||||
if error is None:
|
||||
deploy = Deploy.objects.get(pk=form.id)
|
||||
if deploy.repository_set.exists():
|
||||
return json_response(error='已存在关联的构建版本,请删除关联的构建版本后再尝试删除发布配置')
|
||||
if deploy.deployrequest_set.exists():
|
||||
return json_response(error='已存在关联的发布记录,请删除关联的发布记录后再尝试删除发布配置')
|
||||
for item in deploy.repository_set.all():
|
||||
item.delete()
|
||||
deploy.delete()
|
||||
repo_dir = os.path.join(settings.REPOS_DIR, str(form.id))
|
||||
subprocess.Popen(f'rm -rf {repo_dir} {repo_dir + "_*"}', shell=True)
|
||||
return json_response(error=error)
|
||||
|
||||
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
# Copyright: (c) <spug.dev@gmail.com>
|
||||
# Released under the AGPL-3.0 License.
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from libs import ModelMixin, human_datetime
|
||||
from apps.account.models import User
|
||||
from apps.app.models import Deploy
|
||||
from apps.repository.models import Repository
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
class DeployRequest(models.Model, ModelMixin):
|
||||
|
@ -50,6 +52,17 @@ class DeployRequest(models.Model, ModelMixin):
|
|||
return extra[0] in ('branch', 'tag')
|
||||
return False
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
super().delete(using, keep_parents)
|
||||
if self.repository_id:
|
||||
if not DeployRequest.objects.filter(repository=self.repository).exists():
|
||||
self.repository.delete()
|
||||
if self.deploy.extend == '2':
|
||||
try:
|
||||
os.remove(os.path.join(settings.REPOS_DIR, str(self.deploy_id), self.spug_version))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return f'<DeployRequest name={self.name}>'
|
||||
|
||||
|
|
|
@ -85,36 +85,37 @@ class RequestView(View):
|
|||
def delete(self, request):
|
||||
form, error = JsonParser(
|
||||
Argument('id', type=int, required=False),
|
||||
Argument('expire', required=False),
|
||||
Argument('count', type=int, required=False, help='请输入数字')
|
||||
Argument('mode', filter=lambda x: x in ('count', 'expire', 'deploy'), required=False, help='参数错误'),
|
||||
Argument('value', required=False),
|
||||
).parse(request.GET)
|
||||
if error is None:
|
||||
rds = get_redis_connection()
|
||||
if form.id:
|
||||
DeployRequest.objects.filter(pk=form.id, status__in=('0', '1', '-1')).delete()
|
||||
deploy = DeployRequest.objects.filter(pk=form.id).first()
|
||||
if not deploy or deploy.status not in ('0', '1', '-1'):
|
||||
return json_response(error='未找到指定发布申请或当前状态不允许删除')
|
||||
deploy.delete()
|
||||
return json_response()
|
||||
elif form.count:
|
||||
if form.count < 1:
|
||||
|
||||
count = 0
|
||||
if form.mode == 'count':
|
||||
if not str(form.value).isdigit() or int(form.value) < 1:
|
||||
return json_response(error='请输入正确的保留数量')
|
||||
counter, ids = defaultdict(int), []
|
||||
counter, form.value = defaultdict(int), int(form.value)
|
||||
for item in DeployRequest.objects.all():
|
||||
if counter[item.deploy_id] == form.count:
|
||||
ids.append(item.id)
|
||||
else:
|
||||
counter[item.deploy_id] += 1
|
||||
count, _ = DeployRequest.objects.filter(id__in=ids).delete()
|
||||
if ids:
|
||||
rds.delete(*(f'{settings.REQUEST_KEY}:{x}' for x in ids))
|
||||
return json_response(count)
|
||||
elif form.expire:
|
||||
requests = DeployRequest.objects.filter(created_at__lt=form.expire)
|
||||
ids = [x.id for x in requests]
|
||||
count, _ = requests.delete()
|
||||
if ids:
|
||||
rds.delete(*(f'{settings.REQUEST_KEY}:{x}' for x in ids))
|
||||
return json_response(count)
|
||||
else:
|
||||
return json_response(error='请至少使用一个删除条件')
|
||||
counter[item.deploy_id] += 1
|
||||
if counter[item.deploy_id] > form.value:
|
||||
count += 1
|
||||
item.delete()
|
||||
elif form.mode == 'expire':
|
||||
for item in DeployRequest.objects.filter(created_at__lt=form.value):
|
||||
count += 1
|
||||
item.delete()
|
||||
elif form.mode == 'deploy':
|
||||
app_id, env_id = str(form.value).split(',')
|
||||
for item in DeployRequest.objects.filter(deploy__app_id=app_id, deploy__env_id=env_id):
|
||||
count += 1
|
||||
item.delete()
|
||||
return json_response(count)
|
||||
return json_response(error=error)
|
||||
|
||||
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
# Copyright: (c) <spug.dev@gmail.com>
|
||||
# Released under the AGPL-3.0 License.
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from libs.mixins import ModelMixin
|
||||
from apps.app.models import App, Environment, Deploy
|
||||
from apps.account.models import User
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
class Repository(models.Model, ModelMixin):
|
||||
|
@ -43,7 +45,14 @@ class Repository(models.Model, ModelMixin):
|
|||
tmp['created_by_user'] = self.created_by_user
|
||||
return tmp
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
super().delete(using, keep_parents)
|
||||
try:
|
||||
build_file = f'{self.spug_version}.tar.gz'
|
||||
os.remove(os.path.join(settings.BUILD_DIR, build_file))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
class Meta:
|
||||
db_table = 'repositories'
|
||||
ordering = ('-id',)
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ from apps.repository.utils import dispatch
|
|||
from apps.app.models import Deploy
|
||||
from threading import Thread
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
class RepositoryView(View):
|
||||
|
@ -83,11 +82,6 @@ class RepositoryView(View):
|
|||
if repository.deployrequest_set.exists():
|
||||
return json_response(error='已关联发布申请无法删除')
|
||||
repository.delete()
|
||||
build_file = f'{repository.spug_version}.tar.gz'
|
||||
try:
|
||||
os.remove(os.path.join(settings.REPOS_DIR, 'build', build_file))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return json_response(error=error)
|
||||
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ EXEC_WORKER_KEY = 'spug:exec:worker'
|
|||
REQUEST_KEY = 'spug:request'
|
||||
BUILD_KEY = 'spug:build'
|
||||
REPOS_DIR = os.path.join(BASE_DIR, 'repos')
|
||||
BUILD_DIR = os.path.join(REPOS_DIR, 'build')
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* Released under the AGPL-3.0 License.
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Modal, Form, Input, Select, Radio, DatePicker, Space, message } from 'antd';
|
||||
import { http, includes } from 'libs';
|
||||
import store from './store';
|
||||
import appStore from '../app/store';
|
||||
import envStore from 'pages/config/environment/store';
|
||||
|
||||
export default observer(function () {
|
||||
const [mode, setMode] = useState('expire')
|
||||
const [value, setValue] = useState()
|
||||
const [appId, setAppId] = useState()
|
||||
const [envId, setEnvId] = useState()
|
||||
const [loading, setLoading] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(appStore.records).length === 0) appStore.fetchRecords()
|
||||
if (envStore.records.length === 0) envStore.fetchRecords()
|
||||
}, [])
|
||||
|
||||
function handleSubmit() {
|
||||
const formData = {mode, value};
|
||||
if (mode === 'deploy') {
|
||||
if (!appId || !envId) return message.error('请选择要删除的应用和环境')
|
||||
formData.value = `${appId},${envId}`
|
||||
} else if (mode === 'expire') {
|
||||
if (!value) return message.error('请选择截止日期')
|
||||
formData.value = value.format('YYYY-MM-DD')
|
||||
} else if (!value) {
|
||||
return message.error('请输入保留个数')
|
||||
}
|
||||
setLoading(true);
|
||||
http.delete('/api/deploy/request/', {params: formData})
|
||||
.then(res => {
|
||||
message.success(`删除 ${res} 条发布记录`);
|
||||
store.batchVisible = false;
|
||||
store.fetchRecords()
|
||||
}, () => setLoading(false))
|
||||
}
|
||||
|
||||
function handleChange(e) {
|
||||
setMode(e.target.value)
|
||||
setValue()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible
|
||||
width={400}
|
||||
maskClosable={false}
|
||||
title="批量删除发布申请"
|
||||
onCancel={() => store.batchVisible = false}
|
||||
confirmLoading={loading}
|
||||
onOk={handleSubmit}>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="删除方式 :">
|
||||
<Radio.Group value={mode} placeholder="请选择" style={{width: 280}} onChange={handleChange}>
|
||||
<Radio.Button value="expire">截止时间</Radio.Button>
|
||||
<Radio.Button value="count">保留记录</Radio.Button>
|
||||
<Radio.Button value="deploy">发布配置</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{mode === 'expire' && (
|
||||
<Form.Item
|
||||
label="截止日期 :"
|
||||
help={<div>将删除截止日期<span style={{color: 'red'}}>之前</span>的所有发布申请记录。</div>}>
|
||||
<DatePicker value={value} style={{width: 290}} onChange={setValue} placeholder="请选择截止日期"/>
|
||||
</Form.Item>
|
||||
)}
|
||||
{mode === 'count' && (
|
||||
<Form.Item label="保留记录 :" help="每个应用每个环境仅保留最新的N条发布申请。">
|
||||
<Input value={value} style={{width: 290}} onChange={e => setValue(e.target.value)} placeholder="请输入保留个数"/>
|
||||
</Form.Item>
|
||||
)}
|
||||
{mode === 'deploy' && (
|
||||
<Form.Item label="发布配置 :" help="删除指定应用环境下的发布申请记录。">
|
||||
<Space>
|
||||
<Select
|
||||
showSearch
|
||||
style={{width: 160}}
|
||||
value={appId}
|
||||
onChange={setAppId}
|
||||
filterOption={(i, o) => includes(o.children, i)}
|
||||
placeholder="请选择应用">
|
||||
{Object.values(appStore.records).map(item => (
|
||||
<Select.Option key={item.id} value={item.id}>{item.name}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
showSearch
|
||||
style={{width: 122}}
|
||||
value={envId}
|
||||
onChange={setEnvId}
|
||||
filterOption={(i, o) => includes(o.children, i)}
|
||||
placeholder="请选择环境">
|
||||
{envStore.records.map(item => (
|
||||
<Select.Option key={item.id} value={item.id}>{item.name}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
})
|
|
@ -5,8 +5,8 @@
|
|||
*/
|
||||
import React, { useEffect } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { Form, Select, DatePicker, Modal, Input, Space, message } from 'antd';
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { Select, DatePicker, Space } from 'antd';
|
||||
import { SearchForm, AuthDiv, AuthButton, Breadcrumb, AppSelector } from 'components';
|
||||
import Ext1Form from './Ext1Form';
|
||||
import Ext2Form from './Ext2Form';
|
||||
|
@ -14,7 +14,8 @@ import Approve from './Approve';
|
|||
import ComTable from './Table';
|
||||
import Ext1Console from './Ext1Console';
|
||||
import Ext2Console from './Ext2Console';
|
||||
import { http, includes } from 'libs';
|
||||
import BatchDelete from './BatchDelete';
|
||||
import { includes } from 'libs';
|
||||
import envStore from 'pages/config/environment/store';
|
||||
import appStore from 'pages/config/app/store';
|
||||
import store from './store';
|
||||
|
@ -29,33 +30,6 @@ function Index() {
|
|||
return () => store.leaveConsole()
|
||||
}, [])
|
||||
|
||||
function handleBatchDel() {
|
||||
let [expire, count] = [];
|
||||
Modal.confirm({
|
||||
icon: <ExclamationCircleOutlined/>,
|
||||
title: '批量删除发布申请',
|
||||
content: (
|
||||
<Form layout="vertical" style={{marginTop: 24}}>
|
||||
<Form.Item label="截止日期 :" help={<div>将删除截止日期<span style={{color: 'red'}}>之前</span>的所有发布申请记录。</div>}>
|
||||
<DatePicker style={{width: 200}} placeholder="请输入"
|
||||
onChange={val => expire = val.format('YYYY-MM-DD')}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="保留记录 :" help="每个应用每个环境仅保留最新的N条发布申请,优先级高于截止日期">
|
||||
<Input allowClear style={{width: 200}} placeholder="请输入保留个数"
|
||||
onChange={e => count = e.target.value}/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
),
|
||||
onOk: () => {
|
||||
return http.delete('/api/deploy/request/', {params: {expire, count}})
|
||||
.then(res => {
|
||||
message.success(`成功删除${res}条记录`);
|
||||
store.fetchRecords()
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthDiv auth="deploy.request.view">
|
||||
<Breadcrumb>
|
||||
|
@ -100,7 +74,7 @@ function Index() {
|
|||
auth="deploy.request.del"
|
||||
type="danger"
|
||||
icon={<DeleteOutlined/>}
|
||||
onClick={handleBatchDel}>批量删除</AuthButton>
|
||||
onClick={() => store.batchVisible = true}>批量删除</AuthButton>
|
||||
</SearchForm.Item>
|
||||
</SearchForm>
|
||||
<ComTable/>
|
||||
|
@ -110,6 +84,7 @@ function Index() {
|
|||
onSelect={store.confirmAdd}/>
|
||||
{store.ext1Visible && <Ext1Form/>}
|
||||
{store.ext2Visible && <Ext2Form/>}
|
||||
{store.batchVisible && <BatchDelete/>}
|
||||
{store.approveVisible && <Approve/>}
|
||||
{store.tabs.length > 0 && (
|
||||
<Space className={styles.miniConsole}>
|
||||
|
|
|
@ -19,6 +19,7 @@ class Store {
|
|||
@observable addVisible = false;
|
||||
@observable ext1Visible = false;
|
||||
@observable ext2Visible = false;
|
||||
@observable batchVisible = false;
|
||||
@observable approveVisible = false;
|
||||
|
||||
@observable f_status = 'all';
|
||||
|
|
Loading…
Reference in New Issue