diff --git a/Makefile b/Makefile index 40a4d46..fea2e28 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,9 @@ help: - echo "Read Makefile" - + echo "Read Makefile" && echo "make build" && echo "make push vf=x.x.x vb=x.x.x" build: cd flask-consul && docker build -t flask-consul:latest . cd vue-consul && docker build -t nginx-consul:latest . - echo -e "\n\n自行编译的版本,注意修改docker-compose.yml中的镜像地址为本地仓库后再启动。" - echo -e "\n\nBlackbox-Manager:\nhttp://{ip}:1026" + echo -e "\n\n自行编译的版本,注意修改docker-compose.yml中的镜像地址为本地仓库后再启动。\nBlackbox-Manager:\nhttp://{ip}:1026\n" push: docker login --username=starsliao@163.com registry.cn-shenzhen.aliyuncs.com diff --git a/Thanks.md b/Thanks.md new file mode 100644 index 0000000..041f355 --- /dev/null +++ b/Thanks.md @@ -0,0 +1,2 @@ +@dong9205 +@会飞的鱼 diff --git a/flask-consul/Dockerfile b/flask-consul/Dockerfile index 6ad548b..a088c8b 100644 --- a/flask-consul/Dockerfile +++ b/flask-consul/Dockerfile @@ -1,7 +1,11 @@ FROM python:3-alpine ADD . /flask WORKDIR /flask -RUN pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple -#&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories + +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ +&& apk add --no-cache gcc libc-dev libffi-dev \ +&& rm -rf /var/cache/apk/* \ +&& pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple \ +&& pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple EXPOSE 2026 CMD ["python3","./manager.py"] diff --git a/flask-consul/config.py b/flask-consul/config.py index 5c7ae94..f423da8 100644 --- a/flask-consul/config.py +++ b/flask-consul/config.py @@ -1,8 +1,21 @@ import os -from itsdangerous import TimedJSONWebSignatureSerializer -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') +consul_token = os.environ.get('consul_token','0a79caed-8a45-49b9-97a6-86e50e12b234') +consul_url = os.environ.get('consul_url','http://10.5.148.67:8500/v1') admin_passwd = os.environ.get('admin_passwd','123456') -secret_key = os.environ.get('secret_key',consul_token) -s = TimedJSONWebSignatureSerializer(secret_key) + +vendors = {'alicloud': '阿里云','tencent_cloud': '腾讯云','huaweicloud': '华为云'} +regions = {'huaweicloud':{'none': '无','cn-east-3': '华东-上海一','cn-east-2': '华东-上海二', + 'cn-south-1': '华南-广州','cn-north-1': '华北-北京一','cn-north-4': '华北-北京四', + 'cn-southwest-2': '西南-贵阳一','ap-southeast-1': '中国-香港' }, + 'alicloud':{'none': '无','cn-qingdao':'华北1(青岛)', 'cn-beijing':'华北2(北京)', + 'cn-zhangjiakou':'华北3(张家口)','cn-huhehaote':'华北5(呼和浩特)', + 'cn-wulanchabu':'华北6(乌兰察布)', 'cn-hangzhou':'华东1(杭州)', + 'cn-shanghai':'华东2(上海)', 'cn-shenzhen':'华南1(深圳)', 'cn-heyuan':'华南2(河源)', + 'cn-guangzhou':'华南3(广州)', 'cn-chengdu':'西南1(成都)', 'cn-hongkong':'中国(香港)', + 'cn-nanjing':'华东5(南京-本地地域)'}, + 'tencent_cloud':{'none': '无',"ap-nanjing":"华东地区(南京)","ap-shanghai":"华东地区(上海)", + "ap-guangzhou":"华南地区(广州)","ap-beijing":"华北地区(北京)","ap-tianjin":"华北地区(天津)", + "ap-chengdu":"西南地区(成都)","ap-chongqing":"西南地区(重庆)", + "ap-hongkong":"港澳台地区(中国香港)"} + } diff --git a/flask-consul/manager.py b/flask-consul/manager.py index e229e6f..607a224 100755 --- a/flask-consul/manager.py +++ b/flask-consul/manager.py @@ -1,10 +1,32 @@ #!/usr/bin/env python3 from flask import Flask -from views import login, blackbox, consul +from units import consul_kv +import uuid +skey_path = 'ConsulManager/assets/secret/skey' +if consul_kv.get_kv_dict(skey_path) == {}: + consul_kv.put_kv(skey_path,{'sk':''.join(str(uuid.uuid4()).split('-'))}) +from views import login, blackbox, consul, jobs, nodes +from units.cloud import huaweicloud,alicloud,tencent_cloud app = Flask(__name__) app.register_blueprint(login.blueprint) app.register_blueprint(blackbox.blueprint) app.register_blueprint(consul.blueprint) -if __name__ == "__main__": +app.register_blueprint(jobs.blueprint) +app.register_blueprint(nodes.blueprint) + +class Config(object): + JOBS = [] + SCHEDULER_API_ENABLED = True +init_jobs = consul_kv.get_kv_dict('ConsulManager/jobs') +if init_jobs is not None: + for k,v in init_jobs.items(): + print(f'初始化任务:{k}:\n {v}', flush=True) + Config.JOBS = init_jobs.values() +app.config.from_object(Config()) + +if __name__ == "__main__": + scheduler = jobs.init() + scheduler.init_app(app) + scheduler.start() app.run(host="0.0.0.0", port=2026) diff --git a/flask-consul/requirements.txt b/flask-consul/requirements.txt index e4b776c..6bc357b 100644 --- a/flask-consul/requirements.txt +++ b/flask-consul/requirements.txt @@ -1,6 +1,18 @@ +Werkzeug==2.0.3 +itsdangerous==2.0.1 flask==2.0.2 flask-restful==0.3.9 flask-cors==3.0.10 -itsdangerous==2.0.1 Flask-HTTPAuth==4.5.0 requests==2.27.1 +Flask-APScheduler==1.12.3 +#pyDes==2.0.1 +pycryptodome==3.14.1 +huaweicloudsdkcore==3.0.78 +huaweicloudsdkecs==3.0.78 +huaweicloudsdkeps==3.0.78 +alibabacloud_resourcemanager20200331==2.1.0 +alibabacloud_ecs20140526==2.1.0 +tencentcloud-sdk-python-common==3.0.607 +tencentcloud-sdk-python-cvm==3.0.607 +tencentcloud-sdk-python-dcdb==3.0.607 diff --git a/flask-consul/units/blackbox_manager.py b/flask-consul/units/blackbox_manager.py index 0ce4673..cee1bcd 100644 --- a/flask-consul/units/blackbox_manager.py +++ b/flask-consul/units/blackbox_manager.py @@ -61,3 +61,149 @@ def del_service(module,company,project,env,name): return {"code": 20000, "data": f"【{sid}】删除成功!"} else: return {"code": 50000, "data": f"{reg.status_code}【{sid}】{reg.text}"} + +def get_rules(): + rules = """ +- name: Domain + rules: + - alert: 站点可用性 + expr: probe_success == 0 + for: 1m + labels: + alertype: domain + severity: critical + annotations: + description: "{{ $labels.env }}_{{ $labels.name }}({{ $labels.project }}):站点无法访问\\n> {{ $labels.instance }}" + + - alert: 站点1h可用性低于80% + expr: sum_over_time(probe_success[1h])/count_over_time(probe_success[1h]) * 100 < 80 + for: 3m + labels: + alertype: domain + severity: warning + annotations: + description: "{{ $labels.env }}_{{ $labels.name }}({{ $labels.project }}):站点1h可用性:{{ $value | humanize }}%\\n> {{ $labels.instance }}" + + - alert: 站点状态异常 + expr: (probe_success == 0 and probe_http_status_code > 499) or probe_http_status_code == 0 + for: 1m + labels: + alertype: domain + severity: warning + annotations: + description: "{{ $labels.env }}_{{ $labels.name }}({{ $labels.project }}):站点状态异常:{{ $value }}\\n> {{ $labels.instance }}" + + - alert: 站点耗时过高 + expr: probe_duration_seconds > 0.5 + for: 2m + labels: + alertype: domain + severity: warning + annotations: + description: "{{ $labels.env }}_{{ $labels.name }}({{ $labels.project }}):当前站点耗时:{{ $value | humanize }}s\\n> {{ $labels.instance }}" + + - alert: SSL证书有效期 + expr: (probe_ssl_earliest_cert_expiry-time()) / 3600 / 24 < 15 + for: 2m + labels: + alertype: domain + severity: warning + annotations: + description: "{{ $labels.env }}_{{ $labels.name }}({{ $labels.project }}):证书有效期剩余{{ $value | humanize }}天\\n> {{ $labels.instance }}" +""" + return {"code": 20000, "rules": rules} + +def get_bconfig(): + bconfig = """ +modules: + http_2xx: + prober: http + http: + valid_status_codes: [200,204] + no_follow_redirects: false + preferred_ip_protocol: ip4 + ip_protocol_fallback: false + + # 用于需要检查SSL证书有效性,但是该域名访问后又会重定向到其它域名的情况,这样检查的证书有效期就是重定向后域名的。 + # 如果需要检查源域名信息,需要在blackbox中增加禁止重定向参数。 + httpNoRedirect4ssl: + prober: http + http: + valid_status_codes: [200,204,301,302,303] + no_follow_redirects: true + preferred_ip_protocol: ip4 + ip_protocol_fallback: false + + # 用于忽略SSL证书检查的站点监控。 + http200igssl: + prober: http + http: + valid_status_codes: + - 200 + tls_config: + insecure_skip_verify: true + + http_4xx: + prober: http + http: + valid_status_codes: [401,403,404] + preferred_ip_protocol: ip4 + ip_protocol_fallback: false + + http_5xx: + prober: http + http: + valid_status_codes: [500,502] + preferred_ip_protocol: ip4 + ip_protocol_fallback: false + + http_post_2xx: + prober: http + http: + method: POST + + icmp: + prober: icmp + + tcp_connect: + prober: tcp + + ssh_banner: + prober: tcp + tcp: + query_response: + - expect: "^SSH-2.0-" + - send: "SSH-2.0-blackbox-ssh-check" +""" + return {"code": 20000, "bconfig": bconfig} + +def get_pconfig(): + consul_server = consul_url.split("/")[2] + pconfig = f""" + - job_name: 'blackbox_exporter' + metrics_path: /probe + consul_sd_configs: + - server: '{consul_server}' + token: '{consul_token}' + services: ['blackbox_exporter'] + relabel_configs: + - source_labels: ["__meta_consul_service_metadata_instance"] + target_label: __param_target + - source_labels: [__meta_consul_service_metadata_module] + target_label: __param_module + - source_labels: [__meta_consul_service_metadata_module] + target_label: module + - source_labels: ["__meta_consul_service_metadata_company"] + target_label: company + - source_labels: ["__meta_consul_service_metadata_env"] + target_label: env + - source_labels: ["__meta_consul_service_metadata_name"] + target_label: name + - source_labels: ["__meta_consul_service_metadata_project"] + target_label: project + - source_labels: [__param_target] + target_label: instance + - target_label: __address__ + replacement: 127.0.0.1:9115 +""" + return {"code": 20000, "pconfig": pconfig} diff --git a/flask-consul/units/cloud/alicloud.py b/flask-consul/units/cloud/alicloud.py new file mode 100644 index 0000000..0024d16 --- /dev/null +++ b/flask-consul/units/cloud/alicloud.py @@ -0,0 +1,88 @@ +from alibabacloud_resourcemanager20200331.client import Client as ResourceManager20200331Client +from alibabacloud_tea_openapi import models as open_api_models +from alibabacloud_resourcemanager20200331 import models as resource_manager_20200331_models +from alibabacloud_ecs20140526.client import Client as Ecs20140526Client +from alibabacloud_ecs20140526 import models as ecs_20140526_models +from Tea.exceptions import TeaException + +import sys,datetime +from units import consul_kv +from units.cloud import sync_ecs + +def group(account): + ak,sk = consul_kv.get_aksk('alicloud',account) + now = datetime.datetime.now().strftime('%m%d/%H:%M') + config = open_api_models.Config(access_key_id=ak,access_key_secret=sk) + config.endpoint = f'resourcemanager.aliyuncs.com' + client = ResourceManager20200331Client(config) + list_resource_groups_request = resource_manager_20200331_models.ListResourceGroupsRequest(page_size=100) + try: + proj = client.list_resource_groups(list_resource_groups_request) + proj_list = proj.body.resource_groups.to_map()['ResourceGroup'] + group_dict = {i['Id']:i['DisplayName'] for i in proj_list} + consul_kv.put_kv(f'ConsulManager/assets/alicloud/group/{account}',group_dict) + count = len(group_dict) + data = {'count':count,'update':now,'status':20000,'msg':f'同步资源组成功!总数:{count}'} + consul_kv.put_kv(f'ConsulManager/record/jobs/alicloud/{account}/group', data) + print('【JOB】===>', 'alicloud_group', account, data, flush=True) + except TeaException as e: + emsg = e.message.split('. ',1)[0] + print("【code:】",e.code,"\n【message:】",emsg, flush=True) + data = consul_kv.get_value(f'ConsulManager/record/jobs/alicloud/{account}/group') + if data == {}: + data = {'count':'无','update':f'失败{e.code}','status':50000,'msg':emsg} + else: + data['update'] = f'失败{e.code}' + data['msg'] = emsg + consul_kv.put_kv(f'ConsulManager/record/jobs/alicloud/{account}/group', data) + except Exception as e: + data = {'count':'无','update':f'失败','status':50000,'msg':str(e)} + consul_kv.put_kv(f'ConsulManager/record/jobs/alicloud/{account}/group', data) + +def ecs(account,region): + ak,sk = consul_kv.get_aksk('alicloud',account) + now = datetime.datetime.now().strftime('%m%d/%H:%M') + group_dict = consul_kv.get_value(f'ConsulManager/assets/alicloud/group/{account}') + + config = open_api_models.Config(access_key_id=ak,access_key_secret=sk) + config.endpoint = f'ecs.{region}.aliyuncs.com' + client = Ecs20140526Client(config) + + next_token = '0' + ecs_dict = {} + try: + while next_token != '': + describe_instances_request = ecs_20140526_models.DescribeInstancesRequest( + max_results=100, + region_id=region, + next_token=next_token + ) + ecs = client.describe_instances(describe_instances_request) + ecs_list = ecs.body.instances.to_map()['Instance'] + ecs_dict_temp = {i['InstanceId']:{ + 'name':i['InstanceName'],'group':group_dict.get(i['ResourceGroupId'],'无'),'ostype':i['OSType'].lower(), + 'status':i['Status'],'region':region, + 'ip':i["InnerIpAddress"]["IpAddress"] if len(i["InnerIpAddress"]["IpAddress"]) != 0 else i['NetworkInterfaces']['NetworkInterface'][0]['PrimaryIpAddress'], + 'cpu':f"{i['Cpu']}核",'mem':f"{str(round(i['Memory']/1024,1)).rstrip('.0')}GB",'exp':i['ExpiredTime'].split('T')[0] + }for i in ecs_list} + ecs_dict.update(ecs_dict_temp) + next_token = ecs.body.next_token + + count = len(ecs_dict) + off,on = sync_ecs.w2consul('alicloud',account,region,ecs_dict) + data = {'count':count,'update':now,'status':20000,'on':on,'off':off,'msg':f'ECS同步成功!总数:{count},开机:{on},关机:{off}'} + consul_kv.put_kv(f'ConsulManager/record/jobs/alicloud/{account}/ecs/{region}', data) + print('【JOB】===>', 'alicloud_ecs', account,region, data, flush=True) + except TeaException as e: + emsg = e.message.split('. ',1)[0] + print("【code:】",e.code,"\n【message:】",emsg, flush=True) + data = consul_kv.get_value(f'ConsulManager/record/jobs/alicloud/{account}/ecs/{region}') + if data == {}: + data = {'count':'无','update':f'失败{e.code}','status':50000,'msg':emsg} + else: + data['update'] = f'失败{e.code}' + data['msg'] = emsg + consul_kv.put_kv(f'ConsulManager/record/jobs/alicloud/{account}/ecs/{region}', data) + except Exception as e: + data = {'count':'无','update':f'失败','status':50000,'msg':str(e)} + consul_kv.put_kv(f'ConsulManager/record/jobs/alicloud/{account}/ecs/{region}', data) diff --git a/flask-consul/units/cloud/huaweicloud.py b/flask-consul/units/cloud/huaweicloud.py new file mode 100644 index 0000000..8f1da57 --- /dev/null +++ b/flask-consul/units/cloud/huaweicloud.py @@ -0,0 +1,87 @@ +from huaweicloudsdkcore.auth.credentials import GlobalCredentials,BasicCredentials +from huaweicloudsdkeps.v1.region.eps_region import EpsRegion +from huaweicloudsdkcore.exceptions import exceptions +from huaweicloudsdkeps.v1 import * +from huaweicloudsdkecs.v2.region.ecs_region import EcsRegion +from huaweicloudsdkecs.v2 import * +import sys,datetime +from units import consul_kv +from units.cloud import sync_ecs +def group(account): + ak,sk = consul_kv.get_aksk('huaweicloud',account) + now = datetime.datetime.now().strftime('%m%d/%H:%M') + credentials = GlobalCredentials(ak, sk) + try: + client = EpsClient.new_builder() \ + .with_credentials(credentials) \ + .with_region(EpsRegion.value_of("cn-north-4")) \ + .build() + request = ListEnterpriseProjectRequest() + request.status = 1 + request.offset = 0 + info = client.list_enterprise_project(request).to_dict()['enterprise_projects'] + group_dict = {i['id']:i['name'] for i in info} + consul_kv.put_kv(f'ConsulManager/assets/huaweicloud/group/{account}',group_dict) + count = len(group_dict) + data = {'count':count,'update':now,'status':20000,'msg':f'同步企业项目成功!总数:{count}'} + consul_kv.put_kv(f'ConsulManager/record/jobs/huaweicloud/{account}/group', data) + print('【JOB】===>', 'huaweicloud_group', account, data, flush=True) + except exceptions.ClientRequestException as e: + print(e.status_code, flush=True) + print(e.request_id, flush=True) + print(e.error_code, flush=True) + print(e.error_msg, flush=True) + data = consul_kv.get_value(f'ConsulManager/record/jobs/huaweicloud/{account}/group') + if data == {}: + data = {'count':'无','update':f'失败{e.status_code}','status':50000,'msg':e.error_msg} + else: + data['update'] = f'失败{e.status_code}' + data['msg'] = e.error_msg + consul_kv.put_kv(f'ConsulManager/record/jobs/huaweicloud/{account}/group', data) + except Exception as e: + data = {'count':'无','update':f'失败','status':50000,'msg':str(e)} + consul_kv.put_kv(f'ConsulManager/record/jobs/huaweicloud/{account}/group', data) + +def ecs(account,region): + ak,sk = consul_kv.get_aksk('huaweicloud',account) + now = datetime.datetime.now().strftime('%m%d/%H:%M') + group_dict = consul_kv.get_value(f'ConsulManager/assets/huaweicloud/group/{account}') + credentials = BasicCredentials(ak, sk) + try: + client = EcsClient.new_builder() \ + .with_credentials(credentials) \ + .with_region(EcsRegion.value_of(region)) \ + .build() + request = ListServersDetailsRequest() + request.limit = 1000 + info = client.list_servers_details(request).to_dict()['servers'] + ecs_dict = {i['id']:{'name':i['name'], + 'ip':i['addresses'][i['metadata']['vpc_id']][0].addr, + 'region':region, + 'group':group_dict[i['enterprise_project_id']], + 'status':i['status'], + 'ostype':i['metadata']['os_type'].lower(), + 'cpu':i['flavor']['vcpus'] + '核', + 'mem':f"{str(round(int(i['flavor']['ram'])/1024,1)).rstrip('.0')}GB", + 'exp': '-' + } for i in info} + count = len(ecs_dict) + off,on = sync_ecs.w2consul('huaweicloud',account,region,ecs_dict) + data = {'count':count,'update':now,'status':20000,'on':on,'off':off,'msg':f'ECS同步成功!总数:{count},开机:{on},关机:{off}'} + consul_kv.put_kv(f'ConsulManager/record/jobs/huaweicloud/{account}/ecs/{region}', data) + print('【JOB】===>', 'huaweicloud_ecs', account,region, data, flush=True) + except exceptions.ClientRequestException as e: + print(e.status_code, flush=True) + print(e.request_id, flush=True) + print(e.error_code, flush=True) + print(e.error_msg, flush=True) + data = consul_kv.get_value(f'ConsulManager/record/jobs/huaweicloud/{account}/ecs/{region}') + if data == {}: + data = {'count':'无','update':f'失败{e.status_code}','status':50000,'on':0,'off':0,'msg':e.error_msg} + else: + data['update'] = f'失败{e.status_code}' + data['msg'] = e.error_msg + consul_kv.put_kv(f'ConsulManager/record/jobs/huaweicloud/{account}/ecs/{region}', data) + except Exception as e: + data = {'count':'无','update':f'失败','status':50000,'msg':str(e)} + consul_kv.put_kv(f'ConsulManager/record/jobs/huaweicloud/{account}/ecs/{region}', data) diff --git a/flask-consul/units/cloud/sync_ecs.py b/flask-consul/units/cloud/sync_ecs.py new file mode 100644 index 0000000..0ef43e8 --- /dev/null +++ b/flask-consul/units/cloud/sync_ecs.py @@ -0,0 +1,70 @@ +#!/usr/bin/python3 +import requests,json +from config import consul_token,consul_url,vendors,regions +headers = {'X-Consul-Token': consul_token} +geturl = f'{consul_url}/agent/services' +delurl = f'{consul_url}/agent/service/deregister' +puturl = f'{consul_url}/agent/service/register' +def w2consul(vendor,account,region,ecs_dict): + service_name = f'{vendor}_{account}_ecs' + params = {'filter': f'Service == "{service_name}" and "{region}" in Tags and Meta.account == "{account}"'} + try: + consul_ecs_iid_list = requests.get(geturl, headers=headers, params=params).json().keys() + except: + consul_ecs_iid_list = [] + + #在consul中删除云厂商不存在的ecs + for del_ecs in [x for x in consul_ecs_iid_list if x not in ecs_dict.keys()]: + dereg = requests.put(f'{delurl}/{del_ecs}', headers=headers) + if dereg.status_code == 200: + print({"code": 20000,"data": f"{account}-删除成功!"}, flush=True) + else: + print({"code": 50000,"data": f'{dereg.status_code}:{dereg.text}'}, flush=True) + off,on = 0,0 + for k,v in ecs_dict.items(): + iid = k + #去除consul中关机的ecs + if v['status'] in ['SHUTOFF','Stopped','STOPPED']: + off = off + 1 + if k in consul_ecs_iid_list: + dereg = requests.put(f'{delurl}/{iid}', headers=headers) + if dereg.status_code == 200: + print({"code": 20000,"data": f"{account}-删除成功!"}, flush=True) + else: + print({"code": 50000,"data": f'{dereg.status_code}:{dereg.text}'}, flush=True) + else: + on = on + 1 + port = 9100 if v['ostype'] == 'linux' else 9182 + ip = v['ip'] if isinstance(v['ip'],list) is False else v['ip'][0] + instance = f'{ip}:{port}' + data = { + 'id': iid, + 'name': service_name, + 'Address': ip, + 'port': port, + 'tags': [v['ostype'],region], + 'Meta': { + 'iid': iid, + 'name': v['name'], + 'region': regions[vendor].get(region,'未找到'), + 'group': v['group'], + 'instance': instance, + 'account': account, + 'vendor': vendors.get(vendor,'未找到'), + 'os': v['ostype'], + 'cpu': v['cpu'], + 'mem': v['mem'], + 'exp': v['exp'] + }, + "check": { + "tcp": f"{ip}:{port}", + "interval": "60s" + } + } + reg = requests.put(puturl, headers=headers, data=json.dumps(data)) + if reg.status_code == 200: + pass + #print({f"{account}:code": 20000,"data": "增加成功!"}, flush=True) + else: + print({f"{account}:code": 50000,"data": f'{reg.status_code}:{reg.text}'}, flush=True) + return off,on diff --git a/flask-consul/units/cloud/tencent_cloud.py b/flask-consul/units/cloud/tencent_cloud.py new file mode 100644 index 0000000..4dc0f8b --- /dev/null +++ b/flask-consul/units/cloud/tencent_cloud.py @@ -0,0 +1,95 @@ +import json +from tencentcloud.common import credential +from tencentcloud.common.profile.client_profile import ClientProfile +from tencentcloud.common.profile.http_profile import HttpProfile +from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException + +import sys,datetime +#sys.path.append("..") +#import consul_kv,sync_ecs +from units import consul_kv +from units.cloud import sync_ecs + +def group(account): + from tencentcloud.dcdb.v20180411 import dcdb_client, models + ak,sk = consul_kv.get_aksk('tencent_cloud',account) + now = datetime.datetime.now().strftime('%m%d/%H:%M') + try: + cred = credential.Credential(ak, sk) + httpProfile = HttpProfile() + httpProfile.endpoint = "dcdb.tencentcloudapi.com" + clientProfile = ClientProfile() + clientProfile.httpProfile = httpProfile + client = dcdb_client.DcdbClient(cred, "ap-guangzhou", clientProfile) + req = models.DescribeProjectsRequest() + params = {} + req.from_json_string(json.dumps(params)) + proj_list = client.DescribeProjects(req).Projects + + group_dict = {i.ProjectId:i.Name for i in proj_list} + consul_kv.put_kv(f'ConsulManager/assets/tencent_cloud/group/{account}',group_dict) + count = len(group_dict) + data = {'count':count,'update':now,'status':20000,'msg':f'同步资源组成功!总数:{count}'} + consul_kv.put_kv(f'ConsulManager/record/jobs/tencent_cloud/{account}/group', data) + print('【JOB】===>', 'tencent_cloud_group', account, data, flush=True) + except TencentCloudSDKException as err: + print(err, flush=True) + data = consul_kv.get_value(f'ConsulManager/record/jobs/tencent_cloud/{account}/group') + if data == {}: + data = {'count':'无','update':f'失败','status':50000,'msg':str(err)} + else: + data['update'] = f'失败' + data['msg'] = str(err) + consul_kv.put_kv(f'ConsulManager/record/jobs/tencent_cloud/{account}/group', data) + except Exception as e: + data = {'count':'无','update':f'失败','status':50000,'msg':str(e)} + consul_kv.put_kv(f'ConsulManager/record/jobs/tencent_cloud/{account}/group', data) + +def ecs(account,region): + from tencentcloud.cvm.v20170312 import cvm_client, models + ak,sk = consul_kv.get_aksk('tencent_cloud',account) + now = datetime.datetime.now().strftime('%m%d/%H:%M') + group_dict = consul_kv.get_value(f'ConsulManager/assets/tencent_cloud/group/{account}') + try: + cred = credential.Credential(ak, sk) + httpProfile = HttpProfile() + httpProfile.endpoint = "cvm.tencentcloudapi.com" + + clientProfile = ClientProfile() + clientProfile.httpProfile = httpProfile + client = cvm_client.CvmClient(cred, region, clientProfile) + req = models.DescribeInstancesRequest() + offset = 0 + total = 0 + ecs_dict = {} + while offset <= total: + params = {"Offset": offset, "Limit": 100} + req.from_json_string(json.dumps(params)) + resp = client.DescribeInstances(req) + ecs_list = resp.InstanceSet + total = resp.TotalCount + ecs_dict_temp = {i.InstanceId:{'name':i.InstanceName,'group':group_dict.get(str(i.Placement.ProjectId),'无'), + 'ostype': 'windows' if 'win' in i.OsName.lower() else 'linux', + 'status': i.InstanceState, 'region': region, 'ip':i.PrivateIpAddresses[0], + 'cpu': f'{i.CPU}核','mem': f'{i.Memory}GB','exp': i.ExpiredTime.split('T')[0] + } for i in ecs_list} + offset = offset + 100 + ecs_dict.update(ecs_dict_temp) + + count = len(ecs_dict) + off,on = sync_ecs.w2consul('tencent_cloud',account,region,ecs_dict) + data = {'count':count,'update':now,'status':20000,'on':on,'off':off,'msg':f'ECS同步成功!总数:{count},开机:{on},关机:{off}'} + consul_kv.put_kv(f'ConsulManager/record/jobs/tencent_cloud/{account}/ecs/{region}', data) + print('【JOB】===>', 'tencent_cloud_ecs', account,region, data, flush=True) + except TencentCloudSDKException as err: + print(err, flush=True) + data = consul_kv.get_value(f'ConsulManager/record/jobs/tencent_cloud/{account}/ecs/{region}') + if data == {}: + data = {'count':'无','update':f'失败','status':50000,'msg':str(err)} + else: + data['update'] = f'失败' + data['msg'] = str(err) + consul_kv.put_kv(f'ConsulManager/record/jobs/tencent_cloud/{account}/ecs/{region}', data) + except Exception as e: + data = {'count':'无','update':f'失败','status':50000,'msg':str(e)} + consul_kv.put_kv(f'ConsulManager/record/jobs/tencent_cloud/{account}/ecs/{region}', data) diff --git a/flask-consul/units/consul_kv.py b/flask-consul/units/consul_kv.py new file mode 100644 index 0000000..7cad9d5 --- /dev/null +++ b/flask-consul/units/consul_kv.py @@ -0,0 +1,76 @@ +# -*- coding:utf-8 -*- +import requests,json,sys,os +from base64 import b64decode +from config import consul_token,consul_url +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +#import myaes + +headers = {'X-Consul-Token': consul_token} + +def get_value(path): + url = f'{consul_url}/kv/{path}?raw' + response = requests.get(url, headers=headers) + response.encoding='utf-8' + if response.status_code == 200: + return response.json() + else: + return {} +def get_kv_dict(path): + url = f'{consul_url}/kv/{path}?recurse' + response = requests.get(url, headers=headers) + response.encoding='utf-8' + if response.status_code == 200 and response.text != '': + info = response.json() + kv_dict = {i['Key']:json.loads(b64decode(i['Value']).decode('utf-8')) for i in info if i['Value'] != None} + if kv_dict != {}: + return kv_dict + else: + return {} + else: + return {} +def get_keys_list(path): + url = f'{consul_url}/kv/{path}?keys' + response = requests.get(url, headers=headers) + response.encoding='utf-8' + if response.status_code == 200: + return response.json() + else: + return [] +def put_kv(path,value): + url = f'{consul_url}/kv/{path}' + payload = json.dumps(value,ensure_ascii=False).encode("utf-8") + response = requests.put(url, headers=headers, data=payload) + return response.json() + +def del_key(path): + url = f'{consul_url}/kv/{path}' + response = requests.delete(url, headers=headers) + if response.status_code == 200: + return response.json() + else: + return None + +def get_ecs_services(job_id): + cloud,account,itype,region = job_id.split('/') + service = f'{cloud}_{account}_{itype}' + region = f'and "{region}" in Tags' + url = f'{consul_url}/agent/services?filter=Service == "{service}" {region}' + response = requests.get(url, headers=headers) + if response.status_code == 200: + info = response.json() + ecs_list = [i['Meta'] for i in info.values()] + return {'code': 20000,'ecs_list': ecs_list} + else: + return {'code': 50000, 'data': f'{response.status_code}:{response.text}'} + +def get_aksk(cloud,account): + import myaes + aksk_dict = get_value(f'ConsulManager/assets/{cloud}/aksk/{account}') + ak = myaes.decrypt(aksk_dict['ak']) + sk = myaes.decrypt(aksk_dict['sk']) + return ak, sk + +def put_aksk(cloud,account,ak,sk): + import myaes + encrypt_aksk = {'ak': myaes.encrypt(ak), 'sk': myaes.encrypt(sk)} + return put_kv(f'ConsulManager/assets/{cloud}/aksk/{account}', encrypt_aksk) diff --git a/flask-consul/units/consul_manager.py b/flask-consul/units/consul_manager.py index a69c067..7a1645e 100644 --- a/flask-consul/units/consul_manager.py +++ b/flask-consul/units/consul_manager.py @@ -112,7 +112,7 @@ def add_instance(instance_dict): del instance_dict['metaInfo'] del instance_dict['checkInfo'] - print(instance_dict) + print(instance_dict, flush=True) reg = requests.put(f'{consul_url}/agent/service/register', headers=headers, data=json.dumps(instance_dict)) if reg.status_code == 200: diff --git a/flask-consul/units/gen_config.py b/flask-consul/units/gen_config.py new file mode 100644 index 0000000..9d63e8c --- /dev/null +++ b/flask-consul/units/gen_config.py @@ -0,0 +1,39 @@ +from config import consul_token,consul_url +def ecs_config(services_list,ostype_list): + consul_server = consul_url.split("/")[2] + job_dict = {'linux':'node_exporter','windows':'windows_exporter'} + configs = '' + for ostype in ostype_list: + job_name = job_dict[ostype] + config_str = f""" + - job_name: {job_name} + scrape_interval: 15s + scrape_timeout: 5s + consul_sd_configs: + - server: '{consul_server}' + token: '{consul_token}' + refresh_interval: 30s + services: {services_list} + tags: ['{ostype}'] + relabel_configs: + - source_labels: ['__meta_consul_service_metadata_vendor'] + target_label: vendor + - source_labels: ['__meta_consul_service_metadata_region'] + target_label: region + - source_labels: ['__meta_consul_service_metadata_group'] + target_label: group + - source_labels: ['__meta_consul_service_metadata_account'] + target_label: account + - source_labels: ['__meta_consul_service_metadata_name'] + target_label: name + - source_labels: ['__meta_consul_service_metadata_iid'] + target_label: iid + - source_labels: ['__meta_consul_service_metadata_exp'] + target_label: exp + - source_labels: ['__meta_consul_service_metadata_instance'] + target_label: instance + - source_labels: [instance] + target_label: __address__ +""" + configs = configs + config_str + return {'code': 20000,'configs': configs } diff --git a/flask-consul/units/myaes.py b/flask-consul/units/myaes.py new file mode 100644 index 0000000..2d0c462 --- /dev/null +++ b/flask-consul/units/myaes.py @@ -0,0 +1,20 @@ +# encoding:utf-8 +from base64 import b64encode,b64decode +from Crypto.Util.Padding import pad,unpad +from Crypto.Cipher import AES +import consul_kv +secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk'].encode('utf8') + +def encrypt(data): + data = data.encode('utf8') + cipher = AES.new(secret_key, AES.MODE_CBC) + encrypted_data = cipher.encrypt(pad(data, 16)) + data = cipher.iv + encrypted_data + return b64encode(data).decode('utf8') + +def decrypt(data): + data = b64decode(data) + iv = data[:16] + cipher = AES.new(secret_key, AES.MODE_CBC, iv) + data = unpad(cipher.decrypt(data[16:]), 16) + return data.decode('utf8') diff --git a/flask-consul/units/mydes.py b/flask-consul/units/mydes.py new file mode 100644 index 0000000..b3f17c2 --- /dev/null +++ b/flask-consul/units/mydes.py @@ -0,0 +1,14 @@ +from pyDes import des, ECB, PAD_PKCS5 +import binascii, consul_kv +secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk'] +key = secret_key[:8] +iv = key +k = des(key, ECB, iv, pad=None, padmode=PAD_PKCS5) + +def encrypt(s): + en = k.encrypt(s, padmode=PAD_PKCS5) + return binascii.b2a_hex(en).decode() + +def decrypt(s): + de = k.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5) + return de.decode() diff --git a/flask-consul/units/token_auth.py b/flask-consul/units/token_auth.py index 33801b8..98fa31c 100644 --- a/flask-consul/units/token_auth.py +++ b/flask-consul/units/token_auth.py @@ -1,8 +1,8 @@ from flask_httpauth import HTTPTokenAuth -import sys -sys.path.append("..") -from config import s - +from itsdangerous import TimedJSONWebSignatureSerializer +from units import consul_kv +secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk'] +s = TimedJSONWebSignatureSerializer(secret_key) auth = HTTPTokenAuth() @auth.verify_token diff --git a/flask-consul/views/blackbox.py b/flask-consul/views/blackbox.py index 5e2930c..acbefed 100644 --- a/flask-consul/views/blackbox.py +++ b/flask-consul/views/blackbox.py @@ -23,6 +23,16 @@ class GetAllList(Resource): args = parser.parse_args() return blackbox_manager.get_all_list(args['module'],args['company'],args['project'],args['env']) +class GetConfig(Resource): + @token_auth.auth.login_required + def get(self, stype): + if stype == 'rules': + return blackbox_manager.get_rules() + elif stype == 'bconfig': + return blackbox_manager.get_bconfig() + elif stype == 'pconfig': + return blackbox_manager.get_pconfig() + class BlackboxApi(Resource): decorators = [token_auth.auth.login_required] def get(self): @@ -49,3 +59,4 @@ class BlackboxApi(Resource): api.add_resource(GetAllList,'/api/blackbox/alllist') api.add_resource(BlackboxApi, '/api/blackbox/service') +api.add_resource(GetConfig,'/api/blackboxcfg/') diff --git a/flask-consul/views/jobs.py b/flask-consul/views/jobs.py new file mode 100644 index 0000000..ab7eafe --- /dev/null +++ b/flask-consul/views/jobs.py @@ -0,0 +1,115 @@ +from flask import Blueprint +from flask_restful import reqparse, Resource, Api +from flask_apscheduler import APScheduler +from config import vendors,regions +from units import token_auth,consul_kv +import json +blueprint = Blueprint('jobs',__name__) +api = Api(blueprint) + +parser = reqparse.RequestParser() +parser.add_argument('job_id',type=str) +parser.add_argument('job_dict',type=dict) +parser.add_argument('query_dict',type=str) + +def init(): + global Scheduler + Scheduler = APScheduler() + return Scheduler + +class Jobs(Resource): + decorators = [token_auth.auth.login_required] + def get(self): + args = parser.parse_args() + query_dict = json.loads(args['query_dict']) + if query_dict['vendor'] != '': + query_dict['vendor'] = {v : k for k, v in vendors.items()}[query_dict['vendor']] + query_set = set({k:v for k,v in query_dict.items() if v != ''}.items()) + job_list = list(consul_kv.get_kv_dict(f'ConsulManager/jobs').values()) + job_run_dict = {job.id:job.next_run_time.strftime("%m%d/%H:%M") for job in Scheduler.get_jobs()} + job_count_dict = consul_kv.get_kv_dict('ConsulManager/record/jobs') + jobs = [] + for i in job_list: + vendor,account,itype = i['id'].split('/')[0:3] + job_info_dict = {'vendor':vendor,'account':account,'itype':itype} + if query_set.issubset(job_info_dict.items()): + pass + else: + continue + region = i['args'][-1] if len(i['args']) == 2 else 'none' + interval = i['minutes'] + if f'ConsulManager/record/jobs/{i["id"]}' in job_count_dict: + count = job_count_dict[f'ConsulManager/record/jobs/{i["id"]}']['count'] + runtime = job_count_dict[f'ConsulManager/record/jobs/{i["id"]}']['update'] + on = job_count_dict[f'ConsulManager/record/jobs/{i["id"]}'].get('on',0) + off = job_count_dict[f'ConsulManager/record/jobs/{i["id"]}'].get('off',0) + else: + count = '无' + runtime = '无' + on,off = 0,0 + jobs.append({'region':regions[vendor][region],'vendor':vendors[vendor],'account':account,'itype':itype, + 'interval':interval,'jobid':i['id'],'nextime':job_run_dict[i['id']],'on':on,'off':off, + 'count':count, 'runtime':runtime}) + vendor_list = sorted(list(set([i['vendor'] for i in jobs]))) + account_list = sorted(list(set([i['account'] for i in jobs]))) + itype_list = sorted(list(set([i['itype'] for i in jobs]))) + return {'code': 20000,'all_jobs':jobs,'vendor_list':vendor_list,'account_list':account_list,'itype_list':itype_list} + + def post(self): + args = parser.parse_args() + job_dict = args['job_dict'] + job_status = job_dict['dialogStatus'] + if job_status == 'create': + ak = job_dict['ak'] + sk = job_dict['sk'] + consul_kv.put_aksk(job_dict['vendor'],job_dict['account'],ak,sk) + + proj_job_id = f"{job_dict['vendor']}/{job_dict['account']}/group" + proj_job_func = f"__main__:{job_dict['vendor']}.group" + proj_job_args = [job_dict['account']] + proj_job_interval = int(job_dict['proj_interval']) + + ecs_job_id = f"{job_dict['vendor']}/{job_dict['account']}/ecs/{job_dict['region']}" + ecs_job_func = f"__main__:{job_dict['vendor']}.ecs" + ecs_job_args = [job_dict['account'],job_dict['region']] + ecs_job_interval = int(job_dict['ecs_interval']) + + Scheduler.add_job(id=proj_job_id, func=proj_job_func, args=proj_job_args, trigger='interval', + minutes=proj_job_interval, replace_existing=True) + Scheduler.add_job(id=ecs_job_id, func=ecs_job_func, args=ecs_job_args, trigger='interval', + minutes=ecs_job_interval, replace_existing=True) + + proj_job_dict = {'id':proj_job_id,'func':proj_job_func,'args':proj_job_args,'minutes':proj_job_interval, + "trigger": "interval","replace_existing": True} + Scheduler.run_job(proj_job_id) + ecs_job_dict = {'id':ecs_job_id,'func':ecs_job_func,'args':ecs_job_args,'minutes':ecs_job_interval, + "trigger": "interval","replace_existing": True} + record_dict = consul_kv.get_value(f"ConsulManager/record/jobs/{proj_job_id}") + if record_dict['status'] == 20000: + consul_kv.put_kv(f'ConsulManager/jobs/{proj_job_id}',proj_job_dict) + consul_kv.put_kv(f'ConsulManager/jobs/{ecs_job_id}',ecs_job_dict) + else: + Scheduler.remove_job(proj_job_id) + Scheduler.remove_job(ecs_job_id) + return {'code': record_dict['status'], 'data': f"{record_dict['update']}:{record_dict['msg']}"} + elif job_status == 'update': + jobid = job_dict['jobid'] + interval = int(job_dict['interval']) + Scheduler.modify_job(jobid,trigger='interval',minutes=interval) + upjob_dict = consul_kv.get_value(f'ConsulManager/jobs/{jobid}') + upjob_dict['minutes'] = interval + consul_kv.put_kv(f'ConsulManager/jobs/{jobid}',upjob_dict) + return {'code': 20000, 'data': '更新成功!'} + elif job_status == 'run': + Scheduler.run_job(job_dict['jobid']) + record_dict = consul_kv.get_value(f"ConsulManager/record/jobs/{job_dict['jobid']}") + return {'code': record_dict['status'], 'data': f"{record_dict['update']}:{record_dict['msg']}"} + + def delete(self): + args = parser.parse_args() + job_id = args['job_id'] + Scheduler.remove_job(job_id) + del_job = consul_kv.del_key(f'ConsulManager/jobs/{job_id}') + return {'code': 20000, 'data': '删除成功!'} + +api.add_resource(Jobs, '/api/jobs') diff --git a/flask-consul/views/login.py b/flask-consul/views/login.py index 9801ad3..25bb043 100644 --- a/flask-consul/views/login.py +++ b/flask-consul/views/login.py @@ -1,9 +1,12 @@ from flask import Blueprint from flask_restful import reqparse, Resource, Api +from itsdangerous import TimedJSONWebSignatureSerializer import sys sys.path.append("..") -from config import admin_passwd,s -from units import token_auth +from config import admin_passwd +from units import token_auth, consul_kv +secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk'] +s = TimedJSONWebSignatureSerializer(secret_key) blueprint = Blueprint('login',__name__) api = Api(blueprint) diff --git a/flask-consul/views/nodes.py b/flask-consul/views/nodes.py new file mode 100644 index 0000000..1cfc266 --- /dev/null +++ b/flask-consul/views/nodes.py @@ -0,0 +1,47 @@ +from flask import Blueprint +from flask_restful import reqparse, Resource, Api +from flask_apscheduler import APScheduler +#import sys +#sys.path.append("..") +from units import token_auth,consul_kv,gen_config + +blueprint = Blueprint('nodes',__name__) +api = Api(blueprint) + +parser = reqparse.RequestParser() +parser.add_argument('job_id',type=str) +parser.add_argument('services_dict',type=dict) + +class Nodes(Resource): + decorators = [token_auth.auth.login_required] + def get(self, stype): + job_id = parser.parse_args()['job_id'] + if stype == 'group': + cloud,account,itype = job_id.split('/') + group_dict = consul_kv.get_value(f'ConsulManager/assets/{cloud}/group/{account}') + group_list = [{'gid':k,'gname':v}for k,v in group_dict.items()] + return {'code': 20000,'group':group_list} + elif stype == 'ecs': + if job_id == '': + return {'code': 20000,'ecs_list': [] } + else: + return consul_kv.get_ecs_services(job_id) + elif stype == 'jobecs': + jobecs = consul_kv.get_keys_list('ConsulManager/jobs') + jobecs_list = [i.split('/jobs/')[1] for i in jobecs if '/ecs/' in i] + return {'code': 20000,'jobecs':jobecs_list} + elif stype == 'ecs_services': + jobecs = consul_kv.get_keys_list('ConsulManager/jobs') + jobecs_list = [i.split('/jobs/')[1] for i in jobecs if '/ecs/' in i] + services_list = [] + for i in jobecs_list: + serivces = i.split("/") + services_list.append(f'{serivces[0]}_{serivces[1]}_{serivces[2]}') + return {'code': 20000,'services_list': sorted(set(services_list))} + + def post(self, stype): + if stype == 'config': + args = parser.parse_args() + services_dict = args['services_dict'] + return gen_config.ecs_config(services_dict['services_list'],services_dict['ostype_list']) +api.add_resource(Nodes, '/api/nodes/') diff --git a/vue-consul/package.json b/vue-consul/package.json index 3151478..155fbd3 100644 --- a/vue-consul/package.json +++ b/vue-consul/package.json @@ -18,14 +18,16 @@ "core-js": "3.6.5", "element-ui": "2.15.7", "file-saver": "2.0.1", - "xlsx": "0.17.0", "js-cookie": "2.2.0", "normalize.css": "7.0.0", "nprogress": "0.2.0", "path-to-regexp": "2.4.0", "vue": "2.6.10", + "vue-clipboard2": "^0.3.3", + "vue-highlightjs": "^1.3.3", "vue-router": "3.0.6", - "vuex": "3.1.0" + "vuex": "3.1.0", + "xlsx": "0.17.0" }, "devDependencies": { "@vue/cli-plugin-babel": "4.4.4", diff --git a/vue-consul/src/api/blackbox.js b/vue-consul/src/api/blackbox.js index 6dda3e5..3c0d3cd 100644 --- a/vue-consul/src/api/blackbox.js +++ b/vue-consul/src/api/blackbox.js @@ -35,3 +35,21 @@ export function delService(data) { data }) } +export function getRules() { + return request({ + url: '/api/blackboxcfg/rules', + method: 'get' + }) +} +export function getPconfig() { + return request({ + url: '/api/blackboxcfg/pconfig', + method: 'get' + }) +} +export function getBconfig() { + return request({ + url: '/api/blackboxcfg/bconfig', + method: 'get' + }) +} diff --git a/vue-consul/src/api/node-exporter.js b/vue-consul/src/api/node-exporter.js new file mode 100644 index 0000000..7186800 --- /dev/null +++ b/vue-consul/src/api/node-exporter.js @@ -0,0 +1,61 @@ +import request from '@/utils/request-ops' + +export function getAllJobs(query_dict) { + return request({ + url: '/api/jobs', + method: 'get', + params: { query_dict } + }) +} + +export function PostJob(job_dict) { + return request({ + url: '/api/jobs', + method: 'post', + data: { job_dict } + }) +} + +export function DelJob(job_id) { + return request({ + url: '/api/jobs', + method: 'delete', + params: { job_id } + }) +} + +export function getGroup(job_id) { + return request({ + url: '/api/nodes/group', + method: 'get', + params: { job_id } + }) +} + +export function getEcsList(job_id) { + return request({ + url: '/api/nodes/ecs', + method: 'get', + params: { job_id } + }) +} + +export function getJobEcs() { + return request({ + url: '/api/nodes/jobecs', + method: 'get' + }) +} +export function getServicesList() { + return request({ + url: '/api/nodes/ecs_services', + method: 'get' + }) +} +export function getConfig(services_dict) { + return request({ + url: '/api/nodes/config', + method: 'post', + data: { services_dict } + }) +} diff --git a/vue-consul/src/main.js b/vue-consul/src/main.js index 566772c..ffc1880 100644 --- a/vue-consul/src/main.js +++ b/vue-consul/src/main.js @@ -15,6 +15,13 @@ import router from './router' import '@/icons' // icon import '@/permission' // permission control import * as filters from './filters' // global filters + +import VueHighlightJS from 'vue-highlightjs' +import 'highlight.js/styles/xt256.css' +Vue.use(VueHighlightJS) +import VueClipboard from 'vue-clipboard2' +Vue.use(VueClipboard) + /** * If you don't want to use mock-server * you want to use MockJs for mock api diff --git a/vue-consul/src/router/index.js b/vue-consul/src/router/index.js index 7221e86..7e7d7a9 100644 --- a/vue-consul/src/router/index.js +++ b/vue-consul/src/router/index.js @@ -70,32 +70,82 @@ export const constantRoutes = [ }, { path: 'services', - name: 'Services', + name: '服务组', component: () => import('@/views/consul/services'), - meta: { title: 'Services', icon: 'el-icon-news' } + meta: { title: '服务组', icon: 'el-icon-news' } }, { path: 'instances', - name: 'Instances', + name: '实例管理', component: () => import('@/views/consul/instances'), - meta: { title: 'Instances', icon: 'el-icon-connection' } + meta: { title: '实例管理', icon: 'el-icon-connection' } + } + ] + }, + { + path: '/nodes', + component: Layout, + redirect: '/nodes/jobs', + name: 'ECS 云主机监控', + meta: { title: 'ECS 云主机监控', icon: 'example' }, + children: [ + { + path: 'jobs', + name: '接入数据源', + component: () => import('@/views/node-exporter/jobs'), + meta: { title: '接入数据源', icon: 'el-icon-school' } + }, + { + path: 'lists', + name: '云主机列表', + component: () => import('@/views/node-exporter/lists'), + meta: { title: '云主机列表', icon: 'el-icon-s-platform' } + }, + { + path: 'pconfig', + name: 'Prometheus 配置', + component: () => import('@/views/node-exporter/pconfig'), + meta: { title: 'Prometheus 配置', icon: 'el-icon-set-up' } } ] }, { path: '/blackbox', component: Layout, - children: [{ - path: 'index', - name: 'Blackbox 站点监控', - component: () => import('@/views/blackbox/index'), - meta: { title: 'Blackbox 站点监控', icon: 'tree' } - }] + redirect: '/blackbox/index', + name: 'Blackbox 站点监控', + meta: { title: 'Blackbox 站点监控', icon: 'tree' }, + children: [ + { + path: 'index', + name: '站点管理', + component: () => import('@/views/blackbox/index'), + meta: { title: '站点管理', icon: 'el-icon-s-order' } + }, + { + path: 'bconfig', + name: 'Blackbox 配置', + component: () => import('@/views/blackbox/bconfig'), + meta: { title: 'Blackbox 配置', icon: 'el-icon-c-scale-to-original' } + }, + { + path: 'pconfig', + name: 'Prometheus 配置', + component: () => import('@/views/blackbox/pconfig'), + meta: { title: 'Prometheus 配置', icon: 'el-icon-set-up' } + }, + { + path: 'rules', + name: '告警规则', + component: () => import('@/views/blackbox/rules'), + meta: { title: '告警规则', icon: 'el-icon-bell' } + } + ] }, { - path: '友情链接', + path: '快速链接', component: Layout, - meta: { title: '友情链接', icon: 'link' }, + meta: { title: '快速链接', icon: 'link' }, children: [ { path: 'https://starsl.cn', @@ -103,18 +153,22 @@ export const constantRoutes = [ }, { path: 'https://github.com/starsliao?tab=repositories', - meta: { title: '我的Github', icon: 'el-icon-star-off' } + meta: { title: '我的 Github', icon: 'el-icon-star-off' } }, { path: 'https://grafana.com/orgs/starsliao/dashboards', - meta: { title: '我的Grafana', icon: 'el-icon-odometer' } + meta: { title: '我的 Grafana', icon: 'el-icon-odometer' } }, { - path: 'https://starsl.cn/static/img/qr.png', + path: 'https://starsl.cn/static/img/thanks.png', meta: { title: '我的公众号', icon: 'el-icon-chat-dot-round' } }, { - path: 'https://element.eleme.cn', + path: 'https://github.com/starsliao/ConsulManager/blob/main/Thanks.md', + meta: { title: '特别鸣谢', icon: 'el-icon-cold-drink' } + }, + { + path: 'https://element.eleme.cn/#/zh-CN/component/icon', meta: { title: 'Element', icon: 'el-icon-eleme' } } diff --git a/vue-consul/src/utils/request-ops.js b/vue-consul/src/utils/request-ops.js index d8e38a0..7e8dace 100644 --- a/vue-consul/src/utils/request-ops.js +++ b/vue-consul/src/utils/request-ops.js @@ -8,7 +8,7 @@ const service = axios.create({ // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url // withCredentials: true, // send cookies when cross-domain requests baseURL: '', - timeout: 5000 // request timeout + timeout: 10000 // request timeout }) // request interceptor diff --git a/vue-consul/src/views/blackbox/bconfig.vue b/vue-consul/src/views/blackbox/bconfig.vue new file mode 100644 index 0000000..9537e10 --- /dev/null +++ b/vue-consul/src/views/blackbox/bconfig.vue @@ -0,0 +1,51 @@ + + + + diff --git a/vue-consul/src/views/blackbox/index.vue b/vue-consul/src/views/blackbox/index.vue index 0c40023..ad0b6b9 100644 --- a/vue-consul/src/views/blackbox/index.vue +++ b/vue-consul/src/views/blackbox/index.vue @@ -92,22 +92,22 @@ - - + + - + - + - + - + - + @@ -118,7 +118,7 @@ - + diff --git a/vue-consul/src/views/node-exporter/jobs.vue b/vue-consul/src/views/node-exporter/jobs.vue new file mode 100644 index 0000000..8841a07 --- /dev/null +++ b/vue-consul/src/views/node-exporter/jobs.vue @@ -0,0 +1,386 @@ + + + + + diff --git a/vue-consul/src/views/node-exporter/lists.vue b/vue-consul/src/views/node-exporter/lists.vue new file mode 100644 index 0000000..a932b66 --- /dev/null +++ b/vue-consul/src/views/node-exporter/lists.vue @@ -0,0 +1,61 @@ + + + diff --git a/vue-consul/src/views/node-exporter/pconfig.vue b/vue-consul/src/views/node-exporter/pconfig.vue new file mode 100644 index 0000000..fe7584e --- /dev/null +++ b/vue-consul/src/views/node-exporter/pconfig.vue @@ -0,0 +1,74 @@ + + + +