diff --git a/spug_api/apps/account/history.py b/spug_api/apps/account/history.py new file mode 100644 index 0000000..558b4bc --- /dev/null +++ b/spug_api/apps/account/history.py @@ -0,0 +1,19 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the AGPL-3.0 License. +from django.views.generic import View +from django.db.models import F +from libs import json_response +from apps.account.models import History + + +class HistoryView(View): + def get(self, request): + histories = [] + for item in History.objects.annotate(nickname=F('user__nickname')): + histories.append({ + 'nickname': item.nickname, + 'ip': item.ip, + 'created_at': item.created_at.split('-', 1)[1], + }) + return json_response(histories) diff --git a/spug_api/apps/account/models.py b/spug_api/apps/account/models.py index bd2499f..f1e5d04 100644 --- a/spug_api/apps/account/models.py +++ b/spug_api/apps/account/models.py @@ -110,3 +110,13 @@ class Role(models.Model, ModelMixin): class Meta: db_table = 'roles' ordering = ('-id',) + + +class History(models.Model, ModelMixin): + user = models.ForeignKey(User, on_delete=models.CASCADE) + ip = models.CharField(max_length=50) + created_at = models.CharField(max_length=20, default=human_datetime) + + class Meta: + db_table = 'login_histories' + ordering = ('-id',) diff --git a/spug_api/apps/account/urls.py b/spug_api/apps/account/urls.py index 9458196..cad7bc1 100644 --- a/spug_api/apps/account/urls.py +++ b/spug_api/apps/account/urls.py @@ -4,11 +4,13 @@ from django.conf.urls import url from apps.account.views import * +from apps.account.history import * urlpatterns = [ - url(r'^login/', login), - url(r'^logout/', logout), + url(r'^login/$', login), + url(r'^logout/$', logout), url(r'^user/$', UserView.as_view()), url(r'^role/$', RoleView.as_view()), url(r'^self/$', SelfView.as_view()), + url(r'^login/history/$', HistoryView.as_view()) ] diff --git a/spug_api/apps/account/utils.py b/spug_api/apps/account/utils.py new file mode 100644 index 0000000..c6b2a5c --- /dev/null +++ b/spug_api/apps/account/utils.py @@ -0,0 +1,9 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Released under the AGPL-3.0 License. +from apps.account.models import History +from datetime import datetime, timedelta + + +def auto_clean_login_history(): + date = datetime.now() - timedelta(days=30) + History.objects.filter(created_at__lt=date.strftime('%Y-%m-%d')).delete() diff --git a/spug_api/apps/account/views.py b/spug_api/apps/account/views.py index 26a7391..6b1db70 100644 --- a/spug_api/apps/account/views.py +++ b/spug_api/apps/account/views.py @@ -6,7 +6,7 @@ from django.views.generic import View from django.db.models import F from libs import JsonParser, Argument, human_datetime, json_response from libs.utils import get_request_real_ip -from apps.account.models import User, Role +from apps.account.models import User, Role, History from apps.setting.models import Setting from libs.ldap import LDAP import ipaddress @@ -198,6 +198,7 @@ def handle_user_info(user, x_real_ip): user.last_login = human_datetime() user.last_ip = x_real_ip user.save() + History.objects.create(user=user, ip=x_real_ip) return json_response({ 'access_token': user.access_token, 'nickname': user.nickname, diff --git a/spug_api/apps/alarm/utils.py b/spug_api/apps/alarm/utils.py index c36cfdd..56eb5f9 100644 --- a/spug_api/apps/alarm/utils.py +++ b/spug_api/apps/alarm/utils.py @@ -4,6 +4,6 @@ from apps.alarm.models import Alarm from datetime import datetime, timedelta -def auto_clean_records(): +def auto_clean_alarm_records(): date = datetime.now() - timedelta(days=30) Alarm.objects.filter(created_at__lt=date.strftime('%Y-%m-%d')).delete() diff --git a/spug_api/apps/home/views.py b/spug_api/apps/home/views.py index 7766efd..06aa550 100644 --- a/spug_api/apps/home/views.py +++ b/spug_api/apps/home/views.py @@ -15,9 +15,16 @@ import json def get_statistic(request): + if request.user.is_supper: + app = App.objects.count() + host = Host.objects.filter(deleted_at__isnull=True).count() + else: + deploy_perms, host_perms = request.user.deploy_perms, request.user.host_perms + app = App.objects.filter(id__in=deploy_perms['apps']).count() + host = Host.objects.filter(id__in=host_perms, deleted_at__isnull=True).count() data = { - 'app': App.objects.count(), - 'host': Host.objects.filter(deleted_at__isnull=True).count(), + 'app': app, + 'host': host, 'task': Task.objects.count(), 'detection': Detection.objects.count() } diff --git a/spug_api/apps/schedule/scheduler.py b/spug_api/apps/schedule/scheduler.py index a217f1a..99dbc4c 100644 --- a/spug_api/apps/schedule/scheduler.py +++ b/spug_api/apps/schedule/scheduler.py @@ -15,7 +15,8 @@ from apps.schedule.utils import send_fail_notify from apps.notify.models import Notify from apps.schedule.executors import dispatch from apps.schedule.utils import auto_clean_schedule_history -from apps.alarm.utils import auto_clean_records +from apps.alarm.utils import auto_clean_alarm_records +from apps.account.utils import auto_clean_login_history from apps.deploy.utils import auto_update_status from django.conf import settings from libs import AttrDict, human_datetime @@ -88,8 +89,9 @@ class Scheduler: send_fail_notify(obj) def _init_builtin_jobs(self): - self.scheduler.add_job(auto_clean_records, 'cron', hour=0, minute=0) - self.scheduler.add_job(auto_clean_schedule_history, 'cron', hour=0, minute=0) + self.scheduler.add_job(auto_clean_alarm_records, 'cron', hour=0, minute=1) + self.scheduler.add_job(auto_clean_login_history, 'cron', hour=0, minute=2) + self.scheduler.add_job(auto_clean_schedule_history, 'cron', hour=0, minute=3) self.scheduler.add_job(auto_update_status, 'interval', minutes=5) def _init(self): diff --git a/spug_web/src/pages/deploy/app/Table.js b/spug_web/src/pages/deploy/app/Table.js index 95d4a99..f14989a 100644 --- a/spug_web/src/pages/deploy/app/Table.js +++ b/spug_web/src/pages/deploy/app/Table.js @@ -45,6 +45,7 @@ class ComTable extends React.Component { ellipsis: true }, { title: '操作', + width: 260, className: hasPermission('deploy.app.edit|deploy.app.del') ? null : 'none', render: info => ( diff --git a/spug_web/src/pages/home/DeployPie.js b/spug_web/src/pages/home/DeployPie.js deleted file mode 100644 index 076bd66..0000000 --- a/spug_web/src/pages/home/DeployPie.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug - * Copyright (c) - * Released under the AGPL-3.0 License. - */ -import React from 'react'; -import { Card } from 'antd'; -import { Chart, Geom, Axis, Tooltip, Coord, Guide, Label } from 'bizcharts'; -import DataSet from "@antv/data-set"; -import { http } from 'libs'; - -export default class AlarmTrend extends React.Component { - constructor(props) { - super(props); - this.state = { - loading: true, - host: 0, - res: [] - }; - } - - componentDidMount() { - http.get('/api/home/deploy/') - .then(res => this.setState(res)) - .finally(() => this.setState({loading: false})) - } - - render() { - const {res, host, loading} = this.state; - const dv = new DataSet.DataView(); - dv.source(res).transform({ - type: "percent", - field: "count", - dimension: "name", - as: "percent" - }); - const cols = { - percent: { - formatter: val => { - val = val * 100 + "%"; - return val; - } - } - }; - return ( - - - - - - - 主机
${host}台`} - alignX="middle" - alignY="middle" - /> -
- { - return { - name: name, - value: count + '台' - }; - } - ]} - style={{lineWidth: 1, stroke: "#fff"}}> - -
-
- ) - } -} diff --git a/spug_web/src/pages/home/LoginActive.js b/spug_web/src/pages/home/LoginActive.js new file mode 100644 index 0000000..5362b60 --- /dev/null +++ b/spug_web/src/pages/home/LoginActive.js @@ -0,0 +1,49 @@ +/** + * 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 { Card, List, Tag } from 'antd'; +import { http } from 'libs'; +import styles from './index.module.css'; + +export default function () { + const [name, setName] = useState(null); + const [ip, setIp] = useState(null); + const [rawData, setRawData] = useState([]); + const [dataSource, setDataSource] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + http.get('/api/account/login/history/') + .then(res => setRawData(res)) + .finally(() => setLoading(false)) + }, []) + + useEffect(() => { + let data = rawData; + if (name) data = data.filter(x => x.nickname === name); + if (ip) data = data.filter(x => x.ip === ip); + setDataSource(data) + }, [name, ip, rawData]) + + return ( + + {name !== null && setName(null)}>{name}} + {ip !== null && setIp(null)}>{ip}} + + )}> + ( + + {item.created_at} + setName(item.nickname)}>{item.nickname} + 通过 + setIp(item.ip)}>{item.ip} + 登录 + + )}/> + + ) +} diff --git a/spug_web/src/pages/home/index.js b/spug_web/src/pages/home/index.js index 7cda915..1baf57c 100644 --- a/spug_web/src/pages/home/index.js +++ b/spug_web/src/pages/home/index.js @@ -9,7 +9,7 @@ import { AuthDiv } from 'components'; import StatisticsCard from './StatisticCard'; import AlarmTrend from './AlarmTrend'; import RequestTop from './RequestTop'; -import DeployPie from './DeployPie'; +import LoginActive from './LoginActive'; class HomeIndex extends React.Component { render() { @@ -22,7 +22,7 @@ class HomeIndex extends React.Component { - + diff --git a/spug_web/src/pages/home/index.module.css b/spug_web/src/pages/home/index.module.css index 57be898..283210d 100644 --- a/spug_web/src/pages/home/index.module.css +++ b/spug_web/src/pages/home/index.module.css @@ -16,4 +16,10 @@ .spanButton:hover { color: #1890ff; +} + +.spanText { + cursor: pointer; + color: #1890ff; + padding: 0 4px; } \ No newline at end of file