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.
 | 
			
		||||
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':
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ class Detection(models.Model, ModelMixin):
 | 
			
		|||
        ('2', '端口检测'),
 | 
			
		||||
        ('3', '进程检测'),
 | 
			
		||||
        ('4', '自定义脚本'),
 | 
			
		||||
        ('5', 'Ping检测'),
 | 
			
		||||
    )
 | 
			
		||||
    STATUS = (
 | 
			
		||||
        (0, '成功'),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,11 +42,6 @@ class ComTable extends React.Component {
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  columns = [{
 | 
			
		||||
    title: '序号',
 | 
			
		||||
    key: 'series',
 | 
			
		||||
    render: (_, __, index) => index + 1,
 | 
			
		||||
    width: 80
 | 
			
		||||
  }, {
 | 
			
		||||
    title: '任务名称',
 | 
			
		||||
    dataIndex: 'name',
 | 
			
		||||
  }, {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue