A 监控中心新增Ping检测 #195

pull/220/head
vapao 2020-09-13 08:44:05 +08:00
parent 2ad8356c0a
commit 62386f98d8
6 changed files with 123 additions and 74 deletions

View File

@ -3,6 +3,8 @@
# Released under the AGPL-3.0 License.
from apps.host.models import Host
from socket import socket
import subprocess
import platform
import requests
import logging
@ -22,11 +24,27 @@ def port_check(addr, port):
sock = socket()
sock.settimeout(5)
sock.connect((addr, int(port)))
sock.close()
return True, '端口状态检测正常'
except Exception as e:
return False, f'异常信息:{e}'
def ping_check(addr):
try:
if platform.system().lower() == 'windows':
command = f'ping -n 1 -w 3000 {addr}'
else:
command = f'ping -c 1 -t 3 {addr}'
task = subprocess.run(command, shell=True, stdout=subprocess.PIPE)
if task.returncode == 0:
return True, 'Ping检测正常'
else:
return False, 'Ping检测失败'
except Exception as e:
return False, f'异常信息:{e}'
def host_executor(host, command):
try:
cli = host.get_ssh()
@ -44,6 +62,8 @@ def dispatch(tp, addr, extra):
return site_check(addr)
elif tp == '2':
return port_check(addr, extra)
elif tp == '5':
return ping_check(addr)
elif tp == '3':
command = f'ps -ef|grep -v grep|grep {extra!r}'
elif tp == '4':

View File

@ -13,6 +13,7 @@ class Detection(models.Model, ModelMixin):
('2', '端口检测'),
('3', '进程检测'),
('4', '自定义脚本'),
('5', 'Ping检测'),
)
STATUS = (
(0, '成功'),

View File

@ -27,7 +27,7 @@ class Scheduler:
timezone = settings.TIME_ZONE
def __init__(self):
self.scheduler = BackgroundScheduler(timezone=self.timezone, executors={'default': ThreadPoolExecutor(20)})
self.scheduler = BackgroundScheduler(timezone=self.timezone, executors={'default': ThreadPoolExecutor(30)})
self.scheduler.add_listener(
self._handle_event,
EVENT_SCHEDULER_SHUTDOWN | EVENT_JOB_ERROR | EVENT_JOB_MAX_INSTANCES | EVENT_JOB_EXECUTED)
@ -61,7 +61,7 @@ class Scheduler:
if obj.latest_status == 0:
if is_notified:
self._record_alarm(obj, '2')
logger.info(f'{human_datetime()} recover job_id: {obj.id}')
logger.info(f'{human_datetime()} recover job_id: {obj.id}, job_name: {obj.name}')
self._do_notify('2', obj, out)
else:
if obj.fault_times >= obj.threshold:
@ -69,7 +69,7 @@ class Scheduler:
obj.latest_notify_time = int(time.time())
obj.save()
self._record_alarm(obj, '1')
logger.info(f'{human_datetime()} notify job_id: {obj.id}')
logger.info(f'{human_datetime()} notify job_id: {obj.id}, job_name: {obj.name}')
self._do_notify('1', obj, out)
def _handle_event(self, event):

View File

@ -39,7 +39,7 @@ class Scheduler:
}
def __init__(self):
self.scheduler = BackgroundScheduler(timezone=self.timezone, executors={'default': ThreadPoolExecutor(20)})
self.scheduler = BackgroundScheduler(timezone=self.timezone, executors={'default': ThreadPoolExecutor(30)})
self.scheduler.add_listener(
self._handle_event,
EVENT_SCHEDULER_SHUTDOWN | EVENT_JOB_ERROR | EVENT_JOB_MAX_INSTANCES | EVENT_JOB_EXECUTED)

View File

@ -13,56 +13,98 @@ import store from './store';
import hostStore from '../host/store';
import groupStore from '../alarm/group/store';
import styles from './index.module.css';
import lds from 'lodash';
@observer
class ComForm extends React.Component {
constructor(props) {
super(props);
this.fieldMap = {
'1': ['domain'],
'2': ['addr', 'port'],
'3': ['host', 'process'],
'4': ['host', 'command'],
'5': ['addr'],
}
this.modeOptions = [
{label: '微信', 'value': '1'},
{label: '短信', 'value': '2', disabled: true},
{label: '钉钉', 'value': '3'},
{label: '邮件', 'value': '4'},
{label: '企业微信', 'value': '5'},
]
this.helpMap = {
'1': '返回HTTP状态码200-399则判定为正常其他为异常。',
'4': '脚本执行退出状态码为 0 则判定为正常,其他为异常。'
}
this.state = {
loading: false,
sitePrefix: 'http://',
extra: {[store.record.type]: store.record.extra},
addr: {},
domain: undefined,
addr: undefined,
port: undefined,
host: undefined,
process: undefined,
command: undefined,
showTmp: false,
page: 0,
modeOptions: [
{label: '微信', 'value': '1'},
{label: '短信', 'value': '2', disabled: true},
{label: '钉钉', 'value': '3'},
{label: '邮件', 'value': '4'},
{label: '企业微信', 'value': '5'},
],
helpMap: {
'1': '返回HTTP状态码200-399则判定为正常其他为异常。',
'4': '脚本执行退出状态码为 0 则判定为正常,其他为异常。'
}
}
}
componentDidMount() {
let [sitePrefix, value] = ['http://', ''];
if (store.record.type === '1') {
if (store.record.addr.includes('http://')) {
value = store.record.addr.replace('http://', '')
} else {
sitePrefix = 'https://';
value = store.record.addr.replace('https://', '')
}
this.setState({sitePrefix, addr: {'1': value}})
} else if ('34'.includes(store.record.type)) {
this.setState({addr: {'3': store.record.addr, '4': store.record.addr}})
} else {
this.setState({addr: {[store.record.type]: store.record.addr}})
const {type, addr, extra} = store.record;
switch (type) {
case '1':
if (addr.startsWith('http://')) {
this.setState({sitePrefix: 'http://', domain: addr.replace('http://', '')})
} else {
this.setState({sitePrefix: 'https://', domain: addr.replace('https://', '')})
}
break;
case '2':
this.setState({addr, port: extra});
break;
case '3':
this.setState({host: addr, process: extra});
break;
case '4':
this.setState({host: addr, command: extra});
break;
case '5':
this.setState({addr});
break;
default:
}
}
handleSubmit = () => {
this.setState({loading: true});
const {sitePrefix, domain, addr, host, port, command, process} = this.state;
const formData = this.props.form.getFieldsValue();
const type = formData['type'];
formData['id'] = store.record.id;
formData['extra'] = this.state.extra[type];
formData['addr'] = type === '1' ? this.state.sitePrefix + this.state.addr[type] : this.state.addr[type];
switch (type) {
case '1':
formData['addr'] = sitePrefix + domain;
break;
case '2':
formData['addr'] = addr;
formData['extra'] = port;
break;
case '3':
formData['addr'] = host;
formData['extra'] = process
break;
case '4':
formData['addr'] = host;
formData['extra'] = command;
break;
case '5':
formData['addr'] = addr;
break;
default:
throw Error('unknown type')
}
http.post('/api/monitor/', formData)
.then(() => {
message.success('操作成功');
@ -73,21 +115,12 @@ class ComForm extends React.Component {
getStyle = (t) => {
const type = this.props.form.getFieldValue('type');
return t.indexOf(type) !== -1 ? {display: 'block'} : {display: 'none'}
return this.fieldMap[type].includes(t) ? {display: 'block'} : {display: 'none'}
};
handleExtra = (t, e) => {
const value = t === '4' ? e : e.target.value;
this.setState({extra: Object.assign({}, this.state.extra, {[t]: value})})
};
handleAddr = (t, e) => {
if (t === '3') {
this.setState({addr: Object.assign({}, this.state.addr, {'3': e, '4': e})})
} else {
this.setState({addr: Object.assign({}, this.state.addr, {[t]: e.target.value})})
}
};
handleInput = (key, value) => {
this.setState({[key]: value})
}
siteBefore = () => (
<Select style={{width: 90}} value={this.state.sitePrefix} onChange={v => this.setState({sitePrefix: v})}>
@ -99,17 +132,15 @@ class ComForm extends React.Component {
verifyButtonStatus = () => {
const data = this.props.form.getFieldsValue();
const {notify_grp, notify_mode, type, name} = data;
let b1 = this.state.addr[type] && name;
if (type !== '1') {
b1 = b1 && this.state.extra[type]
}
const fields = Object.values(lds.pick(this.state, this.fieldMap[type])).filter(x => x)
const b1 = name && fields.length === this.fieldMap[type].length
const b2 = notify_grp && notify_grp.length && notify_mode && notify_mode.length;
return [b1, b2];
};
render() {
const info = store.record;
const {loading, extra, addr, showTmp, page, modeOptions, helpMap} = this.state;
const {loading, domain, host, port, process, command, addr, showTmp, page} = this.state;
const {getFieldDecorator, getFieldValue} = this.props.form;
const [b1, b2] = this.verifyButtonStatus();
return (
@ -126,11 +157,12 @@ class ComForm extends React.Component {
</Steps>
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
<div style={{display: page === 0 ? 'block' : 'none'}}>
<Form.Item label="监控类型" help={helpMap[getFieldValue('type')]}>
<Form.Item label="监控类型" help={this.helpMap[getFieldValue('type') || '1']}>
{getFieldDecorator('type', {initialValue: info['type'] || '1'})(
<Select placeholder="请选择监控类型">
<Select.Option value="1">站点检测</Select.Option>
<Select.Option value="2">端口检测</Select.Option>
<Select.Option value="5">Ping检测</Select.Option>
<Select.Option value="3">进程检测</Select.Option>
<Select.Option value="4">自定义脚本</Select.Option>
</Select>
@ -141,45 +173,46 @@ class ComForm extends React.Component {
<Input placeholder="请输入任务名称"/>
)}
</Form.Item>
<Form.Item required label="监控地址" style={this.getStyle('1')}>
<Input value={addr['1']} addonBefore={this.siteBefore()} placeholder="请输入监控地址"
onChange={e => this.handleAddr('1', e)}/>
<Form.Item required label="监控地址" style={this.getStyle('domain')}>
<Input value={domain} addonBefore={this.siteBefore()} placeholder="请输入监控地址"
onChange={e => this.handleInput('domain', e.target.value)}/>
</Form.Item>
<Form.Item required label="监控地址" style={this.getStyle('2')}>
<Input value={addr['2']} placeholder="请输入监控地址IP/域名)" onChange={e => this.handleAddr('2', e)}/>
<Form.Item required label="监控地址" style={this.getStyle('addr')}>
<Input value={addr} placeholder="请输入监控地址IP/域名)"
onChange={e => this.handleInput('addr', e.target.value)}/>
</Form.Item>
<Form.Item required label="监控主机" style={this.getStyle('34')}>
<Form.Item required label="监控主机" style={this.getStyle('host')}>
<Select
showSearch
value={addr['3']}
value={host}
placeholder="请选择主机"
optionFilterProp="children"
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
onChange={v => this.handleAddr('3', v)}>
{hostStore.records.filter(x => x.id === Number(addr['3']) || hasHostPermission(x.id)).map(item => (
onChange={v => this.handleInput('host', v)}>
{hostStore.records.filter(x => x.id === Number(host) || hasHostPermission(x.id)).map(item => (
<Select.Option value={String(item.id)} key={item.id}>
{`${item.name}(${item.hostname}:${item.port})`}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item required label="检测端口" style={this.getStyle('2')}>
<Input value={extra['2']} placeholder="请输入端口号" onChange={e => this.handleExtra('2', e)}/>
<Form.Item required label="检测端口" style={this.getStyle('port')}>
<Input value={port} placeholder="请输入端口号" onChange={e => this.handleInput('port', e.target.value)}/>
</Form.Item>
<Form.Item required label="进程名称" style={this.getStyle('3')} help="执行 ps -ef 看到的进程名称。">
<Input value={extra['3']} placeholder="请输入进程名称" onChange={e => this.handleExtra('3', e)}/>
<Form.Item required label="进程名称" style={this.getStyle('process')} help="执行 ps -ef 看到的进程名称。">
<Input value={process} placeholder="请输入进程名称" onChange={e => this.handleInput('process', e.target.value)}/>
</Form.Item>
<Form.Item
required
label="脚本内容"
style={this.getStyle('4')}
style={this.getStyle('command')}
extra={<LinkButton onClick={() => this.setState({showTmp: true})}>从模板添加</LinkButton>}>
<ACEditor
mode="sh"
value={extra['4']}
value={command}
width="100%"
height="200px"
onChange={e => this.handleExtra('4', cleanCommand(e))}/>
onChange={e => this.handleInput('command', cleanCommand(e))}/>
</Form.Item>
<Form.Item label="备注信息">
{getFieldDecorator('desc', {initialValue: info['desc']})(
@ -223,7 +256,7 @@ class ComForm extends React.Component {
</Form.Item>
<Form.Item required label="报警方式">
{getFieldDecorator('notify_mode', {initialValue: info['notify_mode']})(
<Checkbox.Group options={modeOptions}/>
<Checkbox.Group options={this.modeOptions}/>
)}
</Form.Item>
<Form.Item label="通道沉默" help="相同的告警信息,沉默期内只发送一次。">
@ -252,7 +285,7 @@ class ComForm extends React.Component {
</Form.Item>
</Form>
{showTmp && <TemplateSelector
onOk={v => this.handleExtra('4', extra['4'] + v)}
onOk={v => this.handleInput('command', command + v)}
onCancel={() => this.setState({showTmp: false})}/>}
</Modal>
)

View File

@ -42,11 +42,6 @@ class ComTable extends React.Component {
};
columns = [{
title: '序号',
key: 'series',
render: (_, __, index) => index + 1,
width: 80
}, {
title: '任务名称',
dataIndex: 'name',
}, {