parent
e1827c3078
commit
1e4398b845
|
@ -1,10 +0,0 @@
|
|||
### Blackbox Exporter Dashboard
|
||||
- 支持Grafana 8,基于blackbox_exporter 0.19.0设计
|
||||
- 采用图表+曲线图方式展示TCP,ICMP,HTTPS的服务状态,各阶段请求延时,HTTPS证书信息等
|
||||
- 优化展示效果,支持监控目标的分组、分类级联展示,多服务同时对比展示。
|
||||
|
||||
导入ID:9965
|
||||
详细URL:https://grafana.com/grafana/dashboards/9965
|
||||
|
||||
|
||||
![1](https://raw.githubusercontent.com/starsliao/ConsulManager/main/screenshot/1.png)![2](https://raw.githubusercontent.com/starsliao/ConsulManager/main/screenshot/2.png)
|
|
@ -26,6 +26,16 @@
|
|||
- **导入ID:8919**
|
||||
- 详细URL:[https://grafana.com/grafana/dashboards/8919](https://grafana.com/grafana/dashboards/8919)
|
||||
|
||||
---
|
||||
|
||||
### 批量导入自建主机脚本
|
||||
|
||||
在units目录下`selfnode-instance.list`中写入监控目标的信息:机房/公司 租户/部门 区域/项目 分组/环境 名称 实例(ip:端口) 系统(linux/windows),每行一个,空格分隔。
|
||||
|
||||
**注意:前5个字段组合起来必须唯一,作为一个监控项的ID。即Consul的ServiceID**
|
||||
|
||||
修改units目录下导入脚本中的consul_token和consul_url,保存后执行selfnode-input.py,即可导入所有监控目标到Consul,并符合Prometheus的自动发现配置。
|
||||
|
||||
### 注意:
|
||||
|
||||
##### 主动关机的ECS,会在同步时候从Consul中清除,即会在Prometheus中去除(减少无效的告警),重新开机后会增加回去。
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
|
||||
#### 批量导入脚本
|
||||
|
||||
在units目录下`instance.list`中写入监控目标的信息:JOB名称,公司/部门,项目,环境,名称,实例url,每行一个,空格分隔。
|
||||
在units目录下`blackbox-instance.list`中写入监控目标的信息:监控类型,公司/部门,项目,环境,名称,实例url,每行一个,空格分隔。
|
||||
|
||||
**注意:前5个字段组合起来必须唯一,作为一个监控项的ID。即Consul的ServiceID**
|
||||
|
||||
修改units目录下导入脚本中的consul_token和consul_url,保存后执行input.py,即可导入所有监控目标到Consul,并符合Prometheus的自动发现配置。
|
||||
修改units目录下导入脚本中的consul_token和consul_url,保存后执行blackbox-input.py,即可导入所有监控目标到Consul,并符合Prometheus的自动发现配置。
|
||||
|
|
|
@ -13,7 +13,8 @@ regions = {'huaweicloud':{'none': '无','cn-east-3': '华东-上海一','cn-east
|
|||
'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(南京-本地地域)', 'us-east-1':'美国东部1(弗吉尼亚)'},
|
||||
'cn-nanjing':'华东5(南京-本地地域)', 'us-east-1':'美国东部1(弗吉尼亚)','us-west-1':'美国(硅谷)',
|
||||
'eu-west-1':'英国(伦敦)','ap-southeast-1':'新加坡','ap-northeast-1':'日本(东京)'},
|
||||
'tencent_cloud':{'none': '无',"ap-nanjing":"华东地区(南京)","ap-shanghai":"华东地区(上海)",
|
||||
"ap-guangzhou":"华南地区(广州)","ap-beijing":"华北地区(北京)","ap-tianjin":"华北地区(天津)",
|
||||
"ap-chengdu":"西南地区(成都)","ap-chongqing":"西南地区(重庆)",
|
||||
|
|
|
@ -6,7 +6,7 @@ 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 views import login, blackbox, consul, jobs, nodes, selfnode
|
||||
from units.cloud import huaweicloud,alicloud,tencent_cloud
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(login.blueprint)
|
||||
|
@ -14,7 +14,7 @@ app.register_blueprint(blackbox.blueprint)
|
|||
app.register_blueprint(consul.blueprint)
|
||||
app.register_blueprint(jobs.blueprint)
|
||||
app.register_blueprint(nodes.blueprint)
|
||||
|
||||
app.register_blueprint(selfnode.blueprint)
|
||||
class Config(object):
|
||||
JOBS = []
|
||||
SCHEDULER_API_ENABLED = True
|
||||
|
|
|
@ -16,6 +16,8 @@ def ecs_config(services_list,ostype_list):
|
|||
services: {services_list}
|
||||
tags: ['{ostype}']
|
||||
relabel_configs:
|
||||
- source_labels: ['__meta_consul_service']
|
||||
target_label: cservice
|
||||
- source_labels: ['__meta_consul_service_metadata_vendor']
|
||||
target_label: vendor
|
||||
- source_labels: ['__meta_consul_service_metadata_region']
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import requests,json
|
||||
import sys
|
||||
sys.path.append("..")
|
||||
from config import consul_token,consul_url
|
||||
|
||||
headers = {'X-Consul-Token': consul_token}
|
||||
|
||||
def get_all_list(vendor,account,region,group):
|
||||
vendor = f'and Meta.vendor=="{vendor}"' if vendor != '' else f'and Meta.vendor != ""'
|
||||
account = f'and Meta.account=="{account}"' if account != '' else f'and Meta.account != ""'
|
||||
region = f'and Meta.region=="{region}"' if region != '' else f'and Meta.region != ""'
|
||||
group = f'and Meta.group=="{group}"' if group != '' else f'and Meta.group != ""'
|
||||
url = f'{consul_url}/agent/services?filter=Service == selfnode_exporter {vendor} {account} {region} {group}'
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
info = response.json()
|
||||
all_list = [i['Meta'] for i in info.values()]
|
||||
vendor_list = sorted(list(set([i['vendor'] for i in all_list])))
|
||||
account_list = sorted(list(set([i['account'] for i in all_list])))
|
||||
region_list = sorted(list(set([i['region'] for i in all_list])))
|
||||
group_list = sorted(list(set([i['group'] for i in all_list])))
|
||||
return {'code': 20000,'all_list':all_list,'vendor_list':vendor_list,
|
||||
'account_list':account_list,'region_list':region_list,'group_list':group_list}
|
||||
else:
|
||||
return {'code': 50000, 'data': f'{response.status_code}:{response.text}'}
|
||||
|
||||
def get_service():
|
||||
response = requests.get(f'{consul_url}/agent/services?filter=Service == selfnode_exporter', headers=headers)
|
||||
if response.status_code == 200:
|
||||
info = response.json()
|
||||
all_list = [i['Meta'] for i in info.values()]
|
||||
vendor_list = sorted(list(set([i['vendor'] for i in all_list])))
|
||||
account_list = sorted(list(set([i['account'] for i in all_list])))
|
||||
region_list = sorted(list(set([i['region'] for i in all_list])))
|
||||
group_list = sorted(list(set([i['group'] for i in all_list])))
|
||||
return {'code': 20000,'all_list':all_list,'vendor_list':vendor_list,
|
||||
'account_list':account_list,'region_list':region_list,'group_list':group_list}
|
||||
else:
|
||||
return {'code': 50000, 'data': f'{response.status_code}:{response.text}'}
|
||||
|
||||
def add_service(vendor,account,region,group,name,ip,port,os):
|
||||
sid = f"{vendor}/{account}/{region}/{group}@{name}"
|
||||
instance = f'{ip}:{port}'
|
||||
if '//' in sid or sid.startswith('/') or sid.endswith('/'):
|
||||
return {"code": 50000, "data": f"服务ID【{sid}】首尾不能包含'/',并且不能包含两个连续的'/'"}
|
||||
data = {
|
||||
"id": sid,
|
||||
"name": 'selfnode_exporter',
|
||||
'Address': ip,
|
||||
'port': int(port),
|
||||
"tags": [vendor,os],
|
||||
"Meta": {'vendor':vendor,'account':account,'region':region,'group':group,
|
||||
'name':name,'instance':instance,'os':os},
|
||||
"check": {"tcp": instance,"interval": "60s"}
|
||||
}
|
||||
reg = requests.put(f'{consul_url}/agent/service/register', headers=headers, data=json.dumps(data))
|
||||
if reg.status_code == 200:
|
||||
return {"code": 20000, "data": f"【{sid}】增加成功!"}
|
||||
else:
|
||||
return {"code": 50000, "data": f"{reg.status_code}【{sid}】{reg.text}"}
|
||||
|
||||
def del_service(vendor,account,region,group,name):
|
||||
sid = f"{vendor}/{account}/{region}/{group}@{name}"
|
||||
reg = requests.put(f'{consul_url}/agent/service/deregister/{sid}', headers=headers)
|
||||
if reg.status_code == 200:
|
||||
return {"code": 20000, "data": f"【{sid}】删除成功!"}
|
||||
else:
|
||||
return {"code": 50000, "data": f"{reg.status_code}【{sid}】{reg.text}"}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
from flask import Blueprint
|
||||
from flask_restful import reqparse, Resource, Api
|
||||
import sys
|
||||
sys.path.append("..")
|
||||
from units import token_auth,selfnode_manager
|
||||
|
||||
blueprint = Blueprint('selfnode',__name__)
|
||||
api = Api(blueprint)
|
||||
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('vendor',type=str)
|
||||
parser.add_argument('account',type=str)
|
||||
parser.add_argument('region',type=str)
|
||||
parser.add_argument('group',type=str)
|
||||
parser.add_argument('name',type=str)
|
||||
parser.add_argument('ip',type=str)
|
||||
parser.add_argument('port',type=str)
|
||||
parser.add_argument('os',type=str)
|
||||
parser.add_argument('del_dict',type=dict)
|
||||
parser.add_argument('up_dict',type=dict)
|
||||
|
||||
class GetAllList(Resource):
|
||||
@token_auth.auth.login_required
|
||||
def get(self):
|
||||
args = parser.parse_args()
|
||||
return selfnode_manager.get_all_list(args['vendor'],args['account'],args['region'],args['group'])
|
||||
|
||||
class SelfnodeApi(Resource):
|
||||
decorators = [token_auth.auth.login_required]
|
||||
def get(self):
|
||||
return selfnode_manager.get_service()
|
||||
def post(self):
|
||||
args = parser.parse_args()
|
||||
return selfnode_manager.add_service(args['vendor'],args['account'],args['region'],
|
||||
args['group'],args['name'],args['ip'],args['port'],args['os'])
|
||||
def put(self):
|
||||
args = parser.parse_args()
|
||||
del_dict = args['del_dict']
|
||||
up_dict = args['up_dict']
|
||||
resp_del = selfnode_manager.del_service(del_dict['vendor'],del_dict['account'],
|
||||
del_dict['region'],del_dict['group'],del_dict['name'])
|
||||
resp_add = selfnode_manager.add_service(up_dict['vendor'],up_dict['account'],up_dict['region'],
|
||||
up_dict['group'],up_dict['name'],up_dict['ip'],
|
||||
up_dict['port'],up_dict['os'])
|
||||
if resp_del["code"] == 20000 and resp_add["code"] == 20000:
|
||||
return {"code": 20000, "data": f"更新成功!"}
|
||||
else:
|
||||
return {"code": 50000, "data": f"更新失败!"}
|
||||
def delete(self):
|
||||
args = parser.parse_args()
|
||||
return selfnode_manager.del_service(args['vendor'],args['account'],args['region'],args['group'],args['name'])
|
||||
|
||||
api.add_resource(GetAllList,'/api/selfnode/alllist')
|
||||
api.add_resource(SelfnodeApi, '/api/selfnode/service')
|
|
@ -1,11 +1,13 @@
|
|||
#!/usr/bin/python3
|
||||
import requests,json
|
||||
consul_token = 'xxxxxxxxxx'
|
||||
consul_token = 'xxxxxxxxxx' #Consul SecretID
|
||||
consul_url = 'http://x.x.x.x:8500/v1'
|
||||
|
||||
with open('instance.list', 'r') as file:
|
||||
with open('blackbox-instance.list', 'r') as file:
|
||||
lines = file.readlines()
|
||||
for line in lines:
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
module,company,project,env,name,instance = line.split()
|
||||
headers = {'X-Consul-Token': consul_token}
|
||||
data = {
|
|
@ -0,0 +1,5 @@
|
|||
# 监控类型和Blackbox配置中的module名必须保持一致
|
||||
# 每行需要有6个字段,空格分隔:
|
||||
# 监控类型 公司/部门 项目 环境 名称 实例url
|
||||
# 以下为例子,#开头的行不会被导入,需要导入的行不要以#开头:
|
||||
# http_2xx 阿里巴巴 电商 prod 淘宝 https://www.taobao.com
|
|
@ -1 +0,0 @@
|
|||
JOB名称 公司/部门 项目 环境 名称 实例url
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/python3
|
||||
import requests,json
|
||||
consul_token = 'xxxxxxxxxx' #Consul SecretID
|
||||
consul_url = 'http://x.x.x.x:8500/v1'
|
||||
|
||||
with open('selfnode-instance.list', 'r') as file:
|
||||
lines = file.readlines()
|
||||
for line in lines:
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
vendor,account,region,group,name,instance,os = line.split()
|
||||
headers = {'X-Consul-Token': consul_token}
|
||||
sid = f"{vendor}/{account}/{region}/{group}@{name}"
|
||||
ip = instance.split(':')[0]
|
||||
port = instance.split(':')[1]
|
||||
data = {
|
||||
"id": sid,
|
||||
"name": 'selfnode_exporter',
|
||||
'Address': ip,
|
||||
'port': int(port),
|
||||
"tags": [vendor,os],
|
||||
"Meta": {'vendor':vendor,'account':account,'region':region,'group':group,
|
||||
'name':name,'instance':instance,'os':os},
|
||||
"check": {"tcp": instance,"interval": "60s"}
|
||||
}
|
||||
|
||||
reg = requests.put(f"{consul_url}/agent/service/register", headers=headers, data=json.dumps(data))
|
||||
if reg.status_code == 200:
|
||||
print({"code": 20000,"data": "增加成功!"})
|
||||
else:
|
||||
print({"code": 50000,"data": f'{reg.status_code}:{reg.text}'})
|
|
@ -0,0 +1,4 @@
|
|||
# 每行需要有7个字段,空格分隔:
|
||||
# 机房/公司 租户/部门 区域/项目 分组/环境 名称 实例(ip:端口) 系统(linux/windows)
|
||||
# 以下为例子,#开头的行不会被导入,需要导入的行不要以#开头:
|
||||
# openstack 运维 深圳 prod 监控主机 10.0.0.26:9100 linux
|
|
@ -1 +1,2 @@
|
|||
node_modules
|
||||
package-lock.json
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import request from '@/utils/request-ops'
|
||||
|
||||
export function getAllList(vendor, account, region, group) {
|
||||
return request({
|
||||
url: '/api/selfnode/alllist',
|
||||
method: 'get',
|
||||
params: { vendor, account, region, group }
|
||||
})
|
||||
}
|
||||
|
||||
export function getAllInfo() {
|
||||
return request({
|
||||
url: '/api/selfnode/service',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
export function addService(data) {
|
||||
return request({
|
||||
url: '/api/selfnode/service',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
export function updateService(del_dict, up_dict) {
|
||||
return request({
|
||||
url: '/api/selfnode/service',
|
||||
method: 'put',
|
||||
data: { del_dict, up_dict }
|
||||
})
|
||||
}
|
||||
export function delService(data) {
|
||||
return request({
|
||||
url: '/api/selfnode/service',
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
|
@ -86,8 +86,8 @@ export const constantRoutes = [
|
|||
path: '/nodes',
|
||||
component: Layout,
|
||||
redirect: '/nodes/jobs',
|
||||
name: 'ECS 云主机监控',
|
||||
meta: { title: 'ECS 云主机监控', icon: 'example' },
|
||||
name: 'Node 主机监控',
|
||||
meta: { title: 'Node 主机监控', icon: 'example' },
|
||||
children: [
|
||||
{
|
||||
path: 'jobs',
|
||||
|
@ -99,7 +99,13 @@ export const constantRoutes = [
|
|||
path: 'lists',
|
||||
name: '云主机列表',
|
||||
component: () => import('@/views/node-exporter/lists'),
|
||||
meta: { title: '云主机列表', icon: 'el-icon-s-platform' }
|
||||
meta: { title: '云主机列表', icon: 'el-icon-cloudy' }
|
||||
},
|
||||
{
|
||||
path: 'self',
|
||||
name: '自建主机管理',
|
||||
component: () => import('@/views/node-exporter/self'),
|
||||
meta: { title: '自建主机管理', icon: 'el-icon-s-platform' }
|
||||
},
|
||||
{
|
||||
path: 'pconfig',
|
||||
|
|
|
@ -94,7 +94,15 @@
|
|||
|
||||
<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-form-item prop="module">
|
||||
<span slot="label">
|
||||
<span class="span-box">
|
||||
<span>监控类型</span>
|
||||
<el-tooltip style="diaplay:inline" effect="dark" content="该字段必须和Blackbox配置中的module名称保持一致,如:http_2xx,http_post_2xx,http_post_2xx 等。" placement="top">
|
||||
<i class="el-icon-info" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
<el-autocomplete v-model="temp.module" :fetch-suggestions="Sugg_module" placeholder="优先选择" clearable class="filter-item" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司部门" prop="company">
|
||||
|
@ -113,7 +121,7 @@
|
|||
<span slot="label">
|
||||
<span class="span-box">
|
||||
<span>实例</span>
|
||||
<el-tooltip style="diaplay:inline" effect="dark" content="TCP类检查请不要输入http(s)://" placement="top">
|
||||
<el-tooltip style="diaplay:inline" effect="dark" content="TCP类检查格式为:IP:端口 ,HTTP类检查格式为完整的URL,必须以http(s)://开头。" placement="top">
|
||||
<i class="el-icon-info" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
|
|
@ -4,6 +4,15 @@
|
|||
<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/5/8" placement="top">
|
||||
<el-card>
|
||||
<h4>v0.5.1</h4>
|
||||
<p><el-button type="warning" size="mini" icon="el-icon-star-off" circle /> Node 主机监控可以方便在页面上管理自建主机了。</p>
|
||||
<p>优化了导入脚本,并且支持了自建主机的批量导入。</p>
|
||||
<p>云主机同步增加了部分国外的区域。</p>
|
||||
<p>web页面的描述做了优化,修复了一些bug。</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item timestamp="2022/4/7" placement="top">
|
||||
<el-card>
|
||||
<h4>v0.5.0</h4>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
</el-form>
|
||||
<div align="center" class="title-container">
|
||||
<span style="font-size:10px" class="title">v0.5.0</span>
|
||||
<span style="font-size:10px" class="title">v0.5.1</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -223,7 +223,11 @@ export default {
|
|||
{ value: 'cn-chengdu', label: '西南1(成都)' },
|
||||
{ value: 'cn-hongkong', label: '中国(香港)' },
|
||||
{ value: 'cn-nanjing', label: '华东5(南京-本地地域)' },
|
||||
{ value: 'us-east-1', label: '美国东部1(弗吉尼亚)' }
|
||||
{ value: 'us-east-1', label: '美国东部1(弗吉尼亚)' },
|
||||
{ value: 'us-west-1', label: '美国(硅谷)' },
|
||||
{ value: 'eu-west-1', label: '英国(伦敦)' },
|
||||
{ value: 'ap-southeast-1', label: '新加坡' },
|
||||
{ value: 'ap-northeast-1', label: '日本(东京)' }
|
||||
],
|
||||
tencent_cloud: [
|
||||
{ value: 'ap-nanjing', label: '华东地区(南京)' },
|
||||
|
|
|
@ -47,6 +47,7 @@ export default {
|
|||
this.listLoading = true
|
||||
getServicesList().then(response => {
|
||||
this.services_list = response.services_list
|
||||
this.services_list.push('selfnode_exporter')
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
|
|
|
@ -0,0 +1,533 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.vendor" placeholder="机房/公司" clearable collapse-tags style="width: 150px" class="filter-item">
|
||||
<el-option v-for="item in vendor_list" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.account" placeholder="租户/部门" clearable style="width: 150px" class="filter-item">
|
||||
<el-option v-for="item in account_list" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.region" filterable placeholder="区域/项目" clearable style="width: 160px" class="filter-item">
|
||||
<el-option v-for="item in region_list" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.group" filterable placeholder="分组/环境" clearable style="width: 100px" class="filter-item">
|
||||
<el-option v-for="item in group_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="primary" icon="el-icon-refresh" circle @click="handleReset" />
|
||||
</el-tooltip>
|
||||
<el-button class="filter-item" type="primary" icon="el-icon-edit" @click="handleCreate">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload">
|
||||
导出
|
||||
</el-button>
|
||||
<el-button class="filter-item" type="danger" icon="el-icon-delete" @click="handleDelAll">
|
||||
批量删除
|
||||
</el-button>
|
||||
<div style="float: right;">
|
||||
<el-input v-model="iname" prefix-icon="el-icon-search" placeholder="请输入名称或实例进行筛选" clearable style="width:230px" class="filter-item" @input="inameFilter(iname)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="listLoading"
|
||||
:data="all_list.filter(data => !iname || (data.name.toLowerCase().includes(iname.toLowerCase()) || data.instance.toLowerCase().includes(iname.toLowerCase())))"
|
||||
border
|
||||
fit
|
||||
highlight-current-row
|
||||
style="width: 100%;"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" align="center" width="35" />
|
||||
<el-table-column label="ID" align="center" width="40px">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.$index+1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="vendor" label="机房/公司" sortable align="center">
|
||||
<template slot-scope="{row}">
|
||||
<span>{{ row.vendor }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="account" label="租户/部门" sortable align="center">
|
||||
<template slot-scope="{row}">
|
||||
<span>{{ row.account }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="region" label="区域/项目" sortable align="center" show-overflow-tooltip>
|
||||
<template slot-scope="{row}">
|
||||
<span>{{ row.region }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="group" label="分组/环境" sortable align="center">
|
||||
<template slot-scope="{row}">
|
||||
<span>{{ row.group }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称" sortable align="center" show-overflow-tooltip>
|
||||
<template slot-scope="{row}">
|
||||
<span>{{ row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="instance" label="实例" sortable align="center" width="160" show-overflow-tooltip>
|
||||
<template slot-scope="{row}">
|
||||
<span>{{ row.instance }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="os" label="系统" sortable align="center" width="80">
|
||||
<template slot-scope="{row}">
|
||||
<span>{{ row.os }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
|
||||
<template slot-scope="{row}">
|
||||
<el-button type="primary" size="mini" @click="handleUpdate(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button size="mini" type="danger" @click="handleDelete(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<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="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="vendor">
|
||||
<el-autocomplete v-model="temp.vendor" :fetch-suggestions="Sugg_vendor" placeholder="优先选择" clearable class="filter-item" />
|
||||
</el-form-item>
|
||||
<el-form-item label="租户/部门" prop="account">
|
||||
<el-autocomplete v-model="temp.account" :fetch-suggestions="Sugg_account" placeholder="优先选择" clearable class="filter-item" />
|
||||
</el-form-item>
|
||||
<el-form-item label="区域/项目" prop="region">
|
||||
<el-autocomplete v-model="temp.region" :fetch-suggestions="Sugg_region" placeholder="优先选择" clearable class="filter-item" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分组/环境" prop="group">
|
||||
<el-autocomplete v-model="temp.group" :fetch-suggestions="Sugg_group" placeholder="优先选择" clearable class="filter-item" />
|
||||
</el-form-item>
|
||||
<el-form ref="dataForm" :inline="true" :rules="rules" :model="temp" class="demo-form-inline" label-position="right" label-width="50px">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="temp.name" placeholder="请输入" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="系统" prop="os">
|
||||
<el-select v-model="temp.os" placeholder="请选择" style="width: 130px;" @change="temp.port=osport[temp.os]">
|
||||
<el-option label="linux" value="linux" />
|
||||
<el-option label="windows" value="windows" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="IP" prop="ip">
|
||||
<el-input v-model="temp.ip" placeholder="请输入" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="端口" prop="port">
|
||||
<el-input v-model="temp.port" placeholder="请输入" clearable style="width: 130px;" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button v-if="dialogStatus==='create'" type="primary" @click="createAndNew">
|
||||
确认并新增
|
||||
</el-button>
|
||||
<el-button @click="dialogFormVisible = false">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import waves from '@/directive/waves' // waves directive
|
||||
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
|
||||
|
||||
import { getAllList, getAllInfo, addService, updateService, delService } from '@/api/selfnode'
|
||||
export default {
|
||||
name: 'ComplexTable',
|
||||
components: { Pagination },
|
||||
directives: { waves },
|
||||
filters: {
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
published: 'success',
|
||||
draft: 'info',
|
||||
deleted: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const validateInput = (rule, value, callback) => {
|
||||
if (!this.checkSpecialKey(value)) {
|
||||
callback(new Error('不能含有空格或 [ ]`~!#$^&*=|"{}\':;?'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
all_list: [],
|
||||
pall_list: [],
|
||||
iname: '',
|
||||
new_list: [],
|
||||
total: 0,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
page: 1,
|
||||
limit: 30,
|
||||
vendor: '',
|
||||
account: '',
|
||||
region: '',
|
||||
group: ''
|
||||
},
|
||||
value_vendor: [],
|
||||
value_account: [],
|
||||
value_region: [],
|
||||
value_group: [],
|
||||
multipleSelection: [],
|
||||
del_dict: {},
|
||||
osport: { linux: '9100', windows: '9128' },
|
||||
temp: {
|
||||
vendor: '',
|
||||
account: '',
|
||||
region: '',
|
||||
group: '',
|
||||
name: '',
|
||||
os: '',
|
||||
ip: '',
|
||||
port: ''
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: '',
|
||||
textMap: {
|
||||
update: '更新',
|
||||
create: '创建'
|
||||
},
|
||||
rules: {
|
||||
vendor: [{ required: true, message: '此为必填项', trigger: 'change' },
|
||||
{ validator: validateInput, trigger: ['blur', 'change'] }],
|
||||
account: [{ required: true, message: '此为必填项', trigger: 'change' },
|
||||
{ validator: validateInput, trigger: ['blur', 'change'] }],
|
||||
region: [{ required: true, message: '此为必填项', trigger: 'change' },
|
||||
{ validator: validateInput, trigger: ['blur', 'change'] }],
|
||||
group: [{ required: true, message: '此为必填项', trigger: 'change' },
|
||||
{ validator: validateInput, trigger: ['blur', 'change'] }],
|
||||
name: [{ required: true, message: '此为必填项', trigger: 'change' },
|
||||
{ validator: validateInput, trigger: ['blur', 'change'] }],
|
||||
ip: [{ required: true, message: '此为必填项', trigger: 'change' },
|
||||
{ validator: validateInput, trigger: ['blur', 'change'] }],
|
||||
port: [{ required: true, message: '此为必填项', trigger: 'change' },
|
||||
{ validator: validateInput, trigger: ['blur', 'change'] }],
|
||||
os: [{ required: true, message: '此为必填项', trigger: 'change' },
|
||||
{ validator: validateInput, trigger: ['blur', 'change'] }]
|
||||
},
|
||||
downloadLoading: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
cvendor() {
|
||||
return this.listQuery.vendor
|
||||
},
|
||||
caccount() {
|
||||
return this.listQuery.account
|
||||
},
|
||||
cregion() {
|
||||
return this.listQuery.region
|
||||
},
|
||||
cgroup() {
|
||||
return this.listQuery.group
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
cvendor(new_vendor) {
|
||||
this.fetchList(new_vendor, this.listQuery.account, this.listQuery.region, this.listQuery.group)
|
||||
},
|
||||
caccount(new_account) {
|
||||
this.fetchList(this.listQuery.vendor, new_account, this.listQuery.region, this.listQuery.group)
|
||||
},
|
||||
cregion(new_region) {
|
||||
this.fetchList(this.listQuery.vendor, this.listQuery.account, new_region, this.listQuery.group)
|
||||
},
|
||||
cgroup(new_group) {
|
||||
this.fetchList(this.listQuery.vendor, this.listQuery.account, this.listQuery.region, new_group)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
inameFilter(iname) {
|
||||
if (iname === '') {
|
||||
this.handleFilter()
|
||||
} else {
|
||||
this.total = 0
|
||||
this.all_list = this.pall_list
|
||||
}
|
||||
},
|
||||
handleFilter() {
|
||||
this.total = this.pall_list.length
|
||||
this.all_list = this.pall_list.slice(0 + this.listQuery.limit * (this.listQuery.page - 1), this.listQuery.limit + this.listQuery.limit * (this.listQuery.page - 1))
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val
|
||||
},
|
||||
checkSpecialKey(str) {
|
||||
const specialKey = '[]`~!#$^&*=|{}\'":;? '
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (specialKey.indexOf(str.substr(i, 1)) !== -1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
fetchData() {
|
||||
this.listLoading = true
|
||||
getAllInfo().then(response => {
|
||||
this.all_list = response.all_list
|
||||
this.vendor_list = response.vendor_list
|
||||
this.account_list = response.account_list
|
||||
this.region_list = response.region_list
|
||||
this.group_list = response.group_list
|
||||
this.listLoading = false
|
||||
this.xvendor = this.load_vendor()
|
||||
this.xaccount = this.load_account()
|
||||
this.xregion = this.load_region()
|
||||
this.xgroup = this.load_group()
|
||||
this.pall_list = response.all_list
|
||||
this.handleFilter()
|
||||
})
|
||||
},
|
||||
fetchList(vendor, account, region, group) {
|
||||
this.listLoading = true
|
||||
getAllList(vendor, account, region, group).then(response => {
|
||||
this.all_list = response.all_list
|
||||
this.listLoading = false
|
||||
this.vendor_list = response.vendor_list
|
||||
this.account_list = response.account_list
|
||||
this.region_list = response.region_list
|
||||
this.group_list = response.group_list
|
||||
this.listQuery.page = 1
|
||||
this.pall_list = response.all_list
|
||||
this.handleFilter()
|
||||
})
|
||||
},
|
||||
resetTemp() {
|
||||
this.temp = {}
|
||||
},
|
||||
|
||||
handleCreate() {
|
||||
this.resetTemp()
|
||||
var newone = {
|
||||
vendor: this.listQuery.vendor,
|
||||
account: this.listQuery.account,
|
||||
region: this.listQuery.region,
|
||||
group: this.listQuery.group,
|
||||
os: 'linux',
|
||||
port: '9100'
|
||||
}
|
||||
this.temp = Object.assign({}, this.temp, newone)
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
createData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
addService(this.temp).then(response => {
|
||||
this.temp.instance = this.temp.ip + ':' + this.temp.port
|
||||
this.all_list.unshift(this.temp)
|
||||
this.dialogFormVisible = false
|
||||
this.$message({
|
||||
message: response.data,
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
createAndNew() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
addService(this.temp).then(response => {
|
||||
this.temp.instance = this.temp.ip + ':' + this.temp.port
|
||||
this.all_list.unshift(this.temp)
|
||||
this.dialogFormVisible = false
|
||||
this.$message({
|
||||
message: response.data,
|
||||
type: 'success'
|
||||
})
|
||||
var newtemp = {
|
||||
vendor: this.temp.vendor,
|
||||
account: this.temp.account,
|
||||
region: this.temp.region,
|
||||
group: this.temp.group,
|
||||
os: 'linux',
|
||||
port: '9100'
|
||||
}
|
||||
this.resetTemp()
|
||||
this.temp = Object.assign({}, this.temp, newtemp)
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleReset() {
|
||||
this.listQuery.vendor = ''
|
||||
this.listQuery.account = ''
|
||||
this.listQuery.region = ''
|
||||
this.listQuery.group = ''
|
||||
},
|
||||
handleUpdate(row) {
|
||||
row.ip = row.instance.split(':')[0]
|
||||
row.port = row.instance.split(':')[1]
|
||||
this.temp = Object.assign({}, row) // copy obj
|
||||
this.del_dict = row
|
||||
this.dialogStatus = 'update'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
updateData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
const up_dict = Object.assign({}, this.temp)
|
||||
updateService(this.del_dict, up_dict).then(response => {
|
||||
this.dialogFormVisible = false
|
||||
this.$message({
|
||||
message: response.data,
|
||||
type: 'success'
|
||||
})
|
||||
up_dict.instance = up_dict.ip + ':' + up_dict.port
|
||||
this.all_list.splice(this.all_list.indexOf(this.del_dict), 1, up_dict)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('此操作将删除【' + row.group + ':' + row.region + ':' + row.name + '】,是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
delService(row).then(response => {
|
||||
this.$message({
|
||||
message: response.data,
|
||||
type: 'success'
|
||||
})
|
||||
this.$delete(this.all_list, this.all_list.indexOf(row))
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '已取消删除'
|
||||
})
|
||||
})
|
||||
},
|
||||
handleDelAll() {
|
||||
this.$confirm('此操作将批量删除选中行,是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
for (let i = 0; i < this.multipleSelection.length; i++) {
|
||||
delService(this.multipleSelection[i]).then(response => {
|
||||
this.$message({
|
||||
message: response.data,
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
this.$delete(this.all_list, this.all_list.indexOf(this.multipleSelection[i]))
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '已取消删除'
|
||||
})
|
||||
})
|
||||
},
|
||||
handleDownload() {
|
||||
this.downloadLoading = true
|
||||
import('@/vendor/Export2Excel').then(excel => {
|
||||
const tHeader = ['机房/公司', '租户/部门', '区域/项目', '分组/环境', '名称', '实例', '系统']
|
||||
const filterVal = ['vendor', 'account', 'region', 'group', 'name', 'instance', 'os']
|
||||
const data = this.formatJson(filterVal)
|
||||
excel.export_json_to_excel({
|
||||
header: tHeader,
|
||||
data,
|
||||
filename: 'selfnode-list'
|
||||
})
|
||||
this.downloadLoading = false
|
||||
})
|
||||
},
|
||||
formatJson(filterVal) {
|
||||
return this.all_list.map(v => filterVal.map(j => {
|
||||
return v[j]
|
||||
}))
|
||||
},
|
||||
Sugg_vendor(queryString, cb) {
|
||||
var xvendor = this.xvendor
|
||||
var results = queryString ? xvendor.filter(this.createFilter(queryString)) : xvendor
|
||||
cb(results)
|
||||
},
|
||||
Sugg_account(queryString, cb) {
|
||||
var xaccount = this.xaccount
|
||||
var results = queryString ? xaccount.filter(this.createFilter(queryString)) : xaccount
|
||||
cb(results)
|
||||
},
|
||||
Sugg_region(queryString, cb) {
|
||||
var xregion = this.xregion
|
||||
var results = queryString ? xregion.filter(this.createFilter(queryString)) : xregion
|
||||
cb(results)
|
||||
},
|
||||
Sugg_group(queryString, cb) {
|
||||
var xgroup = this.xgroup
|
||||
var results = queryString ? xgroup.filter(this.createFilter(queryString)) : xgroup
|
||||
cb(results)
|
||||
},
|
||||
createFilter(queryString) {
|
||||
return (restaurant) => {
|
||||
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
|
||||
}
|
||||
},
|
||||
load_vendor() {
|
||||
for (const x in this.vendor_list) {
|
||||
this.value_vendor.push({ 'value': this.vendor_list[x] })
|
||||
}
|
||||
return this.value_vendor
|
||||
},
|
||||
load_account() {
|
||||
for (const x in this.account_list) {
|
||||
this.value_account.push({ 'value': this.account_list[x] })
|
||||
}
|
||||
return this.value_account
|
||||
},
|
||||
load_region() {
|
||||
for (const x in this.region_list) {
|
||||
this.value_region.push({ 'value': this.region_list[x] })
|
||||
}
|
||||
return this.value_region
|
||||
},
|
||||
load_group() {
|
||||
for (const x in this.group_list) {
|
||||
this.value_group.push({ 'value': this.group_list[x] })
|
||||
}
|
||||
return this.value_group
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue