2025-12-18 13:50:39 +08:00
< template >
< AppLayout >
< div class = "space-y-6" >
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
< UsageStatsCards :stats = "usageStats" / >
2026-01-04 20:10:15 -08:00
<!-- Charts Section -- >
< div class = "space-y-4" >
< div class = "card p-4" >
< div class = "flex items-center gap-4" >
< span class = "text-sm font-medium text-gray-700 dark:text-gray-300" > { { t ( 'admin.dashboard.granularity' ) } } : < / span >
< div class = "w-28" >
< Select v-model = "granularity" :options="granularityOptions" @change="loadChartData" / >
< / div >
< / div >
< / div >
< div class = "grid grid-cols-1 gap-6 lg:grid-cols-2" >
< ModelDistributionChart :model-stats = "modelStats" :loading = "chartsLoading" / >
< TokenUsageTrend :trend-data = "trendData" :loading = "chartsLoading" / >
< / div >
< / div >
2026-02-27 19:41:26 +08:00
< UsageFilters v-model = "filters" v-model:startDate="startDate" v-model:endDate="endDate" :exporting="exporting" @change="applyFilters" @refresh="refreshData" @reset="resetFilters" @cleanup="openCleanupDialog" @export="exportToExcel" >
< template # after -reset >
< div class = "relative" ref = "columnDropdownRef" >
< button
@ click = "showColumnDropdown = !showColumnDropdown"
class = "btn btn-secondary px-2 md:px-3"
: title = "t('admin.users.columnSettings')"
>
< svg class = "h-4 w-4 md:mr-1.5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" stroke -width = " 1.5 " >
< path stroke -linecap = " round " stroke -linejoin = " round " d = "M9 4.5v15m6-15v15m-10.875 0h15.75c.621 0 1.125-.504 1.125-1.125V5.625c0-.621-.504-1.125-1.125-1.125H4.125C3.504 4.5 3 5.004 3 5.625v12.75c0 .621.504 1.125 1.125 1.125z" / >
< / svg >
< span class = "hidden md:inline" > { { t ( 'admin.users.columnSettings' ) } } < / span >
< / button >
< div
v - if = "showColumnDropdown"
class = "absolute right-0 top-full z-50 mt-1 max-h-80 w-48 overflow-y-auto rounded-lg border border-gray-200 bg-white py-1 shadow-lg dark:border-dark-600 dark:bg-dark-800"
>
< button
v - for = "col in toggleableColumns"
: key = "col.key"
@ click = "toggleColumn(col.key)"
class = "flex w-full items-center justify-between px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-dark-700"
>
< span > { { col . label } } < / span >
< Icon
v - if = "isColumnVisible(col.key)"
name = "check"
size = "sm"
class = "text-primary-500"
: stroke - width = "2"
/ >
< / button >
< / div >
< / div >
< / template >
< / UsageFilters >
< UsageTable :data = "usageLogs" :loading = "loading" :columns = "visibleColumns" / >
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
< Pagination v-if = "pagination.total > 0" :page="pagination.page" :total="pagination.total" :page-size="pagination.page_size" @update:page="handlePageChange" @update:pageSize="handlePageSizeChange" / >
2025-12-18 13:50:39 +08:00
< / div >
< / AppLayout >
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
< UsageExportProgress :show = "exportProgress.show" :progress = "exportProgress.progress" :current = "exportProgress.current" :total = "exportProgress.total" :estimated-time = "exportProgress.estimatedTime" @cancel ="cancelExport" / >
2026-02-02 22:13:50 +08:00
< UsageCleanupDialog
: show = "cleanupDialogVisible"
: filters = "filters"
: start - date = "startDate"
: end - date = "endDate"
@ close = "cleanupDialogVisible = false"
/ >
2025-12-18 13:50:39 +08:00
< / template >
< script setup lang = "ts" >
2026-01-04 20:10:15 -08:00
import { ref , reactive , computed , onMounted , onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
2026-01-06 11:36:38 +08:00
import { saveAs } from 'file-saver'
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
import { useAppStore } from '@/stores/app' ; import { adminAPI } from '@/api/admin' ; import { adminUsageAPI } from '@/api/admin/usage'
2026-02-03 15:36:17 +08:00
import { formatReasoningEffort } from '@/utils/format'
2026-01-04 20:10:15 -08:00
import AppLayout from '@/components/layout/AppLayout.vue' ; import Pagination from '@/components/common/Pagination.vue' ; import Select from '@/components/common/Select.vue'
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
import UsageStatsCards from '@/components/admin/usage/UsageStatsCards.vue' ; import UsageFilters from '@/components/admin/usage/UsageFilters.vue'
import UsageTable from '@/components/admin/usage/UsageTable.vue' ; import UsageExportProgress from '@/components/admin/usage/UsageExportProgress.vue'
2026-02-02 22:13:50 +08:00
import UsageCleanupDialog from '@/components/admin/usage/UsageCleanupDialog.vue'
2026-01-04 20:10:15 -08:00
import ModelDistributionChart from '@/components/charts/ModelDistributionChart.vue' ; import TokenUsageTrend from '@/components/charts/TokenUsageTrend.vue'
2026-02-27 19:41:26 +08:00
import Icon from '@/components/icons/Icon.vue'
2026-02-02 22:13:50 +08:00
import type { AdminUsageLog , TrendDataPoint , ModelStat } from '@/types' ; import type { AdminUsageStatsResponse , AdminUsageQueryParams } from '@/api/admin/usage'
2025-12-18 13:50:39 +08:00
2026-01-04 20:10:15 -08:00
const { t } = useI18n ( )
2025-12-18 13:50:39 +08:00
const appStore = useAppStore ( )
2026-02-02 22:13:50 +08:00
const usageStats = ref < AdminUsageStatsResponse | null > ( null ) ; const usageLogs = ref < AdminUsageLog [ ] > ( [ ] ) ; const loading = ref ( false ) ; const exporting = ref ( false )
2026-01-04 20:10:15 -08:00
const trendData = ref < TrendDataPoint [ ] > ( [ ] ) ; const modelStats = ref < ModelStat [ ] > ( [ ] ) ; const chartsLoading = ref ( false ) ; const granularity = ref < 'day' | 'hour' > ( 'day' )
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
let abortController : AbortController | null = null ; let exportAbortController : AbortController | null = null
const exportProgress = reactive ( { show : false , progress : 0 , current : 0 , total : 0 , estimatedTime : '' } )
2026-02-02 22:13:50 +08:00
const cleanupDialogVisible = ref ( false )
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
2026-01-04 20:10:15 -08:00
const granularityOptions = computed ( ( ) => [ { value : 'day' , label : t ( 'admin.dashboard.day' ) } , { value : 'hour' , label : t ( 'admin.dashboard.hour' ) } ] )
2026-01-14 21:46:39 +08:00
// Use local timezone to avoid UTC timezone issues
const formatLD = ( d : Date ) => {
const year = d . getFullYear ( )
const month = String ( d . getMonth ( ) + 1 ) . padStart ( 2 , '0' )
const day = String ( d . getDate ( ) ) . padStart ( 2 , '0' )
return ` ${ year } - ${ month } - ${ day } `
}
const now = new Date ( ) ; const weekAgo = new Date ( ) ; weekAgo . setDate ( weekAgo . getDate ( ) - 6 )
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
const startDate = ref ( formatLD ( weekAgo ) ) ; const endDate = ref ( formatLD ( now ) )
2026-02-02 22:13:50 +08:00
const filters = ref < AdminUsageQueryParams > ( { user _id : undefined , model : undefined , group _id : undefined , billing _type : null , start _date : startDate . value , end _date : endDate . value } )
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
const pagination = reactive ( { page : 1 , page _size : 20 , total : 0 } )
const loadLogs = async ( ) => {
abortController ? . abort ( ) ; const c = new AbortController ( ) ; abortController = c ; loading . value = true
2025-12-20 10:06:55 +08:00
try {
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
const res = await adminAPI . usage . list ( { page : pagination . page , page _size : pagination . page _size , ... filters . value } , { signal : c . signal } )
if ( ! c . signal . aborted ) { usageLogs . value = res . items ; pagination . total = res . total }
2026-01-06 12:42:06 +08:00
} catch ( error : any ) { if ( error ? . name !== 'AbortError' ) console . error ( 'Failed to load usage logs:' , error ) } finally { if ( abortController === c ) loading . value = false }
2025-12-29 16:13:09 +08:00
}
2026-01-06 12:42:06 +08:00
const loadStats = async ( ) => { try { const s = await adminAPI . usage . getStats ( filters . value ) ; usageStats . value = s } catch ( error ) { console . error ( 'Failed to load usage stats:' , error ) } }
2026-01-04 20:10:15 -08:00
const loadChartData = async ( ) => {
chartsLoading . value = true
try {
2026-02-02 22:13:50 +08:00
const params = { start _date : filters . value . start _date || startDate . value , end _date : filters . value . end _date || endDate . value , granularity : granularity . value , user _id : filters . value . user _id , model : filters . value . model , api _key _id : filters . value . api _key _id , account _id : filters . value . account _id , group _id : filters . value . group _id , stream : filters . value . stream , billing _type : filters . value . billing _type }
const [ trendRes , modelRes ] = await Promise . all ( [ adminAPI . dashboard . getUsageTrend ( params ) , adminAPI . dashboard . getModelStats ( { start _date : params . start _date , end _date : params . end _date , user _id : params . user _id , model : params . model , api _key _id : params . api _key _id , account _id : params . account _id , group _id : params . group _id , stream : params . stream , billing _type : params . billing _type } ) ] )
2026-01-04 20:10:15 -08:00
trendData . value = trendRes . trend || [ ] ; modelStats . value = modelRes . models || [ ]
2026-01-06 12:50:51 +08:00
} catch ( error ) { console . error ( 'Failed to load chart data:' , error ) } finally { chartsLoading . value = false }
2026-01-04 20:10:15 -08:00
}
const applyFilters = ( ) => { pagination . page = 1 ; loadLogs ( ) ; loadStats ( ) ; loadChartData ( ) }
2026-02-07 19:13:43 +08:00
const refreshData = ( ) => { loadLogs ( ) ; loadStats ( ) ; loadChartData ( ) }
2026-02-02 22:13:50 +08:00
const resetFilters = ( ) => { startDate . value = formatLD ( weekAgo ) ; endDate . value = formatLD ( now ) ; filters . value = { start _date : startDate . value , end _date : endDate . value , billing _type : null } ; granularity . value = 'day' ; applyFilters ( ) }
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
const handlePageChange = ( p : number ) => { pagination . page = p ; loadLogs ( ) }
const handlePageSizeChange = ( s : number ) => { pagination . page _size = s ; pagination . page = 1 ; loadLogs ( ) }
const cancelExport = ( ) => exportAbortController ? . abort ( )
2026-02-02 22:13:50 +08:00
const openCleanupDialog = ( ) => { cleanupDialogVisible . value = true }
2025-12-29 16:13:09 +08:00
const exportToExcel = async ( ) => {
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
if ( exporting . value ) return ; exporting . value = true ; exportProgress . show = true
const c = new AbortController ( ) ; exportAbortController = c
2025-12-29 16:13:09 +08:00
try {
2026-02-14 11:56:08 +08:00
let p = 1 ; let total = pagination . total ; let exportedCount = 0
const XLSX = await import ( 'xlsx' )
const headers = [
t ( 'usage.time' ) , t ( 'admin.usage.user' ) , t ( 'usage.apiKeyFilter' ) ,
t ( 'admin.usage.account' ) , t ( 'usage.model' ) , t ( 'usage.reasoningEffort' ) , t ( 'admin.usage.group' ) ,
t ( 'usage.type' ) ,
t ( 'admin.usage.inputTokens' ) , t ( 'admin.usage.outputTokens' ) ,
t ( 'admin.usage.cacheReadTokens' ) , t ( 'admin.usage.cacheCreationTokens' ) ,
t ( 'admin.usage.inputCost' ) , t ( 'admin.usage.outputCost' ) ,
t ( 'admin.usage.cacheReadCost' ) , t ( 'admin.usage.cacheCreationCost' ) ,
t ( 'usage.rate' ) , t ( 'usage.accountMultiplier' ) , t ( 'usage.original' ) , t ( 'usage.userBilled' ) , t ( 'usage.accountBilled' ) ,
t ( 'usage.firstToken' ) , t ( 'usage.duration' ) ,
t ( 'admin.usage.requestId' ) , t ( 'usage.userAgent' ) , t ( 'admin.usage.ipAddress' )
]
const ws = XLSX . utils . aoa _to _sheet ( [ headers ] )
2025-12-29 16:13:09 +08:00
while ( true ) {
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
const res = await adminUsageAPI . list ( { page : p , page _size : 100 , ... filters . value } , { signal : c . signal } )
if ( c . signal . aborted ) break ; if ( p === 1 ) { total = res . total ; exportProgress . total = total }
2026-02-14 11:56:08 +08:00
const rows = ( res . items || [ ] ) . map ( ( log : AdminUsageLog ) => [
log . created _at , log . user ? . email || '' , log . api _key ? . name || '' , log . account ? . name || '' , log . model ,
formatReasoningEffort ( log . reasoning _effort ) , log . group ? . name || '' , log . stream ? t ( 'usage.stream' ) : t ( 'usage.sync' ) ,
log . input _tokens , log . output _tokens , log . cache _read _tokens , log . cache _creation _tokens ,
log . input _cost ? . toFixed ( 6 ) || '0.000000' , log . output _cost ? . toFixed ( 6 ) || '0.000000' ,
log . cache _read _cost ? . toFixed ( 6 ) || '0.000000' , log . cache _creation _cost ? . toFixed ( 6 ) || '0.000000' ,
log . rate _multiplier ? . toFixed ( 2 ) || '1.00' , ( log . account _rate _multiplier ? ? 1 ) . toFixed ( 2 ) ,
log . total _cost ? . toFixed ( 6 ) || '0.000000' , log . actual _cost ? . toFixed ( 6 ) || '0.000000' ,
( log . total _cost * ( log . account _rate _multiplier ? ? 1 ) ) . toFixed ( 6 ) , log . first _token _ms ? ? '' , log . duration _ms ,
log . request _id || '' , log . user _agent || '' , log . ip _address || ''
] )
if ( rows . length ) {
XLSX . utils . sheet _add _aoa ( ws , rows , { origin : - 1 } )
}
exportedCount += rows . length
exportProgress . current = exportedCount
exportProgress . progress = total > 0 ? Math . min ( 100 , Math . round ( exportedCount / total * 100 ) ) : 0
if ( exportedCount >= total || res . items . length < 100 ) break ; p ++
2025-12-29 16:13:09 +08:00
}
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
if ( ! c . signal . aborted ) {
2026-01-07 09:35:21 +08:00
const wb = XLSX . utils . book _new ( )
XLSX . utils . book _append _sheet ( wb , ws , 'Usage' )
saveAs ( new Blob ( [ XLSX . write ( wb , { bookType : 'xlsx' , type : 'array' } ) ] , { type : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } ) , ` usage_ ${ filters . value . start _date } _to_ ${ filters . value . end _date } .xlsx ` )
appStore . showSuccess ( t ( 'usage.exportSuccess' ) )
2025-12-29 16:13:09 +08:00
}
2026-01-06 12:50:51 +08:00
} catch ( error ) { console . error ( 'Failed to export:' , error ) ; appStore . showError ( 'Export Failed' ) }
refactor(frontend): comprehensive split of large view files into modular components
- Split UsersView.vue into UserCreateModal, UserEditModal, UserApiKeysModal, etc.
- Split UsageView.vue into UsageStatsCards, UsageFilters, UsageTable, etc.
- Split DashboardView.vue into UserDashboardStats, UserDashboardCharts, etc.
- Split AccountsView.vue into AccountTableActions, AccountTableFilters, etc.
- Standardized TypeScript types across new components to resolve implicit 'any' and 'never[]' errors.
- Improved overall frontend maintainability and code clarity.
2026-01-04 22:17:27 +08:00
finally { if ( exportAbortController === c ) { exportAbortController = null ; exporting . value = false ; exportProgress . show = false } }
2025-12-27 16:05:16 +08:00
}
2026-02-27 19:41:26 +08:00
// Column visibility
const ALWAYS _VISIBLE = [ 'user' , 'created_at' ]
const DEFAULT _HIDDEN _COLUMNS = [ 'reasoning_effort' , 'user_agent' ]
const HIDDEN _COLUMNS _KEY = 'usage-hidden-columns'
const allColumns = computed ( ( ) => [
{ key : 'user' , label : t ( 'admin.usage.user' ) , sortable : false } ,
{ key : 'api_key' , label : t ( 'usage.apiKeyFilter' ) , sortable : false } ,
{ key : 'account' , label : t ( 'admin.usage.account' ) , sortable : false } ,
{ key : 'model' , label : t ( 'usage.model' ) , sortable : true } ,
{ key : 'reasoning_effort' , label : t ( 'usage.reasoningEffort' ) , sortable : false } ,
{ key : 'group' , label : t ( 'admin.usage.group' ) , sortable : false } ,
{ key : 'stream' , label : t ( 'usage.type' ) , sortable : false } ,
{ key : 'tokens' , label : t ( 'usage.tokens' ) , sortable : false } ,
{ key : 'cost' , label : t ( 'usage.cost' ) , sortable : false } ,
{ key : 'first_token' , label : t ( 'usage.firstToken' ) , sortable : false } ,
{ key : 'duration' , label : t ( 'usage.duration' ) , sortable : false } ,
{ key : 'created_at' , label : t ( 'usage.time' ) , sortable : true } ,
{ key : 'user_agent' , label : t ( 'usage.userAgent' ) , sortable : false } ,
{ key : 'ip_address' , label : t ( 'admin.usage.ipAddress' ) , sortable : false }
] )
const hiddenColumns = reactive < Set < string > > ( new Set ( ) )
const toggleableColumns = computed ( ( ) =>
allColumns . value . filter ( col => ! ALWAYS _VISIBLE . includes ( col . key ) )
)
const visibleColumns = computed ( ( ) =>
allColumns . value . filter ( col =>
ALWAYS _VISIBLE . includes ( col . key ) || ! hiddenColumns . has ( col . key )
)
)
const isColumnVisible = ( key : string ) => ! hiddenColumns . has ( key )
const toggleColumn = ( key : string ) => {
if ( hiddenColumns . has ( key ) ) {
hiddenColumns . delete ( key )
} else {
hiddenColumns . add ( key )
}
try {
localStorage . setItem ( HIDDEN _COLUMNS _KEY , JSON . stringify ( [ ... hiddenColumns ] ) )
} catch ( e ) {
console . error ( 'Failed to save columns:' , e )
}
}
const loadSavedColumns = ( ) => {
try {
const saved = localStorage . getItem ( HIDDEN _COLUMNS _KEY )
if ( saved ) {
( JSON . parse ( saved ) as string [ ] ) . forEach ( key => hiddenColumns . add ( key ) )
} else {
DEFAULT _HIDDEN _COLUMNS . forEach ( key => hiddenColumns . add ( key ) )
}
} catch {
DEFAULT _HIDDEN _COLUMNS . forEach ( key => hiddenColumns . add ( key ) )
}
}
const showColumnDropdown = ref ( false )
const columnDropdownRef = ref < HTMLElement | null > ( null )
const handleColumnClickOutside = ( event : MouseEvent ) => {
if ( columnDropdownRef . value && ! columnDropdownRef . value . contains ( event . target as HTMLElement ) ) {
showColumnDropdown . value = false
}
}
onMounted ( ( ) => { loadLogs ( ) ; loadStats ( ) ; loadChartData ( ) ; loadSavedColumns ( ) ; document . addEventListener ( 'click' , handleColumnClickOutside ) } )
onUnmounted ( ( ) => { abortController ? . abort ( ) ; exportAbortController ? . abort ( ) ; document . removeEventListener ( 'click' , handleColumnClickOutside ) } )
2026-01-06 11:36:38 +08:00
< / script >