A 监控中心 增加分组

pull/289/head
zyupo 2020-12-02 18:59:35 +08:00
parent 8bf264bcd2
commit d11ee1258d
8 changed files with 80 additions and 31 deletions

View File

@ -21,6 +21,7 @@ class Detection(models.Model, ModelMixin):
) )
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
type = models.CharField(max_length=2, choices=TYPES) type = models.CharField(max_length=2, choices=TYPES)
group = models.CharField(max_length=255, null=True)
addr = models.CharField(max_length=255) addr = models.CharField(max_length=255)
extra = models.TextField(null=True) extra = models.TextField(null=True)
desc = models.CharField(max_length=255, null=True) desc = models.CharField(max_length=255, null=True)

View File

@ -13,12 +13,14 @@ import json
class DetectionView(View): class DetectionView(View):
def get(self, request): def get(self, request):
detections = Detection.objects.all() detections = Detection.objects.all()
return json_response(detections) groups = [x['group'] for x in detections.order_by('group').values('group').distinct()]
return json_response({'groups': groups, 'detections': [x.to_dict() for x in detections]})
def post(self, request): def post(self, request):
form, error = JsonParser( form, error = JsonParser(
Argument('id', type=int, required=False), Argument('id', type=int, required=False),
Argument('name', help='请输入任务名称'), Argument('name', help='请输入任务名称'),
Argument('group', help='请选择任务分组'),
Argument('addr', help='请输入监控地址'), Argument('addr', help='请输入监控地址'),
Argument('type', filter=lambda x: x in dict(Detection.TYPES), help='请选择监控类型'), Argument('type', filter=lambda x: x in dict(Detection.TYPES), help='请选择监控类型'),
Argument('extra', required=False), Argument('extra', required=False),

1
spug_web/.gitignore vendored
View File

@ -4,6 +4,7 @@
/node_modules /node_modules
/.pnp /.pnp
.pnp.js .pnp.js
/.idea/
# testing # testing
/coverage /coverage

View File

@ -5,6 +5,7 @@
*/ */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Modal, Form, Input, Select, Button } from 'antd'; import { Modal, Form, Input, Select, Button } from 'antd';
import TemplateSelector from '../exec/task/TemplateSelector'; import TemplateSelector from '../exec/task/TemplateSelector';
import { LinkButton, ACEditor } from 'components'; import { LinkButton, ACEditor } from 'components';
@ -22,7 +23,7 @@ export default observer(function () {
const [showTmp, setShowTmp] = useState(false); const [showTmp, setShowTmp] = useState(false);
useEffect(() => { useEffect(() => {
const {type, addr} = store.record; const { type, addr } = store.record;
if (type === '1' && addr) { if (type === '1' && addr) {
store.record.sitePrefix = addr.startsWith('http://') ? 'http://' : 'https://'; store.record.sitePrefix = addr.startsWith('http://') ? 'http://' : 'https://';
store.record.domain = store.record.addr.replace(store.record.sitePrefix, '') store.record.domain = store.record.addr.replace(store.record.sitePrefix, '')
@ -31,14 +32,14 @@ export default observer(function () {
function handleTest() { function handleTest() {
setLoading(true) setLoading(true)
const {type, sitePrefix, domain} = store.record; const { type, sitePrefix, domain } = store.record;
if (type === '1') store.record.addr = sitePrefix + domain; if (type === '1') store.record.addr = sitePrefix + domain;
http.post('/api/monitor/test/', store.record, {timeout: 120000}) http.post('/api/monitor/test/', store.record, { timeout: 120000 })
.then(res => { .then(res => {
if (res.is_success) { if (res.is_success) {
Modal.success({content: res.message}) Modal.success({ content: res.message })
} else { } else {
Modal.warning({content: res.message}) Modal.warning({ content: res.message })
} }
}) })
.finally(() => setLoading(false)) .finally(() => setLoading(false))
@ -48,39 +49,59 @@ export default observer(function () {
store.record.type = v; store.record.type = v;
store.record.addr = undefined; store.record.addr = undefined;
store.record.extra = undefined; store.record.extra = undefined;
};
function handleAddGroup() {
Modal.confirm({
icon: <ExclamationCircleOutlined />,
title: '添加监控分组',
content: (
<Form layout="vertical" style={{ marginTop: 24 }}>
<Form.Item required label="监控分组">
<Input onChange={e => store.record.group = e.target.value} />
</Form.Item>
</Form>
),
onOk: () => {
if (store.record.group) {
store.groups.push(store.record.group);
}
},
})
} }
const SiteBefore = ( const SiteBefore = (
<Select style={{width: 90}} value={store.record.sitePrefix} onChange={v => store.record.sitePrefix = v}> <Select style={{ width: 90 }} value={store.record.sitePrefix} onChange={v => store.record.sitePrefix = v}>
<Select.Option value="http://">http://</Select.Option> <Select.Option value="http://">http://</Select.Option>
<Select.Option value="https://">https://</Select.Option> <Select.Option value="https://">https://</Select.Option>
</Select> </Select>
) )
function canNext() { function canNext() {
const {type, addr, extra, domain} = store.record; const { type, addr, extra, domain, group } = store.record;
if (type === '1') { if (type === '1') {
return name && domain return name && domain && group
} else if (type === '5') { } else if (type === '5') {
return name && addr return name && addr && group
} else { } else {
return name && addr && extra return name && addr && extra && group
} }
} }
function toNext() { function toNext() {
store.page += 1; store.page += 1;
const {type, sitePrefix, domain} = store.record; const { type, sitePrefix, domain } = store.record;
if (type === '1') store.record.addr = sitePrefix + domain; if (type === '1') store.record.addr = sitePrefix + domain;
} }
function getStyle(t) { function getStyle(t) {
return t.includes(store.record.type) ? {display: 'flex'} : {display: 'none'} return t.includes(store.record.type) ? { display: 'flex' } : { display: 'none' }
} }
const {name, desc, type, addr, extra, domain} = store.record; const { name, desc, type, addr, extra, domain, group } = store.record;
return ( return (
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}> <Form labelCol={{ span: 6 }} wrapperCol={{ span: 14 }}>
<Form.Item label="监控类型" help={helpMap[type]}> <Form.Item label="监控类型" help={helpMap[type]}>
<Select placeholder="请选择监控类型" value={type} onChange={handleChangeType}> <Select placeholder="请选择监控类型" value={type} onChange={handleChangeType}>
<Select.Option value="1">站点检测</Select.Option> <Select.Option value="1">站点检测</Select.Option>
@ -90,18 +111,30 @@ export default observer(function () {
<Select.Option value="4">自定义脚本</Select.Option> <Select.Option value="4">自定义脚本</Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item required label="任务名称"> <Form.Item required label="监控分组" style={{ marginBottom: 0 }}>
<Input value={name} onChange={e => store.record.name = e.target.value} placeholder="请输入任务名称"/> <Form.Item style={{ display: 'inline-block', width: 'calc(75%)', marginRight: 8 }}>
<Select value={group} placeholder="请选择监控分组" onChange={v => store.record.group = v}>
{store.groups.map(item => (
<Select.Option value={item} key={item}>{item}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item style={{ display: 'inline-block', width: 'calc(25%-8px)' }}>
<Button type="link" onClick={handleAddGroup}>添加分组</Button>
</Form.Item>
</Form.Item>
<Form.Item required label="监控名称">
<Input value={name} onChange={e => store.record.name = e.target.value} placeholder="请输入监控名称" />
</Form.Item> </Form.Item>
<Form.Item required label="监控地址" style={getStyle(['1'])}> <Form.Item required label="监控地址" style={getStyle(['1'])}>
<Input <Input
value={domain} value={domain}
addonBefore={SiteBefore} addonBefore={SiteBefore}
placeholder="请输入监控地址" placeholder="请输入监控地址"
onChange={e => store.record.domain = e.target.value}/> onChange={e => store.record.domain = e.target.value} />
</Form.Item> </Form.Item>
<Form.Item required label="监控地址" style={getStyle(['2', '5'])}> <Form.Item required label="监控地址" style={getStyle(['2', '5'])}>
<Input value={addr} placeholder="请输入监控地址IP/域名)" onChange={e => store.record.addr = e.target.value}/> <Input value={addr} placeholder="请输入监控地址IP/域名)" onChange={e => store.record.addr = e.target.value} />
</Form.Item> </Form.Item>
<Form.Item required label="监控主机" style={getStyle(['3', '4'])}> <Form.Item required label="监控主机" style={getStyle(['3', '4'])}>
<Select <Select
@ -119,10 +152,10 @@ export default observer(function () {
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item required label="检测端口" style={getStyle(['2'])}> <Form.Item required label="检测端口" style={getStyle(['2'])}>
<Input value={extra} placeholder="请输入端口号" onChange={e => store.record.extra = e.target.value}/> <Input value={extra} placeholder="请输入端口号" onChange={e => store.record.extra = e.target.value} />
</Form.Item> </Form.Item>
<Form.Item required label="进程名称" help="执行 ps -ef 看到的进程名称。" style={getStyle(['3'])}> <Form.Item required label="进程名称" help="执行 ps -ef 看到的进程名称。" style={getStyle(['3'])}>
<Input value={extra} placeholder="请输入进程名称" onChange={e => store.record.extra = e.target.value}/> <Input value={extra} placeholder="请输入进程名称" onChange={e => store.record.extra = e.target.value} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
required required
@ -134,17 +167,17 @@ export default observer(function () {
value={extra || ''} value={extra || ''}
width="100%" width="100%"
height="200px" height="200px"
onChange={e => store.record.extra = cleanCommand(e)}/> onChange={e => store.record.extra = cleanCommand(e)} />
</Form.Item> </Form.Item>
<Form.Item label="备注信息"> <Form.Item label="备注信息">
<Input.TextArea value={desc} onChange={e => store.record.desc = e.target.value} placeholder="请输入备注信息"/> <Input.TextArea value={desc} onChange={e => store.record.desc = e.target.value} placeholder="请输入备注信息" />
</Form.Item> </Form.Item>
<Form.Item wrapperCol={{span: 14, offset: 6}} style={{marginTop: 12}}> <Form.Item wrapperCol={{ span: 14, offset: 6 }} style={{ marginTop: 12 }}>
<Button disabled={!canNext()} type="primary" onClick={toNext}>下一步</Button> <Button disabled={!canNext()} type="primary" onClick={toNext}>下一步</Button>
<Button disabled={false} type="link" loading={loading} onClick={handleTest}>执行测试</Button> <Button disabled={false} type="link" loading={loading} onClick={handleTest}>执行测试</Button>
</Form.Item> </Form.Item>
{showTmp && <TemplateSelector onOk={v => store.record.extra += v} onCancel={() => setShowTmp(false)}/>} {showTmp && <TemplateSelector onOk={v => store.record.extra += v} onCancel={() => setShowTmp(false)} />}
</Form> </Form>
) )
}) })

View File

@ -34,7 +34,7 @@ export default observer(function () {
function handleSubmit() { function handleSubmit() {
setLoading(true) setLoading(true)
const formData = form.getFieldsValue(); const formData = form.getFieldsValue();
Object.assign(formData, lds.pick(store.record, ['id', 'name', 'desc', 'addr', 'extra', 'type'])) Object.assign(formData, lds.pick(store.record, ['id', 'name', 'desc', 'addr', 'extra', 'type', 'group']))
formData['id'] = store.record.id; formData['id'] = store.record.id;
http.post('/api/monitor/', formData) http.post('/api/monitor/', formData)
.then(() => { .then(() => {

View File

@ -96,7 +96,8 @@ class ComTable extends React.Component {
showTotal: total => `${total}`, showTotal: total => `${total}`,
pageSizeOptions: ['10', '20', '50', '100'] pageSizeOptions: ['10', '20', '50', '100']
}}> }}>
<Table.Column title="任务名称" dataIndex="name"/> <Table.Column title="监控分组" dataIndex="group" />
<Table.Column title="监控名称" dataIndex="name"/>
<Table.Column title="类型" dataIndex="type_alias"/> <Table.Column title="类型" dataIndex="type_alias"/>
<Table.Column ellipsis title="地址" render={info => { <Table.Column ellipsis title="地址" render={info => {
if ('34'.includes(info.type)) { if ('34'.includes(info.type)) {

View File

@ -19,7 +19,14 @@ export default observer(function () {
<Breadcrumb.Item>监控中心</Breadcrumb.Item> <Breadcrumb.Item>监控中心</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
<SearchForm> <SearchForm>
<SearchForm.Item span={7} title="任务名称"> <SearchForm.Item span={7} title="监控分组">
<Select allowClear value={store.f_group} onChange={v => store.f_group = v} placeholder="请选择">
{store.groups.map(item => (
<Select.Option value={item} key={item}>{item}</Select.Option>
))}
</Select>
</SearchForm.Item>
<SearchForm.Item span={7} title="监控名称">
<Input allowClear value={store.f_name} onChange={e => store.f_name = e.target.value} placeholder="请输入"/> <Input allowClear value={store.f_name} onChange={e => store.f_name = e.target.value} placeholder="请输入"/>
</SearchForm.Item> </SearchForm.Item>
<SearchForm.Item span={7} title="检测类型"> <SearchForm.Item span={7} title="检测类型">

View File

@ -12,6 +12,7 @@ class Store {
@observable records = []; @observable records = [];
@observable record = {}; @observable record = {};
@observable types = []; @observable types = [];
@observable groups = [];
@observable page = 0; @observable page = 0;
@observable isFetching = false; @observable isFetching = false;
@observable formVisible = false; @observable formVisible = false;
@ -20,12 +21,14 @@ class Store {
@observable f_type; @observable f_type;
@observable f_status; @observable f_status;
@observable f_active = ''; @observable f_active = '';
@observable f_group;
@computed get dataSource() { @computed get dataSource() {
let records = this.records; let records = this.records;
if (this.f_active) records = records.filter(x => x.is_active === (this.f_active === '1')); if (this.f_active) records = records.filter(x => x.is_active === (this.f_active === '1'));
if (this.f_name) records = records.filter(x => x.name.toLowerCase().includes(this.f_name.toLowerCase())); if (this.f_name) records = records.filter(x => x.name.toLowerCase().includes(this.f_name.toLowerCase()));
if (this.f_type) records = records.filter(x => x.type_alias === this.f_type); if (this.f_type) records = records.filter(x => x.type_alias === this.f_type);
if (this.f_group) records = records.filter(x => x.group === this.f_group);
if (this.f_status !== undefined) { if (this.f_status !== undefined) {
if (this.f_status === -1) { if (this.f_status === -1) {
records = records.filter(x => x.is_active && !x.latest_status_alias); records = records.filter(x => x.is_active && !x.latest_status_alias);
@ -39,16 +42,17 @@ class Store {
fetchRecords = () => { fetchRecords = () => {
this.isFetching = true; this.isFetching = true;
http.get('/api/monitor/') http.get('/api/monitor/')
.then(res => { .then(({groups, detections}) => {
const tmp = new Set(); const tmp = new Set();
res.map(item => { detections.map(item => {
tmp.add(item['type_alias']); tmp.add(item['type_alias']);
const value = item['latest_run_time']; const value = item['latest_run_time'];
item['latest_run_time_alias'] = value ? moment(value).fromNow() : null; item['latest_run_time_alias'] = value ? moment(value).fromNow() : null;
return null return null
}); });
this.types = Array.from(tmp); this.types = Array.from(tmp);
this.records = res this.records = detections;
this.groups = groups;
}) })
.finally(() => this.isFetching = false) .finally(() => this.isFetching = false)
}; };