mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-02 15:52:26 +08:00
refactor(frontend): 优化管理后台视图
- 改进 AliasManagement, ApiKeys, AuditLogs, CacheMonitoring, ModelManagement, SystemSettings, Users 页面
This commit is contained in:
@@ -252,6 +252,7 @@ import {
|
|||||||
} from '@/api/endpoints/aliases'
|
} from '@/api/endpoints/aliases'
|
||||||
import { listGlobalModels, type GlobalModelResponse } from '@/api/global-models'
|
import { listGlobalModels, type GlobalModelResponse } from '@/api/global-models'
|
||||||
import { getProvidersSummary } from '@/api/endpoints/providers'
|
import { getProvidersSummary } from '@/api/endpoints/providers'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
|
|
||||||
const { success, error: showError } = useToast()
|
const { success, error: showError } = useToast()
|
||||||
const { confirmDanger } = useConfirm()
|
const { confirmDanger } = useConfirm()
|
||||||
@@ -332,7 +333,7 @@ async function loadGlobalModelsList() {
|
|||||||
const response = await listGlobalModels()
|
const response = await listGlobalModels()
|
||||||
globalModels.value = response.models || []
|
globalModels.value = response.models || []
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('加载模型失败:', err)
|
log.error('加载模型失败:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -689,6 +689,7 @@ import {
|
|||||||
|
|
||||||
import { StandaloneKeyFormDialog, type StandaloneKeyFormData } from '@/features/api-keys'
|
import { StandaloneKeyFormDialog, type StandaloneKeyFormData } from '@/features/api-keys'
|
||||||
import { parseNumberInput } from '@/utils/form'
|
import { parseNumberInput } from '@/utils/form'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
|
|
||||||
const { success, error } = useToast()
|
const { success, error } = useToast()
|
||||||
const { confirmDanger } = useConfirm()
|
const { confirmDanger } = useConfirm()
|
||||||
@@ -803,7 +804,7 @@ async function loadApiKeys() {
|
|||||||
apiKeys.value = response.api_keys
|
apiKeys.value = response.api_keys
|
||||||
total.value = response.total
|
total.value = response.total
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('加载独立Keys失败:', err)
|
log.error('加载独立Keys失败:', err)
|
||||||
error(err.response?.data?.detail || '加载独立 Keys 失败')
|
error(err.response?.data?.detail || '加载独立 Keys 失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -824,7 +825,7 @@ async function toggleApiKey(apiKey: AdminApiKey) {
|
|||||||
}
|
}
|
||||||
success(response.message)
|
success(response.message)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('切换密钥状态失败:', err)
|
log.error('切换密钥状态失败:', err)
|
||||||
error(err.response?.data?.detail || '操作失败')
|
error(err.response?.data?.detail || '操作失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -843,7 +844,7 @@ async function deleteApiKey(apiKey: AdminApiKey) {
|
|||||||
total.value = total.value - 1
|
total.value = total.value - 1
|
||||||
success(response.message)
|
success(response.message)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('删除密钥失败:', err)
|
log.error('删除密钥失败:', err)
|
||||||
error(err.response?.data?.detail || '删除失败')
|
error(err.response?.data?.detail || '删除失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -916,7 +917,7 @@ async function handleAddBalance() {
|
|||||||
const amount = Math.abs(addBalanceAmount.value).toFixed(2)
|
const amount = Math.abs(addBalanceAmount.value).toFixed(2)
|
||||||
success(response.message || `余额${action}成功,${action} $${amount}`)
|
success(response.message || `余额${action}成功,${action} $${amount}`)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('余额调整失败:', err)
|
log.error('余额调整失败:', err)
|
||||||
error(err.response?.data?.detail || '调整失败')
|
error(err.response?.data?.detail || '调整失败')
|
||||||
} finally {
|
} finally {
|
||||||
addingBalance.value = false
|
addingBalance.value = false
|
||||||
@@ -931,7 +932,7 @@ async function copyKey() {
|
|||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(newKeyValue.value)
|
await navigator.clipboard.writeText(newKeyValue.value)
|
||||||
success('API Key 已复制到剪贴板')
|
success('API Key 已复制到剪贴板')
|
||||||
} catch (err) {
|
} catch {
|
||||||
error('复制失败,请手动复制')
|
error('复制失败,请手动复制')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -943,7 +944,7 @@ async function copyKeyPrefix(apiKey: AdminApiKey) {
|
|||||||
await navigator.clipboard.writeText(response.key)
|
await navigator.clipboard.writeText(response.key)
|
||||||
success('完整密钥已复制到剪贴板')
|
success('完整密钥已复制到剪贴板')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('复制密钥失败:', err)
|
log.error('复制密钥失败:', err)
|
||||||
error('复制失败,请重试')
|
error('复制失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1077,7 +1078,7 @@ async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
|||||||
closeKeyFormDialog()
|
closeKeyFormDialog()
|
||||||
await loadApiKeys()
|
await loadApiKeys()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('保存独立Key失败:', err)
|
log.error('保存独立Key失败:', err)
|
||||||
error(err.response?.data?.detail || '保存失败')
|
error(err.response?.data?.detail || '保存失败')
|
||||||
} finally {
|
} finally {
|
||||||
keyFormDialogRef.value?.setSaving(false)
|
keyFormDialogRef.value?.setSaving(false)
|
||||||
|
|||||||
@@ -460,7 +460,7 @@ async function loadLogs() {
|
|||||||
logs.value = data.items || []
|
logs.value = data.items || []
|
||||||
totalRecords.value = data.meta?.total ?? logs.value.length
|
totalRecords.value = data.meta?.total ?? logs.value.length
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取审计日志失败:', error)
|
log.error('获取审计日志失败:', error)
|
||||||
logs.value = []
|
logs.value = []
|
||||||
totalRecords.value = 0
|
totalRecords.value = 0
|
||||||
} finally {
|
} finally {
|
||||||
@@ -472,19 +472,6 @@ function refreshLogs() {
|
|||||||
loadLogs()
|
loadLogs()
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearFilters() {
|
|
||||||
filters.value = {
|
|
||||||
userId: '',
|
|
||||||
eventType: '__all__',
|
|
||||||
days: 7,
|
|
||||||
limit: 50
|
|
||||||
}
|
|
||||||
filtersDaysString.value = '7'
|
|
||||||
filtersLimitString.value = '50'
|
|
||||||
currentPage.value = 1
|
|
||||||
loadLogs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索变化处理
|
// 搜索变化处理
|
||||||
function handleSearchChange() {
|
function handleSearchChange() {
|
||||||
filters.value.userId = searchQuery.value
|
filters.value.userId = searchQuery.value
|
||||||
@@ -519,12 +506,6 @@ function handleDaysChange(value: string) {
|
|||||||
resetAndLoad()
|
resetAndLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLimitChange(value: string) {
|
|
||||||
filtersLimitString.value = value
|
|
||||||
filters.value.limit = parseInt(value)
|
|
||||||
loadLogs()
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetAndLoad() {
|
function resetAndLoad() {
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
loadLogs()
|
loadLogs()
|
||||||
@@ -578,12 +559,13 @@ async function exportLogs() {
|
|||||||
link.download = `audit-logs-${new Date().toISOString().split('T')[0]}.csv`
|
link.download = `audit-logs-${new Date().toISOString().split('T')[0]}.csv`
|
||||||
link.click()
|
link.click()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('导出失败:', error)
|
log.error('导出失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用复用的行点击逻辑
|
// 使用复用的行点击逻辑
|
||||||
import { useRowClick } from '@/composables/useRowClick'
|
import { useRowClick } from '@/composables/useRowClick'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
const { handleMouseDown, shouldTriggerRowClick } = useRowClick()
|
const { handleMouseDown, shouldTriggerRowClick } = useRowClick()
|
||||||
|
|
||||||
function handleRowClick(event: MouseEvent, log: AuditLog) {
|
function handleRowClick(event: MouseEvent, log: AuditLog) {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
getFrequencyLabel,
|
getFrequencyLabel,
|
||||||
getFrequencyClass
|
getFrequencyClass
|
||||||
} from '@/composables/useTTLAnalysis'
|
} from '@/composables/useTTLAnalysis'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
|
|
||||||
// ==================== 缓存统计与亲和性列表 ====================
|
// ==================== 缓存统计与亲和性列表 ====================
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ async function fetchCacheStats() {
|
|||||||
stats.value = await cacheApi.getStats()
|
stats.value = await cacheApi.getStats()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError('获取缓存统计失败')
|
showError('获取缓存统计失败')
|
||||||
console.error(error)
|
log.error('获取缓存统计失败', error)
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,7 @@ async function fetchCacheConfig() {
|
|||||||
try {
|
try {
|
||||||
config.value = await cacheApi.getConfig()
|
config.value = await cacheApi.getConfig()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
log.error('获取缓存配置失败', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ async function fetchAffinityList(keyword?: string) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError('获取缓存列表失败')
|
showError('获取缓存列表失败')
|
||||||
console.error(error)
|
log.error('获取缓存列表失败', error)
|
||||||
} finally {
|
} finally {
|
||||||
listLoading.value = false
|
listLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -159,7 +160,7 @@ async function clearUserCache(identifier: string, displayName?: string) {
|
|||||||
await fetchAffinityList(tableKeyword.value.trim() || undefined)
|
await fetchAffinityList(tableKeyword.value.trim() || undefined)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError('清除失败')
|
showError('清除失败')
|
||||||
console.error(error)
|
log.error('清除用户缓存失败', error)
|
||||||
} finally {
|
} finally {
|
||||||
clearingRowAffinityKey.value = null
|
clearingRowAffinityKey.value = null
|
||||||
}
|
}
|
||||||
@@ -189,7 +190,7 @@ async function clearAllCache() {
|
|||||||
await fetchAffinityList(tableKeyword.value.trim() || undefined)
|
await fetchAffinityList(tableKeyword.value.trim() || undefined)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError('清除失败')
|
showError('清除失败')
|
||||||
console.error(error)
|
log.error('清除所有缓存失败', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ import {
|
|||||||
batchAssignToProviders,
|
batchAssignToProviders,
|
||||||
type GlobalModelResponse,
|
type GlobalModelResponse,
|
||||||
} from '@/api/global-models'
|
} from '@/api/global-models'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
import {
|
import {
|
||||||
getAliases,
|
getAliases,
|
||||||
createAlias,
|
createAlias,
|
||||||
@@ -997,7 +998,7 @@ async function loadGlobalModels() {
|
|||||||
// API 返回 { models: [...], total: number }
|
// API 返回 { models: [...], total: number }
|
||||||
globalModels.value = response.models || []
|
globalModels.value = response.models || []
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('加载模型失败:', err)
|
log.error('加载模型失败:', err)
|
||||||
showError(err.response?.data?.detail || err.message, '加载模型失败')
|
showError(err.response?.data?.detail || err.message, '加载模型失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -1075,7 +1076,7 @@ async function loadModelProviders(_globalModelId: string) {
|
|||||||
selectedModelProviders.value = []
|
selectedModelProviders.value = []
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('加载关联提供商失败:', err)
|
log.error('加载关联提供商失败:', err)
|
||||||
showError(parseApiError(err, '加载关联提供商失败'), '错误')
|
showError(parseApiError(err, '加载关联提供商失败'), '错误')
|
||||||
selectedModelProviders.value = []
|
selectedModelProviders.value = []
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1090,7 +1091,7 @@ async function loadModelAliases(globalModelId: string) {
|
|||||||
const aliases = await getAliases({ limit: 1000 })
|
const aliases = await getAliases({ limit: 1000 })
|
||||||
selectedModelAliases.value = aliases.filter(a => a.global_model_id === globalModelId)
|
selectedModelAliases.value = aliases.filter(a => a.global_model_id === globalModelId)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('加载别名失败:', err)
|
log.error('加载别名失败:', err)
|
||||||
selectedModelAliases.value = []
|
selectedModelAliases.value = []
|
||||||
} finally {
|
} finally {
|
||||||
loadingModelAliases.value = false
|
loadingModelAliases.value = false
|
||||||
@@ -1295,12 +1296,6 @@ async function loadAliases() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCreateAliasDialog() {
|
|
||||||
editingAliasId.value = null
|
|
||||||
isTargetModelFixed.value = false // 目标模型需要选择
|
|
||||||
createAliasDialogOpen.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function openEditAliasDialog(alias: ModelAlias) {
|
function openEditAliasDialog(alias: ModelAlias) {
|
||||||
editingAliasId.value = alias.id
|
editingAliasId.value = alias.id
|
||||||
isTargetModelFixed.value = false
|
isTargetModelFixed.value = false
|
||||||
@@ -1409,7 +1404,7 @@ async function loadCapabilities() {
|
|||||||
try {
|
try {
|
||||||
capabilities.value = await getAllCapabilities()
|
capabilities.value = await getAllCapabilities()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load capabilities:', err)
|
log.error('Failed to load capabilities:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -392,6 +392,7 @@ import SelectItem from '@/components/ui/select-item.vue'
|
|||||||
import { PageHeader, PageContainer, CardSection } from '@/components/layout'
|
import { PageHeader, PageContainer, CardSection } from '@/components/layout'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
import { adminApi } from '@/api/admin'
|
import { adminApi } from '@/api/admin'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
|
|
||||||
const { success, error } = useToast()
|
const { success, error } = useToast()
|
||||||
|
|
||||||
@@ -508,13 +509,13 @@ async function loadSystemConfig() {
|
|||||||
if (response.value !== null && response.value !== undefined) {
|
if (response.value !== null && response.value !== undefined) {
|
||||||
(systemConfig.value as any)[key] = response.value
|
(systemConfig.value as any)[key] = response.value
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
// 配置不存在时使用默认值,无需处理
|
// 配置不存在时使用默认值,无需处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('加载系统配置失败')
|
error('加载系统配置失败')
|
||||||
console.error('加载系统配置失败:', err)
|
log.error('加载系统配置失败:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,7 +618,7 @@ async function saveSystemConfig() {
|
|||||||
success('系统配置已保存')
|
success('系统配置已保存')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('保存配置失败')
|
error('保存配置失败')
|
||||||
console.error('保存配置失败:', err)
|
log.error('保存配置失败:', err)
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -678,6 +678,7 @@ import {
|
|||||||
|
|
||||||
// 功能组件
|
// 功能组件
|
||||||
import UserFormDialog, { type UserFormData } from '@/features/users/components/UserFormDialog.vue'
|
import UserFormDialog, { type UserFormData } from '@/features/users/components/UserFormDialog.vue'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
|
|
||||||
const { success, error } = useToast()
|
const { success, error } = useToast()
|
||||||
const { confirmDanger, confirmWarning } = useConfirm()
|
const { confirmDanger, confirmWarning } = useConfirm()
|
||||||
@@ -775,7 +776,7 @@ async function loadUserStats() {
|
|||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载用户统计失败:', err)
|
log.error('加载用户统计失败:', err)
|
||||||
} finally {
|
} finally {
|
||||||
loadingStats.value = false
|
loadingStats.value = false
|
||||||
}
|
}
|
||||||
@@ -900,7 +901,7 @@ async function loadUserApiKeys(userId: string) {
|
|||||||
try {
|
try {
|
||||||
userApiKeys.value = await usersStore.getUserApiKeys(userId)
|
userApiKeys.value = await usersStore.getUserApiKeys(userId)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载API Keys失败:', err)
|
log.error('加载API Keys失败:', err)
|
||||||
userApiKeys.value = []
|
userApiKeys.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -932,7 +933,7 @@ async function copyApiKey() {
|
|||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(newApiKey.value)
|
await navigator.clipboard.writeText(newApiKey.value)
|
||||||
success('API Key已复制到剪贴板')
|
success('API Key已复制到剪贴板')
|
||||||
} catch (err) {
|
} catch {
|
||||||
error('复制失败,请手动复制')
|
error('复制失败,请手动复制')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -966,7 +967,7 @@ async function copyFullKey(apiKey: any) {
|
|||||||
await navigator.clipboard.writeText(response.key)
|
await navigator.clipboard.writeText(response.key)
|
||||||
success('完整密钥已复制到剪贴板')
|
success('完整密钥已复制到剪贴板')
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('复制密钥失败:', err)
|
log.error('复制密钥失败:', err)
|
||||||
error(err.response?.data?.detail || '未知错误', '复制密钥失败')
|
error(err.response?.data?.detail || '未知错误', '复制密钥失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1002,24 +1003,4 @@ async function deleteUser(user: any) {
|
|||||||
error(err.response?.data?.detail || '未知错误', '删除用户失败')
|
error(err.response?.data?.detail || '未知错误', '删除用户失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRelativeTime(dateString: string): string {
|
|
||||||
const date = new Date(dateString)
|
|
||||||
const now = new Date()
|
|
||||||
const diff = now.getTime() - date.getTime()
|
|
||||||
|
|
||||||
const seconds = Math.floor(diff / 1000)
|
|
||||||
const minutes = Math.floor(seconds / 60)
|
|
||||||
const hours = Math.floor(minutes / 60)
|
|
||||||
const days = Math.floor(hours / 24)
|
|
||||||
const months = Math.floor(days / 30)
|
|
||||||
const years = Math.floor(days / 365)
|
|
||||||
|
|
||||||
if (years > 0) return `${years}年前`
|
|
||||||
if (months > 0) return `${months}月前`
|
|
||||||
if (days > 0) return `${days}天前`
|
|
||||||
if (hours > 0) return `${hours}小时前`
|
|
||||||
if (minutes > 0) return `${minutes}分钟前`
|
|
||||||
return '刚刚'
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user