feat: 添加新插件支持及多项功能改进

- 新增磁盘加密、打印审计和剪贴板管控插件支持
- 优化水印插件显示效果,支持中文及更多Unicode字符
- 改进硬件资产收集逻辑,更准确获取磁盘和显卡信息
- 增强API错误处理,添加详细日志记录
- 完善前端界面,新增插件管理页面
- 修复多个UI问题,优化页面过渡效果
- 添加环境变量覆盖配置功能
- 实现插件状态管理API
- 更新文档和变更日志
- 添加安装程序脚本支持
This commit is contained in:
iven
2026-04-10 22:21:05 +08:00
parent 3d39f0e426
commit b5333d8c93
101 changed files with 4487 additions and 661 deletions

View File

@@ -33,6 +33,43 @@ function clearAuth() {
window.location.href = '/login'
}
let refreshPromise: Promise<boolean> | null = null
async function tryRefresh(): Promise<boolean> {
// Coalesce concurrent refresh attempts
if (refreshPromise) return refreshPromise
refreshPromise = (async () => {
const refreshToken = localStorage.getItem('refresh_token')
if (!refreshToken || refreshToken.trim() === '') return false
try {
const response = await fetch(`${API_BASE}/api/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken }),
})
if (!response.ok) return false
const result = await response.json()
if (!result.success || !result.data?.access_token) return false
localStorage.setItem('token', result.data.access_token)
if (result.data.refresh_token) {
localStorage.setItem('refresh_token', result.data.refresh_token)
}
return true
} catch {
return false
} finally {
refreshPromise = null
}
})()
return refreshPromise
}
async function request<T>(
path: string,
options: RequestInit = {},
@@ -53,8 +90,28 @@ async function request<T>(
headers,
})
// Handle 401 - token expired or invalid
// Handle 401 - try refresh before giving up
if (response.status === 401) {
const refreshed = await tryRefresh()
if (refreshed) {
// Retry the original request with new token
const newToken = getToken()
headers.set('Authorization', `Bearer ${newToken}`)
const retryResponse = await fetch(`${API_BASE}${path}`, { ...options, headers })
if (retryResponse.status === 401) {
clearAuth()
throw new ApiError(401, 'UNAUTHORIZED', 'Session expired')
}
const retryContentType = retryResponse.headers.get('content-type')
if (!retryContentType || !retryContentType.includes('application/json')) {
throw new ApiError(retryResponse.status, 'NON_JSON_RESPONSE', `Server returned ${retryResponse.status}`)
}
const retryResult: ApiResult<T> = await retryResponse.json()
if (!retryResult.success) {
throw new ApiError(retryResponse.status, 'API_ERROR', retryResult.error || 'Unknown error')
}
return retryResult.data as T
}
clearAuth()
throw new ApiError(401, 'UNAUTHORIZED', 'Session expired')
}