diff --git a/spug_api/apps/home/views.py b/spug_api/apps/home/views.py index 2343cbc..45f9446 100644 --- a/spug_api/apps/home/views.py +++ b/spug_api/apps/home/views.py @@ -1,7 +1,6 @@ # Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug # Copyright: (c) # Released under the AGPL-3.0 License. -from django.db.models import F from apps.app.models import App from apps.host.models import Host from apps.schedule.models import Task diff --git a/spug_api/apps/monitor/urls.py b/spug_api/apps/monitor/urls.py index 7ff0661..4233f3e 100644 --- a/spug_api/apps/monitor/urls.py +++ b/spug_api/apps/monitor/urls.py @@ -7,5 +7,6 @@ from .views import * urlpatterns = [ path('', DetectionView.as_view()), + path('overview/', get_overview), path('test/', run_test), ] diff --git a/spug_api/apps/monitor/views.py b/spug_api/apps/monitor/views.py index 400fe9b..ed0cae7 100644 --- a/spug_api/apps/monitor/views.py +++ b/spug_api/apps/monitor/views.py @@ -8,6 +8,7 @@ from libs import json_response, JsonParser, Argument, human_datetime, auth from apps.monitor.models import Detection from apps.monitor.executors import dispatch from apps.setting.utils import AppSetting +from datetime import datetime import json @@ -104,3 +105,36 @@ def run_test(request): is_success, message = dispatch(form.type, form.targets[0], form.extra) return json_response({'is_success': is_success, 'message': message}) return json_response(error=error) + + +@auth('monitor.monitor.view') +def get_overview(request): + response = [] + rds = get_redis_connection() + for item in Detection.objects.all(): + data = {} + for key in json.loads(item.targets): + data[key] = { + 'id': f'{item.id}_{key}', + 'group': item.group, + 'name': item.name, + 'type': item.get_type_display(), + 'target': key, + 'desc': item.desc, + 'status': '1' if item.is_active else '0', + 'latest_run_time': item.latest_run_time, + } + if item.is_active: + for key, val in rds.hgetall(f'spug:det:{item.id}').items(): + prefix, key = key.decode().split('_', 1) + if key in data: + val = int(val) + if prefix == 'c': + if data[key]['status'] == '1': + data[key]['status'] = '2' + data[key]['count'] = val + elif prefix == 't': + date = datetime.fromtimestamp(val).strftime('%Y-%m-%d %H:%M:%S') + data[key].update(status='3', notified_at=date) + response.extend(list(data.values())) + return json_response(response) diff --git a/spug_web/src/pages/monitor/Form.js b/spug_web/src/pages/monitor/Form.js index 7a48a0f..e540c43 100644 --- a/spug_web/src/pages/monitor/Form.js +++ b/spug_web/src/pages/monitor/Form.js @@ -9,7 +9,7 @@ import { Modal, Steps } from 'antd'; import Step1 from './Step1'; import Step2 from './Step2'; import store from './store'; -import styles from './index.module.css'; +import styles from './index.module.less'; import groupStore from '../alarm/group/store'; export default observer(function () { diff --git a/spug_web/src/pages/monitor/MonitorCard.js b/spug_web/src/pages/monitor/MonitorCard.js new file mode 100644 index 0000000..b6284dd --- /dev/null +++ b/spug_web/src/pages/monitor/MonitorCard.js @@ -0,0 +1,158 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React, { useState, useEffect } from 'react'; +import { observer } from 'mobx-react'; +import { Card, Input, Select, Space, Tooltip, Spin, message } from 'antd'; +import { FrownOutlined, RedoOutlined, SyncOutlined } from '@ant-design/icons'; +import styles from './index.module.less'; +import { http, includes } from 'libs'; +import moment from 'moment'; +import store from './store'; + +const ColorMap = { + '0': '#cccccc', + '1': '#009400', + '2': '#ffba00', + '3': '#fa383e', +} + +const StatusMap = { + '1': '正常', + '2': '警告', + '3': '紧急', + '0': '禁用', +} + +let AutoReload = null + +function CardItem(props) { + const {status, type, desc, name, target, latest_run_time} = props.data + const title = ( +
+
类型: {type}
+
名称: {name}
+
描述: {desc}
+
目标: {target}
+
状态: {StatusMap[status]}
+ {latest_run_time ?
更新: {latest_run_time}
: null} +
+ ) + return ( + +
+ {moment(latest_run_time).fromNow()} +
+
+ ) +} + +function MonitorCard() { + const [fetching, setFetching] = useState(true); + const [autoReload, setAutoReload] = useState(false); + const [status, setStatus] = useState(); + const [records, setRecords] = useState([]); + const [dataSource, setDataSource] = useState([]); + + useEffect(() => { + fetchRecords() + + return () => AutoReload = null + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + function fetchRecords() { + if (AutoReload === false) return + setFetching(true); + return http.get('/api/monitor/overview/') + .then(res => setRecords(res)) + .finally(() => { + setFetching(false) + if (AutoReload) setTimeout(fetchRecords, 5000) + }) + } + + useEffect(() => { + const data = records.filter(x => + (!store.f_type || x.type === store.f_type) && + (!store.f_group || x.group === store.f_group) && + (!store.f_name || includes(x.name, store.f_name)) + ) + setDataSource(data) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [records, store.f_type, store.f_group, store.f_name]) + + function handleAutoReload() { + AutoReload = !autoReload + message.info(autoReload ? '关闭自动刷新' : '开启自动刷新') + if (!autoReload) fetchRecords() + setAutoReload(!autoReload) + } + + const filteredRecords = dataSource.filter(x => !status || x.status === status) + return ( + + +
分组:
+ +
+ +
类型:
+ +
+ +
名称:
+ store.f_name = e.target.value} placeholder="请输入"/> +
+ + )}> + +
+ {Object.entries(StatusMap).map(([s, desc]) => { + const count = dataSource.filter(x => x.status === s).length; + return count ? ( +
setStatus(s === status ? '' : s)}> + {dataSource.filter(x => x.status === s).length} +
+ ) : null + })} +
+ {autoReload ? : } +
+
+ {filteredRecords.length > 0 ? ( + + {filteredRecords.map(item => ( + + ))} + + ) : ( +
+ )} +
+
+ ) +} + +export default observer(MonitorCard) \ No newline at end of file diff --git a/spug_web/src/pages/monitor/index.js b/spug_web/src/pages/monitor/index.js index 5eff7f4..f377e59 100644 --- a/spug_web/src/pages/monitor/index.js +++ b/spug_web/src/pages/monitor/index.js @@ -5,10 +5,10 @@ */ import React from 'react'; import { observer } from 'mobx-react'; -import { Input, Select } from 'antd'; -import { SearchForm, AuthDiv, Breadcrumb } from 'components'; +import { AuthDiv, Breadcrumb } from 'components'; import ComTable from './Table'; import ComForm from './Form'; +import MonitorCard from './MonitorCard'; import store from './store'; export default observer(function () { @@ -18,23 +18,7 @@ export default observer(function () { 首页 监控中心 - - - - - - store.f_name = e.target.value} placeholder="请输入"/> - - - - - + {store.formVisible && } diff --git a/spug_web/src/pages/monitor/index.module.css b/spug_web/src/pages/monitor/index.module.css deleted file mode 100644 index 9c95830..0000000 --- a/spug_web/src/pages/monitor/index.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.steps { - width: 520px; - margin: 0 auto 30px; -} diff --git a/spug_web/src/pages/monitor/index.module.less b/spug_web/src/pages/monitor/index.module.less new file mode 100644 index 0000000..1814627 --- /dev/null +++ b/spug_web/src/pages/monitor/index.module.less @@ -0,0 +1,59 @@ +.steps { + width: 520px; + margin: 0 auto 30px; +} + +.card { + display: flex; + justify-content: center; + align-items: center; + width: 60px; + height: 50px; + font-size: 12px; + color: #fff; + border-radius: 2px; +} + +.header { + display: flex; + justify-content: flex-end; + margin-bottom: 12px; + margin-top: -6px; + + .item { + display: flex; + justify-content: center; + align-items: center; + width: 30px; + height: 26px; + margin-left: 12px; + border-radius: 2px; + color: #fff; + font-weight: bold; + cursor: pointer; + } + + .authLoad { + display: flex; + justify-content: center; + align-items: center; + width: 30px; + height: 26px; + color: #fff; + margin-left: 24px; + border-radius: 2px; + } +} + +.notMatch { + display: flex; + justify-content: center; + align-items: center; + color: #999; + + :global(.anticon) { + font-size: 18px; + margin-right: 8px; + } +} +