mirror of https://github.com/openspug/spug
A - 通用 - 添加系统设置,添加发布钉钉通知。
parent
a614e377ed
commit
892db7b9e1
|
@ -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'])
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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 '<DeployMenu %r>' % self.name
|
||||
|
||||
|
||||
class AppMenuRel(db.Model, ModelMixin):
|
||||
__tablename__ = 'deploy_app_menu_rel'
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -12,3 +12,15 @@ class GlobalConfig(db.Model, ModelMixin):
|
|||
|
||||
def __repr__(self):
|
||||
return '<GlobalConfig %r>' % 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 '<NoticeWay %r>' % self.name
|
|
@ -0,0 +1,5 @@
|
|||
from apps.system import notify
|
||||
|
||||
|
||||
def register_blueprint(app):
|
||||
app.register_blueprint(notify.blueprint, url_prefix='/system/notify')
|
|
@ -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 '<NotifyWay %r>' % self.name
|
|
@ -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('/<int:u_id>', 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('/<int:n_id>', 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)
|
|
@ -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', '关联配置 - 编辑');
|
||||
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', '删除通知设置');
|
|
@ -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 --
|
|
@ -223,3 +223,21 @@ class Git(object):
|
|||
|
||||
def __repr__(self):
|
||||
return '<Git %r>' % 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
|
||||
|
|
|
@ -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__':
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -187,6 +187,13 @@
|
|||
<tag-td :item="codes['job_task_log']"></tag-td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">系统管理</td>
|
||||
<td rowspan="1">通知设置</td>
|
||||
<tag-td :item="codes['system_notify_view']"></tag-td>
|
||||
<tag-td :item="codes['system_notify_add']"></tag-td>
|
||||
<tag-td :item="codes['system_notify_edit']"></tag-td>
|
||||
</tr>
|
||||
</table>
|
||||
<el-table v-else v-loading="loading"></el-table>
|
||||
<div slot="footer">
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -14,19 +14,31 @@
|
|||
<el-table :data="tableData" v-loading="tableLoading" style="width: 100%; margin-top: 20px">
|
||||
<el-table-column prop="group" label="分组" min-width="100"></el-table-column>
|
||||
<el-table-column prop="name" label="名称" min-width="100"></el-table-column>
|
||||
<el-table-column prop="notify_way_name" label="通知名称"></el-table-column>
|
||||
<el-table-column prop="desc" label="描述" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="primary" @click="go_deploy(scope.row)" style="margin-right: 15px">管理
|
||||
</el-button>
|
||||
<el-dropdown trigger="click" @command="do_action" v-if="has_permission('publish_app_edit|publish_app_del|publish_app_ctr_view|publish_app_var_view|publish_app_menu_view')">
|
||||
<el-dropdown trigger="click" @command="do_action"
|
||||
v-if="has_permission('publish_app_edit|publish_app_del|publish_app_ctr_view|publish_app_var_view|publish_app_menu_view')">
|
||||
<el-button type="text">更多<i class="el-icon-caret-bottom el-icon--right"></i></el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-if="has_permission('publish_app_edit')" :command="`edit ${scope.$index}`">编辑</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_del')" :command="`del ${scope.$index}`">删除</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_ctr_view')" divided :command="`set ${scope.$index}`">容器设置</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_var_view')" :command="`env ${scope.$index}`">应用设置</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_menu_view')" :command="`menu ${scope.$index}`">菜单管理</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_edit')"
|
||||
:command="`edit ${scope.$index}`">编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_del')" :command="`del ${scope.$index}`">
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_ctr_view')" divided
|
||||
:command="`set ${scope.$index}`">容器设置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_var_view')"
|
||||
:command="`env ${scope.$index}`">应用设置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="has_permission('publish_app_menu_view')"
|
||||
:command="`menu ${scope.$index}`">菜单管理
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
@ -43,6 +55,13 @@
|
|||
<el-form-item label="应用名称" required>
|
||||
<el-input v-model="form.name" placeholder="请输入应用名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="通知名称" required>
|
||||
<el-select v-model="form.notify_way_id" placeholder="选择通知名称">
|
||||
<el-option v-for="n in notifyWays" :value="n.id" :label="n.name"
|
||||
:key="n.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="应用描述" required>
|
||||
<el-input type="textarea" autosize v-model="form.desc" placeholder="请输入应用描述"></el-input>
|
||||
</el-form-item>
|
||||
|
@ -62,8 +81,10 @@
|
|||
<el-button type="primary" @click="saveCommit" :loading="btnSaveLoading">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<app-setting v-if="dialogSetVisible" :owner="form" :environments="environments" @close="dialogSetVisible = false"></app-setting>
|
||||
<app-config v-if="dialogEnvVisible" :owner="form" :environments="environments" @close="dialogEnvVisible = false"></app-config>
|
||||
<app-setting v-if="dialogSetVisible" :owner="form" :environments="environments"
|
||||
@close="dialogSetVisible = false"></app-setting>
|
||||
<app-config v-if="dialogEnvVisible" :owner="form" :environments="environments"
|
||||
@close="dialogEnvVisible = false"></app-config>
|
||||
<app-menu v-if="dialogMenuVisible" :owner="form" @close="dialogMenuVisible = false"></app-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,623 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-steps :active="appStep">
|
||||
<el-step title="项目信息" description="基本信息"></el-step>
|
||||
<el-step title="容器设置" description="容器启动端口,目录,主机名等设置"></el-step>
|
||||
<el-step title="容器变量" description="容器内部参数变量设置"></el-step>
|
||||
<el-step title="发布参数" description="容器启动,项目更新部署相关设置"></el-step>
|
||||
<el-step title="主机设置" description="部署的主机"></el-step>
|
||||
<el-step title="完成" description="确认配置信息"></el-step>
|
||||
</el-steps>
|
||||
|
||||
<p style="text-align: center; margin: 30px 0 20px"></p>
|
||||
<div :style="base_display">
|
||||
<el-form ref="appInfo" :model="appInfo" :rules="baseRules" label-width="80px">
|
||||
<el-form-item label="项目类型" prop="group" required>
|
||||
<el-select v-model="appInfo.group" placeholder="项目类型">
|
||||
<el-option v-for="v in group_options" :value="v" :key="v"></el-option>
|
||||
</el-select>
|
||||
<el-button style="margin-left: 15px" type="text" @click="addAppType">添加项目类型</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="项目名称" required>
|
||||
<el-input v-model="appInfo.name" auto-complete="off" ></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="关联镜像" >
|
||||
<el-cascader placeholder="输入要搜索的镜像" clearable
|
||||
:options="app_images_options" filterable
|
||||
@focus="fetch_images_options"
|
||||
@change="bindImageTag"
|
||||
@active-item-change="loadImageTag" >
|
||||
<!--@active-item-change="loadImageTag" >-->
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item prop="desc" label="项目描述">
|
||||
<el-input v-model="appInfo.desc" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!--<p style="text-align: center; margin: 20px 0 20px"></p>-->
|
||||
<div>
|
||||
<el-table :data="dockerData" border style="width: 100%" :style="docker_display">
|
||||
<el-table-column fixed prop="desc" label="名称" ></el-table-column>
|
||||
<el-table-column v-for="item in this.environments" :key="item.id" :label="item.name" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.value[item.id]">{{scope.row.value[item.id]}}</span>
|
||||
<span v-else style="color: #D3DCE6">{{scope.row.tip}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="80px" v-if="has_permission('publish_app_ctr_edit')">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="primary"
|
||||
@click="dockerForm = scope.row; dockerVisible = true">编辑
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-dialog :title="`设置 - ${dockerForm.desc}`" :visible.sync="dockerVisible" :close-on-click-modal="false">
|
||||
<el-form :model="dockerForm" label-width="80px" label-position="left">
|
||||
<el-form-item v-for="env in this.environments" :key="env.id" :label="env.name">
|
||||
<el-input v-model="dockerForm.value[env.id]" :placeholder="dockerForm.tip"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dockerVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dockerVisible = false" :loading="dockerSaveLoading">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<div :style="env_display">
|
||||
<el-table :data="envData" border style="width: 100%" >
|
||||
<el-table-column fixed prop="name" label="名称" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-for="item in this.environments" :key="item.id" :label="item.name" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.value[item.id]">{{scope.row.value[item.id]}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="desc" label="描述" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column fixed="right" label="操作" v-if="has_permission('publish_app_var_edit|publish_app_var_del')">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="has_permission('publish_app_var_edit')" size="small" @click="editEnv(scope.row)">编辑</el-button>
|
||||
<el-button v-if="has_permission('publish_app_var_del')" size="small" type="danger" @click="delEnv(scope.row)"
|
||||
:loading="envDelLoading[scope.row.id]">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<p style="text-align: center; margin: 20px 0 20px"></p>
|
||||
<div slot="footer" style="text-align: right">
|
||||
<el-button v-if="has_permission('publish_app_var_add')" type="primary" @click="addEnv">新建</el-button>
|
||||
</div>
|
||||
<el-dialog title="编辑配置" :visible.sync="envVisible" :close-on-click-modal="false">
|
||||
<el-form :model="envForm" label-width="80px" label-position="left">
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="envForm.name" placeholder="请输入变量名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" >
|
||||
<el-input v-model="envForm.desc" type="textarea" placeholder="请输入变量描述"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-for="env in environments" :key="env.id" :label="env.name" required>
|
||||
<el-input v-model="envForm.value[env.id]" placeholder="请输入变量值"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="envVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="envSave" :loading="envSaveLoading">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<div :style="menu_display">
|
||||
<el-table :data="menuData" >
|
||||
<el-table-column prop="name" label="菜单名称" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="desc" label="菜单描述" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="command" label="菜单命令" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" v-if="has_permission('publish_app_menu_view')">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="primary" @click="menuEditOpen(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-dialog title="应用发布" :title="menuDialogTitle" :visible.sync="menuVisible" :close-on-click-modal="false">
|
||||
<el-form :model="menuForm" label-width="80px" label-position="left">
|
||||
<el-form-item label="命令内容">
|
||||
<color-input v-model="menuForm.command"></color-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="menuVisible = false">取消</el-button>
|
||||
<el-button v-if="has_permission('publish_app_menu_edit')" type="primary" @click="menuSave"
|
||||
:loading="menuSaveLoading">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<host-relationship :style="host_display" :owner="environments" ></host-relationship>
|
||||
<!--<template>-->
|
||||
<!--<!–<div style="text-align: left" :style="host_display">–>-->
|
||||
<!--<div :style="host_display">-->
|
||||
<!--<el-select placeholder="请选择环境" v-model="env_id" style="margin-bottom: 15px" >-->
|
||||
<!--<el-option v-for="item in environments" :key="item.id" :value="item.id" :label="item.name"></el-option>-->
|
||||
<!--</el-select>-->
|
||||
<!--<el-transfer-->
|
||||
<!--v-model="deploy_host" :props="{key: 'key',label: 'name'}"-->
|
||||
<!--filterable-->
|
||||
<!--:titles="['未选主机', '已选主机']"-->
|
||||
<!--:filter-method="hostSearch"-->
|
||||
<!--:data="ecs_host" >-->
|
||||
<!--<!–<span slot-scope="{ option }">{{ option.key }} - [ {{ option.name }} ]</span>–>-->
|
||||
<!--</el-transfer>-->
|
||||
<!--</div>-->
|
||||
<!--</template>-->
|
||||
|
||||
<div :style="complete_display">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>项目信息:</span>
|
||||
</div>
|
||||
<div class="text item">
|
||||
{{'项目类型 ' + appInfo.group }}
|
||||
{{'项目名称 ' + appInfo.name }}
|
||||
{{'关联镜像 ' + appInfo.image_tag_name }}
|
||||
<p v-if="appInfo.desc">{{'项目描述 ' + appInfo.desc }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
<p style="text-align: center; margin: 5px 0 5px"></p>
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>容器设置:</span>
|
||||
</div>
|
||||
<div class="text item">
|
||||
<el-table :data="dockerData" border style="width: 100%" >
|
||||
<el-table-column fixed prop="desc" label="名称" ></el-table-column>
|
||||
<el-table-column v-for="item in this.environments" :key="item.id" :label="item.name" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.value[item.id]">{{scope.row.value[item.id]}}</span>
|
||||
<span v-else style="color: #D3DCE6">{{scope.row.tip}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
<p style="text-align: center; margin: 5px 0 5px"></p>
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>容器参数:</span>
|
||||
</div>
|
||||
<div class="text item">
|
||||
<el-table :data="envData" border style="width: 100%" >
|
||||
<el-table-column fixed prop="name" label="名称" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-for="item in this.environments" :key="item.id" :label="item.name" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.value[item.id]">{{scope.row.value[item.id]}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="desc" label="描述" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
<p style="text-align: center; margin: 5px 0 5px"></p>
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>发布设置:</span>
|
||||
</div>
|
||||
<div class="text item">
|
||||
<el-table :data="menuData" >
|
||||
<el-table-column prop="name" label="菜单名称" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="desc" label="菜单描述" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="command" label="菜单命令" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
<p style="text-align: center; margin: 5px 0 5px"></p>
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>主机信息:</span>
|
||||
</div>
|
||||
<div v-for="n in deploy_host" :key="n" class="text item">
|
||||
{{'主机ID: ' + n }}
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
</div>
|
||||
|
||||
<el-dialog title="应用发布" :visible.sync="dialogDeployVisible" width="80%"
|
||||
:close-on-click-modal="false">
|
||||
<el-collapse :value="showItem" accordion>
|
||||
<el-collapse-item v-for="item in updateHosts" :key="item.id" :name="item.id">
|
||||
<template slot="title">
|
||||
<el-tag :type="item.type" style="margin-right: 15px">{{item.instance_name}}</el-tag>
|
||||
{{item.latest}}
|
||||
</template>
|
||||
<pre v-for="line in item.detail">{{line}}</pre>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-dialog>
|
||||
|
||||
<p style="text-align: center; margin: 20px 0 20px"></p>
|
||||
<el-button-group>
|
||||
|
||||
<el-button type="primary" :style="base_display" @click="baseNext('appInfo')">下一步
|
||||
<i class="el-icon-arrow-right el-icon--right"></i></el-button>
|
||||
<el-button type="primary" :style="docker_display" @click="basePrev" icon="el-icon-arrow-left">上一步</el-button>
|
||||
<el-button type="primary" :style="docker_display" @click="dockerNext">下一步<i class="el-icon-arrow-right el-icon--right"></i></el-button>
|
||||
|
||||
<el-button type="primary" :style="env_display" @click="dockePrev" icon="el-icon-arrow-left">上一步</el-button>
|
||||
<el-button type="primary" :style="env_display" @click="envNext" >下一步<i class="el-icon-arrow-right el-icon--right"></i></el-button>
|
||||
|
||||
<el-button type="primary" :style="menu_display" @click="envPrev" icon="el-icon-arrow-left">上一步</el-button>
|
||||
<el-button type="primary" :style="menu_display" @click="menuNext" >下一步<i class="el-icon-arrow-right el-icon--right"></i></el-button>
|
||||
|
||||
<el-button type="primary" :style="host_display" @click="menuPrev" icon="el-icon-arrow-left">上一步</el-button>
|
||||
<el-button type="primary" :style="host_display" @click="host_next" >下一步<i class="el-icon-arrow-right el-icon--right"></i></el-button>
|
||||
|
||||
<el-button type="primary" :style="complete_display" @click="host_prev" icon="el-icon-arrow-left">上一步</el-button>
|
||||
<el-button type="primary" :style="complete_display" :loading="CompleteLoading" @click="complete_next">开始部署
|
||||
<i class="el-icon-arrow-right el-icon--right"></i></el-button>
|
||||
<el-button type="primary" :style="dep_display" @click="dep_prev" icon="el-icon-arrow-left">上一步</el-button>
|
||||
|
||||
|
||||
</el-button-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.transfer-footer {
|
||||
margin-left: 20px;
|
||||
padding: 6px 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import ColorInput from '../publish/ColorInput.vue'
|
||||
import HostRelationship from './HostRel.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'color-input': ColorInput,
|
||||
'host-relationship': HostRelationship
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
appStep: 1,
|
||||
ecs_host: [],
|
||||
deploy_host: [],
|
||||
CompleteLoading: false,
|
||||
dockerSaveLoading: false,
|
||||
envSaveLoading: false,
|
||||
menuSaveLoading: false,
|
||||
envDelLoading: false,
|
||||
group_options:[],
|
||||
app_images_options: [],
|
||||
menuDialogTitle: '',
|
||||
appInfo: {
|
||||
group: '',
|
||||
name: '',
|
||||
},
|
||||
images: [],
|
||||
groups: [],
|
||||
updateHosts: [],
|
||||
dockerVisible: false,
|
||||
menuVisible: false,
|
||||
envVisible: false,
|
||||
dialogDeployVisible: false,
|
||||
base_display: '',
|
||||
host_display: 'display:none',
|
||||
complete_display: 'display:none',
|
||||
env_display: 'display:none',
|
||||
exec_display: 'display:none',
|
||||
docker_display: 'display:none',
|
||||
menu_display: 'display:none',
|
||||
dep_display: 'display:none',
|
||||
environments: undefined,
|
||||
env_id: '',
|
||||
exec_output: [],
|
||||
exec_token: null,
|
||||
dockerData: [
|
||||
{value: {}, desc: '限制内存', name: '__MEM_LIMIT', tip: '默认无限制'},
|
||||
{value: {}, desc: '网络模式', name: '__NETWORK_MODE', tip: 'default'},
|
||||
{value: {}, desc: '映射端口', name: '__EXPOSE_PORT', tip: '示例:127.0.0.1:80:3000'},
|
||||
{value: {}, desc: '映射目录', name: '__BIND_VOLUME', tip: '示例:/home/user1:/mnt/vol1:ro'},
|
||||
{value: {}, desc: 'DNS地址', name: '__DNS_SERVER', tip: '示例:8.8.8.8;4.4.4.4'},
|
||||
{value: {}, desc: '主机名称', name: '__HOST_NAME', tip: '默认随机名称'}
|
||||
],
|
||||
dockerForm: {
|
||||
value: {}
|
||||
},
|
||||
envData: [],
|
||||
// envData: [{value: {}, name: '', desc: ''}],
|
||||
envForm: {
|
||||
name: '',
|
||||
desc: '',
|
||||
value: {}
|
||||
},
|
||||
menuData: [
|
||||
{command: '',name: "容器初始化", desc: '容器被创建后,执行的初始化命令'},
|
||||
{command: '',name: "应用发布", desc: '点击发布按钮,执行的更新命令'},
|
||||
{command: '/usr/sbin/nginx -g "daemon off;"',name: "容器启动", desc: '容器启动时,执行的命令'},
|
||||
],
|
||||
menuForm: {},
|
||||
baseRules: {
|
||||
group: [
|
||||
{ required: true, message: '请选择项目类型', trigger: 'change' }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showItem() {
|
||||
return (this.updateHosts.length === 1) ? this.updateHosts[0].id : ''
|
||||
},
|
||||
deployForm() {
|
||||
return {
|
||||
appInfo: this.appInfo,
|
||||
dockerData: this.dockerData,
|
||||
envData: this.envData,
|
||||
menuData: this.menuData,
|
||||
host_ids: this.deploy_host.map(x => x.split(',')[0]),
|
||||
env_id: this.env_id
|
||||
// host_ids: this.deploy_host.map(x => x.id)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
get_ecs_host () {
|
||||
// this.tableLoading = true;
|
||||
this.$http.get(`/api/assets/hosts/`,{params: {page: -1, host_query: {}}}).then(res => {
|
||||
console.log('res.result.data', res.result.data);
|
||||
for (let n of res.result.data){
|
||||
const name = `${n.name} - ${n.ssh_ip}`;
|
||||
this.ecs_host.push({
|
||||
key: n.id + ',' + name,
|
||||
name: name,
|
||||
// label: `${ n.instance_name }`
|
||||
// "ip": n.primary_ip,
|
||||
// "n_type": n.instance_network_type,
|
||||
})
|
||||
}
|
||||
}, res => this.$layer_message(res.result)).finally(() => this.tableLoading = false)
|
||||
},
|
||||
hostSearch(query, item) {
|
||||
if (item.name){
|
||||
return item.name.indexOf(query) > -1;
|
||||
}
|
||||
},
|
||||
handleChange(value, direction, movedKeys) {
|
||||
console.log(13, this.selectd_ng_host);
|
||||
console.log(value, 11, direction, 22, movedKeys);
|
||||
},
|
||||
fetchEnvironments () {
|
||||
if (this.environments === undefined) {
|
||||
this.$http.get('/api/configuration/environments/').then(res => {
|
||||
this.environments = res.result
|
||||
}, res => this.$layer_message(res.result))
|
||||
}
|
||||
},
|
||||
fetchImages () {
|
||||
this.$http.get('/api/deploy/images/').then(res => this.images = res.result, res => this.$layer_message(res.result))
|
||||
},
|
||||
// saveCommit () {
|
||||
// console.log('savedata', this.appInfo, this.deploy_host);
|
||||
//
|
||||
// this.CompleteLoading = true;
|
||||
// this.exec_display = 'display:block';
|
||||
// this.$http.post('/api/deploy/pre_app/', {
|
||||
// app_info: this.appInfo,
|
||||
// deploy_host: this.deploy_host}).then(res => {
|
||||
//
|
||||
// console.log('res.result', res.result);
|
||||
// this.exec_token = res.result;
|
||||
// this.fetchExecResult();
|
||||
// }, res => this.$layer_message(res.result)).finally(() => this.CompleteLoading = false)
|
||||
//
|
||||
// },
|
||||
fetchExecResult() {
|
||||
this.$http.get(`/api/common/queue/state/${this.exec_token}`).then(res => {
|
||||
if (res.result['complete'] === true) return;
|
||||
this.fetchExecResult(this.exec_token);
|
||||
console.log('fetcheexecresult', res.result);
|
||||
this.exec_output.push(res.result);
|
||||
console.log('this.exec_output', this.exec_output);
|
||||
}, res => this.$layer_message(res.result))
|
||||
},
|
||||
addOpen () {
|
||||
this.form = this.init_form();
|
||||
if (this.images.length === 0) this.fetchImages()
|
||||
},
|
||||
baseNext(baseName) {
|
||||
this.$refs[baseName].validate((valid) => {
|
||||
if (valid) {
|
||||
this.base_display = 'display:none';
|
||||
this.docker_display = 'display:block';
|
||||
this.appStep = 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
basePrev(){
|
||||
this.base_display = 'display:block';
|
||||
// this.host_display = 'display:none';
|
||||
this.docker_display = 'display:none';
|
||||
this.appStep = 0;
|
||||
},
|
||||
dockerNext(){
|
||||
this.docker_display = 'display:none';
|
||||
this.env_display = 'display:block';
|
||||
this.appStep = 2;
|
||||
},
|
||||
dockePrev(){
|
||||
this.docker_display = 'display:block';
|
||||
this.env_display = 'display:none';
|
||||
this.appStep = 1;
|
||||
},
|
||||
envNext(){
|
||||
this.env_display = 'display:none';
|
||||
this.menu_display = 'display:block';
|
||||
this.appStep = 3;
|
||||
},
|
||||
envPrev(){
|
||||
this.env_display = 'display:block';
|
||||
this.menu_display = 'display:none';
|
||||
this.appStep = 2;
|
||||
},
|
||||
menuNext(){
|
||||
this.menu_display = 'display:none';
|
||||
this.host_display = 'display:block';
|
||||
this.appStep = 4;
|
||||
},
|
||||
menuPrev(){
|
||||
this.menu_display = 'display:block';
|
||||
this.host_display = 'display:none';
|
||||
this.appStep = 3;
|
||||
},
|
||||
host_next(){
|
||||
this.host_display = 'display:none';
|
||||
this.complete_display = 'display:block';
|
||||
this.appStep = 5;
|
||||
},
|
||||
host_prev(){
|
||||
this.host_display = 'display:block';
|
||||
this.complete_display = 'display:none';
|
||||
this.appStep = 4;
|
||||
},
|
||||
dep_prev(){
|
||||
this.complete_display = 'display:block';
|
||||
},
|
||||
complete_next(){
|
||||
console.log(2003);
|
||||
// this.complete_display = 'display:none';
|
||||
// this.saveCommit()
|
||||
// this.dep_display = 'display:block';
|
||||
this.handleDeploy();
|
||||
|
||||
},
|
||||
addGroup () {
|
||||
this.$prompt('请输入新分组名称', '提示', {
|
||||
inputPattern: /.+/,
|
||||
inputErrorMessage: '请输入分组名称!'
|
||||
}).then(({value}) => {
|
||||
this.form.group = value
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
addAppType () {
|
||||
this.$prompt('请输入项目类型', '提示', {
|
||||
inputPattern: /.+/,
|
||||
inputErrorMessage: '请输入类型!'
|
||||
}).then(({value}) => {
|
||||
this.appInfo.group = value
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
fetch_type_options(){
|
||||
this.$http.get('/api/deploy/apps/groups/').then(res => {
|
||||
this.group_options = res.result;
|
||||
}, res => this.$layer_message(res.result))
|
||||
},
|
||||
fetch_images_options () {
|
||||
this.$http.get('/api/deploy/images/').then(res => {
|
||||
this.app_images_options = res.result.map(x => Object({label: x.name, value: x.id, children: []}));
|
||||
}, res => this.$layer_message(res.result)).finally(() => this.tableLoading = false)
|
||||
},
|
||||
loadImageTag(val) {
|
||||
console.log('appInfo', val);
|
||||
this.$http.get(`/api/deploy/images/${val}/tags/`).then(res => {
|
||||
for (let tag of this.app_images_options) {
|
||||
if (tag.value == val) {
|
||||
tag.children = res.result.map(x => Object({label: x.name, value: x.id}));
|
||||
}
|
||||
}
|
||||
}, res => this.$layer_message(res.result))
|
||||
},
|
||||
fetchEnvironments () {
|
||||
if (this.environments === undefined) {
|
||||
this.$http.get('/api/configuration/environments/').then(res => {
|
||||
this.environments = res.result
|
||||
}, res => this.$layer_message(res.result))
|
||||
}
|
||||
},
|
||||
bindImageTag(val){
|
||||
this.appInfo['image_id'] = val[0];
|
||||
this.appInfo['image_tag_id'] = val[1];
|
||||
},
|
||||
addEnv () {
|
||||
this.envForm = { name: '', desc: '', value: {}};
|
||||
this.envVisible = true
|
||||
},
|
||||
envSave(){
|
||||
this.envVisible = false;
|
||||
if (this.envData.length !== 0 ) {
|
||||
for (let e of this.envData ){
|
||||
if (e.name == this.envForm.name){
|
||||
e = this.envForm;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.envData.push(this.envForm);
|
||||
}else {
|
||||
this.envData.push(this.envForm);
|
||||
}
|
||||
},
|
||||
delEnv(row){
|
||||
for (let x in this.envData){
|
||||
if (this.envData[x].name == row.name){
|
||||
this.envData.splice(x, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
editEnv(row){
|
||||
this.envVisible = true;
|
||||
this.envForm = row;
|
||||
console.log('row', row);
|
||||
},
|
||||
menuEditOpen(row) {
|
||||
this.menuForm = row;
|
||||
this.menuDialogTitle = '菜单编辑 - ' + row['name'];
|
||||
this.menuVisible = true;
|
||||
},
|
||||
menuSave(){
|
||||
this.menuVisible = false;
|
||||
console.log(this.menuData);
|
||||
},
|
||||
handleDeploy() {
|
||||
this.dialogDeployVisible = true;
|
||||
this.$http.post('/api/deploy/publish/deploy', this.deployForm).then(res => {
|
||||
this.updateHosts = this.deploy_host.map(item => {
|
||||
const [id, instance_name] = item.split(',', 2);
|
||||
return {id, instance_name, type: 'info', detail: []}
|
||||
});
|
||||
this.fetchDeployResult(res.result);
|
||||
}, res => this.$layer_message(res.result))
|
||||
},
|
||||
fetchDeployResult(token) {
|
||||
this.$http.get(`/api/common/queue/state/${token}`).then(res => {
|
||||
console.log('result', res.result);
|
||||
if (res.result['complete'] === true) return;
|
||||
this.fetchDeployResult(token);
|
||||
for (let [index, item] of Object.entries(this.updateHosts)) {
|
||||
if (item.id === res.result.hid) {
|
||||
if (res.result.update === true) item.detail.pop();
|
||||
item.detail.push(res.result.msg);
|
||||
if (res.result.level !== 'console') item.latest = res.result.msg;
|
||||
if (res.result.level === 'error') item.type = 'danger';
|
||||
if (res.result.level === 'success') item.type = 'success';
|
||||
this.$set(this.updateHosts, index, item);
|
||||
break
|
||||
}
|
||||
}
|
||||
}, res => this.$layer_message(res.result))
|
||||
},
|
||||
},
|
||||
|
||||
created () {
|
||||
this.fetch_type_options();
|
||||
// this.get_ecs_host();
|
||||
this.fetchEnvironments();
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-select v-model="type" style="margin-bottom: 15px" placeholder="请选择环境">
|
||||
<el-option v-for="env in this.owner" :label="env.name" :value="env.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-transfer :titles="['未选主机', '已选主机']" v-if="type === ''" :data="ecs_host"></el-transfer>
|
||||
<el-transfer v-for="item in this.owner" :titles="['未选主机', '已选主机']" v-if="type === item.id" v-model="app_result" :data="ecs_host"></el-transfer>
|
||||
<!--<el-transfer :titles="['未选主机', '已选主机']" v-if="type === 'app'" v-model="app_result" :data="apps"></el-transfer>-->
|
||||
<!--<el-transfer :titles="['未选主机', '已选主机']" v-if="type === 'service'" v-model="service_result" :data="services"></el-transfer>-->
|
||||
<div slot="footer" v-if="has_permission('config_app_rel_edit')">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveCommit" :loading="btnSaveLoading">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['owner'],
|
||||
data () {
|
||||
return {
|
||||
visible: true,
|
||||
btnSaveLoading: false,
|
||||
type: '',
|
||||
app_result: [],
|
||||
service_result: [],
|
||||
apps: [],
|
||||
services: [],
|
||||
ecs_host: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
get_ecs_host () {
|
||||
console.log('this.owner', this.owner);
|
||||
this.$http.get(`/api/assets/hosts/`,{params: {page: -1, host_query: {}}}).then(res => {
|
||||
console.log('res.result.data', res.result.data);
|
||||
for (let n of res.result.data){
|
||||
const name = `${n.name} - ${n.ssh_ip}`;
|
||||
this.ecs_host.push({
|
||||
key: n.id,
|
||||
label: name,
|
||||
})
|
||||
}
|
||||
}, res => this.$layer_message(res.result)).finally(() => this.tableLoading = false)
|
||||
},
|
||||
|
||||
// fetch_apps() {
|
||||
// this.$http.get(`/api/deploy/apps/`).then(res => {
|
||||
// for (let item of res.result) {
|
||||
// if (item.id === this.owner.id) continue;
|
||||
// this.apps.push({
|
||||
// key: item.id,
|
||||
// label: item.name,
|
||||
// disabled: false
|
||||
// })
|
||||
// }
|
||||
// }, res => this.$layer_message(res.result))
|
||||
// },
|
||||
fetch_services() {
|
||||
this.$http.get(`/api/configuration/services/`).then(res => {
|
||||
for (let item of res.result) {
|
||||
this.services.push({
|
||||
key: item.id,
|
||||
label: item.name,
|
||||
disabled: false
|
||||
})
|
||||
}
|
||||
}, res => this.$layer_message(res.result))
|
||||
},
|
||||
fetch_relationship () {
|
||||
this.$http.get(`/api/configuration/apps/${this.owner.id}/bind/relationship`).then(res => {
|
||||
if (res.result.hasOwnProperty('app_ids')) this.app_result = res.result['app_ids'];
|
||||
if (res.result.hasOwnProperty('service_ids')) this.service_result = res.result['service_ids']
|
||||
}, res => this.$layer_message(res.result))
|
||||
},
|
||||
saveCommit () {
|
||||
this.btnSaveLoading = true;
|
||||
this.$http.post(`/api/configuration/apps/${this.owner.id}/bind/relationship`, {
|
||||
app_ids: this.app_result,
|
||||
service_ids: this.service_result
|
||||
}).then(res => this.visible = false, res => this.$layer_message(res.result)).finally(() => this.btnSaveLoading = false)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// this.fetch_apps();
|
||||
this.get_ecs_host();
|
||||
// this.fetch_services();
|
||||
// this.fetch_relationship()
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -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',
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-form :inline="true" :model="notify_query">
|
||||
<el-form-item>
|
||||
<el-input clearable v-model="notify_query.name_field" placeholder="通知名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="nameSearch()">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: right">
|
||||
<el-button @click="refresh()">刷新</el-button>
|
||||
<el-button v-if="has_permission('system_notify_add')" style="float: right" type="primary"
|
||||
@click="handleAdd()">添加通知
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :data="tableData.data" stripe border v-loading="listLoading" style="width: 100%"
|
||||
:default-sort="{prop: 'username', order: 'descending'}">
|
||||
<el-table-column type="index" width="60"></el-table-column>
|
||||
<el-table-column prop="name" label="通知名称" sortable></el-table-column>
|
||||
<el-table-column prop="value" label="通知URL" sortable></el-table-column>
|
||||
<el-table-column prop="desc" label="备注"></el-table-column>
|
||||
<el-table-column v-if="has_permission('system_notify_view|system_notify_add|system_notify_edit')" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="has_permission('system_notify_edit')" size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!--分页-->
|
||||
<div class="pagination-bar" v-if="tableData.total > 10">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage" layout="total, prev, pager, next"
|
||||
:total="tableData.total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
|
||||
<!--编辑新增界面-->
|
||||
<el-dialog :title="editFormTitle" :visible.sync="dialogShow" :close-on-click-modal="false">
|
||||
<el-form ref="editForm" :model="editForm" :rules="rules" label-width="80px">
|
||||
<el-form-item prop="name" label="通知名称">
|
||||
<el-input v-model="editForm.name" ></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="value" label="通知URL" >
|
||||
<el-input placeholder="输入钉钉webhook" type="textarea" :rows="3" v-model="editForm.value" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="desc" label="备注信息">
|
||||
<el-input v-model="editForm.desc" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button type="text" @click.native="dialogShow = false">取消</el-button>
|
||||
<el-button type="primary" :loading="editLoading" @click.native="editSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
notify_query: {
|
||||
name_field: ''
|
||||
},
|
||||
error: '',
|
||||
dialogShow: false,
|
||||
tableData: {},
|
||||
roles: undefined,
|
||||
roles_map: {},
|
||||
display: '',
|
||||
listLoading: false,
|
||||
btnDelLoading: {},
|
||||
editFormTitle: '',
|
||||
editLoading: false,
|
||||
is_disabled: '',
|
||||
currentPage: 1,
|
||||
addForm: {
|
||||
id: 0,
|
||||
name: '',
|
||||
value: '',
|
||||
desc: '',
|
||||
},
|
||||
editForm: {},
|
||||
rules: {
|
||||
name: [
|
||||
{required: true, message: '请输入通知名称', trigger: 'blur'}
|
||||
],
|
||||
value: [
|
||||
{required: true, message: '请输入通知URL', trigger: 'blur'}
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val;
|
||||
this.getNotify(this.currentPage);
|
||||
},
|
||||
|
||||
//名称查询
|
||||
nameSearch(){
|
||||
this.currentPage = 1;
|
||||
this.getNotify();
|
||||
},
|
||||
|
||||
//刷新操作
|
||||
refresh(){
|
||||
this.getNotify(this.currentPage);
|
||||
},
|
||||
|
||||
//获取通知列表
|
||||
getNotify(page, callback) {
|
||||
if (!page) page = 1;
|
||||
this.listLoading = true;
|
||||
this.$http.get('/api/system/notify/', {
|
||||
params: {notify_query: this.notify_query}
|
||||
}).then((response) => {
|
||||
this.tableData = response.result;
|
||||
if (callback) callback(this)
|
||||
}, (response) => this.$layer_message(response.result)).finally(() => this.listLoading = false)
|
||||
},
|
||||
|
||||
//显示添加界面
|
||||
handleAdd: function () {
|
||||
this.is_disabled = false;
|
||||
this.dialogShow = true;
|
||||
this.editFormTitle = '添加通知';
|
||||
this.display = 'display:block';
|
||||
this.editForm = this.addForm;
|
||||
},
|
||||
|
||||
//显示编辑界面
|
||||
handleEdit: function (index, row) {
|
||||
this.is_disabled = true;
|
||||
this.dialogShow = true;
|
||||
this.editFormTitle = '编辑通知';
|
||||
this.display = 'display:none';
|
||||
//this.editForm = Object.assign({}, row);
|
||||
this.editForm = this.$deepCopy(row);
|
||||
},
|
||||
|
||||
EditData: function (row, msg, pwd) {
|
||||
this.$http.put(`/api/account/users/${row.id}`, this.editForm).then(response => {
|
||||
this.listLoading = false;
|
||||
this.$layer_message(msg + pwd, 'success');
|
||||
this.getUsers(this.currentPage);
|
||||
}, response => this.$layer_message(response.result)).finally(() => this.btnDelLoading = {});
|
||||
},
|
||||
|
||||
editSubmit: function () {
|
||||
this.$refs.editForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.editLoading = true;
|
||||
this.error = '';
|
||||
if (this.editForm.id) {
|
||||
this.$http.put(`/api/system/notify/${this.editForm.id}`, this.editForm).then(this.resp,
|
||||
response => this.$layer_message(response.result)).finally(() => this.editLoading = false)
|
||||
} else {
|
||||
this.$http.post('/api/system/notify/', this.editForm).then(this.resp,
|
||||
response => this.$layer_message(response.result)).finally(() => this.editLoading = false)
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
resp: function (response) {
|
||||
this.editLoading = false;
|
||||
this.$layer_message('提交成功', 'success');
|
||||
this.editForm = {};
|
||||
this.addForm = {role_id: ''};
|
||||
this.dialogShow = false;
|
||||
this.getNotify(this.currentPage);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getNotify();
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
]
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Created by zyupo on 2017/04/20.
|
||||
* https://github.com/openspug/spug
|
||||
*/
|
||||
// 环境配置
|
||||
|
||||
// 后端API接口地址
|
||||
|
|
|
@ -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: '通知设置'},
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
@ -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: '/'
|
||||
|
|
Loading…
Reference in New Issue