fix issue

pull/410/head
vapao 2021-08-27 13:14:30 +08:00
parent ec90b820ba
commit 4e45fe9136
10 changed files with 182 additions and 70 deletions

View File

@ -2,10 +2,13 @@
# Copyright: (c) <spug.dev@gmail.com> # Copyright: (c) <spug.dev@gmail.com>
# Released under the AGPL-3.0 License. # Released under the AGPL-3.0 License.
from django.db import models from django.db import models
from django.conf import settings
from libs import ModelMixin, human_datetime from libs import ModelMixin, human_datetime
from apps.account.models import User from apps.account.models import User
from apps.config.models import Environment from apps.config.models import Environment
import subprocess
import json import json
import os
class App(models.Model, ModelMixin): class App(models.Model, ModelMixin):
@ -62,8 +65,15 @@ class Deploy(models.Model, ModelMixin):
deploy.update(self.extend_obj.to_dict()) deploy.update(self.extend_obj.to_dict())
return deploy 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): 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: class Meta:
db_table = 'deploys' db_table = 'deploys'

View File

@ -3,15 +3,12 @@
# Released under the AGPL-3.0 License. # Released under the AGPL-3.0 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 libs import JsonParser, Argument, json_response from libs import JsonParser, Argument, json_response
from apps.app.models import App, Deploy, DeployExtend1, DeployExtend2 from apps.app.models import App, Deploy, DeployExtend1, DeployExtend2
from apps.config.models import Config from apps.config.models import Config
from apps.app.utils import fetch_versions, remove_repo from apps.app.utils import fetch_versions, remove_repo
from apps.setting.utils import AppSetting from apps.setting.utils import AppSetting
import subprocess
import json import json
import os
import re import re
@ -164,11 +161,11 @@ class DeployView(View):
).parse(request.GET) ).parse(request.GET)
if error is None: if error is None:
deploy = Deploy.objects.get(pk=form.id) deploy = Deploy.objects.get(pk=form.id)
if deploy.repository_set.exists(): if deploy.deployrequest_set.exists():
return json_response(error='已存在关联的构建版本,请删除关联的构建版本后再尝试删除发布配置') return json_response(error='已存在关联的发布记录,请删除关联的发布记录后再尝试删除发布配置')
for item in deploy.repository_set.all():
item.delete()
deploy.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) return json_response(error=error)

View File

@ -2,11 +2,13 @@
# Copyright: (c) <spug.dev@gmail.com> # Copyright: (c) <spug.dev@gmail.com>
# Released under the AGPL-3.0 License. # Released under the AGPL-3.0 License.
from django.db import models from django.db import models
from django.conf import settings
from libs import ModelMixin, human_datetime from libs import ModelMixin, human_datetime
from apps.account.models import User from apps.account.models import User
from apps.app.models import Deploy from apps.app.models import Deploy
from apps.repository.models import Repository from apps.repository.models import Repository
import json import json
import os
class DeployRequest(models.Model, ModelMixin): class DeployRequest(models.Model, ModelMixin):
@ -50,6 +52,17 @@ class DeployRequest(models.Model, ModelMixin):
return extra[0] in ('branch', 'tag') return extra[0] in ('branch', 'tag')
return False 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): def __repr__(self):
return f'<DeployRequest name={self.name}>' return f'<DeployRequest name={self.name}>'

View File

@ -85,36 +85,37 @@ class RequestView(View):
def delete(self, request): def delete(self, request):
form, error = JsonParser( form, error = JsonParser(
Argument('id', type=int, required=False), Argument('id', type=int, required=False),
Argument('expire', required=False), Argument('mode', filter=lambda x: x in ('count', 'expire', 'deploy'), required=False, help='参数错误'),
Argument('count', type=int, required=False, help='请输入数字') Argument('value', required=False),
).parse(request.GET) ).parse(request.GET)
if error is None: if error is None:
rds = get_redis_connection()
if form.id: 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() 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='请输入正确的保留数量') return json_response(error='请输入正确的保留数量')
counter, ids = defaultdict(int), [] counter, form.value = defaultdict(int), int(form.value)
for item in DeployRequest.objects.all(): for item in DeployRequest.objects.all():
if counter[item.deploy_id] == form.count: counter[item.deploy_id] += 1
ids.append(item.id) if counter[item.deploy_id] > form.value:
else: count += 1
counter[item.deploy_id] += 1 item.delete()
count, _ = DeployRequest.objects.filter(id__in=ids).delete() elif form.mode == 'expire':
if ids: for item in DeployRequest.objects.filter(created_at__lt=form.value):
rds.delete(*(f'{settings.REQUEST_KEY}:{x}' for x in ids)) count += 1
return json_response(count) item.delete()
elif form.expire: elif form.mode == 'deploy':
requests = DeployRequest.objects.filter(created_at__lt=form.expire) app_id, env_id = str(form.value).split(',')
ids = [x.id for x in requests] for item in DeployRequest.objects.filter(deploy__app_id=app_id, deploy__env_id=env_id):
count, _ = requests.delete() count += 1
if ids: item.delete()
rds.delete(*(f'{settings.REQUEST_KEY}:{x}' for x in ids)) return json_response(count)
return json_response(count)
else:
return json_response(error='请至少使用一个删除条件')
return json_response(error=error) return json_response(error=error)

View File

@ -2,11 +2,13 @@
# Copyright: (c) <spug.dev@gmail.com> # Copyright: (c) <spug.dev@gmail.com>
# Released under the AGPL-3.0 License. # Released under the AGPL-3.0 License.
from django.db import models from django.db import models
from django.conf import settings
from libs.mixins import ModelMixin from libs.mixins import ModelMixin
from apps.app.models import App, Environment, Deploy from apps.app.models import App, Environment, Deploy
from apps.account.models import User from apps.account.models import User
from datetime import datetime from datetime import datetime
import json import json
import os
class Repository(models.Model, ModelMixin): class Repository(models.Model, ModelMixin):
@ -43,7 +45,14 @@ class Repository(models.Model, ModelMixin):
tmp['created_by_user'] = self.created_by_user tmp['created_by_user'] = self.created_by_user
return tmp 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: class Meta:
db_table = 'repositories' db_table = 'repositories'
ordering = ('-id',) ordering = ('-id',)

View File

@ -12,7 +12,6 @@ from apps.repository.utils import dispatch
from apps.app.models import Deploy from apps.app.models import Deploy
from threading import Thread from threading import Thread
import json import json
import os
class RepositoryView(View): class RepositoryView(View):
@ -83,11 +82,6 @@ class RepositoryView(View):
if repository.deployrequest_set.exists(): if repository.deployrequest_set.exists():
return json_response(error='已关联发布申请无法删除') return json_response(error='已关联发布申请无法删除')
repository.delete() 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) return json_response(error=error)

View File

@ -110,6 +110,7 @@ EXEC_WORKER_KEY = 'spug:exec:worker'
REQUEST_KEY = 'spug:request' REQUEST_KEY = 'spug:request'
BUILD_KEY = 'spug:build' BUILD_KEY = 'spug:build'
REPOS_DIR = os.path.join(BASE_DIR, 'repos') REPOS_DIR = os.path.join(BASE_DIR, 'repos')
BUILD_DIR = os.path.join(REPOS_DIR, 'build')
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/ # https://docs.djangoproject.com/en/2.2/topics/i18n/

View File

@ -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>
)
})

View File

@ -5,8 +5,8 @@
*/ */
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons'; import { DeleteOutlined } from '@ant-design/icons';
import { Form, Select, DatePicker, Modal, Input, Space, message } from 'antd'; import { Select, DatePicker, Space } from 'antd';
import { SearchForm, AuthDiv, AuthButton, Breadcrumb, AppSelector } from 'components'; import { SearchForm, AuthDiv, AuthButton, Breadcrumb, AppSelector } from 'components';
import Ext1Form from './Ext1Form'; import Ext1Form from './Ext1Form';
import Ext2Form from './Ext2Form'; import Ext2Form from './Ext2Form';
@ -14,7 +14,8 @@ import Approve from './Approve';
import ComTable from './Table'; import ComTable from './Table';
import Ext1Console from './Ext1Console'; import Ext1Console from './Ext1Console';
import Ext2Console from './Ext2Console'; 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 envStore from 'pages/config/environment/store';
import appStore from 'pages/config/app/store'; import appStore from 'pages/config/app/store';
import store from './store'; import store from './store';
@ -29,33 +30,6 @@ function Index() {
return () => store.leaveConsole() 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 ( return (
<AuthDiv auth="deploy.request.view"> <AuthDiv auth="deploy.request.view">
<Breadcrumb> <Breadcrumb>
@ -100,7 +74,7 @@ function Index() {
auth="deploy.request.del" auth="deploy.request.del"
type="danger" type="danger"
icon={<DeleteOutlined/>} icon={<DeleteOutlined/>}
onClick={handleBatchDel}>批量删除</AuthButton> onClick={() => store.batchVisible = true}>批量删除</AuthButton>
</SearchForm.Item> </SearchForm.Item>
</SearchForm> </SearchForm>
<ComTable/> <ComTable/>
@ -110,6 +84,7 @@ function Index() {
onSelect={store.confirmAdd}/> onSelect={store.confirmAdd}/>
{store.ext1Visible && <Ext1Form/>} {store.ext1Visible && <Ext1Form/>}
{store.ext2Visible && <Ext2Form/>} {store.ext2Visible && <Ext2Form/>}
{store.batchVisible && <BatchDelete/>}
{store.approveVisible && <Approve/>} {store.approveVisible && <Approve/>}
{store.tabs.length > 0 && ( {store.tabs.length > 0 && (
<Space className={styles.miniConsole}> <Space className={styles.miniConsole}>

View File

@ -19,6 +19,7 @@ class Store {
@observable addVisible = false; @observable addVisible = false;
@observable ext1Visible = false; @observable ext1Visible = false;
@observable ext2Visible = false; @observable ext2Visible = false;
@observable batchVisible = false;
@observable approveVisible = false; @observable approveVisible = false;
@observable f_status = 'all'; @observable f_status = 'all';