feat: improve login UX and add admin login lock reset

- Translate all login error messages to Chinese (network errors, HTTP
  status codes, URL validation errors)
- Add password visibility toggle (eye icon) to login form
- Add live countdown timer when login is locked, disable login button
  during cooldown, auto-clear error when countdown ends
- Add X button red hover feedback in server settings dialog
- Add LoginGuard.Unlock() method to clear Redis lock/failure keys
- Expose POST /admin-users/:id/reset-login-lock endpoint in ops API
- Add "重置登录保护" button in ops-web user management table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Xu Liang
2026-05-02 19:25:15 +08:00
parent 40c877ee2d
commit 6dd3bd8e9d
9 changed files with 212 additions and 23 deletions
+31
View File
@@ -138,6 +138,18 @@
</a-button>
</a-tooltip>
<a-tooltip title="重置登录保护">
<a-popconfirm
title="确认清除该用户的登录锁定状态?"
:ok-button-props="{ loading: resetLoginLockLoadingId === record.id }"
@confirm="resetLoginLock(record)"
>
<a-button type="text" shape="circle" size="small" class="action-btn action-unlock">
<UnlockOutlined />
</a-button>
</a-popconfirm>
</a-tooltip>
<a-tooltip :title="record.status === 'active' ? '停用用户' : '启用用户'">
<a-popconfirm
:title="record.status === 'active' ? '确认停用该用户?' : '确认启用该用户?'"
@@ -283,6 +295,7 @@ import {
PlayCircleOutlined,
ReloadOutlined,
StopOutlined,
UnlockOutlined,
} from '@ant-design/icons-vue'
import type { TableColumnsType, TablePaginationConfig } from 'ant-design-vue'
import { message } from 'ant-design-vue'
@@ -608,6 +621,20 @@ async function resetPlanUsage(row: AdminUserRow) {
}
}
const resetLoginLockLoadingId = ref<number | null>(null)
async function resetLoginLock(row: AdminUserRow) {
resetLoginLockLoadingId.value = row.id
try {
await http.post(`/admin-users/${row.id}/reset-login-lock`)
message.success('登录保护已重置,用户可立即重新登录')
} catch (error) {
if (error instanceof OpsApiError) message.error(error.detail || error.message)
} finally {
resetLoginLockLoadingId.value = null
}
}
const resetOpen = ref(false)
const resetLoading = ref(false)
const resetTarget = ref<AdminUserRow | null>(null)
@@ -806,4 +833,8 @@ onMounted(() => {
color: #94a3b8;
font-size: 12px;
}
.action-unlock :deep(.anticon) {
color: #16a34a;
}
</style>