mirror of https://github.com/openspug/spug
A 添加工作台最近30天登录记录
parent
42f3ce1bf2
commit
b283ff4c16
|
@ -0,0 +1,19 @@
|
|||
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
# Copyright: (c) <spug.dev@gmail.com>
|
||||
# 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)
|
|
@ -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',)
|
||||
|
|
|
@ -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())
|
||||
]
|
||||
|
|
|
@ -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()
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 => (
|
||||
<Action>
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* 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 (
|
||||
<Card loading={loading} title="应用部署">
|
||||
<Chart height={300} data={dv} scale={cols} padding={[-30, 0, -30, 50]} forceFit>
|
||||
<Coord type={"theta"} radius={0.75} innerRadius={0.6}/>
|
||||
<Axis name="percent"/>
|
||||
<Tooltip showTitle={false}/>
|
||||
<Guide>
|
||||
<Guide.Html
|
||||
position={["50%", "50%"]}
|
||||
html={`<div style="color:#8c8c8c;font-size:1.16em;text-align: center;width: 10em;">主机<br><span style="color:#262626;font-size:2.5em">${host}</span>台</div>`}
|
||||
alignX="middle"
|
||||
alignY="middle"
|
||||
/>
|
||||
</Guide>
|
||||
<Geom
|
||||
type="intervalStack"
|
||||
position="percent"
|
||||
color="name"
|
||||
tooltip={[
|
||||
"name*count",
|
||||
(name, count) => {
|
||||
return {
|
||||
name: name,
|
||||
value: count + '台'
|
||||
};
|
||||
}
|
||||
]}
|
||||
style={{lineWidth: 1, stroke: "#fff"}}>
|
||||
<Label
|
||||
content="percent"
|
||||
formatter={(val, item) => {
|
||||
const percent = (item.point['percent'] * 100).toFixed(2) + '%';
|
||||
return item.point.name + ": " + percent;
|
||||
}}
|
||||
/>
|
||||
</Geom>
|
||||
</Chart>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* 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 (
|
||||
<Card loading={loading} title="最近30天登录" bodyStyle={{paddingTop: 0}} extra={(
|
||||
<div>
|
||||
{name !== null && <Tag closable color="#1890ff" onClose={() => setName(null)}>{name}</Tag>}
|
||||
{ip !== null && <Tag closable color="#1890ff" onClose={() => setIp(null)}>{ip}</Tag>}
|
||||
</div>
|
||||
)}>
|
||||
<List style={{height: 329, overflow: 'scroll'}} dataSource={dataSource} renderItem={item => (
|
||||
<List.Item>
|
||||
<span>{item.created_at}</span>
|
||||
<span className={styles.spanText} onClick={() => setName(item.nickname)}>{item.nickname}</span>
|
||||
<span>通过</span>
|
||||
<span className={styles.spanText} onClick={() => setIp(item.ip)}>{item.ip}</span>
|
||||
<span>登录</span>
|
||||
</List.Item>
|
||||
)}/>
|
||||
</Card>
|
||||
)
|
||||
}
|
|
@ -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 {
|
|||
<RequestTop/>
|
||||
</Col>
|
||||
<Col span={10} offset={1}>
|
||||
<DeployPie/>
|
||||
<LoginActive/>
|
||||
</Col>
|
||||
</Row>
|
||||
</AuthDiv>
|
||||
|
|
|
@ -16,4 +16,10 @@
|
|||
|
||||
.spanButton:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.spanText {
|
||||
cursor: pointer;
|
||||
color: #1890ff;
|
||||
padding: 0 4px;
|
||||
}
|
Loading…
Reference in New Issue