From 36f9f62490f64c0120d4fb239308566f95c1bdc7 Mon Sep 17 00:00:00 2001 From: vapao Date: Mon, 31 Oct 2022 16:25:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E7=A7=BB=E9=99=A4=E5=85=AC=E5=85=B1/=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E9=85=8D=E7=BD=AE=E6=A6=82=E5=BF=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spug_api/apps/config/models.py | 6 +- spug_api/apps/config/utils.py | 46 ++++++++- spug_web/src/pages/config/app/Rel.js | 2 +- .../src/pages/config/setting/DiffConfig.js | 7 +- spug_web/src/pages/config/setting/Form.js | 44 ++++----- spug_web/src/pages/config/setting/JSONView.js | 49 +++------- .../src/pages/config/setting/TableView.js | 28 +++--- spug_web/src/pages/config/setting/TextView.js | 48 +++------- spug_web/src/pages/config/setting/index.js | 94 ++++++++++++------- .../src/pages/config/setting/index.module.css | 35 ------- .../pages/config/setting/index.module.less | 48 ++++++++++ spug_web/src/pages/config/setting/store.js | 10 ++ 12 files changed, 223 insertions(+), 194 deletions(-) delete mode 100644 spug_web/src/pages/config/setting/index.module.css create mode 100644 spug_web/src/pages/config/setting/index.module.less diff --git a/spug_api/apps/config/models.py b/spug_api/apps/config/models.py index 216a2df..1010826 100644 --- a/spug_api/apps/config/models.py +++ b/spug_api/apps/config/models.py @@ -48,9 +48,8 @@ class Config(models.Model, ModelMixin): env = models.ForeignKey(Environment, on_delete=models.PROTECT) value = models.TextField(null=True) desc = models.CharField(max_length=255, null=True) - is_public = models.BooleanField(default=False) updated_at = models.CharField(max_length=20) - updated_by = models.ForeignKey(User, on_delete=models.PROTECT) + updated_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True) def __repr__(self): return f'' @@ -72,11 +71,10 @@ class ConfigHistory(models.Model, ModelMixin): env_id = models.IntegerField() value = models.TextField(null=True) desc = models.CharField(max_length=255, null=True) - is_public = models.BooleanField() old_value = models.TextField(null=True) action = models.CharField(max_length=2, choices=ACTIONS) updated_at = models.CharField(max_length=20) - updated_by = models.ForeignKey(User, on_delete=models.PROTECT) + updated_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True) def __repr__(self): return f'' diff --git a/spug_api/apps/config/utils.py b/spug_api/apps/config/utils.py index 3a21951..57f71d5 100644 --- a/spug_api/apps/config/utils.py +++ b/spug_api/apps/config/utils.py @@ -1,8 +1,9 @@ # Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug # Copyright: (c) # Released under the AGPL-3.0 License. -from apps.config.models import Config, Service +from apps.config.models import Config, Service, Environment, ConfigHistory from apps.app.models import App +from libs.utils import SpugError, human_datetime import json @@ -18,7 +19,7 @@ def compose_configs(app, env_id, no_prefix=False): app_ids = json.loads(app.rel_apps) if app_ids: id_key_map = {x.id: x.key for x in App.objects.filter(id__in=app_ids)} - for item in Config.objects.filter(type='app', o_id__in=app_ids, env_id=env_id, is_public=True) \ + for item in Config.objects.filter(type='app', o_id__in=app_ids, env_id=env_id) \ .only('key', 'value'): key = item.key if no_prefix else f'{id_key_map[item.o_id]}_{item.key}' configs[key] = item.value @@ -32,3 +33,44 @@ def compose_configs(app, env_id, no_prefix=False): key = item.key if no_prefix else f'{id_key_map[item.o_id]}_{item.key}' configs[key] = item.value return configs + + +def update_config_by_var(val): + try: + keys, value = val.split('=', 1) + key, env_key, var = keys.split(':') + except ValueError: + raise SpugError('通过SPUG_SET动态更新配置出错,请遵循export SPUG_SET=应用/服务标识符:环境标识符:变量名=变量值') + env = Environment.objects.filter(key=env_key).first() + if not env: + raise SpugError(f'通过SPUG_SET动态更新配置出错,未找到环境标识符{env_key}') + app = App.objects.filter(key=key).first() + if app: + query = dict(key=var, type='app', o_id=app.id, env_id=env.id) + else: + service = Service.objects.filter(key=key).first() + if not service: + raise SpugError(f'通过SPUG_SET动态更新配置出错,未找到应用或服务标识符{key}') + query = dict(key=var, type='src', o_id=service.id, env_id=env.id) + + config = Config.objects.filter(**query).first() + if config: + if config.value != value: + old_value = config.value + config.value = value + config.updated_at = human_datetime() + config.updated_by_id = None + config.desc = '通过SPUG_SET动态更新' + ConfigHistory.objects.create( + action='2', + old_value=old_value, + **config.to_dict(excludes=('id',))) + else: + config = Config.objects.create( + value=value, + desc='通过SPUG_SET动态创建', + updated_at=human_datetime(), + updated_by_id=None, + **query + ) + ConfigHistory.objects.create(action='1', **config.to_dict(excludes=('id',))) diff --git a/spug_web/src/pages/config/app/Rel.js b/spug_web/src/pages/config/app/Rel.js index dc45af1..93ce491 100644 --- a/spug_web/src/pages/config/app/Rel.js +++ b/spug_web/src/pages/config/app/Rel.js @@ -5,7 +5,7 @@ */ import React from 'react'; import { observer } from 'mobx-react'; -import { Modal, Form, Transfer, message, Tabs, Alert } from 'antd'; +import { Modal, Form, Transfer, message, Tabs } from 'antd'; import { http, hasPermission } from 'libs'; import serviceStore from '../service/store'; import store from './store'; diff --git a/spug_web/src/pages/config/setting/DiffConfig.js b/spug_web/src/pages/config/setting/DiffConfig.js index ff79f3f..cad97a0 100644 --- a/spug_web/src/pages/config/setting/DiffConfig.js +++ b/spug_web/src/pages/config/setting/DiffConfig.js @@ -9,7 +9,7 @@ import { ArrowLeftOutlined } from '@ant-design/icons'; import { Modal, Form, Table, Row, Col, Checkbox, Button, Alert } from 'antd'; import http from 'libs/http'; import envStore from '../environment/store'; -import styles from './index.module.css'; +import styles from './index.module.less'; import store from './store'; @observer @@ -76,9 +76,8 @@ class Record extends React.Component { onClick={() => this.handleEnvCheck(item)} style={{cursor: 'pointer', borderTop: index ? '1px solid #e8e8e8' : ''}}> x.id).includes(item.id)}/> - {item.key} - {item.name} - {item.desc} + {item.key} + {item.name} ))} diff --git a/spug_web/src/pages/config/setting/Form.js b/spug_web/src/pages/config/setting/Form.js index d262848..ef715ac 100644 --- a/spug_web/src/pages/config/setting/Form.js +++ b/spug_web/src/pages/config/setting/Form.js @@ -5,11 +5,11 @@ */ import React, { useState } from 'react'; import { observer } from 'mobx-react'; -import { Modal, Form, Input, Checkbox, Switch, Row, Col, message } from 'antd'; +import { Modal, Form, Input, Checkbox, Row, Col, message } from 'antd'; import http from 'libs/http'; import store from './store'; import envStore from '../environment/store' -import styles from './index.module.css'; +import styles from './index.module.less'; import lds from 'lodash'; export default observer(function () { @@ -21,7 +21,6 @@ export default observer(function () { function handleSubmit() { setLoading(true); const formData = form.getFieldsValue(); - formData['is_public'] = store.type === 'src' ? false : formData['is_public']; let request; if (isModify) { formData['id'] = store.record.id; @@ -62,38 +61,29 @@ export default observer(function () { confirmLoading={loading} onOk={handleSubmit}>
- - + + - + - {store.type === 'app' && ( - 什么是公共/私有配置?}> - + {isModify ? null : ( + + {envStore.records.map((item, index) => ( + handleEnvCheck(item.id)} + style={{cursor: 'pointer', borderTop: index ? '1px solid #e8e8e8' : ''}}> + + {item.key} + {item.name} + + ))} )} - - {envStore.records.map((item, index) => ( - handleEnvCheck(item.id)} - style={{cursor: 'pointer', borderTop: index ? '1px solid #e8e8e8' : ''}}> - - {item.key} - {item.name} - {item.desc} - - ))} - ) diff --git a/spug_web/src/pages/config/setting/JSONView.js b/spug_web/src/pages/config/setting/JSONView.js index 7ef909e..61f9bde 100644 --- a/spug_web/src/pages/config/setting/JSONView.js +++ b/spug_web/src/pages/config/setting/JSONView.js @@ -5,9 +5,8 @@ */ import React from 'react'; import { observer } from 'mobx-react'; -import { SaveOutlined, EditOutlined } from '@ant-design/icons'; -import { Button, message } from 'antd'; -import { AuthButton, ACEditor } from 'components'; +import { message } from 'antd'; +import { ACEditor } from 'components'; import { http } from 'libs'; import store from './store'; @@ -16,8 +15,6 @@ class JSONView extends React.Component { constructor(props) { super(props); this.state = { - loading: false, - readOnly: true, body: '' } } @@ -31,53 +28,35 @@ class JSONView extends React.Component { for (let item of store.records) { body[item.key] = item.value } - this.setState({readOnly: true, body: JSON.stringify(body, null, 2)}) + this.setState({body: JSON.stringify(body, null, 2)}) }; handleSubmit = () => { try { const data = JSON.parse(this.state.body); - this.setState({loading: true}); const formData = {type: store.type, o_id: store.id, env_id: store.env.id, data}; - http.post('/api/config/parse/json/', formData) + return http.post('/api/config/parse/json/', formData) .then(res => { message.success('保存成功'); store.fetchRecords().then(this.updateValue) }) - .finally(() => this.setState({loading: false})) } catch (err) { message.error('解析JSON失败,请检查输入内容') } }; render() { - const {body, readOnly, loading} = this.state; + const {body} = this.state; return ( -
- this.setState({body: v})}/> - {readOnly && } - type="link" - size="large" - auth={`config.${store.type}.edit_config`} - style={{position: 'absolute', top: 0, right: 0}} - onClick={() => this.setState({readOnly: false})}>编辑} - {readOnly || } -
+ this.setState({body: v})}/> ) } } diff --git a/spug_web/src/pages/config/setting/TableView.js b/spug_web/src/pages/config/setting/TableView.js index 8f3d1f2..0d462a3 100644 --- a/spug_web/src/pages/config/setting/TableView.js +++ b/spug_web/src/pages/config/setting/TableView.js @@ -5,30 +5,25 @@ */ import React from 'react'; import { observer } from 'mobx-react'; -import { LockOutlined } from '@ant-design/icons'; import { Table, Modal, Tooltip, message } from 'antd'; import { Action } from 'components'; import ComForm from './Form'; -import { http, hasPermission } from 'libs'; +import { http, hasPermission, includes } from 'libs'; import store from './store'; -import styles from './index.module.css'; +import styles from './index.module.less'; @observer class TableView extends React.Component { - lockIcon = - - ; - columns = [{ title: 'Key', key: 'key', render: info => { - let prefix = (store.type === 'app' && info.is_public === false) ? this.lockIcon : null; - let content = info.desc ? {info.key} : info.key; - return - {prefix} - {content} - + const value = `_SPUG_${store.obj.key}_${info.key}`.toUpperCase() + return info.desc ? ( + + {value} + + ) : value } }, { title: 'Value', @@ -48,7 +43,8 @@ class TableView extends React.Component { className: hasPermission(`config.${store.type}.edit_config`) ? null : 'none', render: info => ( - store.showForm(info)}>编辑 + store.showForm(info)}>编辑 item['key'].toLowerCase().includes(store.f_name.toLowerCase())) - } + if (store.f_name) data = data.filter(x => includes(x.key, store.f_name)) return ( { - this.setState({loading: true}); const formData = {type: store.type, o_id: store.id, env_id: store.env.id, data: this.state.body}; - http.post('/api/config/parse/text/', formData) + return http.post('/api/config/parse/text/', formData) .then(res => { message.success('保存成功'); store.fetchRecords().then(this.updateValue) }) - .finally(() => this.setState({loading: false})) }; render() { - const {body, loading, readOnly} = this.state; + const {body} = this.state; return ( -
- this.setState({body: v})}/> - {readOnly && } - type="link" - size="large" - auth={`config.${store.type}.edit_config`} - style={{position: 'absolute', top: 0, right: 0}} - onClick={() => this.setState({readOnly: false})}>编辑} - {readOnly || } -
+ this.setState({body: v})}/> ) } } diff --git a/spug_web/src/pages/config/setting/index.js b/spug_web/src/pages/config/setting/index.js index 64a3097..11b24fe 100644 --- a/spug_web/src/pages/config/setting/index.js +++ b/spug_web/src/pages/config/setting/index.js @@ -12,10 +12,12 @@ import { NumberOutlined, TableOutlined, UnorderedListOutlined, - PlusOutlined + PlusOutlined, + EditOutlined, + SaveOutlined } from '@ant-design/icons'; import envStore from '../environment/store'; -import styles from './index.module.css'; +import styles from './index.module.less'; import history from 'libs/history'; import { AuthDiv, AuthButton, Breadcrumb } from 'components'; import DiffConfig from './DiffConfig'; @@ -24,8 +26,6 @@ import TextView from './TextView'; import JSONView from './JSONView'; import Record from './Record'; import store from './store'; -import appStore from '../app/store'; -import srcStore from '../service/store'; @observer class Index extends React.Component { @@ -34,28 +34,36 @@ class Index extends React.Component { this.textView = null; this.JSONView = null; this.state = { - view: '1' + view: '1', + editable: false, + loading: false, } } componentDidMount() { const {type, id} = this.props.match.params; - store.type = type; - store.id = id; - if (envStore.records.length === 0) { - envStore.fetchRecords().then(() => { + store.initial(type, id) + .then(() => { if (envStore.records.length === 0) { - Modal.error({ - title: '无可用环境', - content:
配置依赖应用的运行环境,请在 环境管理 中创建环境。
+ envStore.fetchRecords().then(() => { + if (envStore.records.length === 0) { + Modal.error({ + title: '无可用环境', + content:
配置依赖应用的运行环境,请在 环境管理 中创建环境。
+ }) + } else { + this.updateEnv() + } }) } else { this.updateEnv() } }) - } else { - this.updateEnv() - } + } + + componentWillUnmount() { + store.obj = {} + store.records = [] } updateEnv = (env) => { @@ -70,16 +78,25 @@ class Index extends React.Component { }) }; + handleSubmit = () => { + this.setState({loading: true}) + const ref = this.state.view === '2' ? this.textView : this.JSONView + ref.handleSubmit() + .then(() => this.setState({editable: false})) + .finally(() => this.setState({loading: false})) + } + render() { - const {view} = this.state; + const {view, editable, loading} = this.state; const isApp = store.type === 'app'; - const record = isApp ? appStore.record : srcStore.record; return ( - + } + onClick={store.showRecord}>更改历史)}> + 首页 配置中心 history.goBack()}>{isApp ? '应用配置' : '服务配置'} - {record.name} + {store.obj.name}
@@ -108,26 +125,37 @@ class Index extends React.Component { - store.f_name = e.target.value} placeholder="请输入"/> + store.f_name = e.target.value} + placeholder="请输入"/> - } - onClick={() => store.showForm()}>新增配置 - + {['2', '3'].includes(view) ? editable ? ( + + ) : ( + } + type="primary" + auth={`config.${store.type}.edit_config`} + onClick={() => this.setState({editable: true})}>编辑 + ) : ( + } + onClick={() => store.showForm()}>新增配置 + )} + {view === '1' && } - {view === '2' && this.textView = ref}/>} - {view === '3' && this.JSONView = ref}/>} + {view === '2' && this.textView = ref} editable={editable}/>} + {view === '3' && this.JSONView = ref} editable={editable}/>}
{store.recordVisible && } diff --git a/spug_web/src/pages/config/setting/index.module.css b/spug_web/src/pages/config/setting/index.module.css deleted file mode 100644 index 56cd2b8..0000000 --- a/spug_web/src/pages/config/setting/index.module.css +++ /dev/null @@ -1,35 +0,0 @@ -.container { - display: flex; - background-color: #fff; - padding: 16px 0; -} -.left { - width: 250px; - border-right: 1px solid #e8e8e8; -} -.right { - flex: 1; - padding: 8px 40px; -} - -.title { - margin-bottom: 12px; - color: rgba(0, 0, 0, .85); - font-weight: 500; - font-size: 20px; - line-height: 28px; -} - -.form { - max-width: 320px; -} - -.ellipsis { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.value { - word-break: break-all; -} \ No newline at end of file diff --git a/spug_web/src/pages/config/setting/index.module.less b/spug_web/src/pages/config/setting/index.module.less new file mode 100644 index 0000000..c070128 --- /dev/null +++ b/spug_web/src/pages/config/setting/index.module.less @@ -0,0 +1,48 @@ +.container { + display: flex; + background-color: #fff; + padding: 16px 0; +} + +.left { + width: 250px; + border-right: 1px solid #e8e8e8; +} + +.right { + flex: 1; + padding: 8px 24px; +} + +.historyBtn { + background-color: orange; + border-color: orange; + + &:hover { + background-color: orange; + border-color: orange; + opacity: 0.9; + } +} + +.title { + margin-bottom: 12px; + color: rgba(0, 0, 0, .85); + font-weight: 500; + font-size: 20px; + line-height: 28px; +} + +.form { + max-width: 320px; +} + +.ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.value { + word-break: break-all; +} \ No newline at end of file diff --git a/spug_web/src/pages/config/setting/store.js b/spug_web/src/pages/config/setting/store.js index 5e3f477..319be66 100644 --- a/spug_web/src/pages/config/setting/store.js +++ b/spug_web/src/pages/config/setting/store.js @@ -10,6 +10,7 @@ class Store { @observable records = []; @observable record = {}; @observable env = {}; + @observable obj = {}; @observable type; @observable id; @observable isFetching = false; @@ -19,6 +20,15 @@ class Store { @observable f_name; + initial = (type, id) => { + this.type = type + this.id = id + const url = type === 'app' ? '/api/app/' : '/api/config/service/' + this.isFetching = true + return http.get(url, {params: {id}}) + .then(res => this.obj = res) + } + fetchRecords = () => { const params = {type: this.type, id: this.id, env_id: this.env.id}; this.isFetching = true;