Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

防爆增加前端手动解锁的功能 #345

Merged
merged 2 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions server/admin/lockmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,25 @@ func UnlockUser(w http.ResponseWriter, r *http.Request) {
lm.mu.Lock()
defer lm.mu.Unlock()

lm.Unlock(lockinfo.State)
// 根据用户名和IP查找锁定状态
var state *LockState
switch {
case lockinfo.IP == "" && lockinfo.Username != "":
state = lm.userLocks[lockinfo.Username] // 全局用户锁定
case lockinfo.Username != "" && lockinfo.IP != "":
if userIPMap, exists := lm.ipUserLocks[lockinfo.Username]; exists {
state = userIPMap[lockinfo.IP] // 单用户 IP 锁定
}
default:
state = lm.ipLocks[lockinfo.IP] // 全局 IP 锁定
}

if state == nil || !state.Locked {
RespError(w, RespInternalErr, fmt.Errorf("锁定状态未找到或已解锁"))
return
}

lm.Unlock(state)
base.Info("解锁成功:", lockinfo.Description, lockinfo.Username, lockinfo.IP)

RespSucess(w, "解锁成功!")
Expand All @@ -110,9 +128,9 @@ func (lm *LockManager) GetLocksInfo() []LockInfo {
defer lm.mu.Unlock()

for ip, state := range lm.ipLocks {
if state.Locked {
if base.Cfg.MaxGlobalIPBanCount > 0 && state.Locked {
info := LockInfo{
Description: "全局 IP 锁定",
Description: "全局IP锁定",
Username: "",
IP: ip,
State: &LockState{
Expand All @@ -127,7 +145,7 @@ func (lm *LockManager) GetLocksInfo() []LockInfo {
}

for username, state := range lm.userLocks {
if state.Locked {
if base.Cfg.MaxGlobalUserBanCount > 0 && state.Locked {
info := LockInfo{
Description: "全局用户锁定",
Username: username,
Expand All @@ -145,9 +163,9 @@ func (lm *LockManager) GetLocksInfo() []LockInfo {

for username, ipStates := range lm.ipUserLocks {
for ip, state := range ipStates {
if state.Locked {
if base.Cfg.MaxBanCount > 0 && state.Locked {
info := LockInfo{
Description: "单用户 IP 锁定",
Description: "单用户IP锁定",
Username: username,
IP: ip,
State: &LockState{
Expand Down Expand Up @@ -205,7 +223,7 @@ func (lm *LockManager) IsWhitelisted(ip string) bool {
}

func (lm *LockManager) StartCleanupTicker() {
lm.cleanupTicker = time.NewTicker(5 * time.Minute)
lm.cleanupTicker = time.NewTicker(1 * time.Minute)
go func() {
for range lm.cleanupTicker.C {
lm.CleanupExpiredLocks()
Expand All @@ -220,20 +238,23 @@ func (lm *LockManager) CleanupExpiredLocks() {
defer lm.mu.Unlock()

for ip, state := range lm.ipLocks {
if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
if !lm.CheckLockState(state, now, base.Cfg.GlobalIPLockTime) ||
now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
delete(lm.ipLocks, ip)
}
}

for user, state := range lm.userLocks {
if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
if !lm.CheckLockState(state, now, base.Cfg.GlobalUserLockTime) ||
now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
delete(lm.userLocks, user)
}
}

for user, ipMap := range lm.ipUserLocks {
for ip, state := range ipMap {
if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
if !lm.CheckLockState(state, now, base.Cfg.LockTime) ||
now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
delete(ipMap, ip)
if len(ipMap) == 0 {
delete(lm.ipUserLocks, user)
Expand Down
1 change: 1 addition & 0 deletions server/admin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func StartAdmin() {

r.HandleFunc("/statsinfo/list", StatsInfoList)
r.HandleFunc("/locksinfo/list", GetLocksInfo)
r.HandleFunc("/locksinfo/unlok", UnlockUser)

// pprof
if base.Cfg.Pprof {
Expand Down
15 changes: 4 additions & 11 deletions web/src/layout/LayoutAside.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,9 @@

<!--<div class="layout-aside" :style="aside_style">-->

<el-menu :collapse="!is_active"
:default-active="route_path"
:style="is_active?'width:200px':''"
router
class="layout-menu"
:collapse-transition="false"

background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu :collapse="!is_active" :default-active="route_path" :style="is_active ? 'width:200px' : ''" router
class="layout-menu" :collapse-transition="false" background-color="#545c64" text-color="#fff"
active-text-color="#ffd04b">
<el-menu-item index="/admin/home">
<i class="el-icon-s-home"></i>
<span slot="title">首页</span>
Expand All @@ -44,6 +36,7 @@
<el-menu-item index="/admin/user/list">用户列表</el-menu-item>
<el-menu-item index="/admin/user/policy">用户策略</el-menu-item>
<el-menu-item index="/admin/user/online">在线用户</el-menu-item>
<el-menu-item index="/admin/user/lockmanager">锁定管理</el-menu-item>
<el-menu-item index="/admin/user/ip_map">IP映射</el-menu-item>
</el-submenu>

Expand Down
109 changes: 109 additions & 0 deletions web/src/pages/user/LockManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<template>
<div id="lock-manager">
<el-card>
<div slot="header">
<el-button type="primary" @click="getLocks">刷新信息</el-button>
</div>
<el-table :data="locksInfo" style="width: 100%" border>
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column prop="username" label="用户名"></el-table-column>
<el-table-column prop="ip" label="IP地址"></el-table-column>
<el-table-column prop="state.locked" label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.state.locked ? 'danger' : 'success'">
{{ scope.row.state.locked ? '已锁定' : '未锁定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="state.attempts" label="失败次数"></el-table-column>
<el-table-column prop="state.lock_time" label="锁定截止时间">
<template slot-scope="scope">
{{ formatDate(scope.row.state.lock_time) }}
</template>
</el-table-column>
<el-table-column prop="state.lastAttempt" label="最后尝试时间">
<template slot-scope="scope">
{{ formatDate(scope.row.state.lastAttempt) }}
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<div class="button">
<el-button size="small" type="danger" @click="unlock(scope.row)">
解锁
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>

<script>
import axios from 'axios';

export default {
name: 'LockManager',
data() {
return {
locksInfo: []
};
},
methods: {
getLocks() {
axios.get('/locksinfo/list')
.then(response => {
this.locksInfo = response.data.data;
})
.catch(error => {
console.error('Failed to get locks info:', error);
this.$message.error('无法获取锁信息,请稍后再试。');
});
},
unlock(lock) {
const lockInfo = {
state: { locked: false },
username: lock.username,
ip: lock.ip,
description: lock.description
};

axios.post('/locksinfo/unlok', lockInfo)
.then(() => {
this.$message.success('解锁成功!');
this.getLocks();
})
.catch(error => {
console.error('Failed to unlock:', error);
this.$message.error('解锁失败,请稍后再试。');
});
},
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(date);
}
},
created() {
this.getLocks();
}
};
</script>

<style scoped>
.button {
display: flex;
justify-content: center;
align-items: center;
}
</style>
29 changes: 15 additions & 14 deletions web/src/plugins/router.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import Vue from "vue";
import VueRouter from "vue-router";
import {getToken} from "./token";
import { getToken } from "./token";

Vue.use(VueRouter)


const routes = [
{path: '/login', component: () => import('@/pages/Login')},
{ path: '/login', component: () => import('@/pages/Login') },
{
path: '/admin',
component: () => import('@/layout/Layout'),
redirect: '/admin/home',
children: [
{path: 'home', component: () => import('@/pages/Home')},
{ path: 'home', component: () => import('@/pages/Home') },

{path: 'set/system', component: () => import('@/pages/set/System')},
{path: 'set/soft', component: () => import('@/pages/set/Soft')},
{path: 'set/other', component: () => import('@/pages/set/Other')},
{path: 'set/audit', component: () => import('@/pages/set/Audit')},
{ path: 'set/system', component: () => import('@/pages/set/System') },
{ path: 'set/soft', component: () => import('@/pages/set/Soft') },
{ path: 'set/other', component: () => import('@/pages/set/Other') },
{ path: 'set/audit', component: () => import('@/pages/set/Audit') },

{path: 'user/list', component: () => import('@/pages/user/List')},
{path: 'user/policy', component: () => import('@/pages/user/Policy')},
{path: 'user/online', component: () => import('@/pages/user/Online')},
{path: 'user/ip_map', component: () => import('@/pages/user/IpMap')},
{ path: 'user/list', component: () => import('@/pages/user/List') },
{ path: 'user/policy', component: () => import('@/pages/user/Policy') },
{ path: 'user/online', component: () => import('@/pages/user/Online') },
{ path: 'user/ip_map', component: () => import('@/pages/user/IpMap') },
{ path: 'user/lockmanager', component: () => import('@/pages/user/LockManager') },

{path: 'group/list', component: () => import('@/pages/group/List')},
{ path: 'group/list', component: () => import('@/pages/group/List') },

],
},

{path: '*', redirect: '/admin/home'},
{ path: '*', redirect: '/admin/home' },
]

// 3. 创建 router 实例,然后传 `routes` 配置
Expand Down Expand Up @@ -64,7 +65,7 @@ router.beforeEach((to, from, next) => {
}

if (to.path === "/login") {
next({path: '/admin/home'});
next({ path: '/admin/home' });
return;
}

Expand Down
Loading