Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
9c1f4b8e
Unverified
Commit
9c1f4b8e
authored
Mar 17, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 17, 2026
Browse files
Merge pull request #1068 from Ethan0x0000/pr/frontend-last24h
feat(frontend): set last 24h as default range in Usage and Dashboard
parents
9857c176
aef7c3b9
Changes
9
Hide whitespace changes
Inline
Side-by-side
frontend/src/components/admin/usage/UsageFilters.vue
View file @
9c1f4b8e
...
@@ -139,17 +139,6 @@
...
@@ -139,17 +139,6 @@
<
Select
v
-
model
=
"
filters.group_id
"
:
options
=
"
groupOptions
"
searchable
@
change
=
"
emitChange
"
/>
<
Select
v
-
model
=
"
filters.group_id
"
:
options
=
"
groupOptions
"
searchable
@
change
=
"
emitChange
"
/>
<
/div
>
<
/div
>
<!--
Date
Range
Filter
-->
<
div
class
=
"
w-full sm:w-auto [&_.date-picker-trigger]:w-full
"
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
usage.timeRange
'
)
}}
<
/label
>
<
DateRangePicker
:
start
-
date
=
"
startDate
"
:
end
-
date
=
"
endDate
"
@
update
:
startDate
=
"
updateStartDate
"
@
update
:
endDate
=
"
updateEndDate
"
@
change
=
"
emitChange
"
/>
<
/div
>
<
/div
>
<
/div
>
<!--
Right
:
actions
-->
<!--
Right
:
actions
-->
...
@@ -177,7 +166,6 @@ import { ref, onMounted, onUnmounted, toRef, watch } from 'vue'
...
@@ -177,7 +166,6 @@ import { ref, onMounted, onUnmounted, toRef, watch } from 'vue'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
adminAPI
}
from
'
@/api/admin
'
import
{
adminAPI
}
from
'
@/api/admin
'
import
Select
,
{
type
SelectOption
}
from
'
@/components/common/Select.vue
'
import
Select
,
{
type
SelectOption
}
from
'
@/components/common/Select.vue
'
import
DateRangePicker
from
'
@/components/common/DateRangePicker.vue
'
import
type
{
SimpleApiKey
,
SimpleUser
}
from
'
@/api/admin/usage
'
import
type
{
SimpleApiKey
,
SimpleUser
}
from
'
@/api/admin/usage
'
type
ModelValue
=
Record
<
string
,
any
>
type
ModelValue
=
Record
<
string
,
any
>
...
@@ -195,8 +183,6 @@ const props = withDefaults(defineProps<Props>(), {
...
@@ -195,8 +183,6 @@ const props = withDefaults(defineProps<Props>(), {
}
)
}
)
const
emit
=
defineEmits
([
const
emit
=
defineEmits
([
'
update:modelValue
'
,
'
update:modelValue
'
,
'
update:startDate
'
,
'
update:endDate
'
,
'
change
'
,
'
change
'
,
'
refresh
'
,
'
refresh
'
,
'
reset
'
,
'
reset
'
,
...
@@ -248,16 +234,6 @@ const billingTypeOptions = ref<SelectOption[]>([
...
@@ -248,16 +234,6 @@ const billingTypeOptions = ref<SelectOption[]>([
const
emitChange
=
()
=>
emit
(
'
change
'
)
const
emitChange
=
()
=>
emit
(
'
change
'
)
const
updateStartDate
=
(
value
:
string
)
=>
{
emit
(
'
update:startDate
'
,
value
)
filters
.
value
.
start_date
=
value
}
const
updateEndDate
=
(
value
:
string
)
=>
{
emit
(
'
update:endDate
'
,
value
)
filters
.
value
.
end_date
=
value
}
const
debounceUserSearch
=
()
=>
{
const
debounceUserSearch
=
()
=>
{
if
(
userSearchTimeout
)
clearTimeout
(
userSearchTimeout
)
if
(
userSearchTimeout
)
clearTimeout
(
userSearchTimeout
)
userSearchTimeout
=
setTimeout
(
async
()
=>
{
userSearchTimeout
=
setTimeout
(
async
()
=>
{
...
@@ -441,7 +417,11 @@ onMounted(async () => {
...
@@ -441,7 +417,11 @@ onMounted(async () => {
groupOptions
.
value
.
push
(...
gs
.
items
.
map
((
g
:
any
)
=>
({
value
:
g
.
id
,
label
:
g
.
name
}
)))
groupOptions
.
value
.
push
(...
gs
.
items
.
map
((
g
:
any
)
=>
({
value
:
g
.
id
,
label
:
g
.
name
}
)))
const
uniqueModels
=
new
Set
<
string
>
()
const
uniqueModels
=
new
Set
<
string
>
()
ms
.
models
?.
forEach
((
s
:
any
)
=>
s
.
model
&&
uniqueModels
.
add
(
s
.
model
))
ms
.
models
?.
forEach
((
s
:
any
)
=>
{
if
(
s
.
model
)
{
uniqueModels
.
add
(
s
.
model
)
}
}
)
modelOptions
.
value
.
push
(
modelOptions
.
value
.
push
(
...
Array
.
from
(
uniqueModels
)
...
Array
.
from
(
uniqueModels
)
.
sort
()
.
sort
()
...
...
frontend/src/components/common/DateRangePicker.vue
View file @
9c1f4b8e
...
@@ -106,7 +106,7 @@ const isOpen = ref(false)
...
@@ -106,7 +106,7 @@ const isOpen = ref(false)
const
containerRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
containerRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
localStartDate
=
ref
(
props
.
startDate
)
const
localStartDate
=
ref
(
props
.
startDate
)
const
localEndDate
=
ref
(
props
.
endDate
)
const
localEndDate
=
ref
(
props
.
endDate
)
const
activePreset
=
ref
<
string
|
null
>
(
'
7day
s
'
)
const
activePreset
=
ref
<
string
|
null
>
(
'
last24Hour
s
'
)
const
today
=
computed
(()
=>
{
const
today
=
computed
(()
=>
{
// Use local timezone to avoid UTC timezone issues
// Use local timezone to avoid UTC timezone issues
...
@@ -152,6 +152,18 @@ const presets: DatePreset[] = [
...
@@ -152,6 +152,18 @@ const presets: DatePreset[] = [
return
{
start
:
yesterday
,
end
:
yesterday
}
return
{
start
:
yesterday
,
end
:
yesterday
}
}
}
},
},
{
labelKey
:
'
dates.last24Hours
'
,
value
:
'
last24Hours
'
,
getRange
:
()
=>
{
const
end
=
new
Date
()
const
start
=
new
Date
(
end
.
getTime
()
-
24
*
60
*
60
*
1000
)
return
{
start
:
formatDateToString
(
start
),
end
:
formatDateToString
(
end
)
}
}
},
{
{
labelKey
:
'
dates.last7Days
'
,
labelKey
:
'
dates.last7Days
'
,
value
:
'
7days
'
,
value
:
'
7days
'
,
...
...
frontend/src/components/common/__tests__/DateRangePicker.spec.ts
0 → 100644
View file @
9c1f4b8e
import
{
describe
,
expect
,
it
,
vi
}
from
'
vitest
'
import
{
mount
}
from
'
@vue/test-utils
'
import
{
ref
}
from
'
vue
'
import
DateRangePicker
from
'
../DateRangePicker.vue
'
const
messages
:
Record
<
string
,
string
>
=
{
'
dates.today
'
:
'
Today
'
,
'
dates.yesterday
'
:
'
Yesterday
'
,
'
dates.last24Hours
'
:
'
Last 24 Hours
'
,
'
dates.last7Days
'
:
'
Last 7 Days
'
,
'
dates.last14Days
'
:
'
Last 14 Days
'
,
'
dates.last30Days
'
:
'
Last 30 Days
'
,
'
dates.thisMonth
'
:
'
This Month
'
,
'
dates.lastMonth
'
:
'
Last Month
'
,
'
dates.startDate
'
:
'
Start Date
'
,
'
dates.endDate
'
:
'
End Date
'
,
'
dates.apply
'
:
'
Apply
'
,
'
dates.selectDateRange
'
:
'
Select date range
'
}
vi
.
mock
(
'
vue-i18n
'
,
()
=>
({
useI18n
:
()
=>
({
t
:
(
key
:
string
)
=>
messages
[
key
]
??
key
,
locale
:
ref
(
'
en
'
)
})
}))
const
formatLocalDate
=
(
date
:
Date
):
string
=>
{
const
year
=
date
.
getFullYear
()
const
month
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'
0
'
)
const
day
=
String
(
date
.
getDate
()).
padStart
(
2
,
'
0
'
)
return
`
${
year
}
-
${
month
}
-
${
day
}
`
}
describe
(
'
DateRangePicker
'
,
()
=>
{
it
(
'
uses last 24 hours as the default recognized preset
'
,
()
=>
{
const
now
=
new
Date
()
const
yesterday
=
new
Date
(
now
.
getTime
()
-
24
*
60
*
60
*
1000
)
const
wrapper
=
mount
(
DateRangePicker
,
{
props
:
{
startDate
:
formatLocalDate
(
yesterday
),
endDate
:
formatLocalDate
(
now
)
},
global
:
{
stubs
:
{
Icon
:
true
}
}
})
expect
(
wrapper
.
text
()).
toContain
(
'
Last 24 Hours
'
)
})
it
(
'
emits range updates with last24Hours preset when applied
'
,
async
()
=>
{
const
now
=
new
Date
()
const
today
=
formatLocalDate
(
now
)
const
wrapper
=
mount
(
DateRangePicker
,
{
props
:
{
startDate
:
today
,
endDate
:
today
},
global
:
{
stubs
:
{
Icon
:
true
}
}
})
await
wrapper
.
find
(
'
.date-picker-trigger
'
).
trigger
(
'
click
'
)
const
presetButton
=
wrapper
.
findAll
(
'
.date-picker-preset
'
).
find
((
node
)
=>
node
.
text
().
includes
(
'
Last 24 Hours
'
)
)
expect
(
presetButton
).
toBeDefined
()
await
presetButton
!
.
trigger
(
'
click
'
)
await
wrapper
.
find
(
'
.date-picker-apply
'
).
trigger
(
'
click
'
)
const
nowAfterClick
=
new
Date
()
const
yesterdayAfterClick
=
new
Date
(
nowAfterClick
.
getTime
()
-
24
*
60
*
60
*
1000
)
const
expectedStart
=
formatLocalDate
(
yesterdayAfterClick
)
const
expectedEnd
=
formatLocalDate
(
nowAfterClick
)
expect
(
wrapper
.
emitted
(
'
update:startDate
'
)?.[
0
]).
toEqual
([
expectedStart
])
expect
(
wrapper
.
emitted
(
'
update:endDate
'
)?.[
0
]).
toEqual
([
expectedEnd
])
expect
(
wrapper
.
emitted
(
'
change
'
)?.[
0
]).
toEqual
([
{
startDate
:
expectedStart
,
endDate
:
expectedEnd
,
preset
:
'
last24Hours
'
}
])
})
})
frontend/src/i18n/locales/en.ts
View file @
9c1f4b8e
...
@@ -920,6 +920,7 @@ export default {
...
@@ -920,6 +920,7 @@ export default {
lastWeek
:
'
Last Week
'
,
lastWeek
:
'
Last Week
'
,
thisMonth
:
'
This Month
'
,
thisMonth
:
'
This Month
'
,
lastMonth
:
'
Last Month
'
,
lastMonth
:
'
Last Month
'
,
last24Hours
:
'
Last 24 Hours
'
,
last7Days
:
'
Last 7 Days
'
,
last7Days
:
'
Last 7 Days
'
,
last14Days
:
'
Last 14 Days
'
,
last14Days
:
'
Last 14 Days
'
,
last30Days
:
'
Last 30 Days
'
,
last30Days
:
'
Last 30 Days
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
9c1f4b8e
...
@@ -925,6 +925,7 @@ export default {
...
@@ -925,6 +925,7 @@ export default {
lastWeek
:
'
上周
'
,
lastWeek
:
'
上周
'
,
thisMonth
:
'
本月
'
,
thisMonth
:
'
本月
'
,
lastMonth
:
'
上月
'
,
lastMonth
:
'
上月
'
,
last24Hours
:
'
近24小时
'
,
last7Days
:
'
近 7 天
'
,
last7Days
:
'
近 7 天
'
,
last14Days
:
'
近 14 天
'
,
last14Days
:
'
近 14 天
'
,
last30Days
:
'
近 30 天
'
,
last30Days
:
'
近 30 天
'
,
...
...
frontend/src/views/admin/DashboardView.vue
View file @
9c1f4b8e
...
@@ -348,12 +348,20 @@ const formatLocalDate = (date: Date): string => {
...
@@ -348,12 +348,20 @@ const formatLocalDate = (date: Date): string => {
return
`
${
date
.
getFullYear
()}
-
${
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'
0
'
)}
-
${
String
(
date
.
getDate
()).
padStart
(
2
,
'
0
'
)}
`
return
`
${
date
.
getFullYear
()}
-
${
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'
0
'
)}
-
${
String
(
date
.
getDate
()).
padStart
(
2
,
'
0
'
)}
`
}
}
const
getTodayLocalDate
=
()
=>
formatLocalDate
(
new
Date
())
const
getLast24HoursRangeDates
=
():
{
start
:
string
;
end
:
string
}
=>
{
const
end
=
new
Date
()
const
start
=
new
Date
(
end
.
getTime
()
-
24
*
60
*
60
*
1000
)
return
{
start
:
formatLocalDate
(
start
),
end
:
formatLocalDate
(
end
)
}
}
// Date range
// Date range
const
granularity
=
ref
<
'
day
'
|
'
hour
'
>
(
'
hour
'
)
const
granularity
=
ref
<
'
day
'
|
'
hour
'
>
(
'
hour
'
)
const
startDate
=
ref
(
getTodayLocalDate
())
const
defaultRange
=
getLast24HoursRangeDates
()
const
endDate
=
ref
(
getTodayLocalDate
())
const
startDate
=
ref
(
defaultRange
.
start
)
const
endDate
=
ref
(
defaultRange
.
end
)
// Granularity options for Select component
// Granularity options for Select component
const
granularityOptions
=
computed
(()
=>
[
const
granularityOptions
=
computed
(()
=>
[
...
...
frontend/src/views/admin/UsageView.vue
View file @
9c1f4b8e
...
@@ -5,10 +5,20 @@
...
@@ -5,10 +5,20 @@
<!-- Charts Section -->
<!-- Charts Section -->
<div
class=
"space-y-4"
>
<div
class=
"space-y-4"
>
<div
class=
"card p-4"
>
<div
class=
"card p-4"
>
<div
class=
"flex items-center gap-4"
>
<div
class=
"flex flex-wrap items-center gap-4"
>
<span
class=
"text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{
t
(
'
admin.dashboard.granularity
'
)
}}
:
</span>
<div
class=
"flex items-center gap-2"
>
<div
class=
"w-28"
>
<span
class=
"text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{
t
(
'
admin.dashboard.timeRange
'
)
}}
:
</span>
<Select
v-model=
"granularity"
:options=
"granularityOptions"
@
change=
"loadChartData"
/>
<DateRangePicker
v-model:start-date=
"startDate"
v-model:end-date=
"endDate"
@
change=
"onDateRangeChange"
/>
</div>
<div
class=
"ml-auto flex items-center gap-2"
>
<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>
</div>
</div>
</div>
...
@@ -41,7 +51,7 @@
...
@@ -41,7 +51,7 @@
<TokenUsageTrend
:trend-data=
"trendData"
:loading=
"chartsLoading"
/>
<TokenUsageTrend
:trend-data=
"trendData"
:loading=
"chartsLoading"
/>
</div>
</div>
</div>
</div>
<UsageFilters
v-model=
"filters"
v-model
:start
D
ate=
"startDate"
v-model
:end
D
ate=
"endDate"
:exporting=
"exporting"
@
change=
"applyFilters"
@
refresh=
"refreshData"
@
reset=
"resetFilters"
@
cleanup=
"openCleanupDialog"
@
export=
"exportToExcel"
>
<UsageFilters
v-model=
"filters"
:start
-d
ate=
"startDate"
:end
-d
ate=
"endDate"
:exporting=
"exporting"
@
change=
"applyFilters"
@
refresh=
"refreshData"
@
reset=
"resetFilters"
@
cleanup=
"openCleanupDialog"
@
export=
"exportToExcel"
>
<template
#after-reset
>
<template
#after-reset
>
<div
class=
"relative"
ref=
"columnDropdownRef"
>
<div
class=
"relative"
ref=
"columnDropdownRef"
>
<button
<button
...
@@ -106,7 +116,7 @@ import { useRoute } from 'vue-router'
...
@@ -106,7 +116,7 @@ import { useRoute } from 'vue-router'
import
{
useAppStore
}
from
'
@/stores/app
'
;
import
{
adminAPI
}
from
'
@/api/admin
'
;
import
{
adminUsageAPI
}
from
'
@/api/admin/usage
'
import
{
useAppStore
}
from
'
@/stores/app
'
;
import
{
adminAPI
}
from
'
@/api/admin
'
;
import
{
adminUsageAPI
}
from
'
@/api/admin/usage
'
import
{
formatReasoningEffort
}
from
'
@/utils/format
'
import
{
formatReasoningEffort
}
from
'
@/utils/format
'
import
{
resolveUsageRequestType
,
requestTypeToLegacyStream
}
from
'
@/utils/usageRequestType
'
import
{
resolveUsageRequestType
,
requestTypeToLegacyStream
}
from
'
@/utils/usageRequestType
'
import
AppLayout
from
'
@/components/layout/AppLayout.vue
'
;
import
Pagination
from
'
@/components/common/Pagination.vue
'
;
import
Select
from
'
@/components/common/Select.vue
'
import
AppLayout
from
'
@/components/layout/AppLayout.vue
'
;
import
Pagination
from
'
@/components/common/Pagination.vue
'
;
import
Select
from
'
@/components/common/Select.vue
'
;
import
DateRangePicker
from
'
@/components/common/DateRangePicker.vue
'
import
UsageStatsCards
from
'
@/components/admin/usage/UsageStatsCards.vue
'
;
import
UsageFilters
from
'
@/components/admin/usage/UsageFilters.vue
'
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
'
import
UsageTable
from
'
@/components/admin/usage/UsageTable.vue
'
;
import
UsageExportProgress
from
'
@/components/admin/usage/UsageExportProgress.vue
'
import
UsageCleanupDialog
from
'
@/components/admin/usage/UsageCleanupDialog.vue
'
import
UsageCleanupDialog
from
'
@/components/admin/usage/UsageCleanupDialog.vue
'
...
@@ -158,9 +168,22 @@ const formatLD = (d: Date) => {
...
@@ -158,9 +168,22 @@ const formatLD = (d: Date) => {
const
day
=
String
(
d
.
getDate
()).
padStart
(
2
,
'
0
'
)
const
day
=
String
(
d
.
getDate
()).
padStart
(
2
,
'
0
'
)
return
`
${
year
}
-
${
month
}
-
${
day
}
`
return
`
${
year
}
-
${
month
}
-
${
day
}
`
}
}
const
getTodayLocalDate
=
()
=>
formatLD
(
new
Date
())
const
getLast24HoursRangeDates
=
():
{
start
:
string
;
end
:
string
}
=>
{
const
getGranularityForRange
=
(
start
:
string
,
end
:
string
):
'
day
'
|
'
hour
'
=>
start
===
end
?
'
hour
'
:
'
day
'
const
end
=
new
Date
()
const
startDate
=
ref
(
getTodayLocalDate
());
const
endDate
=
ref
(
getTodayLocalDate
())
const
start
=
new
Date
(
end
.
getTime
()
-
24
*
60
*
60
*
1000
)
return
{
start
:
formatLD
(
start
),
end
:
formatLD
(
end
)
}
}
const
getGranularityForRange
=
(
start
:
string
,
end
:
string
):
'
day
'
|
'
hour
'
=>
{
const
startTime
=
new
Date
(
`
${
start
}
T00:00:00`
).
getTime
()
const
endTime
=
new
Date
(
`
${
end
}
T00:00:00`
).
getTime
()
const
daysDiff
=
Math
.
ceil
((
endTime
-
startTime
)
/
(
1000
*
60
*
60
*
24
))
return
daysDiff
<=
1
?
'
hour
'
:
'
day
'
}
const
defaultRange
=
getLast24HoursRangeDates
()
const
startDate
=
ref
(
defaultRange
.
start
);
const
endDate
=
ref
(
defaultRange
.
end
)
const
filters
=
ref
<
AdminUsageQueryParams
>
({
user_id
:
undefined
,
model
:
undefined
,
group_id
:
undefined
,
request_type
:
undefined
,
billing_type
:
null
,
start_date
:
startDate
.
value
,
end_date
:
endDate
.
value
})
const
filters
=
ref
<
AdminUsageQueryParams
>
({
user_id
:
undefined
,
model
:
undefined
,
group_id
:
undefined
,
request_type
:
undefined
,
billing_type
:
null
,
start_date
:
startDate
.
value
,
end_date
:
endDate
.
value
})
const
pagination
=
reactive
({
page
:
1
,
page_size
:
20
,
total
:
0
})
const
pagination
=
reactive
({
page
:
1
,
page_size
:
20
,
total
:
0
})
...
@@ -197,6 +220,18 @@ const applyRouteQueryFilters = () => {
...
@@ -197,6 +220,18 @@ const applyRouteQueryFilters = () => {
granularity
.
value
=
getGranularityForRange
(
startDate
.
value
,
endDate
.
value
)
granularity
.
value
=
getGranularityForRange
(
startDate
.
value
,
endDate
.
value
)
}
}
const
onDateRangeChange
=
(
range
:
{
startDate
:
string
;
endDate
:
string
;
preset
:
string
|
null
})
=>
{
startDate
.
value
=
range
.
startDate
endDate
.
value
=
range
.
endDate
filters
.
value
=
{
...
filters
.
value
,
start_date
:
range
.
startDate
,
end_date
:
range
.
endDate
}
granularity
.
value
=
getGranularityForRange
(
range
.
startDate
,
range
.
endDate
)
applyFilters
()
}
const
loadLogs
=
async
()
=>
{
const
loadLogs
=
async
()
=>
{
abortController
?.
abort
();
const
c
=
new
AbortController
();
abortController
=
c
;
loading
.
value
=
true
abortController
?.
abort
();
const
c
=
new
AbortController
();
abortController
=
c
;
loading
.
value
=
true
try
{
try
{
...
@@ -260,7 +295,14 @@ const loadChartData = async () => {
...
@@ -260,7 +295,14 @@ const loadChartData = async () => {
}
}
const
applyFilters
=
()
=>
{
pagination
.
page
=
1
;
loadLogs
();
loadStats
();
loadChartData
()
}
const
applyFilters
=
()
=>
{
pagination
.
page
=
1
;
loadLogs
();
loadStats
();
loadChartData
()
}
const
refreshData
=
()
=>
{
loadLogs
();
loadStats
();
loadChartData
()
}
const
refreshData
=
()
=>
{
loadLogs
();
loadStats
();
loadChartData
()
}
const
resetFilters
=
()
=>
{
startDate
.
value
=
getTodayLocalDate
();
endDate
.
value
=
getTodayLocalDate
();
filters
.
value
=
{
start_date
:
startDate
.
value
,
end_date
:
endDate
.
value
,
request_type
:
undefined
,
billing_type
:
null
};
granularity
.
value
=
getGranularityForRange
(
startDate
.
value
,
endDate
.
value
);
applyFilters
()
}
const
resetFilters
=
()
=>
{
const
range
=
getLast24HoursRangeDates
()
startDate
.
value
=
range
.
start
endDate
.
value
=
range
.
end
filters
.
value
=
{
start_date
:
startDate
.
value
,
end_date
:
endDate
.
value
,
request_type
:
undefined
,
billing_type
:
null
}
granularity
.
value
=
getGranularityForRange
(
startDate
.
value
,
endDate
.
value
)
applyFilters
()
}
const
handlePageChange
=
(
p
:
number
)
=>
{
pagination
.
page
=
p
;
loadLogs
()
}
const
handlePageChange
=
(
p
:
number
)
=>
{
pagination
.
page
=
p
;
loadLogs
()
}
const
handlePageSizeChange
=
(
s
:
number
)
=>
{
pagination
.
page_size
=
s
;
pagination
.
page
=
1
;
loadLogs
()
}
const
handlePageSizeChange
=
(
s
:
number
)
=>
{
pagination
.
page_size
=
s
;
pagination
.
page
=
1
;
loadLogs
()
}
const
cancelExport
=
()
=>
exportAbortController
?.
abort
()
const
cancelExport
=
()
=>
exportAbortController
?.
abort
()
...
...
frontend/src/views/admin/__tests__/DashboardView.spec.ts
0 → 100644
View file @
9c1f4b8e
import
{
beforeEach
,
describe
,
expect
,
it
,
vi
}
from
'
vitest
'
import
{
flushPromises
,
mount
}
from
'
@vue/test-utils
'
import
type
{
DashboardStats
}
from
'
@/types
'
import
DashboardView
from
'
../DashboardView.vue
'
const
{
getSnapshotV2
,
getUserUsageTrend
,
getUserSpendingRanking
}
=
vi
.
hoisted
(()
=>
({
getSnapshotV2
:
vi
.
fn
(),
getUserUsageTrend
:
vi
.
fn
(),
getUserSpendingRanking
:
vi
.
fn
()
}))
vi
.
mock
(
'
@/api/admin
'
,
()
=>
({
adminAPI
:
{
dashboard
:
{
getSnapshotV2
,
getUserUsageTrend
,
getUserSpendingRanking
}
}
}))
vi
.
mock
(
'
@/stores/app
'
,
()
=>
({
useAppStore
:
()
=>
({
showError
:
vi
.
fn
()
})
}))
vi
.
mock
(
'
vue-router
'
,
()
=>
({
useRouter
:
()
=>
({
push
:
vi
.
fn
()
})
}))
vi
.
mock
(
'
vue-i18n
'
,
async
()
=>
{
const
actual
=
await
vi
.
importActual
<
typeof
import
(
'
vue-i18n
'
)
>
(
'
vue-i18n
'
)
return
{
...
actual
,
useI18n
:
()
=>
({
t
:
(
key
:
string
)
=>
key
})
}
})
const
formatLocalDate
=
(
date
:
Date
):
string
=>
{
const
year
=
date
.
getFullYear
()
const
month
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'
0
'
)
const
day
=
String
(
date
.
getDate
()).
padStart
(
2
,
'
0
'
)
return
`
${
year
}
-
${
month
}
-
${
day
}
`
}
const
createDashboardStats
=
():
DashboardStats
=>
({
total_users
:
0
,
today_new_users
:
0
,
active_users
:
0
,
hourly_active_users
:
0
,
stats_updated_at
:
''
,
stats_stale
:
false
,
total_api_keys
:
0
,
active_api_keys
:
0
,
total_accounts
:
0
,
normal_accounts
:
0
,
error_accounts
:
0
,
ratelimit_accounts
:
0
,
overload_accounts
:
0
,
total_requests
:
0
,
total_input_tokens
:
0
,
total_output_tokens
:
0
,
total_cache_creation_tokens
:
0
,
total_cache_read_tokens
:
0
,
total_tokens
:
0
,
total_cost
:
0
,
total_actual_cost
:
0
,
today_requests
:
0
,
today_input_tokens
:
0
,
today_output_tokens
:
0
,
today_cache_creation_tokens
:
0
,
today_cache_read_tokens
:
0
,
today_tokens
:
0
,
today_cost
:
0
,
today_actual_cost
:
0
,
average_duration_ms
:
0
,
uptime
:
0
,
rpm
:
0
,
tpm
:
0
})
describe
(
'
admin DashboardView
'
,
()
=>
{
beforeEach
(()
=>
{
getSnapshotV2
.
mockReset
()
getUserUsageTrend
.
mockReset
()
getUserSpendingRanking
.
mockReset
()
getSnapshotV2
.
mockResolvedValue
({
stats
:
createDashboardStats
(),
trend
:
[],
models
:
[]
})
getUserUsageTrend
.
mockResolvedValue
({
trend
:
[],
start_date
:
''
,
end_date
:
''
,
granularity
:
'
hour
'
})
getUserSpendingRanking
.
mockResolvedValue
({
ranking
:
[],
total_actual_cost
:
0
,
total_requests
:
0
,
total_tokens
:
0
,
start_date
:
''
,
end_date
:
''
})
})
it
(
'
uses last 24 hours as default dashboard range
'
,
async
()
=>
{
mount
(
DashboardView
,
{
global
:
{
stubs
:
{
AppLayout
:
{
template
:
'
<div><slot /></div>
'
},
LoadingSpinner
:
true
,
Icon
:
true
,
DateRangePicker
:
true
,
Select
:
true
,
ModelDistributionChart
:
true
,
TokenUsageTrend
:
true
,
Line
:
true
}
}
})
await
flushPromises
()
const
now
=
new
Date
()
const
yesterday
=
new
Date
(
now
.
getTime
()
-
24
*
60
*
60
*
1000
)
expect
(
getSnapshotV2
).
toHaveBeenCalledTimes
(
1
)
expect
(
getSnapshotV2
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
start_date
:
formatLocalDate
(
yesterday
),
end_date
:
formatLocalDate
(
now
),
granularity
:
'
hour
'
}))
})
})
frontend/src/views/admin/__tests__/UsageView.spec.ts
View file @
9c1f4b8e
...
@@ -19,11 +19,19 @@ const { list, getStats, getSnapshotV2, getById } = vi.hoisted(() => {
...
@@ -19,11 +19,19 @@ const { list, getStats, getSnapshotV2, getById } = vi.hoisted(() => {
})
})
const
messages
:
Record
<
string
,
string
>
=
{
const
messages
:
Record
<
string
,
string
>
=
{
'
admin.dashboard.timeRange
'
:
'
Time Range
'
,
'
admin.dashboard.day
'
:
'
Day
'
,
'
admin.dashboard.day
'
:
'
Day
'
,
'
admin.dashboard.hour
'
:
'
Hour
'
,
'
admin.dashboard.hour
'
:
'
Hour
'
,
'
admin.usage.failedToLoadUser
'
:
'
Failed to load user
'
,
'
admin.usage.failedToLoadUser
'
:
'
Failed to load user
'
,
}
}
const
formatLocalDate
=
(
date
:
Date
):
string
=>
{
const
year
=
date
.
getFullYear
()
const
month
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'
0
'
)
const
day
=
String
(
date
.
getDate
()).
padStart
(
2
,
'
0
'
)
return
`
${
year
}
-
${
month
}
-
${
day
}
`
}
vi
.
mock
(
'
@/api/admin
'
,
()
=>
({
vi
.
mock
(
'
@/api/admin
'
,
()
=>
({
adminAPI
:
{
adminAPI
:
{
usage
:
{
usage
:
{
...
@@ -68,6 +76,12 @@ vi.mock('vue-i18n', async () => {
...
@@ -68,6 +76,12 @@ vi.mock('vue-i18n', async () => {
}
}
})
})
vi
.
mock
(
'
vue-router
'
,
()
=>
({
useRoute
:
()
=>
({
query
:
{}
})
}))
const
AppLayoutStub
=
{
template
:
'
<div><slot /></div>
'
}
const
AppLayoutStub
=
{
template
:
'
<div><slot /></div>
'
}
const
UsageFiltersStub
=
{
template
:
'
<div><slot name="after-reset" /></div>
'
}
const
UsageFiltersStub
=
{
template
:
'
<div><slot name="after-reset" /></div>
'
}
const
ModelDistributionChartStub
=
{
const
ModelDistributionChartStub
=
{
...
@@ -138,6 +152,7 @@ describe('admin UsageView distribution metric toggles', () => {
...
@@ -138,6 +152,7 @@ describe('admin UsageView distribution metric toggles', () => {
UserBalanceHistoryModal
:
true
,
UserBalanceHistoryModal
:
true
,
Pagination
:
true
,
Pagination
:
true
,
Select
:
true
,
Select
:
true
,
DateRangePicker
:
true
,
Icon
:
true
,
Icon
:
true
,
TokenUsageTrend
:
true
,
TokenUsageTrend
:
true
,
ModelDistributionChart
:
ModelDistributionChartStub
,
ModelDistributionChart
:
ModelDistributionChartStub
,
...
@@ -150,6 +165,13 @@ describe('admin UsageView distribution metric toggles', () => {
...
@@ -150,6 +165,13 @@ describe('admin UsageView distribution metric toggles', () => {
await
flushPromises
()
await
flushPromises
()
expect
(
getSnapshotV2
).
toHaveBeenCalledTimes
(
1
)
expect
(
getSnapshotV2
).
toHaveBeenCalledTimes
(
1
)
const
now
=
new
Date
()
const
yesterday
=
new
Date
(
now
.
getTime
()
-
24
*
60
*
60
*
1000
)
expect
(
getSnapshotV2
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
start_date
:
formatLocalDate
(
yesterday
),
end_date
:
formatLocalDate
(
now
),
granularity
:
'
hour
'
}))
const
modelChart
=
wrapper
.
find
(
'
[data-test="model-chart"]
'
)
const
modelChart
=
wrapper
.
find
(
'
[data-test="model-chart"]
'
)
const
groupChart
=
wrapper
.
find
(
'
[data-test="group-chart"]
'
)
const
groupChart
=
wrapper
.
find
(
'
[data-test="group-chart"]
'
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment