add ecs sync consul

This commit is contained in:
StarsL.cn 2022-04-09 09:35:50 +08:00
parent 64e919f265
commit 1c4aa683df
38 changed files with 1771 additions and 86 deletions

View File

@ -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

2
Thanks.md Normal file
View File

@ -0,0 +1,2 @@
@dong9205
@会飞的鱼

View File

@ -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"]

View File

@ -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":"港澳台地区(中国香港)"}
}

View File

@ -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)
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)

View File

@ -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

View File

@ -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}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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 }

View File

@ -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')

View File

@ -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()

View File

@ -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

View File

@ -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/<stype>')

115
flask-consul/views/jobs.py Normal file
View File

@ -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')

View File

@ -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)

View File

@ -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/<stype>')

View File

@ -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",

View File

@ -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'
})
}

View File

@ -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 }
})
}

View File

@ -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

View File

@ -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',
redirect: '/blackbox/index',
name: 'Blackbox 站点监控',
meta: { title: 'Blackbox 站点监控', icon: 'tree' },
children: [
{
path: 'index',
name: '站点管理',
component: () => import('@/views/blackbox/index'),
meta: { title: 'Blackbox 站点监控', icon: 'tree' }
}]
meta: { title: '站点管理', icon: 'el-icon-s-order' }
},
{
path: '友情链接',
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: '快速链接',
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' }
}

View File

@ -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

View File

@ -0,0 +1,51 @@
<template>
<div class="app-container">
<el-button v-clipboard:copy="bconfig" v-clipboard:success="onCopy" v-clipboard:error="onError" class="filter-item" type="warning" icon="el-icon-document-copy">
复制配置
</el-button>
<pre v-highlightjs="bconfig" style="line-height:120%"><code class="yaml yamlcode" /></pre>
</div>
</template>
<script>
import { getBconfig } from '@/api/blackbox'
export default {
data() {
return {
listLoading: false,
bconfig: ''
}
},
created() {
this.fetchBconfig()
},
methods: {
onCopy() {
this.$message({
message: '复制成功!',
type: 'success'
})
},
onError() {
this.$message.error('复制失败!')
},
fetchBconfig() {
this.listLoading = true
getBconfig().then(response => {
this.bconfig = response.bconfig
this.listLoading = false
})
}
}
}
</script>
<style>
.yamlcode {
font-family:'Consolas';
}
pre {
max-height: 640px;
white-space: pre-wrap;
overflow:auto;
}
</style>

View File

@ -92,22 +92,22 @@
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="handleFilter" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="40%">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="80px" style="width: 400px; margin-left:50px;">
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="37%">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="auto" style="width: 90%; margin-left: 20px;">
<el-form-item label="监控类型" prop="module">
<el-autocomplete v-model="temp.module" :fetch-suggestions="Sugg_module" placeholder="优先选择" clearable style="width: 360px" class="filter-item" />
<el-autocomplete v-model="temp.module" :fetch-suggestions="Sugg_module" placeholder="优先选择" clearable class="filter-item" />
</el-form-item>
<el-form-item label="公司部门" prop="company">
<el-autocomplete v-model="temp.company" :fetch-suggestions="Sugg_company" placeholder="优先选择" clearable style="width: 360px" class="filter-item" />
<el-autocomplete v-model="temp.company" :fetch-suggestions="Sugg_company" placeholder="优先选择" clearable class="filter-item" />
</el-form-item>
<el-form-item label="项目" prop="project">
<el-autocomplete v-model="temp.project" :fetch-suggestions="Sugg_project" placeholder="优先选择" clearable style="width: 360px" class="filter-item" />
<el-autocomplete v-model="temp.project" :fetch-suggestions="Sugg_project" placeholder="优先选择" clearable class="filter-item" />
</el-form-item>
<el-form-item label="环境" prop="env">
<el-autocomplete v-model="temp.env" :fetch-suggestions="Sugg_env" placeholder="优先选择" clearable style="width: 360px" class="filter-item" />
<el-autocomplete v-model="temp.env" :fetch-suggestions="Sugg_env" placeholder="优先选择" clearable class="filter-item" />
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="temp.name" placeholder="请输入" clearable style="width: 360px" class="filter-item" />
<el-input v-model="temp.name" placeholder="请输入" clearable class="filter-item" />
</el-form-item>
<el-form-item prop="instance">
<span slot="label">
@ -118,7 +118,7 @@
</el-tooltip>
</span>
</span>
<el-input v-model="temp.instance" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入" style="width: 360px" class="filter-item" />
<el-input v-model="temp.instance" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入" class="filter-item" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">

View File

@ -0,0 +1,51 @@
<template>
<div class="app-container">
<el-button v-clipboard:copy="pconfig" v-clipboard:success="onCopy" v-clipboard:error="onError" class="filter-item" type="warning" icon="el-icon-document-copy">
复制配置
</el-button>
<pre v-highlightjs="pconfig" style="line-height:120%"><code class="yaml yamlcode" /></pre>
</div>
</template>
<script>
import { getPconfig } from '@/api/blackbox'
export default {
data() {
return {
listLoading: false,
pconfig: ''
}
},
created() {
this.fetchPconfig()
},
methods: {
onCopy() {
this.$message({
message: '复制成功!',
type: 'success'
})
},
onError() {
this.$message.error('复制失败!')
},
fetchPconfig() {
this.listLoading = true
getPconfig().then(response => {
this.pconfig = response.pconfig
this.listLoading = false
})
}
}
}
</script>
<style>
.yamlcode {
font-family:'Consolas';
}
pre {
max-height: 640px;
white-space: pre-wrap;
overflow:auto;
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<div class="app-container">
<el-button v-clipboard:copy="rules" v-clipboard:success="onCopy" v-clipboard:error="onError" class="filter-item" type="warning" icon="el-icon-document-copy">
复制配置
</el-button>
<pre v-highlightjs="rules" style="line-height:120%"><code class="yaml yamlcode" /></pre>
</div>
</template>
<script>
import { getRules } from '@/api/blackbox'
export default {
data() {
return {
listLoading: false,
rules: ''
}
},
created() {
this.fetchRules()
},
methods: {
onCopy() {
this.$message({
message: '复制成功!',
type: 'success'
})
},
onError() {
this.$message.error('复制失败!')
},
fetchRules() {
this.listLoading = true
getRules().then(response => {
this.rules = response.rules
this.listLoading = false
})
}
}
}
</script>
<style>
.yamlcode {
font-family:'Consolas';
}
pre {
max-height: 640px;
white-space: pre-wrap;
overflow:auto;
}
</style>

View File

@ -78,32 +78,32 @@
<el-table-column type="expand" width="1">
<template slot-scope="{row}">
<el-table style="width: 100%" :data="row.meta" row-class-name="success-row" fit border>
<el-table-column v-for="{ prop, label } in row.meta_label" :key="prop" :prop="prop" :label="label" />
<el-table-column v-for="{ prop, label } in row.meta_label" :key="prop" :prop="prop" :label="label" align="center" />
</el-table>
</template>
</el-table-column>
</el-table>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="45%">
<el-form ref="dataForm" :rules="rules" :model="newService" label-position="right" label-width="100px" style="width: 500px; margin-left: 50px;">
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="40%">
<el-form ref="dataForm" :rules="rules" :model="newService" label-position="right" label-width="auto" style="width: 90%; margin-left: 20px;">
<el-form-item label="所属服务组" prop="name">
<el-autocomplete v-model="newService.name" :fetch-suggestions="Sugg_name" placeholder="优先选择" clearable style="width: 360px" class="filter-item" />
<el-autocomplete v-model="newService.name" :fetch-suggestions="Sugg_name" placeholder="优先选择" clearable class="filter-item" />
</el-form-item>
<div v-if="dialogStatus==='update'">
<el-form-item label="服务实例ID" prop="ID">
<el-input v-model="newService.ID" placeholder="请输入" clearable style="width: 360px" :disabled="true" />
<el-input v-model="newService.ID" placeholder="请输入" clearable :disabled="true" />
</el-form-item>
</div>
<div v-else>
<el-form-item label="服务实例ID" prop="ID">
<el-input v-model="newService.ID" placeholder="请输入" clearable style="width: 360px" class="filter-item" />
<el-input v-model="newService.ID" placeholder="请输入" clearable class="filter-item" />
</el-form-item>
</div>
<el-form-item label="地址" prop="address">
<el-input v-model="newService.address" placeholder="请输入" clearable style="width: 360px" class="filter-item" />
<el-input v-model="newService.address" placeholder="请输入" clearable class="filter-item" />
</el-form-item>
<el-form-item label="端口" prop="port">
<el-input v-model="newService.port" placeholder="请输入" clearable style="width: 360px" class="filter-item" />
<el-input v-model="newService.port" placeholder="请输入" clearable class="filter-item" />
</el-form-item>
<el-form-item label="Tags" prop="tags">
<el-tag v-for="tag in newService.tags" :key="tag" closable :disable-transitions="false" @close="handleClose(tag)">{{ tag }}</el-tag>
@ -123,7 +123,7 @@
</el-tooltip>
</span>
</span>
<el-input v-model="newService.metaInfo.metaJson" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder='{ "aaa": "bbb", "ccc": "ddd" }' clearable style="width: 360px" class="filter-item" />
<el-input v-model="newService.metaInfo.metaJson" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder='{ "aaa": "bbb", "ccc": "ddd" }' clearable class="filter-item" />
</el-form-item>
<el-form-item v-if="coption !== '' && dialogStatus==='update'" label="健康检查操作" prop="coption">
@ -134,7 +134,7 @@
</el-radio-group>
</el-form-item>
<el-form :inline="true" class="demo-form-inline" label-position="right" label-width="100px">
<el-form :inline="true" class="demo-form-inline" label-position="right" label-width="94px">
<el-form-item v-if="coption === '' || coption === 'modf'" label="健康检查" prop="isCheck">
<el-switch v-model="newService.checkInfo.isCheck" active-text="     " />
</el-form-item>
@ -161,7 +161,7 @@
</el-tooltip>
</span>
</span>
<el-input v-model="newService.checkInfo.caddress" placeholder="请输入" clearable style="width: 360px" />
<el-input v-model="newService.checkInfo.caddress" placeholder="请输入" clearable />
</el-form-item>
<el-form v-if="newService.checkInfo.isCheck" :inline="true" class="demo-form-inline" label-position="right" label-width="100px">
@ -225,6 +225,7 @@ export default {
}
},
coption: '',
listLoading: false,
dialogFormVisible: false,
dialogStatus: '',
textMap: {
@ -369,10 +370,8 @@ export default {
})
},
fetchServicesName() {
this.listLoading = true
getServicesName().then(response => {
this.services_name_list = response.services_name
this.listLoading = false
this.xname = this.load_name()
})
},
@ -409,7 +408,6 @@ export default {
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.coption === 'delete') {
delSid(this.newService.ID).then(response => {
addSid(this.newService).then(response => {
this.fetchServicesName()
@ -422,18 +420,6 @@ export default {
})
})
})
} else {
addSid(this.newService).then(response => {
this.fetchServicesName()
this.services_name = this.newService.name
this.fetchData(this.newService.name)
this.dialogFormVisible = false
this.$message({
message: response.data,
type: 'success'
})
})
}
}
})
},

View File

@ -9,39 +9,41 @@
highlight-current-row
style="width: 100%;"
>
<el-table-column label="ID" width="73px" align="center">
<el-table-column label="ID" width="50px" align="center">
<template slot-scope="scope">
<span>{{ scope.$index+1 }}</span>
</template>
</el-table-column>
<el-table-column prop="Name" label="服务" sortable align="center">
<el-table-column prop="Name" label="服务" sortable align="center">
<template slot-scope="{row}">
<el-link type="primary" @click="handleInstances(row.Name)">{{ row.Name }}</el-link>
<el-link type="primary" style="font-weight:bold" @click="handleInstances(row.Name)"><i class="el-icon-view el-icon--left" />{{ row.Name }}</el-link>
</template>
</el-table-column>
<el-table-column prop="Nodes" label="节点" sortable align="center" width="200">
<el-table-column prop="Nodes" label="节点" align="center" width="120">
<template slot-scope="{row}">
<el-tag v-for="atag in row.Nodes" :key="atag" size="mini" effect="dark">{{ atag }}</el-tag>
<el-tag v-for="atag in row.Nodes" :key="atag" type="info" size="mini">{{ atag }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="Datacenter" label="数据中心" sortable align="center" width="120">
<el-table-column prop="Datacenter" label="数据中心" align="center" width="110">
<template slot-scope="{row}">
<span>{{ row.Datacenter }}</span>
</template>
</el-table-column>
<el-table-column prop="Tags" label="Tags" sortable align="center">
<template slot-scope="{row}">
<el-tag v-for="atag in row.Tags" :key="atag" size="mini">{{ atag }}</el-tag>
<el-tag v-for="atag in row.Tags" :key="atag" size="small">{{ atag }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="InstanceCount" label="实例数" sortable align="center" width="100">
<template slot-scope="{row}">
<span>{{ row.InstanceCount }}</span>
<span style="font-weight:bold">{{ row.InstanceCount }}</span>
</template>
</el-table-column>
<el-table-column prop="ChecksPassing" label="健康实例" sortable align="center" width="120">
<template slot-scope="{row}">
<span>{{ row.ChecksPassing - 1 }} </span>
<el-tooltip class="item" effect="dark" content="健康检查成功的实例数" placement="top">
<el-button size="mini" type="success" icon="el-icon-check" circle>{{ row.ChecksPassing - 1 }}</el-button>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="ChecksCritical" label="实例状态" sortable align="center" width="120">

View File

@ -4,6 +4,25 @@
<el-link :underline="false" type="primary" icon="el-icon-star-on" href="https://github.com/starsliao/ConsulManager" target="_blank" class="dashboard-text">StarsL.cn</el-link>
</el-badge>
<el-timeline>
<el-timeline-item timestamp="2022/4/7" placement="top">
<el-card>
<h4>v0.5.0</h4>
<p>重要增加ECS云主机监控支持自动同步阿里腾讯华为云的ECS分组信息到Consul并接入到Prometheus监控</p>
<p>增加了从Consul同步ECS站点信息到Prometheus的配置生成界面</p>
<p>增加了Blackbox的配置信息与告警规则信息页面</p>
<p>优化了Blackbox接入Prometheus的配置只需配置1个job即可接入所有类型的站点监控</p>
<p>更新了站点监控的grafana看板增加了URL筛选查询关联所有图表并支持展示单job的配置方式</p>
<p>更新了主机监控的grafana看板可匹配自动同步方式采集ECS信息字段的展示 优化了大量图表使用新版表格重建新增健康评分概念并新增了整体资源消耗信息的一些图表</p>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2022/2/23" placement="top">
<el-card>
<h4>v0.3.1</h4>
<p>允许在实例ID字段使用'/'(可以在Consul管理中对blackbox-exporter的监控实例做自定义编辑了例如增加标签增加Meta)</p>
<p>优化了描述和引导使用Blackbox站点监控</p>
<p>增加了Makefile文件可以使用make update来更新等操作</p>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2022/2/10" placement="top">
<el-card>
<h4>v0.3.0</h4>

View File

@ -45,7 +45,7 @@
</el-form>
<div align="center" class="title-container">
<span style="font-size:10px" class="title">v0.3.1</span>
<span style="font-size:10px" class="title">v0.5.0</span>
</div>
</div>
</template>

View File

@ -0,0 +1,386 @@
<template>
<div class="app-container">
<el-select v-model="query.vendor" placeholder="云厂商" clearable style="width: 150px" class="filter-item" @change="fetchData(query)">
<el-option v-for="item in vendor_list" :key="item" :label="item" :value="item" />
</el-select>
<el-select v-model="query.account" placeholder="账户" clearable style="width: 150px" class="filter-item" @change="fetchData(query)">
<el-option v-for="item in account_list" :key="item" :label="item" :value="item" />
</el-select>
<el-select v-model="query.itype" placeholder="类型" clearable style="width: 150px" class="filter-item" @change="fetchData(query)">
<el-option v-for="item in itype_list" :key="item" :label="item" :value="item" />
</el-select>
<el-tooltip class="item" effect="light" content="清空查询条件" placement="top">
<el-button class="filter-item" style="margin-left: 10px;" type="info" icon="el-icon-delete" circle @click="resetData" />
</el-tooltip>
<el-button class="filter-item" type="primary" icon="el-icon-edit" @click="handleCreate">
新增同步源
</el-button>
<div style="float: right;">
<el-tooltip class="item" effect="light" content="刷新当前页面" placement="top">
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-refresh" circle @click="fetchData" />
</el-tooltip>
</div>
<el-table v-loading="listLoading" :data="joblist" :row-class-name="tableRowClassName" border fit highlight-current-row style="width: 100%;">
<el-table-column type="index" align="center" />
<el-table-column prop="vendor" label="云厂商" sortable align="center" />
<el-table-column prop="account" label="账户" sortable align="center" />
<el-table-column prop="itype" label="资源" sortable align="center">
<template slot-scope="{row}">
<div v-if="row.itype === 'ecs'" slot="reference" class="name-wrapper">
<el-tag size="medium">{{ row.itype.toUpperCase() }}</el-tag>
</div>
<div v-else>
<span>{{ row.itype }} </span>
</div>
</template>
</el-table-column>
<el-table-column prop="region" label="区域" sortable align="center" />
<el-table-column prop="count" label="资源数" sortable align="center">
<template slot-scope="{row}">
<span style="font-weight:bold">{{ row.count }} </span>
<el-tooltip v-if="row.itype === 'ecs'" style="diaplay:inline" effect="dark" placement="top">
<div slot="content"> 开机{{ row.on }}关机{{ row.off }} </div>
<i class="el-icon-info" />
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="runtime" label="上次同步" sortable align="center" />
<el-table-column prop="interval" label="同步间隔" sortable align="center">
<template slot-scope="{row}">
<span>{{ row.interval }}分钟</span>
</template>
</el-table-column>
<el-table-column prop="nextime" label="下次同步" sortable align="center" />
<el-table-column label="操作" align="center" width="280" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="success" size="mini" @click="row.itype==='ecs'?handleEcs(row.jobid):handleEnt(row.jobid)">
查看
</el-button>
<el-button type="warning" size="mini" @click="handleRun(row.jobid)">
同步
</el-button>
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button type="danger" size="mini" @click="handleDelete(row.jobid)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="查看组信息" :visible.sync="entFormVisible" width="60%">
<el-table v-loading="listLoading" :data="entlist" height="540" border fit highlight-current-row style="width: 100%;">
<el-table-column type="index" align="center" />
<el-table-column prop="gid" label="组ID" sortable align="center" />
<el-table-column prop="gname" label="名称" sortable align="center" />
</el-table>
</el-dialog>
<el-dialog title="新增同步源" :visible.sync="newFormVisible" width="40%">
<el-form ref="dataForm" :rules="rules" :model="ecsJob" label-position="right" label-width="auto" style="width: 90%; margin-left: 1px;">
<el-form-item label="云厂商" prop="vendor">
<el-select v-model="ecsJob.vendor" placeholder="请选择" @change="ecsJob.region=''">
<el-option v-for="item in vendors" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item prop="account">
<span slot="label">
<span class="span-box">
<span>账户</span>
<el-tooltip style="diaplay:inline" effect="dark" content="用来区分云厂商不同云账户的标识,支持中文,例如用主账户的名称。" placement="top">
<i class="el-icon-info" />
</el-tooltip>
</span>
</span>
<el-input v-model="ecsJob.account" />
</el-form-item>
<el-form-item label="Access Key" prop="ak">
<el-input v-model="ecsJob.ak" placeholder="请输AccessKey ID" />
</el-form-item>
<el-form-item label="Secret Key" prop="sk">
<el-input v-model="ecsJob.sk" placeholder="请输入AccessKey Secret" show-password />
</el-form-item>
<el-form-item label="区域" prop="region">
<el-select v-model="ecsJob.region" placeholder="请选择">
<el-option v-for="item in regions[ecsJob.vendor]" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item prop="proj_interval">
<span slot="label">
<span class="span-box">
<span>分组同步间隔(分钟)</span>
<el-tooltip style="diaplay:inline" effect="dark" content="分组是采集云厂商用于资源分组的字段,阿里云:资源组,华为云:企业项目,腾讯云:所属项目。请在创建云主机时设置好属组。" placement="top">
<i class="el-icon-info" />
</el-tooltip>
</span>
</span>
<el-input v-model="ecsJob.proj_interval" />
</el-form-item>
<el-form-item label="ECS同步间隔(分钟)" prop="ecs_interval">
<el-input v-model="ecsJob.ecs_interval" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="createAndNew()">
确认并新增
</el-button>
<el-button @click="newFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="createData()">
确认
</el-button>
</div>
</el-dialog>
<el-dialog title="更新同步间隔" :visible.sync="upFormVisible" width="30%">
<el-form ref="dataForm" :rules="rules" :model="upjob" label-position="right" label-width="130px" style="margin-left: 20px;">
<el-form-item label="同步间隔(分钟)" prop="interval">
<el-input v-model="upjob.interval" placeholder="请输入" clearable style="width: 150px" class="filter-item" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="upFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="updateData()">
确认
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getAllJobs, PostJob, DelJob, getGroup } from '@/api/node-exporter'
export default {
data() {
const validateInput = (rule, value, callback) => {
if (!this.checkSpecialKey(value)) {
callback(new Error('不能含有空格或 [ ]`~!#$^&*=|"{}\':/;\\?'))
} else {
callback()
}
}
return {
dialogStatus: '',
listLoading: false,
joblist: [],
entlist: [],
job_dict: {},
vendor_list: [],
account_list: [],
itype: [],
query: { vendor: '', account: '', itype: '' },
rules: {
vendor: [{ required: true, message: '此为必填项', trigger: 'change' },
{ validator: validateInput, trigger: ['blur', 'change'] }],
account: [{ required: true, message: '此为必填项', trigger: 'change' },
{ validator: validateInput, trigger: ['blur', 'change'] }],
ak: [{ required: true, message: '此为必填项', trigger: 'change' },
{ validator: validateInput, trigger: ['blur', 'change'] }],
sk: [{ required: true, message: '此为必填项', trigger: 'change' },
{ validator: validateInput, trigger: ['blur', 'change'] }],
region: [{ required: true, message: '此为必填项', trigger: 'blur' },
{ validator: validateInput, trigger: ['blur'] }],
proj_interval: [{ required: true, message: '此为必填项', trigger: 'change' },
{ validator: validateInput, trigger: ['blur', 'change'] }],
ecs_interval: [{ required: true, message: '此为必填项', trigger: 'change' },
{ validator: validateInput, trigger: ['blur', 'change'] }]
},
vendors: [{ value: 'alicloud', label: '阿里云' },
{ value: 'tencent_cloud', label: '腾讯云' },
{ value: 'huaweicloud', label: '华为云' }],
regions: {
huaweicloud: [
{ value: 'cn-east-3', label: '华东-上海一' },
{ value: 'cn-east-2', label: '华东-上海二' },
{ value: 'cn-south-1', label: '华南-广州' },
{ value: 'cn-north-1', label: '华北-北京一' },
{ value: 'cn-north-4', label: '华北-北京四' },
{ value: 'cn-southwest-2', label: '西南-贵阳一' },
{ value: 'ap-southeast-1', label: '中国-香港' }
],
alicloud: [
{ value: 'cn-qingdao', label: '华北1(青岛)' },
{ value: 'cn-beijing', label: '华北2(北京)' },
{ value: 'cn-zhangjiakou', label: '华北3(张家口)' },
{ value: 'cn-huhehaote', label: '华北5(呼和浩特)' },
{ value: 'cn-wulanchabu', label: '华北6(乌兰察布)' },
{ value: 'cn-hangzhou', label: '华东1(杭州)' },
{ value: 'cn-shanghai', label: '华东2(上海)' },
{ value: 'cn-shenzhen', label: '华南1(深圳)' },
{ value: 'cn-heyuan', label: '华南2(河源)' },
{ value: 'cn-guangzhou', label: '华南3(广州)' },
{ value: 'cn-chengdu', label: '西南1(成都)' },
{ value: 'cn-hongkong', label: '中国(香港)' },
{ value: 'cn-nanjing', label: '华东5(南京-本地地域)' }
],
tencent_cloud: [
{ value: 'ap-nanjing', label: '华东地区(南京)' },
{ value: 'ap-shanghai', label: '华东地区(上海)' },
{ value: 'ap-guangzhou', label: '华南地区(广州)' },
{ value: 'ap-beijing', label: '华北地区(北京)' },
{ value: 'ap-tianjin', label: '华北地区(天津)' },
{ value: 'ap-chengdu', label: '西南地区(成都)' },
{ value: 'ap-chongqing', label: '西南地区(重庆)' },
{ value: 'ap-hongkong', label: '港澳台地区(中国香港)' }
]
},
ecsJob: { vendor: '', ak: '', sk: '', region: '', account: '', proj_interval: 60, ecs_interval: 5 },
upjob: { jobid: '', interval: '' },
newFormVisible: false,
upFormVisible: false,
entFormVisible: false
}
},
created() {
this.fetchData()
},
methods: {
checkSpecialKey(str) {
const specialKey = '[]`~!#$^&*/=\\|{}\'":;? '
for (let i = 0; i < str.length; i++) {
if (specialKey.indexOf(str.substr(i, 1)) !== -1) {
return false
}
}
return true
},
tableRowClassName({ row }) {
if (row.itype === 'ecs') {
return 'success-row'
}
return ''
},
resetData() {
this.query = { vendor: '', account: '', itype: '' }
this.fetchData()
},
fetchData() {
this.listLoading = true
getAllJobs(this.query).then(response => {
this.joblist = response.all_jobs
this.vendor_list = response.vendor_list
this.account_list = response.account_list
this.itype_list = response.itype_list
this.listLoading = false
})
},
handleCreate() {
this.ecsJob = { vendor: '', ak: '', sk: '', region: '', account: '', proj_interval: 60, ecs_interval: 5 }
this.ecsJob.account = this.query.account
this.newFormVisible = true
},
createAndNew() {
this.createData()
this.newFormVisible = true
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.upFormVisible = false
this.listLoading = true
this.upjob.dialogStatus = 'update'
PostJob(this.upjob).then(response => {
this.fetchData()
this.$message({
message: response.data,
type: 'success'
})
})
}
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.newFormVisible = false
this.listLoading = true
this.ecsJob.dialogStatus = 'create'
PostJob(this.ecsJob).then(response => {
this.fetchData()
this.$message({
message: response.data,
type: 'success'
})
this.ecsJob.region = ''
})
}
})
},
handleRun(jobid) {
this.$confirm('此操作将立刻同步一次【' + jobid + '】是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.listLoading = true
this.dialogStatus = 'run'
this.job_dict = { dialogStatus: this.dialogStatus, jobid: jobid }
PostJob(this.job_dict).then(response => {
this.fetchData()
this.$message({
message: response.data,
type: 'success'
})
})
}).catch(() => {
this.$message({
type: 'info',
message: '同步已取消'
})
})
},
handleUpdate(row) {
this.upjob.jobid = row.jobid
this.upjob.interval = row.interval
this.upFormVisible = true
},
handleEcs(jobid) {
this.$router.push({
path: '/nodes/lists',
query: { job_id: jobid }
})
},
handleEnt(jobid) {
getGroup(jobid).then(response => {
this.entFormVisible = true
this.entlist = response.group
})
},
handleDelete(jobid) {
this.$confirm('此操作将删除【' + jobid + '】是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
DelJob(jobid).then(response => {
this.fetchData()
this.$message({
message: response.data,
type: 'success'
})
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
}
}
</script>
<style>
.el-table .success-row {
background: #f0f9eb;
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<div class="app-container">
<el-select v-model="jobecs_name" placeholder="请选择需要查询的ECS列表" filterable collapse-tags clearable style="width: 350px" class="filter-item" @change="fetchEcs(jobecs_name)">
<el-option v-for="item in jobecs_list" :key="item" :label="item" :value="item" />
</el-select>
<el-tooltip class="item" effect="light" content="刷新当前ECS列表" placement="top">
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-refresh" circle @click="fetchEcs(jobecs_name)" />
</el-tooltip>
<el-table v-loading="listLoading" :data="ecs_list" :default-sort="{ prop: 'group', order: 'ascending' }" border fit highlight-current-row style="width: 100%;">
<el-table-column type="index" align="center" />
<el-table-column prop="group" label="分组" sortable align="center" width="180" show-overflow-tooltip />
<el-table-column prop="name" label="名称" sortable align="center" width="280" />
<el-table-column prop="instance" label="实例" sortable align="center" width="180" />
<el-table-column prop="os" label="系统" sortable align="center" width="100" />
<el-table-column prop="cpu" label="CPU" sortable align="center" width="80" />
<el-table-column prop="mem" label="内存" sortable align="center" width="80" />
<el-table-column prop="exp" label="到期日" sortable align="center" width="120" />
<el-table-column prop="iid" label="实例ID" sortable align="center" />
</el-table>
</div>
</template>
<script>
import { getEcsList, getJobEcs } from '@/api/node-exporter'
export default {
data() {
return {
listLoading: false,
jobecs_name: '',
jobecs_list: [],
ecs_list: []
}
},
created() {
this.fetchJobEcs()
if (this.$route.query.job_id) {
this.fetchEcs(this.$route.query.job_id)
}
},
mounted() {
if (this.$route.query.job_id) {
this.jobecs_name = this.$route.query.job_id
}
},
methods: {
fetchJobEcs() {
getJobEcs().then(response => {
this.jobecs_list = response.jobecs
})
},
fetchEcs(job_id) {
this.listLoading = true
getEcsList(job_id).then(response => {
this.ecs_list = response.ecs_list
this.listLoading = false
})
}
}
}
</script>

View File

@ -0,0 +1,74 @@
<template>
<div class="app-container">
<el-select v-model="services" multiple placeholder="请选择需要生成配置的服务" filterable collapse-tags clearable style="width: 350px" class="filter-item">
<el-option v-for="item in services_list" :key="item" :label="item" :value="item" />
</el-select>
<el-select v-model="ostype" multiple placeholder="请选择系统" filterable clearable class="filter-item">
<el-option v-for="item in ostype_list" :key="item" :label="item" :value="item" />
</el-select>
<el-button class="filter-item" type="primary" icon="el-icon-magic-stick" @click="fetchEcsConfig">
生成配置
</el-button>
<el-button v-clipboard:copy="configs" v-clipboard:success="onCopy" v-clipboard:error="onError" class="filter-item" type="warning" icon="el-icon-document-copy">
复制配置
</el-button>
<pre v-highlightjs="configs" style="line-height:120%"><code class="yaml yamlcode" /></pre>
</div>
</template>
<script>
import { getServicesList, getConfig } from '@/api/node-exporter'
export default {
data() {
return {
listLoading: false,
services: [],
ostype: [],
services_list: [],
ostype_list: ['linux', 'windows'],
services_dict: {},
configs: ''
}
},
created() {
this.fetchEcsList()
},
methods: {
onCopy() {
this.$message({
message: '复制成功!',
type: 'success'
})
},
onError() {
this.$message.error('复制失败!')
},
fetchEcsList() {
this.listLoading = true
getServicesList().then(response => {
this.services_list = response.services_list
this.listLoading = false
})
},
fetchEcsConfig() {
this.listLoading = true
this.services_dict.services_list = this.services
this.services_dict.ostype_list = this.ostype
getConfig(this.services_dict).then(response => {
this.configs = response.configs
this.listLoading = false
})
}
}
}
</script>
<style>
.yamlcode {
font-family:'Consolas';
}
pre {
max-height: 640px;
white-space: pre-wrap;
overflow:auto;
}
</style>