add rds monit

pull/42/head
StarsL.cn 2022-10-22 01:46:30 +08:00
parent 1987a1bc7d
commit 033d2bb80e
15 changed files with 1144 additions and 56 deletions

View File

@ -14,6 +14,7 @@ huaweicloudsdkcore==3.0.95
huaweicloudsdkecs==3.0.95
huaweicloudsdkeps==3.0.95
huaweicloudsdkbss==3.0.95
huaweicloudsdkrds==3.1.5
alibabacloud_resourcemanager20200331==2.1.0
alibabacloud_ecs20140526==2.1.0
alibabacloud_bssopenapi20171214==2.0.5

View File

@ -3,12 +3,15 @@ from huaweicloudsdkeps.v1.region.eps_region import EpsRegion
from huaweicloudsdkcore.exceptions import exceptions
from huaweicloudsdkbss.v2.region.bss_region import BssRegion
from huaweicloudsdkeps.v1 import *
from huaweicloudsdkecs.v2.region.ecs_region import EcsRegion
from huaweicloudsdkecs.v2 import *
from huaweicloudsdkbss.v2 import *
from huaweicloudsdkecs.v2 import *
from huaweicloudsdkecs.v2.region.ecs_region import EcsRegion
from huaweicloudsdkrds.v3 import *
from huaweicloudsdkrds.v3.region.rds_region import RdsRegion
import sys,datetime,hashlib
from units import consul_kv
from units.cloud import sync_ecs
from units.cloud import sync_rds
from units.cloud import notify
def exp(account,collect_days,notify_days,notify_amount):
@ -166,3 +169,54 @@ def ecs(account,region):
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)
def rds(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 = RdsClient.new_builder() \
.with_credentials(credentials) \
.with_region(RdsRegion.value_of(region)) \
.build()
request = ListInstancesRequest()
request.datastore_type = "MySQL"
request.limit = 100
info = client.list_instances(request).to_dict()['instances']
rds_dict = {i['id']:{'name':i['name'],
'domain':i['private_dns_names'][0],
'ip':i['private_ips'][0],
'port':i['port'],
'region':region,
'group':group_dict[i['enterprise_project_id']],
'status':i['status'],
'itype':i['type'],
'ver':i['datastore']['version'],
'cpu':f"{i['cpu']}",
'mem':f"{i['mem']}GB",
'disk':f"{i['volume']['size']}GB",
'exp': i['expiration_time']
} for i in info}
count = len(rds_dict)
off,on = sync_rds.w2consul('huaweicloud',account,region,rds_dict)
data = {'count':count,'update':now,'status':20000,'on':on,'off':off,'msg':f'RDS同步成功总数{count},开机:{on},关机:{off}'}
consul_kv.put_kv(f'ConsulManager/record/jobs/huaweicloud/{account}/rds/{region}', data)
print('【JOB】===>', 'huaweicloud_rds', 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}/rds/{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}/rds/{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}/rds/{region}', data)

View File

@ -0,0 +1,78 @@
#!/usr/bin/python3
import requests,json
from units import consul_kv
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,rds_dict):
service_name = f'{vendor}_{account}_rds'
params = {'filter': f'Service == "{service_name}" and "{region}" in Tags and Meta.account == "{account}"'}
try:
consul_rds_iid_list = requests.get(geturl, headers=headers, params=params).json().keys()
except:
consul_rds_iid_list = []
#在consul中删除云厂商不存在的rds
for del_rds in [x for x in consul_rds_iid_list if x not in rds_dict.keys()]:
dereg = requests.put(f'{delurl}/{del_rds}', 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 rds_dict.items():
iid = k
#对consul中关机的rds做标记。
if v['status'] in ['SHUTDOWN']:
off = off + 1
tags = ['shutoff',v['itype'],v['ver'], region]
stat = 'off'
else:
on = on + 1
tags = [v['itype'],v['ver'],region]
stat = 'on'
custom_rds = consul_kv.get_value(f'ConsulManager/assets/sync_rds_custom/{iid}')
port = custom_rds.get('port')
ip = custom_rds.get('ip')
if port == None:
port = v['port']
if ip == None:
ip = v['ip']
instance = f'{ip}:{port}'
data = {
'id': iid,
'name': service_name,
'Address': ip,
'port': port,
'tags': tags,
'Meta': {
'iid': iid,
'name': v['name'],
'region': regions[vendor].get(region,'未找到'),
'group': v['group'],
'instance': instance,
'account': account,
'vendor': vendors.get(vendor,'未找到'),
'disk': v['disk'],
'cpu': v['cpu'],
'mem': v['mem'],
'ver': v['ver'],
'domain':v['domain'],
'exp': v['exp'],
'stat': stat
},
"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 {"code": 50000,"data": f'{reg.status_code}:{reg.text}'}
return off,on

View File

@ -75,37 +75,39 @@ class Jobs(Resource):
ak = job_dict['ak']
sk = job_dict['sk']
consul_kv.put_aksk(job_dict['vendor'],job_dict['account'],ak,sk)
restype = job_dict['restype']
restype.remove('group')
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)
for res in restype:
res_job_id = f"{job_dict['vendor']}/{job_dict['account']}/{res}/{job_dict['region']}"
res_job_func = f"__main__:{job_dict['vendor']}.{res}"
res_job_args = [job_dict['account'],job_dict['region']]
res_job_interval = int(job_dict[f'{res}_interval'])
Scheduler.add_job(id=res_job_id, func=res_job_func, args=res_job_args, trigger='interval',
minutes=res_job_interval, replace_existing=True)
res_job_dict = {'id':res_job_id,'func':res_job_func,'args':res_job_args,'minutes':res_job_interval,
"trigger": "interval","replace_existing": True}
consul_kv.put_kv(f'ConsulManager/jobs/{res_job_id}',res_job_dict)
else:
Scheduler.remove_job(proj_job_id)
Scheduler.remove_job(ecs_job_id)
exp = consul_kv.get_value(f'ConsulManager/exp/config')
switch = exp.get('switch',False)
if switch:
if switch and record_dict['status'] == 20000:
vendor = job_dict['vendor']
account = job_dict['account']
exp_job_id = f'{vendor}/{account}/exp'

Binary file not shown.

View File

@ -95,18 +95,24 @@ export const constantRoutes = [
component: () => import('@/views/node-exporter/jobs'),
meta: { title: '接入数据源', icon: 'el-icon-school' }
},
{
path: 'jms',
name: 'JumpServer',
component: () => import('@/views/jms/index'),
meta: { title: 'JumpServer 同步', icon: 'el-icon-copy-document' }
},
{
path: 'exp',
name: '余额与到期通知',
component: () => import('@/views/node-exporter/exp'),
meta: { title: '余额与到期通知', icon: 'el-icon-alarm-clock' }
},
{
path: 'ecs',
name: 'ECS',
component: () => import('@/views/node-exporter/index'),
meta: { title: 'ECS管理', icon: 'el-icon-cpu' },
children: [
{
path: 'jms',
name: 'JumpServer',
component: () => import('@/views/jms/index'),
meta: { title: 'JumpServer 同步', icon: 'el-icon-copy-document' }
},
{
path: 'lists',
name: '云主机列表',
@ -139,6 +145,46 @@ export const constantRoutes = [
}
]
},
{
path: 'rds',
name: 'RDS',
component: () => import('@/views/rds/index'),
meta: { title: 'MySQL管理', icon: 'el-icon-cpu' },
children: [
{
path: 'lists',
name: '云MySQL列表',
component: () => import('@/views/rds/lists'),
meta: { title: '云MySQL列表', icon: 'el-icon-cloudy' }
},
{
path: 'self',
name: '自建MySQL管理',
component: () => import('@/views/rds/self'),
meta: { title: '自建MySQL管理', icon: 'el-icon-s-platform' }
},
{
path: 'pconfig',
name: 'rds-pconfig',
component: () => import('@/views/rds/pconfig'),
meta: { title: 'Prometheus 配置', icon: 'el-icon-set-up' }
},
{
path: 'rules',
name: 'rds-rules',
component: () => import('@/views/rds/rules'),
meta: { title: '告警规则', icon: 'el-icon-bell' }
},
{
path: 'grafana',
name: 'rds-grafana',
component: () => import('@/views/rds/grafana'),
meta: { title: 'Grafana 看板', icon: 'el-icon-data-line' }
}
]
}
]
},
{
path: '/blackbox',
component: Layout,

View File

@ -0,0 +1,3 @@
<template>
<router-view />
</template>

View File

@ -58,7 +58,7 @@
<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 type="success" size="mini" @click="row.itype==='group'?handleEnt(row.jobid):handleRes(row.itype,row.jobid)">
查看
</el-button>
<el-button type="warning" size="mini" @click="handleRun(row.jobid)">
@ -111,6 +111,15 @@
<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 label="资源类型">
<el-checkbox-group v-model="restype">
<el-checkbox label="group" disabled>分组</el-checkbox>
<el-checkbox label="ecs">ECS</el-checkbox>
<el-checkbox label="rds">RDS</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item prop="proj_interval">
<span slot="label">
<span class="span-box">
@ -122,9 +131,12 @@
</span>
<el-input v-model="ecsJob.proj_interval" />
</el-form-item>
<el-form-item label="ECS同步间隔(分钟)" prop="ecs_interval">
<el-form-item v-if="restype.includes('ecs')" label="ECS同步间隔(分钟)" prop="ecs_interval">
<el-input v-model="ecsJob.ecs_interval" />
</el-form-item>
<el-form-item v-if="restype.includes('rds')" label="RDS同步间隔(分钟)" prop="rds_interval">
<el-input v-model="ecsJob.rds_interval" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
@ -179,6 +191,7 @@ export default {
account_list: [],
itype_list: [],
itype: [],
restype: ['group', 'ecs'],
query: { vendor: '', account: '', itype: '' },
rules: {
vendor: [{ required: true, message: '此为必填项', trigger: 'change' },
@ -244,7 +257,7 @@ export default {
]
},
ecsJob: { vendor: '', ak: '', sk: '', region: '', account: '', proj_interval: 60, ecs_interval: 5 },
ecsJob: { vendor: '', ak: '', sk: '', region: '', account: '', proj_interval: 60, ecs_interval: 5, rds_interval: 5 },
upjob: { jobid: '', interval: '' },
newFormVisible: false,
upFormVisible: false,
@ -285,7 +298,7 @@ export default {
})
},
handleCreate() {
this.ecsJob = { vendor: '', ak: '', sk: '', region: '', account: '', proj_interval: 60, ecs_interval: 5 }
this.ecsJob = { vendor: '', ak: '', sk: '', region: '', account: '', proj_interval: 60, ecs_interval: 5, rds_interval: 5 }
this.ecsJob.account = this.query.account
this.newFormVisible = true
},
@ -315,6 +328,7 @@ export default {
this.newFormVisible = false
this.listLoading = true
this.ecsJob.dialogStatus = 'create'
this.ecsJob.restype = this.restype
PostJob(this.ecsJob).then(response => {
this.fetchData()
this.$message({
@ -354,9 +368,9 @@ export default {
this.upjob.interval = row.interval
this.upFormVisible = true
},
handleEcs(jobid) {
handleRes(restype, jobid) {
this.$router.push({
path: '/nodes/lists',
path: '/nodes/' + restype + '/lists',
query: { job_id: jobid }
})
},

Binary file not shown.

View File

@ -0,0 +1,19 @@
<template>
<div>
<br>
<el-row :gutter="20">
<el-col :span="12" :offset="6">
<el-card shadow="always" style="text-align: center">
Grafana 看板详情
<el-link href="https://grafana.com/grafana/dashboards/8919" target="_blank" type="primary">https://grafana.com/grafana/dashboards/8919</el-link><br><br>
Grafana 看板ID<strong>8919</strong>
</el-card>
</el-col>
</el-row>
<br>
<div class="block">
<el-image style="width: 100%; height: 100%" src="/node1.png" />
<el-image style="width: 100%; height: 100%" src="/node2.png" />
</div>
</div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<router-view />
</template>

View File

@ -0,0 +1,146 @@
<template>
<div class="app-container">
<el-select v-model="jobecs_name" placeholder="请选择需要查询的云MySQL列表" 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-checkbox v-model="checked" style="margin-left: 10px;" label="仅显示修改过的" border @change="cstEcsList(jobecs_name,checked)" />
<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>
<div style="float: right;margin-left: 10px;">
<el-input v-model="iname" prefix-icon="el-icon-search" placeholder="请输入名称、实例或实例ID进行筛选" clearable style="width: 300px" class="filter-item" />
</div>
<el-dialog title="自定义实例信息" :visible.sync="dialogFormVisible" width="45%">
<el-form ref="dataForm" :model="cst_ecs" label-position="right" label-width="auto" style="width: 90%; margin-left: 20px;">
<el-form-item label="自定义端口">
<el-switch v-model="cst_ecs.portswitch" />
</el-form-item>
<el-form-item v-if="cst_ecs.portswitch" required label="端口:">
<el-input v-model="cst_ecs.port" />
</el-form-item>
<el-form-item label="自定义IP">
<el-switch v-model="cst_ecs.ipswitch" />
</el-form-item>
<el-form-item v-if="cst_ecs.ipswitch" required label="IP">
<el-input v-model="cst_ecs.ip" />
</el-form-item>
<font size="3px" color="#ff0000">如需恢复同步该实例的IP端口信息请关闭开启的自定义选项后再同步一次所属数据源</font>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="createData">
确认
</el-button>
</div>
</el-dialog>
<el-table
v-loading="listLoading"
:data="ecs_list.filter(data => !iname || (data.name.toLowerCase().includes(iname.toLowerCase()) || data.instance.toLowerCase().includes(iname.toLowerCase()) || data.iid.toLowerCase().includes(iname.toLowerCase())))"
:default-sort="{ prop: 'exp', 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="150" show-overflow-tooltip />
<el-table-column prop="name" label="名称" sortable align="center" width="220" show-overflow-tooltip />
<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-column label="操作" align="center" width="120" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row.iid)">
自定义实例
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getEcsList, getJobEcs, postCstEcs, getCstEcsConfig, getCstEcsList } from '@/api/node-exporter'
export default {
data() {
return {
listLoading: false,
dialogFormVisible: false,
checked: false,
jobecs_name: '',
iname: '',
jobecs_list: [],
ecs_list: [],
cst_ecs: { iid: '', portswitch: false, ipswitch: false, port: '', ip: '' }
}
},
created() {
getJobEcs().then(response => {
this.jobecs_list = response.jobecs
if (this.$route.query.job_id) {
this.fetchEcs(this.$route.query.job_id)
} else {
this.jobecs_name = this.jobecs_list[0]
this.fetchEcs(this.jobecs_name)
}
})
},
mounted() {
if (this.$route.query.job_id) {
this.jobecs_name = this.$route.query.job_id
}
},
methods: {
cstEcsList(jobecs_name, checked) {
this.listLoading = true
getCstEcsList(jobecs_name, checked).then(response => {
this.ecs_list = response.ecs_list
this.listLoading = false
})
},
handleUpdate(iid) {
this.listLoading = true
this.dialogFormVisible = true
getCstEcsConfig(iid).then(response => {
this.cst_ecs = response.cst_ecs
this.listLoading = false
this.dialogFormVisible = true
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.dialogFormVisible = false
this.listLoading = true
postCstEcs(this.cst_ecs).then(response => {
this.fetchEcs(this.jobecs_name)
this.$message({
message: response.data,
type: 'success'
})
})
}
})
},
fetchJobEcs() {
getJobEcs().then(response => {
this.jobecs_list = response.jobecs
})
},
fetchEcs(job_id) {
this.checked = false
this.listLoading = true
getEcsList(job_id).then(response => {
this.ecs_list = response.ecs_list
this.listLoading = false
})
}
}
}
</script>

View File

@ -0,0 +1,75 @@
<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.services_list.push('selfnode_exporter')
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>

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/node-exporter'
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

@ -0,0 +1,596 @@
<template>
<div class="app-container">
<div class="filter-container" style="flex: 1;display: flex;align-items: center;height: 50px;">
<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: 150px" 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: 120px" 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="success" icon="el-icon-download" @click="handleDownload">
导出
</el-button>
<el-upload
style="margin-right: 9px;"
class="upload-demo"
action="/api/selfnode/upload"
:headers="myHeaders"
:on-success="success"
:on-error="error"
accept=".xlsx"
:before-upload="handleBeforeUpload"
:show-file-list="false"
:multiple="false"
>
<el-tooltip class="item" effect="light" content="点击【导出】可获取导入模板" placement="top">
<el-button v-waves style="margin-left: 9px;" :loading="downloadLoading" class="filter-item" type="warning" icon="el-icon-upload2">
导入
</el-button>
</el-tooltip>
</el-upload>
<el-button class="filter-item" type="danger" icon="el-icon-delete" @click="handleDelAll">
批量删除
</el-button>
<div style="float: right;margin-left: 9px;">
<el-input v-model="iname" prefix-icon="el-icon-search" placeholder="请输入名称或实例进行筛选" clearable style="width:180px" 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;">
<font size="3px" color="#ff0000">注意前5个字段组合后需唯一重复会覆盖已有监控项!</font>
<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 {
myHeaders: { Authorization: this.$store.getters.token },
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: [],
vendor_list: [],
account_list: [],
group_list: [],
region_list: [],
multipleSelection: [],
del_dict: {},
osport: { linux: '9100', windows: '9182' },
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: {
handleBeforeUpload(file) {
const uploadLimit = 5
const uploadTypes = ['xlsx']
const filetype = file.name.replace(/.+\./, '')
const isRightSize = (file.size || 0) / 1024 / 1024 < uploadLimit
if (!isRightSize) {
this.$message.error(`文件大小超过${uploadLimit}MB`)
return false
}
if (uploadTypes.indexOf(filetype.toLowerCase()) === -1) {
this.$message.warning({
message: '仅支持上传xlsx格式的文件'
})
return false
}
return true
},
success(response) {
if (response.code === 20000) {
this.fetchData()
this.$message({
message: response.data,
type: 'success'
})
} else {
this.$message({
message: response.data,
type: 'error'
})
}
},
error(response) {
if (response.code === 50000) {
this.$message({
message: response.data,
type: 'error'
})
}
},
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 = ['机房/公司', '租户/部门', '区域/项目', '分组/环境', '名称', '实例(IP:端口)', '系统(linux/windows)']
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>