diff --git a/spug_api/apps/deploy/app.py b/spug_api/apps/deploy/app.py index a0f6d97..3ff52cb 100644 --- a/spug_api/apps/deploy/app.py +++ b/spug_api/apps/deploy/app.py @@ -16,6 +16,7 @@ args = AttrDict( group=Argument('group', help='请选择分组名称!'), name=Argument('name', help='请输入应用名称!'), desc=Argument('desc', help='请输入应用描述!'), + notify_way_id=Argument('notify_way_id', type=int, help='请选择通知方式!'), identify=Argument('identify', help='请输入应用标识!'), image_id=Argument('image_id', type=int, help='请选择应用使用的Docker镜像!') ) @@ -36,7 +37,13 @@ def get(): apps = query.filter(App.id.in_(app_ids.split(','))).all() else: apps = [] - return json_response(apps) + data_list = [] + for i in apps: + data = i.to_json() + data['notify_way_name'] = i.notify_way.name if i.notify_way else '' + data['images'] = i.image.name + data_list.append(data) + return json_response(data_list) @blueprint.route('/', methods=['POST']) diff --git a/spug_api/apps/deploy/image.py b/spug_api/apps/deploy/image.py index 3d3990b..bf55704 100644 --- a/spug_api/apps/deploy/image.py +++ b/spug_api/apps/deploy/image.py @@ -13,6 +13,7 @@ blueprint = Blueprint(__name__, __name__) def get(): return json_response(Image.query.all()) + @blueprint.route('/add', methods=['POST']) @require_permission('publish_image_add') def add(): diff --git a/spug_api/apps/deploy/models.py b/spug_api/apps/deploy/models.py index a0eaf38..ba7be50 100644 --- a/spug_api/apps/deploy/models.py +++ b/spug_api/apps/deploy/models.py @@ -1,5 +1,6 @@ from public import db from libs.model import ModelMixin +from apps.system.models import NotifyWay class Image(db.Model, ModelMixin): @@ -73,8 +74,10 @@ class App(db.Model, ModelMixin): group = db.Column(db.String(50)) image_id = db.Column(db.Integer, db.ForeignKey('deploy_images.id')) + notify_way_id = db.Column(db.Integer, db.ForeignKey('notify_way.id')) image = db.relationship(Image) + notify_way = db.relationship(NotifyWay) menus = db.relationship('DeployMenu', secondary='deploy_app_menu_rel') fields = db.relationship('DeployField', secondary='deploy_app_field_rel') @@ -120,6 +123,7 @@ class DeployMenu(db.Model, ModelMixin): def __repr__(self): return '' % self.name + class AppMenuRel(db.Model, ModelMixin): __tablename__ = 'deploy_app_menu_rel' diff --git a/spug_api/apps/deploy/publish.py b/spug_api/apps/deploy/publish.py index b166c67..3cc45da 100644 --- a/spug_api/apps/deploy/publish.py +++ b/spug_api/apps/deploy/publish.py @@ -15,6 +15,9 @@ import tarfile import uuid import time import os +from apps.system.models import NotifyWay +from libs.utils import send_ding_msg + blueprint = Blueprint(__name__, __name__) @@ -113,10 +116,12 @@ def do_update(q, form, host_id): send_message('启动容器成功!', update=True) # 执行发布操作 send_message('正在执行应用更新 . . . ') + send_publish_message(pro.notify_way_id, pro.name + ' 开始更新 . . .') exec_code, exec_output = ctr.exec_command_with_base64(hooks['应用发布'], form.deploy_message, timeout=120, with_exit_code=True) if exec_code != 0: send_message('执行应用更新失败,退出状态码:{0}'.format(exec_code), level='error') + send_publish_message(pro.notify_way_id, pro.name + ' 发布失败!') send_message(exec_output, level='console') return else: @@ -127,6 +132,7 @@ def do_update(q, form, host_id): ctr.restart(timeout=3) send_message('重启容器成功!', update=True) # 整个流程正常结束 + send_publish_message(pro.notify_way_id, pro.name + ' 发布成功') send_message('完成发布!', level='success') deploy_success = True except Exception as e: @@ -154,3 +160,9 @@ class PublishMessage(object): cls.start_time = time.time() data['duration'] = duration q.put(data) + + +def send_publish_message(notify_way_id, message): + if notify_way_id: + notice_value = NotifyWay.query.filter_by(id=notify_way_id).first() + send_ding_msg(token=notice_value.value, contacts=[], msg=message) diff --git a/spug_api/apps/setting/models.py b/spug_api/apps/setting/models.py index 1e66ccd..9635f99 100644 --- a/spug_api/apps/setting/models.py +++ b/spug_api/apps/setting/models.py @@ -12,3 +12,15 @@ class GlobalConfig(db.Model, ModelMixin): def __repr__(self): return '' % self.name + + +class NoticeWay(db.Model, ModelMixin): + __tablename__ = 'notice_way' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), unique=True) + value = db.Column(db.Text) + desc = db.Column(db.String(255)) + + def __repr__(self): + return '' % self.name \ No newline at end of file diff --git a/spug_api/apps/system/__init__.py b/spug_api/apps/system/__init__.py new file mode 100644 index 0000000..50cde1f --- /dev/null +++ b/spug_api/apps/system/__init__.py @@ -0,0 +1,5 @@ +from apps.system import notify + + +def register_blueprint(app): + app.register_blueprint(notify.blueprint, url_prefix='/system/notify') diff --git a/spug_api/apps/system/models.py b/spug_api/apps/system/models.py new file mode 100644 index 0000000..47ef613 --- /dev/null +++ b/spug_api/apps/system/models.py @@ -0,0 +1,14 @@ +from public import db +from libs.model import ModelMixin + + +class NotifyWay(db.Model, ModelMixin): + __tablename__ = 'notify_way' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), unique=True) + value = db.Column(db.Text) + desc = db.Column(db.String(255)) + + def __repr__(self): + return '' % self.name \ No newline at end of file diff --git a/spug_api/apps/system/notify.py b/spug_api/apps/system/notify.py new file mode 100644 index 0000000..77e06ff --- /dev/null +++ b/spug_api/apps/system/notify.py @@ -0,0 +1,60 @@ +from flask import Blueprint, request +from libs.tools import json_response, JsonParser, Argument +from .models import NotifyWay +from libs.decorators import require_permission + + +blueprint = Blueprint(__name__, __name__) + + +@blueprint.route('/', methods=['GET']) +@require_permission('system_notify_view | system_notify_add | system_notify_edit | system_notify_del') +def get(): + form, error = JsonParser(Argument('page', type=int, default=1, required=False), + Argument('pagesize', type=int, default=10, required=False), + Argument('notify_query', type=dict, required=False), ).parse(request.args) + if error is None: + notify_data = NotifyWay.query + if form.page == -1: + return json_response({'data': [x.to_json() for x in notify_data.all()], 'total': -1}) + if form.notify_query.get('name_field'): + notify_data = notify_data.filter(NotifyWay.name.like('%{}%'.format(form.notify_query['name_field']))) + + result = notify_data.limit(form.pagesize).offset((form.page - 1) * form.pagesize).all() + return json_response({'data': [x.to_json() for x in result], 'total': notify_data.count()}) + return json_response(message=error) + + +@blueprint.route('/', methods=['POST']) +@require_permission('system_notify_add') +def post(): + form, error = JsonParser('name', 'value', + Argument('desc', nullable=True)).parse() + if error is None: + notify_is_exist = NotifyWay.query.filter_by(name=form.name).first() + if notify_is_exist: + return json_response(message="通知名称已存在") + NotifyWay(**form).save() + return json_response() + return json_response(message=error) + + +@blueprint.route('/', methods=['DELETE']) +@require_permission('system_notify_del') +def delete(u_id): + NotifyWay.query.get_or_404(u_id).delete() + return json_response(), 204 + + +@blueprint.route('/', methods=['PUT']) +@require_permission('system_notify_edit') +def put(n_id): + form, error = JsonParser('name', 'value', + Argument('desc', nullable=True)).parse() + + if error is None: + notify_info = NotifyWay.query.get_or_404(n_id) + if not notify_info.update(**form): + notify_info.save() + return json_response(notify_info) + return json_response(message=error) diff --git a/spug_api/libs/sql/permissions.sql b/spug_api/libs/sql/permissions.sql index 4f9ad6a..e742e0c 100644 --- a/spug_api/libs/sql/permissions.sql +++ b/spug_api/libs/sql/permissions.sql @@ -112,4 +112,10 @@ INSERT INTO account_permissions (id, name, `desc`) VALUES (1202, 'publish_field_ INSERT INTO account_permissions (id, name, `desc`) VALUES (1203, 'publish_field_edit', '自定义字段 - 编辑'); INSERT INTO account_permissions (id, name, `desc`) VALUES (1204, 'publish_field_del', '自定义字段 - 删除'); INSERT INTO account_permissions (id, name, `desc`) VALUES (1205, 'publish_field_rel_view', '关联配置 - 查看'); -INSERT INTO account_permissions (id, name, `desc`) VALUES (1206, 'publish_field_rel_edit', '关联配置 - 编辑'); \ No newline at end of file +INSERT INTO account_permissions (id, name, `desc`) VALUES (1206, 'publish_field_rel_edit', '关联配置 - 编辑'); + +-- 系统管理 -> 通知设置 +INSERT INTO account_permissions (id, name, `desc`) VALUES (1301, 'system_notify_view', '系统通知列表'); +INSERT INTO account_permissions (id, name, `desc`) VALUES (1302, 'system_notify_add', '添加通知设置'); +INSERT INTO account_permissions (id, name, `desc`) VALUES (1303, 'system_notify_edit', '编辑通知设置'); +INSERT INTO account_permissions (id, name, `desc`) VALUES (1304, 'system_notify_del', '删除通知设置'); \ No newline at end of file diff --git a/spug_api/libs/sql/update.sql b/spug_api/libs/sql/update.sql new file mode 100644 index 0000000..bf0221b --- /dev/null +++ b/spug_api/libs/sql/update.sql @@ -0,0 +1,20 @@ +-- start update 1.0.0 -- +CREATE TABLE `notify_way` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(50) DEFAULT NULL, + `desc` varchar(255) DEFAULT NULL, + `value` text, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `name` (`name`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT; + +ALTER TABLE `spug`.`deploy_apps` ADD COLUMN `notify_way_id` int(11) NULL DEFAULT NULL AFTER `group`; +ALTER TABLE `spug`.`deploy_apps` ADD CONSTRAINT `deploy_apps_ibfk_2` FOREIGN KEY (`notify_way_id`) REFERENCES `spug`.`notify_way` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; +ALTER TABLE `spug`.`deploy_apps` ADD INDEX `notify_way_id`(`notify_way_id`) USING BTREE; + +INSERT INTO account_permissions (id, name, `desc`) VALUES (1301, 'system_notify_view', '系统通知列表'); +INSERT INTO account_permissions (id, name, `desc`) VALUES (1302, 'system_notify_add', '添加通知设置'); +INSERT INTO account_permissions (id, name, `desc`) VALUES (1303, 'system_notify_edit', '编辑通知设置'); +INSERT INTO account_permissions (id, name, `desc`) VALUES (1304, 'system_notify_del', '删除通知设置'); + +-- end update 1.0.0 -- \ No newline at end of file diff --git a/spug_api/libs/utils.py b/spug_api/libs/utils.py index 6b432b7..7fcd29c 100644 --- a/spug_api/libs/utils.py +++ b/spug_api/libs/utils.py @@ -223,3 +223,21 @@ class Git(object): def __repr__(self): return '' % self.work_tree + + +def send_ding_msg(token_url='', contacts=[], msg=''): + payload = { + "msgtype": "text", + "text": { + "content": msg, + "isAtAll": False + }, + "at": { + "atMobiles": contacts + } + } + req = requests.post(token_url, json=payload) + if req.status_code == 200: + return True + else: + return False diff --git a/spug_api/main.py b/spug_api/main.py index e4de709..b72a26f 100644 --- a/spug_api/main.py +++ b/spug_api/main.py @@ -9,6 +9,7 @@ from apps import apis from apps import schedule from apps import home from apps import common +from apps import system middleware.init_app(app) account.register_blueprint(app) @@ -19,6 +20,7 @@ apis.register_blueprint(app) schedule.register_blueprint(app) home.register_blueprint(app) common.register_blueprint(app) +system.register_blueprint(app) if __name__ == '__main__': diff --git a/spug_api/requirements.txt b/spug_api/requirements.txt index 5a4e3ce..6a3ba8c 100644 --- a/spug_api/requirements.txt +++ b/spug_api/requirements.txt @@ -9,7 +9,7 @@ click==6.7 cryptography==2.0.3 docker==2.5.1 docker-pycreds==0.2.1 -Flask==0.12.2 +Flask ~> 0.12.3 Flask-Excel==0.0.7 Flask-SQLAlchemy==2.2 idna==2.6 diff --git a/spug_web/src/components/account/Permission.vue b/spug_web/src/components/account/Permission.vue index 506d3ff..5801b4f 100644 --- a/spug_web/src/components/account/Permission.vue +++ b/spug_web/src/components/account/Permission.vue @@ -187,6 +187,13 @@ + + 系统管理 + 通知设置 + + + +
diff --git a/spug_web/src/components/account/routes.js b/spug_web/src/components/account/routes.js index dc99ddd..4f742cc 100644 --- a/spug_web/src/components/account/routes.js +++ b/spug_web/src/components/account/routes.js @@ -1,5 +1,6 @@ /** - * Created by aka on 2017/5/22. + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug */ import User from './User.vue'; diff --git a/spug_web/src/components/assets/routes.js b/spug_web/src/components/assets/routes.js index 70e1838..265b086 100644 --- a/spug_web/src/components/assets/routes.js +++ b/spug_web/src/components/assets/routes.js @@ -1,5 +1,6 @@ /** - * Created by aka on 2017/5/22. + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug */ import Host from './Host.vue'; diff --git a/spug_web/src/components/configuration/routes.js b/spug_web/src/components/configuration/routes.js index 5a519f9..1ed474d 100644 --- a/spug_web/src/components/configuration/routes.js +++ b/spug_web/src/components/configuration/routes.js @@ -1,5 +1,6 @@ /** - * Created by aka on 2017/5/22. + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug */ import App from './App.vue' import Service from './Service.vue' diff --git a/spug_web/src/components/publish/App.vue b/spug_web/src/components/publish/App.vue index ba3c86e..94ef93c 100644 --- a/spug_web/src/components/publish/App.vue +++ b/spug_web/src/components/publish/App.vue @@ -14,19 +14,31 @@ + @@ -43,6 +55,13 @@ + + + + + + @@ -62,8 +81,10 @@ 保存
- - + + @@ -73,13 +94,14 @@ import AppSetting from './AppSetting.vue' import AppConfig from './AppConfig.vue' import AppMenu from './AppMenu.vue' + export default { components: { 'app-setting': AppSetting, 'app-config': AppConfig, 'app-menu': AppMenu }, - data () { + data() { return { group: null, dialogAddVisible: false, @@ -92,23 +114,25 @@ environments: undefined, tableData: [], images: [], + notifyWays: [], groups: [], } }, methods: { - init_form () { + init_form() { return { identify: '', name: '', desc: '', group: '', + notify_way_id: '', image_id: '' } }, - go_deploy (row) { + go_deploy(row) { this.$router.push({name: 'publish_deploy', params: {app_id: row.id}}) }, - fetch (force) { + fetch(force) { this.tableLoading = true; let api_uri = '/api/deploy/apps/'; if (this.group) api_uri += '?group=' + this.group; @@ -119,28 +143,36 @@ this.groups = res.result }, res => this.$layer_message(res.result)) }, - fetchEnvironments () { + fetchEnvironments() { if (this.environments === undefined) { this.$http.get('/api/configuration/environments/').then(res => { this.environments = res.result }, res => this.$layer_message(res.result)) } }, - fetchImages () { + fetchImages() { this.$http.get('/api/deploy/images/').then(res => this.images = res.result, res => this.$layer_message(res.result)) }, - do_action (command) { + fetchNotifyWay() { + this.$http.get('/api/system/notify/', {params: {page: -1}}).then(res=>{ + this.notifyWays = res.result.data; + }, res => this.$layer_message(res.result)) + }, + do_action(command) { let [action, index] = command.split(' '); this.form = this.$deepCopy(this.tableData[index]); if (action === 'edit') { + if (this.images.length === 0) this.fetchImages(); - this.dialogAddVisible = true + this.dialogAddVisible = true; + this.fetchNotifyWay(); } else if (action === 'del') { this.$confirm(`此操作将永久删除 ${this.form.name},是否继续?`, '删除确认', {type: 'warning'}).then(() => { this.$http.delete(`/api/deploy/apps/${this.form.id}`).then(() => { this.fetch() }, res => this.$layer_message(res.result)) - }).catch(() => {}) + }).catch(() => { + }) } else if (action === 'set') { this.fetchEnvironments(); this.dialogSetVisible = true @@ -151,7 +183,7 @@ this.dialogMenuVisible = true } }, - saveCommit () { + saveCommit() { this.btnSaveLoading = true; let request; if (this.form.id) { @@ -164,12 +196,13 @@ this.fetch(true) }, res => this.$layer_message(res.result)).finally(() => this.btnSaveLoading = false) }, - addOpen () { + addOpen() { + // this.$router.push({name: 'app_add'}) this.form = this.init_form(); this.dialogAddVisible = true; if (this.images.length === 0) this.fetchImages() }, - addGroup () { + addGroup() { this.$prompt('请输入新分组名称', '提示', { inputPattern: /.+/, inputErrorMessage: '请输入分组名称!' @@ -183,7 +216,7 @@ this.$emit('routerChange') } }, - created () { + created() { this.fetch(true) } } diff --git a/spug_web/src/components/publish/AppAdd.vue b/spug_web/src/components/publish/AppAdd.vue new file mode 100644 index 0000000..98ff71c --- /dev/null +++ b/spug_web/src/components/publish/AppAdd.vue @@ -0,0 +1,623 @@ + + + + + + \ No newline at end of file diff --git a/spug_web/src/components/publish/HostRel.vue b/spug_web/src/components/publish/HostRel.vue new file mode 100644 index 0000000..63588d5 --- /dev/null +++ b/spug_web/src/components/publish/HostRel.vue @@ -0,0 +1,92 @@ + + + \ No newline at end of file diff --git a/spug_web/src/components/publish/routes.js b/spug_web/src/components/publish/routes.js index a42ec11..fe79f5f 100644 --- a/spug_web/src/components/publish/routes.js +++ b/spug_web/src/components/publish/routes.js @@ -1,8 +1,10 @@ /** - * Created by aka on 2017/5/22. + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug */ import App from './App.vue' +import AppAdd from './AppAdd.vue' import Deploy from './Deploy.vue' import Image from './Image.vue' import Field from './Field.vue' @@ -16,6 +18,14 @@ export default [ permission: 'publish_app_view' } }, + { + path: 'app_add', + name: 'app_add', + component: AppAdd, + meta: { + permission: 'publish_app_add' + } + }, { path: 'deploy/:app_id', name: 'publish_deploy', diff --git a/spug_web/src/components/schedule/routes.js b/spug_web/src/components/schedule/routes.js index 65a97da..5e63816 100644 --- a/spug_web/src/components/schedule/routes.js +++ b/spug_web/src/components/schedule/routes.js @@ -1,5 +1,6 @@ /** - * Created by aka on 2017/6/28. + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug */ import Job from './Job.vue' diff --git a/spug_web/src/components/system/Notify.vue b/spug_web/src/components/system/Notify.vue new file mode 100644 index 0000000..1661f17 --- /dev/null +++ b/spug_web/src/components/system/Notify.vue @@ -0,0 +1,187 @@ + + + diff --git a/spug_web/src/components/system/routes.js b/spug_web/src/components/system/routes.js new file mode 100644 index 0000000..88eda8c --- /dev/null +++ b/spug_web/src/components/system/routes.js @@ -0,0 +1,17 @@ +/** + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug + */ + +import Notify from './Notify.vue'; + + +export default [ + { + path: 'notify', + component: Notify, + meta: { + permission: 'system_notify_view' + } + } +] diff --git a/spug_web/src/config/env.js b/spug_web/src/config/env.js index 8a32429..878ad4d 100644 --- a/spug_web/src/config/env.js +++ b/spug_web/src/config/env.js @@ -1,3 +1,7 @@ +/** + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug + */ // 环境配置 // 后端API接口地址 diff --git a/spug_web/src/config/menu.js b/spug_web/src/config/menu.js index d132ca7..4dbed57 100644 --- a/spug_web/src/config/menu.js +++ b/spug_web/src/config/menu.js @@ -1,3 +1,7 @@ +/** + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug + */ let menu = { menus: [ { @@ -44,6 +48,11 @@ let menu = { {key: 'alarm_contact', desc: '报警联系人'}, ] }, + { + key: '8', desc: '系统管理', icon: 'el-icon-setting', permission: 'system_notify_view', subs: [ + {key: '/system/notify', permission: 'system_notify_view', desc: '通知设置'}, + ] + }, ] }; diff --git a/spug_web/src/router.js b/spug_web/src/router.js index a1aab72..c1d84f6 100644 --- a/spug_web/src/router.js +++ b/spug_web/src/router.js @@ -1,5 +1,6 @@ /** - * Created by Yooke on 2017/2/13. + * Created by zyupo on 2017/04/20. + * https://github.com/openspug/spug */ import Home from './components/Home.vue' @@ -12,6 +13,7 @@ import publish_routes from './components/publish/routes' import configuration_routes from './components/configuration/routes' import assets_routes from './components/assets/routes' import schedule_routes from './components/schedule/routes' +import system_routes from './components/system/routes' const routes = [ { @@ -44,6 +46,10 @@ const routes = [ path: 'schedule', routes: schedule_routes }, + { + path: 'system', + routes: system_routes + }, { path: '*', redirect: '/'