mirror of https://github.com/openspug/spug
A 监控中心新增Ping检测 #195
parent
2ad8356c0a
commit
62386f98d8
|
@ -3,6 +3,8 @@
|
||||||
# Released under the AGPL-3.0 License.
|
# Released under the AGPL-3.0 License.
|
||||||
from apps.host.models import Host
|
from apps.host.models import Host
|
||||||
from socket import socket
|
from socket import socket
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
import requests
|
import requests
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -22,11 +24,27 @@ def port_check(addr, port):
|
||||||
sock = socket()
|
sock = socket()
|
||||||
sock.settimeout(5)
|
sock.settimeout(5)
|
||||||
sock.connect((addr, int(port)))
|
sock.connect((addr, int(port)))
|
||||||
|
sock.close()
|
||||||
return True, '端口状态检测正常'
|
return True, '端口状态检测正常'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, f'异常信息:{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):
|
def host_executor(host, command):
|
||||||
try:
|
try:
|
||||||
cli = host.get_ssh()
|
cli = host.get_ssh()
|
||||||
|
@ -44,6 +62,8 @@ def dispatch(tp, addr, extra):
|
||||||
return site_check(addr)
|
return site_check(addr)
|
||||||
elif tp == '2':
|
elif tp == '2':
|
||||||
return port_check(addr, extra)
|
return port_check(addr, extra)
|
||||||
|
elif tp == '5':
|
||||||
|
return ping_check(addr)
|
||||||
elif tp == '3':
|
elif tp == '3':
|
||||||
command = f'ps -ef|grep -v grep|grep {extra!r}'
|
command = f'ps -ef|grep -v grep|grep {extra!r}'
|
||||||
elif tp == '4':
|
elif tp == '4':
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Detection(models.Model, ModelMixin):
|
||||||
('2', '端口检测'),
|
('2', '端口检测'),
|
||||||
('3', '进程检测'),
|
('3', '进程检测'),
|
||||||
('4', '自定义脚本'),
|
('4', '自定义脚本'),
|
||||||
|
('5', 'Ping检测'),
|
||||||
)
|
)
|
||||||
STATUS = (
|
STATUS = (
|
||||||
(0, '成功'),
|
(0, '成功'),
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Scheduler:
|
||||||
timezone = settings.TIME_ZONE
|
timezone = settings.TIME_ZONE
|
||||||
|
|
||||||
def __init__(self):
|
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.scheduler.add_listener(
|
||||||
self._handle_event,
|
self._handle_event,
|
||||||
EVENT_SCHEDULER_SHUTDOWN | EVENT_JOB_ERROR | EVENT_JOB_MAX_INSTANCES | EVENT_JOB_EXECUTED)
|
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 obj.latest_status == 0:
|
||||||
if is_notified:
|
if is_notified:
|
||||||
self._record_alarm(obj, '2')
|
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)
|
self._do_notify('2', obj, out)
|
||||||
else:
|
else:
|
||||||
if obj.fault_times >= obj.threshold:
|
if obj.fault_times >= obj.threshold:
|
||||||
|
@ -69,7 +69,7 @@ class Scheduler:
|
||||||
obj.latest_notify_time = int(time.time())
|
obj.latest_notify_time = int(time.time())
|
||||||
obj.save()
|
obj.save()
|
||||||
self._record_alarm(obj, '1')
|
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)
|
self._do_notify('1', obj, out)
|
||||||
|
|
||||||
def _handle_event(self, event):
|
def _handle_event(self, event):
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Scheduler:
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
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.scheduler.add_listener(
|
||||||
self._handle_event,
|
self._handle_event,
|
||||||
EVENT_SCHEDULER_SHUTDOWN | EVENT_JOB_ERROR | EVENT_JOB_MAX_INSTANCES | EVENT_JOB_EXECUTED)
|
EVENT_SCHEDULER_SHUTDOWN | EVENT_JOB_ERROR | EVENT_JOB_MAX_INSTANCES | EVENT_JOB_EXECUTED)
|
||||||
|
|
|
@ -13,56 +13,98 @@ import store from './store';
|
||||||
import hostStore from '../host/store';
|
import hostStore from '../host/store';
|
||||||
import groupStore from '../alarm/group/store';
|
import groupStore from '../alarm/group/store';
|
||||||
import styles from './index.module.css';
|
import styles from './index.module.css';
|
||||||
|
import lds from 'lodash';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class ComForm extends React.Component {
|
class ComForm extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.fieldMap = {
|
||||||
loading: false,
|
'1': ['domain'],
|
||||||
sitePrefix: 'http://',
|
'2': ['addr', 'port'],
|
||||||
extra: {[store.record.type]: store.record.extra},
|
'3': ['host', 'process'],
|
||||||
addr: {},
|
'4': ['host', 'command'],
|
||||||
showTmp: false,
|
'5': ['addr'],
|
||||||
page: 0,
|
}
|
||||||
modeOptions: [
|
this.modeOptions = [
|
||||||
{label: '微信', 'value': '1'},
|
{label: '微信', 'value': '1'},
|
||||||
{label: '短信', 'value': '2', disabled: true},
|
{label: '短信', 'value': '2', disabled: true},
|
||||||
{label: '钉钉', 'value': '3'},
|
{label: '钉钉', 'value': '3'},
|
||||||
{label: '邮件', 'value': '4'},
|
{label: '邮件', 'value': '4'},
|
||||||
{label: '企业微信', 'value': '5'},
|
{label: '企业微信', 'value': '5'},
|
||||||
],
|
]
|
||||||
helpMap: {
|
this.helpMap = {
|
||||||
'1': '返回HTTP状态码200-399则判定为正常,其他为异常。',
|
'1': '返回HTTP状态码200-399则判定为正常,其他为异常。',
|
||||||
'4': '脚本执行退出状态码为 0 则判定为正常,其他为异常。'
|
'4': '脚本执行退出状态码为 0 则判定为正常,其他为异常。'
|
||||||
}
|
}
|
||||||
|
this.state = {
|
||||||
|
loading: false,
|
||||||
|
sitePrefix: 'http://',
|
||||||
|
domain: undefined,
|
||||||
|
addr: undefined,
|
||||||
|
port: undefined,
|
||||||
|
host: undefined,
|
||||||
|
process: undefined,
|
||||||
|
command: undefined,
|
||||||
|
showTmp: false,
|
||||||
|
page: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
let [sitePrefix, value] = ['http://', ''];
|
const {type, addr, extra} = store.record;
|
||||||
if (store.record.type === '1') {
|
switch (type) {
|
||||||
if (store.record.addr.includes('http://')) {
|
case '1':
|
||||||
value = store.record.addr.replace('http://', '')
|
if (addr.startsWith('http://')) {
|
||||||
|
this.setState({sitePrefix: 'http://', domain: addr.replace('http://', '')})
|
||||||
} else {
|
} else {
|
||||||
sitePrefix = 'https://';
|
this.setState({sitePrefix: 'https://', domain: addr.replace('https://', '')})
|
||||||
value = store.record.addr.replace('https://', '')
|
|
||||||
}
|
}
|
||||||
this.setState({sitePrefix, addr: {'1': value}})
|
break;
|
||||||
} else if ('34'.includes(store.record.type)) {
|
case '2':
|
||||||
this.setState({addr: {'3': store.record.addr, '4': store.record.addr}})
|
this.setState({addr, port: extra});
|
||||||
} else {
|
break;
|
||||||
this.setState({addr: {[store.record.type]: store.record.addr}})
|
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 = () => {
|
handleSubmit = () => {
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
|
const {sitePrefix, domain, addr, host, port, command, process} = this.state;
|
||||||
const formData = this.props.form.getFieldsValue();
|
const formData = this.props.form.getFieldsValue();
|
||||||
const type = formData['type'];
|
const type = formData['type'];
|
||||||
formData['id'] = store.record.id;
|
formData['id'] = store.record.id;
|
||||||
formData['extra'] = this.state.extra[type];
|
switch (type) {
|
||||||
formData['addr'] = type === '1' ? this.state.sitePrefix + this.state.addr[type] : this.state.addr[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)
|
http.post('/api/monitor/', formData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
message.success('操作成功');
|
message.success('操作成功');
|
||||||
|
@ -73,21 +115,12 @@ class ComForm extends React.Component {
|
||||||
|
|
||||||
getStyle = (t) => {
|
getStyle = (t) => {
|
||||||
const type = this.props.form.getFieldValue('type');
|
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) => {
|
handleInput = (key, value) => {
|
||||||
const value = t === '4' ? e : e.target.value;
|
this.setState({[key]: 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})})
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
siteBefore = () => (
|
siteBefore = () => (
|
||||||
<Select style={{width: 90}} value={this.state.sitePrefix} onChange={v => this.setState({sitePrefix: v})}>
|
<Select style={{width: 90}} value={this.state.sitePrefix} onChange={v => this.setState({sitePrefix: v})}>
|
||||||
|
@ -99,17 +132,15 @@ class ComForm extends React.Component {
|
||||||
verifyButtonStatus = () => {
|
verifyButtonStatus = () => {
|
||||||
const data = this.props.form.getFieldsValue();
|
const data = this.props.form.getFieldsValue();
|
||||||
const {notify_grp, notify_mode, type, name} = data;
|
const {notify_grp, notify_mode, type, name} = data;
|
||||||
let b1 = this.state.addr[type] && name;
|
const fields = Object.values(lds.pick(this.state, this.fieldMap[type])).filter(x => x)
|
||||||
if (type !== '1') {
|
const b1 = name && fields.length === this.fieldMap[type].length
|
||||||
b1 = b1 && this.state.extra[type]
|
|
||||||
}
|
|
||||||
const b2 = notify_grp && notify_grp.length && notify_mode && notify_mode.length;
|
const b2 = notify_grp && notify_grp.length && notify_mode && notify_mode.length;
|
||||||
return [b1, b2];
|
return [b1, b2];
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const info = store.record;
|
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 {getFieldDecorator, getFieldValue} = this.props.form;
|
||||||
const [b1, b2] = this.verifyButtonStatus();
|
const [b1, b2] = this.verifyButtonStatus();
|
||||||
return (
|
return (
|
||||||
|
@ -126,11 +157,12 @@ class ComForm extends React.Component {
|
||||||
</Steps>
|
</Steps>
|
||||||
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
|
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
|
||||||
<div style={{display: page === 0 ? 'block' : 'none'}}>
|
<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'})(
|
{getFieldDecorator('type', {initialValue: info['type'] || '1'})(
|
||||||
<Select placeholder="请选择监控类型">
|
<Select placeholder="请选择监控类型">
|
||||||
<Select.Option value="1">站点检测</Select.Option>
|
<Select.Option value="1">站点检测</Select.Option>
|
||||||
<Select.Option value="2">端口检测</Select.Option>
|
<Select.Option value="2">端口检测</Select.Option>
|
||||||
|
<Select.Option value="5">Ping检测</Select.Option>
|
||||||
<Select.Option value="3">进程检测</Select.Option>
|
<Select.Option value="3">进程检测</Select.Option>
|
||||||
<Select.Option value="4">自定义脚本</Select.Option>
|
<Select.Option value="4">自定义脚本</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
|
@ -141,45 +173,46 @@ class ComForm extends React.Component {
|
||||||
<Input placeholder="请输入任务名称"/>
|
<Input placeholder="请输入任务名称"/>
|
||||||
)}
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="监控地址" style={this.getStyle('1')}>
|
<Form.Item required label="监控地址" style={this.getStyle('domain')}>
|
||||||
<Input value={addr['1']} addonBefore={this.siteBefore()} placeholder="请输入监控地址"
|
<Input value={domain} addonBefore={this.siteBefore()} placeholder="请输入监控地址"
|
||||||
onChange={e => this.handleAddr('1', e)}/>
|
onChange={e => this.handleInput('domain', e.target.value)}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="监控地址" style={this.getStyle('2')}>
|
<Form.Item required label="监控地址" style={this.getStyle('addr')}>
|
||||||
<Input value={addr['2']} placeholder="请输入监控地址(IP/域名)" onChange={e => this.handleAddr('2', e)}/>
|
<Input value={addr} placeholder="请输入监控地址(IP/域名)"
|
||||||
|
onChange={e => this.handleInput('addr', e.target.value)}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="监控主机" style={this.getStyle('34')}>
|
<Form.Item required label="监控主机" style={this.getStyle('host')}>
|
||||||
<Select
|
<Select
|
||||||
showSearch
|
showSearch
|
||||||
value={addr['3']}
|
value={host}
|
||||||
placeholder="请选择主机"
|
placeholder="请选择主机"
|
||||||
optionFilterProp="children"
|
optionFilterProp="children"
|
||||||
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||||
onChange={v => this.handleAddr('3', v)}>
|
onChange={v => this.handleInput('host', v)}>
|
||||||
{hostStore.records.filter(x => x.id === Number(addr['3']) || hasHostPermission(x.id)).map(item => (
|
{hostStore.records.filter(x => x.id === Number(host) || hasHostPermission(x.id)).map(item => (
|
||||||
<Select.Option value={String(item.id)} key={item.id}>
|
<Select.Option value={String(item.id)} key={item.id}>
|
||||||
{`${item.name}(${item.hostname}:${item.port})`}
|
{`${item.name}(${item.hostname}:${item.port})`}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="检测端口" style={this.getStyle('2')}>
|
<Form.Item required label="检测端口" style={this.getStyle('port')}>
|
||||||
<Input value={extra['2']} placeholder="请输入端口号" onChange={e => this.handleExtra('2', e)}/>
|
<Input value={port} placeholder="请输入端口号" onChange={e => this.handleInput('port', e.target.value)}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="进程名称" style={this.getStyle('3')} help="执行 ps -ef 看到的进程名称。">
|
<Form.Item required label="进程名称" style={this.getStyle('process')} help="执行 ps -ef 看到的进程名称。">
|
||||||
<Input value={extra['3']} placeholder="请输入进程名称" onChange={e => this.handleExtra('3', e)}/>
|
<Input value={process} placeholder="请输入进程名称" onChange={e => this.handleInput('process', e.target.value)}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
required
|
required
|
||||||
label="脚本内容"
|
label="脚本内容"
|
||||||
style={this.getStyle('4')}
|
style={this.getStyle('command')}
|
||||||
extra={<LinkButton onClick={() => this.setState({showTmp: true})}>从模板添加</LinkButton>}>
|
extra={<LinkButton onClick={() => this.setState({showTmp: true})}>从模板添加</LinkButton>}>
|
||||||
<ACEditor
|
<ACEditor
|
||||||
mode="sh"
|
mode="sh"
|
||||||
value={extra['4']}
|
value={command}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="200px"
|
height="200px"
|
||||||
onChange={e => this.handleExtra('4', cleanCommand(e))}/>
|
onChange={e => this.handleInput('command', cleanCommand(e))}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="备注信息">
|
<Form.Item label="备注信息">
|
||||||
{getFieldDecorator('desc', {initialValue: info['desc']})(
|
{getFieldDecorator('desc', {initialValue: info['desc']})(
|
||||||
|
@ -223,7 +256,7 @@ class ComForm extends React.Component {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="报警方式">
|
<Form.Item required label="报警方式">
|
||||||
{getFieldDecorator('notify_mode', {initialValue: info['notify_mode']})(
|
{getFieldDecorator('notify_mode', {initialValue: info['notify_mode']})(
|
||||||
<Checkbox.Group options={modeOptions}/>
|
<Checkbox.Group options={this.modeOptions}/>
|
||||||
)}
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="通道沉默" help="相同的告警信息,沉默期内只发送一次。">
|
<Form.Item label="通道沉默" help="相同的告警信息,沉默期内只发送一次。">
|
||||||
|
@ -252,7 +285,7 @@ class ComForm extends React.Component {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
{showTmp && <TemplateSelector
|
{showTmp && <TemplateSelector
|
||||||
onOk={v => this.handleExtra('4', extra['4'] + v)}
|
onOk={v => this.handleInput('command', command + v)}
|
||||||
onCancel={() => this.setState({showTmp: false})}/>}
|
onCancel={() => this.setState({showTmp: false})}/>}
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
|
|
@ -42,11 +42,6 @@ class ComTable extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
columns = [{
|
columns = [{
|
||||||
title: '序号',
|
|
||||||
key: 'series',
|
|
||||||
render: (_, __, index) => index + 1,
|
|
||||||
width: 80
|
|
||||||
}, {
|
|
||||||
title: '任务名称',
|
title: '任务名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
}, {
|
}, {
|
||||||
|
|
Loading…
Reference in New Issue