A 添加工作台最近30天登录记录

pull/220/head
vapao 2020-10-19 20:46:43 +08:00
parent 42f3ce1bf2
commit b283ff4c16
13 changed files with 117 additions and 96 deletions

View File

@ -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)

View File

@ -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',)

View File

@ -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())
]

View File

@ -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()

View File

@ -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,

View File

@ -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()

View File

@ -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()
}

View File

@ -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):

View File

@ -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>

View File

@ -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>
)
}
}

View File

@ -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>
)
}

View File

@ -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>

View File

@ -16,4 +16,10 @@
.spanButton:hover {
color: #1890ff;
}
.spanText {
cursor: pointer;
color: #1890ff;
padding: 0 4px;
}