From 1590ac30c09e38cf521df052fcc7ce31316b540c Mon Sep 17 00:00:00 2001 From: "StarsL.cn" Date: Thu, 10 Feb 2022 23:08:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0consul=20web=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-push.sh | 4 +- flask-consul/config.py | 6 +- flask-consul/manager.py | 3 +- flask-consul/units/consul_manager.py | 120 +++++ flask-consul/views/consul.py | 48 ++ vue-consul/.eslintrc.js | 3 +- vue-consul/src/api/consul.js | 54 +++ vue-consul/src/router/index.js | 56 ++- vue-consul/src/settings.js | 2 +- vue-consul/src/views/consul/hosts.vue | 121 ++++++ vue-consul/src/views/consul/instances.vue | 506 ++++++++++++++++++++++ vue-consul/src/views/consul/services.vue | 91 ++++ vue-consul/src/views/dashboard/index.vue | 47 +- vue-consul/src/views/login/index.vue | 4 +- 14 files changed, 1051 insertions(+), 14 deletions(-) create mode 100644 flask-consul/units/consul_manager.py create mode 100644 vue-consul/src/api/consul.js create mode 100644 vue-consul/src/views/consul/hosts.vue create mode 100644 vue-consul/src/views/consul/instances.vue create mode 100644 vue-consul/src/views/consul/services.vue diff --git a/docker-push.sh b/docker-push.sh index b36ebf7..128084e 100755 --- a/docker-push.sh +++ b/docker-push.sh @@ -1,6 +1,6 @@ #!/bin/bash -vf=0.1.3 -vb=0.2.0 +vf=0.3.0 +vb=0.3.0 docker login --username=starsliao@163.com registry.cn-shenzhen.aliyuncs.com docker tag nginx-consul:latest registry.cn-shenzhen.aliyuncs.com/starsl/nginx-consul:latest diff --git a/flask-consul/config.py b/flask-consul/config.py index be39664..5c7ae94 100644 --- a/flask-consul/config.py +++ b/flask-consul/config.py @@ -1,8 +1,8 @@ import os from itsdangerous import TimedJSONWebSignatureSerializer -consul_token = os.environ.get('consul_token','a94d1ecb-81d3-ea0a-4dc8-5e6701e528c5') -consul_url = os.environ.get('consul_url','http://10.5.148.67:8500/v1') -admin_passwd = os.environ.get('admin_passwd','cass.007') +consul_token = os.environ.get('consul_token','635abc53-c18c-f780-58a9-f04feb28fef1') +consul_url = os.environ.get('consul_url','http://10.0.0.26:8500/v1') +admin_passwd = os.environ.get('admin_passwd','123456') secret_key = os.environ.get('secret_key',consul_token) s = TimedJSONWebSignatureSerializer(secret_key) diff --git a/flask-consul/manager.py b/flask-consul/manager.py index 9fd8749..e229e6f 100755 --- a/flask-consul/manager.py +++ b/flask-consul/manager.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 from flask import Flask -from views import login, blackbox +from views import login, blackbox, consul app = Flask(__name__) app.register_blueprint(login.blueprint) app.register_blueprint(blackbox.blueprint) +app.register_blueprint(consul.blueprint) if __name__ == "__main__": app.run(host="0.0.0.0", port=2026) diff --git a/flask-consul/units/consul_manager.py b/flask-consul/units/consul_manager.py new file mode 100644 index 0000000..9b0988e --- /dev/null +++ b/flask-consul/units/consul_manager.py @@ -0,0 +1,120 @@ +import requests,json +import sys +sys.path.append("..") +from config import consul_token,consul_url + +headers = {'X-Consul-Token': consul_token} + +def get_hosts(): + url = f'{consul_url}/agent/host' + response = requests.get(url, headers=headers) + if response.status_code == 200: + info = response.json() + pmem = round(info["Memory"]["usedPercent"]) + pdisk = round(info["Disk"]["usedPercent"]) + host = {'hostname':info["Host"]["hostname"],'uptime':f'{round(info["Host"]["uptime"]/3600/24)}天', + 'os':f'{info["Host"]["platform"]} {info["Host"]["platformVersion"]}','kernel':info["Host"]["kernelVersion"]} + cpu = {'cores':f'{len(info["CPU"])}核','vendorId':info["CPU"][0]["vendorId"],'modelName':info["CPU"][0]["modelName"]} + memory = {'total':f'{round(info["Memory"]["total"]/1024**3)}GB','available':f'{round(info["Memory"]["available"]/1024**3)}GB', + 'used':f'{round(info["Memory"]["used"]/1024**3)}GB','usedPercent':f'{pmem}%'} + disk = {'path':info["Disk"]["path"],'fstype':info["Disk"]["fstype"],'total':f'{round(info["Disk"]["total"]/1024**3)}GB', + 'free':f'{round(info["Disk"]["free"]/1024**3)}GB','used':f'{round(info["Disk"]["used"]/1024**3)}GB','usedPercent':f'{pdisk}%'} + return {'code': 20000,'host':host,'cpu':cpu,'memory':memory,'disk':disk, 'pmem':pmem, 'pdisk':pdisk} + else: + return {'code': 50000, 'data': f'{response.status_code}:{response.text}'} +def get_services(): + url = f'{consul_url}/internal/ui/services' + response = requests.get(url, headers=headers) + if response.status_code == 200: + info = response.json() + services_list = [{'Name':i['Name'],'Datacenter':i['Datacenter'],'InstanceCount':i['InstanceCount'],'ChecksCritical':i['ChecksCritical'],'ChecksPassing':i['ChecksPassing'],'Tags':i['Tags'],'Nodes':list(set(i['Nodes']))} for i in info if i['Name'] != 'consul'] + return {'code': 20000,'services':services_list} + else: + return {'code': 50000, 'data': f'{response.status_code}:{response.text}'} +def get_services_nameonly(): + url = f'{consul_url}/catalog/services' + response = requests.get(url, headers=headers) + if response.status_code == 200: + info = response.json() + info.pop('consul') + return {'code': 20000,'services_name':list(info.keys())} + else: + return {'code': 50000, 'data': f'{response.status_code}:{response.text}'} +def get_instances(service_name): + url = f'{consul_url}/health/service/{service_name}' + response = requests.get(url, headers=headers) + if response.status_code == 200: + info = response.json() + instances_list = [] + for i in info: + instance_dict = {} + instance_dict['ID'] = i['Service']['ID'] + instance_dict['name'] = i['Service']['Service'] + instance_dict['tags'] = '无' if i['Service']['Tags'] == [] else i['Service']['Tags'] + instance_dict['address'] = i['Service'].get('Address') + instance_dict['port'] = i['Service'].get('Port') + if i['Service']['Meta'] == {} or i['Service']['Meta'] is None: + instance_dict['meta'] = '无' + else: + instance_dict['meta'] = [i['Service']['Meta']] + instance_dict['meta_label'] = [{'prop': x, 'label': x} for x in i['Service']['Meta'].keys()] + if len(i['Checks']) ==2: + instance_dict['status'] = i['Checks'][1]['Status'] + instance_dict['output'] = i['Checks'][1]['Output'] + else: + instance_dict['status'] = '无' + instance_dict['output'] = '无' + instances_list.append(instance_dict) + return {'code': 20000,'instances':instances_list} + else: + return {'code': 50000, 'data': f'{response.status_code}:{response.text}'} + +def del_instance(service_id): + reg = requests.put(f'{consul_url}/agent/service/deregister/{service_id}', headers=headers) + if reg.status_code == 200: + return {"code": 20000, "data": f"【{service_id}】删除成功!"} + else: + return {"code": 50000, "data": f"{reg.status_code}【{service_id}】{reg.text}"} + +def add_instance(instance_dict): + isMeta = instance_dict['metaInfo']['isMeta'] + isCheck = instance_dict['checkInfo']['isCheck'] + address = instance_dict['address'] + port = None if instance_dict['port'] == '' else int(instance_dict['port']) + instance_dict['port'] = port + if isMeta: + try: + metaJson = json.loads(instance_dict['metaInfo']['metaJson']) + instance_dict['meta'] = metaJson + except: + return {"code": 50000, "data": "Meta必须JSON字符串格式!"} + if isCheck: + ctype = instance_dict['checkInfo']['ctype'] + interval = instance_dict['checkInfo']['interval'] + timeout = instance_dict['checkInfo']['timeout'] + if instance_dict['checkInfo']['isAddress'] == 'true': + if port is not None and address != '' and ctype != 'HTTP': + checkaddress = f'{address}:{port}' + elif port is not None and address != '' and ctype == 'HTTP': + checkaddress = f'http://{address}:{port}' + else: + return {"code": 50000, "data": "健康检查地址使用与实例IP端口一致时,地址/端口不可为空!"} + else: + checkaddress = instance_dict['checkInfo']['caddress'] + if checkaddress == '': + return {"code": 50000, "data": "自定义健康检查,地址信息不可为空!"} + + check = {ctype: checkaddress,"Interval": interval,"Timeout": timeout} + instance_dict['check'] = check + + del instance_dict['metaInfo'] + del instance_dict['checkInfo'] + print(instance_dict) + + reg = requests.put(f'{consul_url}/agent/service/register', headers=headers, data=json.dumps(instance_dict)) + sid = instance_dict['ID'] + if reg.status_code == 200: + return {"code": 20000, "data": f"【{sid}】增加成功!"} + else: + return {"code": 50000, "data": f"{reg.status_code}【{sid}】{reg.text}"} + diff --git a/flask-consul/views/consul.py b/flask-consul/views/consul.py index e69de29..69a0392 100644 --- a/flask-consul/views/consul.py +++ b/flask-consul/views/consul.py @@ -0,0 +1,48 @@ +from flask import Blueprint +from flask_restful import reqparse, Resource, Api +import sys +sys.path.append("..") +from units import token_auth,consul_manager + +blueprint = Blueprint('consul',__name__) +api = Api(blueprint) + +parser = reqparse.RequestParser() +parser.add_argument('service_name',type=str) +parser.add_argument('sid',type=str) +parser.add_argument('instance_dict',type=dict) + +class ConsulApi(Resource): + decorators = [token_auth.auth.login_required] + def get(self, stype): + if stype == 'services': + return consul_manager.get_services() + elif stype == 'services_name': + return consul_manager.get_services_nameonly() + elif stype == 'instances': + args = parser.parse_args() + return consul_manager.get_instances(args['service_name']) + elif stype == 'hosts': + return consul_manager.get_hosts() + + def post(self, stype): + if stype == 'sid': + args = parser.parse_args() + return consul_manager.add_instance(args['instance_dict']) + + def put(self, stype): + if stype == 'sid': + args = parser.parse_args() + resp_del = consul_manager.del_instance(args['sid']) + resp_add = consul_manager.add_instance(args['instance_dict']) + if resp_del["code"] == 20000 and resp_add["code"] == 20000: + return {"code": 20000, "data": f"更新成功!"} + else: + return {"code": 50000, "data": f"更新失败!"} + + def delete(self, stype): + if stype == 'sid': + args = parser.parse_args() + return consul_manager.del_instance(args['sid']) + +api.add_resource(ConsulApi, '/api/consul/') diff --git a/vue-consul/.eslintrc.js b/vue-consul/.eslintrc.js index c977505..5fb23f3 100644 --- a/vue-consul/.eslintrc.js +++ b/vue-consul/.eslintrc.js @@ -14,6 +14,8 @@ module.exports = { // add your custom rules here //it is base on https://github.com/vuejs/eslint-config-vue rules: { + "no-irregular-whitespace": "off", + "vue/html-quotes": "off", "vue/max-attributes-per-line": [2, { "singleline": 10, "multiline": { @@ -96,7 +98,6 @@ module.exports = { 'no-implied-eval': 2, 'no-inner-declarations': [2, 'functions'], 'no-invalid-regexp': 2, - 'no-irregular-whitespace': 2, 'no-iterator': 2, 'no-label-var': 2, 'no-labels': [2, { diff --git a/vue-consul/src/api/consul.js b/vue-consul/src/api/consul.js new file mode 100644 index 0000000..bb4fb2e --- /dev/null +++ b/vue-consul/src/api/consul.js @@ -0,0 +1,54 @@ +import request from '@/utils/request-ops' + +export function getHosts() { + return request({ + url: '/api/consul/hosts', + method: 'get' + }) +} + +export function getServices() { + return request({ + url: '/api/consul/services', + method: 'get' + }) +} + +export function getServicesName() { + return request({ + url: '/api/consul/services_name', + method: 'get' + }) +} + +export function getInstances(service_name) { + return request({ + url: '/api/consul/instances', + method: 'get', + params: { service_name } + }) +} + +export function delSid(sid) { + return request({ + url: '/api/consul/sid', + method: 'delete', + params: { sid } + }) +} + +export function addSid(instance_dict) { + return request({ + url: '/api/consul/sid', + method: 'post', + data: { instance_dict } + }) +} + +export function updateSid(sid, instance_dict) { + return request({ + url: '/api/consul/sid', + method: 'put', + data: { sid, instance_dict } + }) +} diff --git a/vue-consul/src/router/index.js b/vue-consul/src/router/index.js index 07a8127..1b12312 100644 --- a/vue-consul/src/router/index.js +++ b/vue-consul/src/router/index.js @@ -46,7 +46,7 @@ export const constantRoutes = [ { path: '/', component: Layout, - redirect: '/blackbox/index', + redirect: '/dashboard', children: [{ path: 'dashboard', name: 'Dashboard', @@ -55,16 +55,66 @@ export const constantRoutes = [ }] }, + { + path: '/consul', + component: Layout, + redirect: '/consul/services', + name: 'Consul 管理', + meta: { title: 'Consul 管理', icon: 'example' }, + children: [ + { + path: 'hosts', + name: 'Hosts', + component: () => import('@/views/consul/hosts'), + meta: { title: 'Hosts', icon: 'el-icon-school' } + }, + { + path: 'services', + name: 'Services', + component: () => import('@/views/consul/services'), + meta: { title: 'Services', icon: 'el-icon-news' } + }, + { + path: 'instances', + name: 'Instances', + component: () => import('@/views/consul/instances'), + meta: { title: 'Instances', icon: 'el-icon-connection' } + } + ] + }, { path: '/blackbox', component: Layout, children: [{ path: 'index', - name: '站点监控', + name: 'Blackbox 站点监控', component: () => import('@/views/blackbox/index'), - meta: { title: '站点监控', icon: 'tree' } + meta: { title: 'Blackbox 站点监控', icon: 'tree' } }] }, + { + path: '友情链接', + component: Layout, + meta: { title: '友情链接', icon: 'link' }, + children: [ + { + path: 'https://starsl.cn', + meta: { title: 'StarsL.cn', icon: 'el-icon-s-custom' } + }, + { + path: 'https://github.com/starsliao?tab=repositories', + meta: { title: '我的Github', icon: 'el-icon-star-off' } + }, + { + path: 'https://grafana.com/orgs/starsliao/dashboards', + meta: { title: '我的Grafana', icon: 'el-icon-odometer' } + }, + { + path: 'https://starsl.cn/static/img/qr.png', + meta: { title: '我的公众号', icon: 'el-icon-chat-dot-round' } + } + ] + }, // 404 page must be placed at the end !!! { path: '*', redirect: '/404', hidden: true } diff --git a/vue-consul/src/settings.js b/vue-consul/src/settings.js index e85aaa0..4818808 100644 --- a/vue-consul/src/settings.js +++ b/vue-consul/src/settings.js @@ -1,6 +1,6 @@ module.exports = { - title: 'Blackbox Manager', + title: 'Consul Manager', /** * @type {boolean} true | false diff --git a/vue-consul/src/views/consul/hosts.vue b/vue-consul/src/views/consul/hosts.vue new file mode 100644 index 0000000..7d05325 --- /dev/null +++ b/vue-consul/src/views/consul/hosts.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/vue-consul/src/views/consul/instances.vue b/vue-consul/src/views/consul/instances.vue new file mode 100644 index 0000000..7710474 --- /dev/null +++ b/vue-consul/src/views/consul/instances.vue @@ -0,0 +1,506 @@ + + + + diff --git a/vue-consul/src/views/consul/services.vue b/vue-consul/src/views/consul/services.vue new file mode 100644 index 0000000..e24cdda --- /dev/null +++ b/vue-consul/src/views/consul/services.vue @@ -0,0 +1,91 @@ + + + diff --git a/vue-consul/src/views/dashboard/index.vue b/vue-consul/src/views/dashboard/index.vue index a0bed6a..21905aa 100644 --- a/vue-consul/src/views/dashboard/index.vue +++ b/vue-consul/src/views/dashboard/index.vue @@ -1,6 +1,51 @@ diff --git a/vue-consul/src/views/login/index.vue b/vue-consul/src/views/login/index.vue index b02842b..60a42ff 100644 --- a/vue-consul/src/views/login/index.vue +++ b/vue-consul/src/views/login/index.vue @@ -3,7 +3,7 @@
- v0.1.3 + v0.3.0