jumpserver同步功能完成,blackbox监控优化用户体验

This commit is contained in:
StarsL.cn 2022-07-25 14:20:25 +08:00
parent 43c141f437
commit b049ad7495
9 changed files with 193 additions and 59 deletions

View File

@ -49,7 +49,7 @@ groups: #新rule文件需要加这行开头追加旧的rule文件则不需
interval: 1m
rules:
- record: cpu:usage:rate1m
expr: (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) by (instance,vendor,account,group,name)) * 100
expr: (1 - avg(irate(node_cpu_seconds_total{mode="idle"}[3m])) by (instance,vendor,account,group,name)) * 100
- record: mem:usage:rate1m
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
```

View File

@ -4,7 +4,7 @@ WORKDIR /flask
RUN sed -i 's/dl-cdn.alpinelinux.org/repo.huaweicloud.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://repo.huaweicloud.com/repository/pypi/simple \
&& pip3 install -r requirements.txt -i https://repo.huaweicloud.com/repository/pypi/simple
&& pip3 install --upgrade pip -i https://repo.huaweicloud.com/repository/pypi/simple --no-cache-dir\
&& pip3 install -r requirements.txt -i https://repo.huaweicloud.com/repository/pypi/simple --no-cache-dir
EXPOSE 2026
CMD ["python3","./manager.py"]

View File

@ -38,7 +38,7 @@ init_jobs = { **ecs_jobs, **avd_jobs, **exp_jobs, **jms_jobs }
if init_jobs is not None:
for k,v in init_jobs.items():
print(f'【初始化任务】{k}:\n {v}', flush=True)
print(f"【初始化任务】{k}{v['args']}{v['minutes']}m", flush=True)
Config.JOBS = init_jobs.values()
app.config.from_object(Config())

View File

@ -1,10 +1,8 @@
import requests,json
import sys
sys.path.append("..")
import requests,json,consul_kv
from config import consul_token,consul_url
headers = {'X-Consul-Token': consul_token}
init_module_list = ['http_2xx','http_4xx','tcp_connect','icmp','http200igssl','httpNoRedirect4ssl','http_5xx','http_post_2xx','ssh_banner']
def get_all_list(module,company,project,env):
module = f'and Meta.module=="{module}"' if module != '' else f'and Meta.module != ""'
company = f'and Meta.company=="{company}"' if company != '' else f'and Meta.company != ""'
@ -15,10 +13,14 @@ def get_all_list(module,company,project,env):
if response.status_code == 200:
info = response.json()
all_list = [i['Meta'] for i in info.values()]
module_list = sorted(list(set([i['module'] for i in all_list])))
module_list = consul_kv.get_value('ConsulManager/record/blackbox/module_list')['module_list']
company_list = sorted(list(set([i['company'] for i in all_list])))
project_list = sorted(list(set([i['project'] for i in all_list])))
env_list = sorted(list(set([i['env'] for i in all_list])))
init_m_list = [x for x in init_module_list if x not in module_list]
module_list = module_list + ['---'] + init_m_list
return {'code': 20000,'all_list':all_list,'module_list':module_list,
'company_list':company_list,'project_list':project_list,'env_list':env_list}
else:
@ -33,6 +35,10 @@ def get_service():
company_list = sorted(list(set([i['company'] for i in all_list])))
project_list = sorted(list(set([i['project'] for i in all_list])))
env_list = sorted(list(set([i['env'] for i in all_list])))
consul_kv.put_kv('ConsulManager/record/blackbox/module_list',{'module_list':module_list})
init_m_list = [x for x in init_module_list if x not in module_list]
module_list = module_list + ['------'] + init_m_list
return {'code': 20000,'all_list':all_list,'module_list':module_list,
'company_list':company_list,'project_list':project_list,'env_list':env_list}
else:

View File

@ -50,7 +50,7 @@ def update_jms_ecs(jms_url,headers,new_node_dict,node_id,cloud,account,ecs_info,
admin_user = custom_info[ostype][1]
payload = {
"ip": ip,
"hostname": "cm_" + iname,
"hostname": iname,
"protocols": protocols,
"platform": platform,
"is_active": True,
@ -60,7 +60,7 @@ def update_jms_ecs(jms_url,headers,new_node_dict,node_id,cloud,account,ecs_info,
"comment": comment
}
if ip in jms_ecs_dict.keys():
if jms_ecs_dict[ip]['name'] != "cm_" + iname or jms_ecs_dict[ip]['node'].split('/')[-1] != v['ent']:
if jms_ecs_dict[ip]['name'] != iname or jms_ecs_dict[ip]['node'].split('/')[-1] != v['ent']:
response = requests.request("PUT", f"{ecs_url}{jms_ecs_dict[ip]['id']}/", headers=headers, data = json.dumps(payload))
print(' 【JMS】update主机名',response.json()['hostname'],response.status_code,flush=True)
else:

View File

@ -11,7 +11,8 @@ api = Api(blueprint)
parser = reqparse.RequestParser()
parser.add_argument('query_dict',type=str)
parser.add_argument('jms_config',type=dict)
parser.add_argument('isnotify_dict',type=dict)
parser.add_argument('jms_sync',type=dict)
parser.add_argument('switch_dict',type=dict)
class Jms(Resource):
decorators = [token_auth.auth.login_required]
@ -74,15 +75,17 @@ class Jms(Resource):
if stype == 'config':
ecs_info = consul_kv.get_value('ConsulManager/jms/ecs_info')
jms_info = consul_kv.get_value('ConsulManager/jms/jms_info')
custom_ecs_info = consul_kv.get_value('ConsulManager/jms/custom_ecs_info')
if ecs_info != {} and jms_info != {}:
linuxport = ecs_info['linux'][0][0].split('/')[-1]
linuxuid = ecs_info['linux'][-1]
winport = ecs_info['windows'][0][0].split('/')[-1]
winuid = ecs_info['windows'][-1]
token = myaes.decrypt(jms_info['token'])
custom_ecs_json = json.dumps(custom_ecs_info, indent=8) if custom_ecs_info != {} else ''
jms_config = {'url': jms_info['url'], 'token': token,
'linuxport': linuxport, 'linuxuid': linuxuid,
'winport': winport, 'winuid': winuid}
'winport': winport, 'winuid': winuid, 'custom_ecs_info':custom_ecs_json}
else:
jms_config = {}
return {'code': 20000, 'jms_config': jms_config}
@ -96,5 +99,49 @@ class Jms(Resource):
ecs_info = {"linux": [[f"ssh/{jms_config['linuxport']}"],jms_config['linuxuid']],
"windows": [[f"rdp/{jms_config['winport']}"],jms_config['winuid']]}
consul_kv.put_kv('ConsulManager/jms/ecs_info', ecs_info)
custom_ecs_info = jms_config['custom_ecs_info']
if custom_ecs_info != '':
try:
custom_ecs_dict = json.loads(custom_ecs_info)
consul_kv.put_kv('ConsulManager/jms/custom_ecs_info',custom_ecs_dict)
except Exception as e:
print(e,flush=True)
return {'code': 50000, 'data': 'Json解析错误请检查'}
else:
consul_kv.put_kv('ConsulManager/jms/custom_ecs_info',{})
return {'code': 20000, 'data': '配置完成'}
if stype == 'switch':
args = parser.parse_args()
switch_dict = args['switch_dict']
vendor = {v : k for k, v in vendors.items()}[switch_dict['vendor']]
account = switch_dict['account']
sync = switch_dict['sync']
if sync:
node = consul_kv.get_value(f'ConsulManager/jms/{vendor}/{account}/node_id')
nodeid = node.get('node_id','')
interval = node.get('interval',3)
return {'code': 20000, 'interval': interval, 'nodeid': nodeid}
else:
deljob(f'{vendor}/{account}/jms')
consul_kv.del_key(f'ConsulManager/jms/jobs/{vendor}/{account}')
return {'code': 20000, 'data': f'{vendor}/{account}】同步功能关闭!'}
if stype == 'sync':
args = parser.parse_args()
jms_sync = args['jms_sync']
vendor = {v : k for k, v in vendors.items()}[jms_sync['vendor']]
account = jms_sync['account']
nodeid = jms_sync['nodeid']
interval = int(jms_sync['interval'])
consul_kv.put_kv(f'ConsulManager/jms/{vendor}/{account}/node_id',{'node_id':nodeid,'interval':interval})
jms_job_id = f'{vendor}/{account}/jms'
jms_job_func = "__main__:sync_jms.run"
jms_job_args = [vendor,account]
addjob(jms_job_id,jms_job_func,jms_job_args,interval)
runjob(jms_job_id)
jms_job_dict = {'id':jms_job_id,'func':jms_job_func,'args':jms_job_args,'minutes':interval,
'trigger': 'interval','replace_existing': True}
consul_kv.put_kv(f'ConsulManager/jms/jobs/{vendor}/{account}',jms_job_dict)
return {'code': 20000, 'data': f'{vendor}/{account}】同步JumpServer功能开启首次同步完成'}
api.add_resource(Jms, '/api/jms/<stype>')

View File

@ -22,10 +22,19 @@ export function postJmsConfig(jms_config) {
})
}
export function postExpIsnotify(isnotify_dict) {
export function postJmsSwitch(switch_dict) {
return request({
url: '/api/exp/isnotify',
url: '/api/jms/switch',
method: 'post',
data: { isnotify_dict }
data: { switch_dict }
})
}
export function postJmsSync(jms_sync) {
return request({
url: '/api/jms/sync',
method: 'post',
timeout: 600 * 1000,
data: { jms_sync }
})
}

View File

@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-alert type="success" center close-text="知道了">
<el-alert type="success" center close-text="知道了">
<el-link icon="el-icon-warning" type="success" href="https://github.com/starsliao/ConsulManager/blob/main/docs/blackbox%E7%AB%99%E7%82%B9%E7%9B%91%E6%8E%A7.md" target="_blank">应用场景如何优雅的使用Consul管理Blackbox站点监控</el-link>
</el-alert>
<div class="filter-container" style="flex: 1;display: flex;align-items: center;height: 50px;">
@ -47,7 +47,7 @@
批量删除
</el-button>
<div style="float: right;margin-left: 10px;">
<el-input v-model="iname" prefix-icon="el-icon-search" placeholder="请输入名称或实例进行筛选" clearable style="width:180px" class="filter-item" @input="inameFilter(iname)" />
<el-input v-model="iname" prefix-icon="el-icon-search" placeholder="名称或URL筛选" clearable style="width:180px" class="filter-item" @input="inameFilter(iname)" />
</div>
</div>
@ -91,7 +91,7 @@
<span>{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column prop="instance" label="实例" sortable align="center" show-overflow-tooltip>
<el-table-column prop="instance" label="URL" sortable align="center" show-overflow-tooltip>
<template slot-scope="{row}">
<span style="font-size: 12px">{{ row.instance }}</span>
</template>
@ -121,30 +121,30 @@
</el-tooltip>
</span>
</span>
<el-autocomplete v-model="temp.module" :fetch-suggestions="Sugg_module" placeholder="优先选择" clearable 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 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 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 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 class="filter-item" /><font size="3px" color="#ff0000">5个字段组合后需唯一重复会覆盖已有监控项!</font>
<el-input v-model="temp.name" placeholder="请输入" clearable class="filter-item" /><br><font size="3px" color="#ff0000">上5个字段组合后需唯一重复会覆盖已有监控项!</font>
</el-form-item>
<el-form-item prop="instance">
<span slot="label">
<span class="span-box">
<span>实例</span>
<el-tooltip style="diaplay:inline" effect="dark" content="TCP类检查格式为IP:端口 HTTP类检查格式为完整的URL必须以http(s)://开头。" placement="top">
<span>URL</span>
<el-tooltip style="diaplay:inline" effect="dark" content="TCP类检查格式为IP:端口 HTTP类检查格式为完整的URL必须以http(s)://开头ICMP检查仅填IP或域名。" placement="top">
<i class="el-icon-info" />
</el-tooltip>
</span>
</span>
<el-input v-model="temp.instance" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入" class="filter-item" />
<el-input v-model="temp.instance" placeholder="一次仅添加一个URL批量添加可使用导入" clearable class="filter-item" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
@ -228,7 +228,7 @@ export default {
dialogStatus: '',
textMap: {
update: '更新',
create: '创建'
create: '创建URL监控'
},
rules: {
module: [{ required: true, message: '此为必填项', trigger: 'change' },
@ -437,6 +437,7 @@ export default {
})
},
handleReset() {
this.fetchData()
this.listQuery.module = ''
this.listQuery.company = ''
this.listQuery.project = ''
@ -511,7 +512,7 @@ export default {
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['监控模块', '公司部门', '项目', '环境', '名称', '实例(tcp的格式为IP:端口,URL需要以http(s)://开头)']
const tHeader = ['监控模块', '公司部门', '项目', '环境', '名称', 'URL(tcp的格式为IP:端口,URL需要以http(s)://开头)']
const filterVal = ['module', 'company', 'project', 'env', 'name', 'instance']
const data = this.formatJson(filterVal)
excel.export_json_to_excel({

View File

@ -1,6 +1,8 @@
<template>
<div class="app-container">
<el-alert title="菜单只显示有到期资源的账户,余额可查询所有账户;单个资源的通知可独立关闭。【自动续费、到期转按需、到期不续费的资源不会采集】【腾讯云仅采集主机到期列表(未找到整体到期接口)】" type="error" close-text="朕知道了" />
<el-alert type="success" center close-text="朕知道了">
<el-link icon="el-icon-warning" type="success" href="https://github.com/starsliao/ConsulManager/blob/43c141f4373cb3288e213116a69b33820b6cce10/docs/%E5%A6%82%E4%BD%95%E6%8A%8A%E4%B8%BB%E6%9C%BA%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5%E5%88%B0JumpServer.md" target="_blank">应用场景如何优雅的把主机信息自动同步到JumpServer</el-link>
</el-alert>
<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>
@ -11,10 +13,11 @@
<el-button class="filter-item" style="margin-left: 10px;" type="success" icon="el-icon-magic-stick" circle @click="resetData" />
</el-tooltip>
<el-button class="filter-item" type="primary" icon="el-icon-edit" @click="handleCreate">接入JumpServer</el-button>
<el-tooltip class="item" effect="light" content="根据菜单选择查询对应账户余额,菜单为空时,查询所有账户。" placement="top">
<el-button class="filter-item" type="warning" icon="el-icon-data-line" @click="handleamount">查看余额</el-button>
</el-tooltip>
<el-dialog title="接入JumpServer" :visible.sync="dialogFormVisible" width="45%">
<el-dialog :visible.sync="dialogFormVisible" width="40%">
<div slot="title" class="header-title">
<span style="font-size:16px;font-weight:bold;">接入JumpServer</span>&nbsp;&nbsp;
<el-link type="primary" href="https://github.com/starsliao/ConsulManager/blob/43c141f4373cb3288e213116a69b33820b6cce10/docs/%E5%A6%82%E4%BD%95%E6%8A%8A%E4%B8%BB%E6%9C%BA%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5%E5%88%B0JumpServer.md" target="_blank" icon="el-icon-question">如何填写</el-link>
</div>
<el-form ref="dataForm" :model="jms_config" label-position="right" label-width="auto" style="width: 90%; margin-left: 20px;">
<el-form-item label="JumpServer URL">
<el-input v-model="jms_config.url" placeholder="http开头" style="width: 390px;" />
@ -22,17 +25,21 @@
<el-form-item label="JumpServer Token">
<el-input v-model="jms_config.token" placeholder="请输入Admin Token" style="width: 390px;" show-password />
</el-form-item>
<el-form-item label="全局管理用户信息:" />
<hr style="FILTER: alpha(opacity=100,finishopacity=0,style=2)" align=left width="96%" SIZE=1>
<h3>全局通用主机管理用户信息</h3>
<div class="demo-input-suffix">
<h3>Linux</h3>
<h4>Linux</h4>
ssh端口<el-input v-model="jms_config.linuxport" style="width: 72px;" />
&nbsp;&nbsp;管理用户ID<el-input v-model="jms_config.linuxuid" style="width: 300px;" />
</div>
<div class="demo-input-suffix">
<h3>Windows</h3>
<h4>Windows</h4>
rdp端口<el-input v-model="jms_config.winport" style="width: 72px;" />
&nbsp;&nbsp;管理用户ID<el-input v-model="jms_config.winuid" style="width: 300px;" />
</div>
<hr style="FILTER: alpha(opacity=100,finishopacity=0,style=2)" align=left width="96%" SIZE=1>
<h3>全局特殊主机管理用户信息</h3>
<el-input v-model="jms_config.custom_ecs_info" :autosize="{ minRows: 5, maxRows: 18}" type="textarea" placeholder="请输入标准Json格式无特殊主机请留空。" class="filter-item" style="width: 530px;" />
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
@ -66,50 +73,87 @@
<el-table-column prop="nextime" label="下次同步" sortable align="center" />
<el-table-column label="同步" align="center" width="60" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-switch v-model="row.sync" active-color="#13ce66" @change="fetchNotify(row.vendor, row.account, row.notify_id, row.isnotify)" />
<el-switch v-model="row.sync" active-color="#13ce66" @change="fetchSwitch(row.vendor, row.account, row.sync)" />
</template>
</el-table-column>
</el-table>
<el-dialog title="查询余额" :visible.sync="amountFormVisible" width="60%">
<el-table v-loading="listLoading" :data="amount_list" height="540" :default-sort="{ prop: 'amount', order: 'ascending' }" border fit highlight-current-row style="width: 100%;">
<el-table-column prop="vendor" label="云厂商" sortable align="center" />
<el-table-column prop="account" label="账户" sortable align="center" />
<el-table-column prop="amount" label="余额(元)" sortable align="center" />
</el-table>
<el-dialog title="开启同步JumpServer" :visible.sync="swFormVisible" :before-close="fetchData" width="33%">
<el-form ref="dataForm" :model="jms_sync" label-position="right" label-width="auto" style="width: 90%; margin-left: 20px;">
<el-form-item label="同步间隔">
<el-input v-model="jms_sync.interval" style="width: 180px;" type="number">
<template slot="append">分钟</template>
</el-input>
</el-form-item>
<el-form-item label="新节点ID">
<el-input v-model="jms_sync.nodeid" />
</el-form-item>
<font size="3px" color="#ff0000">注意JumpServer中已有的同名主机不会同步</font>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="fetchData">
取消
</el-button>
<el-button type="primary" @click="createSync(jms_sync)">
确认
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getJmsList, getJmsConfig, postJmsConfig, postExpIsnotify } from '@/api/jms'
import { getJmsList, getJmsConfig, postJmsConfig, postJmsSwitch, postJmsSync } from '@/api/jms'
export default {
data() {
return {
jms_config: { url: '', token: '', linuxport: '22', linuxuid: '', winport: '3389', winuid: '' },
jms_config: { url: '', token: '', linuxport: '22', linuxuid: '', winport: '3389', winuid: '', custom_ecs_info: '' },
listLoading: false,
dialogFormVisible: false,
query: { vendor: '', account: '' },
ecs_list: [],
vendor_list: [],
account_list: [],
amount_list: [],
isnotify_dict: {},
amountFormVisible: false
jms_sync: { vendor: '', account: '', interval: '3', nodeid: '' },
switch_dict: {},
swFormVisible: false
}
},
created() {
this.fetchData()
},
methods: {
fetchNotify(vendor, account, notify_id, isnotify) {
this.isnotify_dict = { vendor: vendor, account: account, notify_id: notify_id, isnotify: isnotify }
postExpIsnotify(this.isnotify_dict).then(response => {
this.$message({
message: response.data,
type: response.type
fetchSwitch(vendor, account, sync) {
this.switch_dict = { vendor: vendor, account: account, sync: sync }
if (sync) {
this.jms_sync.vendor = vendor
this.jms_sync.account = account
postJmsSwitch(this.switch_dict).then(response => {
this.jms_sync.interval = response.interval
this.jms_sync.nodeid = response.nodeid
this.swFormVisible = true
})
})
} else {
this.$confirm('此操作将关闭同步功能,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
postJmsSwitch(this.switch_dict).then(response => {
this.fetchData()
this.$message({
message: response.data,
type: response.type
})
})
}).catch(() => {
this.fetchData()
this.$message({
type: 'info',
message: '操作已取消'
})
})
}
},
handleCreate() {
this.listLoading = true
@ -126,6 +170,7 @@ export default {
this.fetchData()
},
fetchData() {
this.swFormVisible = false
this.listLoading = true
getJmsList(this.query).then(response => {
this.vendor_list = response.vendor_list
@ -137,10 +182,12 @@ export default {
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.dialogFormVisible = false
this.listLoading = true
postJmsConfig(this.jms_config).then(response => {
this.listLoading = false
if (response.code === 20000) {
this.dialogFormVisible = false
}
this.$message({
message: response.data,
type: 'success'
@ -149,8 +196,32 @@ export default {
}
})
},
handleamount() {
this.amountFormVisible = true
createSync(jms_sync) {
this.$confirm('此操作将开启同步功能,并进行首次同步,请等待(耗时依主机数而定,可在日志中查看进度)是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.swFormVisible = false
this.listLoading = true
postJmsSync(jms_sync).then(response => {
this.listLoading = false
this.fetchData()
this.$message({
message: response.data,
type: 'success'
})
})
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '操作已取消'
})
})
}
}
}