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
94e067a2
Unverified
Commit
94e067a2
authored
Mar 16, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 16, 2026
Browse files
Merge pull request #1040 from 0xObjc/codex/fix-user-spending-ranking-others
fix(admin): polish spending ranking and usage defaults
parents
4293c891
8147866c
Changes
10
Show whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/dashboard_handler.go
View file @
94e067a2
...
...
@@ -512,6 +512,8 @@ func (h *DashboardHandler) GetUserSpendingRanking(c *gin.Context) {
payload
:=
gin
.
H
{
"ranking"
:
ranking
.
Ranking
,
"total_actual_cost"
:
ranking
.
TotalActualCost
,
"total_requests"
:
ranking
.
TotalRequests
,
"total_tokens"
:
ranking
.
TotalTokens
,
"start_date"
:
startTime
.
Format
(
"2006-01-02"
),
"end_date"
:
endTime
.
Add
(
-
24
*
time
.
Hour
)
.
Format
(
"2006-01-02"
),
}
...
...
backend/internal/handler/admin/dashboard_handler_request_type_test.go
View file @
94e067a2
...
...
@@ -61,6 +61,8 @@ func (s *dashboardUsageRepoCapture) GetUserSpendingRanking(
return
&
usagestats
.
UserSpendingRankingResponse
{
Ranking
:
s
.
ranking
,
TotalActualCost
:
s
.
rankingTotal
,
TotalRequests
:
44
,
TotalTokens
:
1234
,
},
nil
}
...
...
@@ -164,6 +166,8 @@ func TestDashboardUsersRankingLimitAndCache(t *testing.T) {
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
50
,
repo
.
rankingLimit
)
require
.
Contains
(
t
,
rec
.
Body
.
String
(),
"
\"
total_actual_cost
\"
:88.8"
)
require
.
Contains
(
t
,
rec
.
Body
.
String
(),
"
\"
total_requests
\"
:44"
)
require
.
Contains
(
t
,
rec
.
Body
.
String
(),
"
\"
total_tokens
\"
:1234"
)
require
.
Equal
(
t
,
"miss"
,
rec
.
Header
()
.
Get
(
"X-Snapshot-Cache"
))
req2
:=
httptest
.
NewRequest
(
http
.
MethodGet
,
"/admin/dashboard/users-ranking?limit=100&start_date=2025-01-01&end_date=2025-01-02"
,
nil
)
...
...
backend/internal/pkg/usagestats/usage_log_types.go
View file @
94e067a2
...
...
@@ -125,6 +125,8 @@ type UserSpendingRankingItem struct {
type
UserSpendingRankingResponse
struct
{
Ranking
[]
UserSpendingRankingItem
`json:"ranking"`
TotalActualCost
float64
`json:"total_actual_cost"`
TotalRequests
int64
`json:"total_requests"`
TotalTokens
int64
`json:"total_tokens"`
}
// APIKeyUsageTrendPoint represents API key usage trend data point
...
...
backend/internal/repository/usage_log_repo.go
View file @
94e067a2
...
...
@@ -2161,7 +2161,9 @@ func (r *usageLogRepository) GetUserSpendingRanking(ctx context.Context, startTi
actual_cost,
requests,
tokens,
COALESCE(SUM(actual_cost) OVER (), 0) as total_actual_cost
COALESCE(SUM(actual_cost) OVER (), 0) as total_actual_cost,
COALESCE(SUM(requests) OVER (), 0) as total_requests,
COALESCE(SUM(tokens) OVER (), 0) as total_tokens
FROM user_spend
ORDER BY actual_cost DESC, tokens DESC, user_id ASC
LIMIT $3
...
...
@@ -2172,7 +2174,9 @@ func (r *usageLogRepository) GetUserSpendingRanking(ctx context.Context, startTi
actual_cost,
requests,
tokens,
total_actual_cost
total_actual_cost,
total_requests,
total_tokens
FROM ranked
ORDER BY actual_cost DESC, tokens DESC, user_id ASC
`
...
...
@@ -2190,9 +2194,11 @@ func (r *usageLogRepository) GetUserSpendingRanking(ctx context.Context, startTi
ranking
:=
make
([]
UserSpendingRankingItem
,
0
)
totalActualCost
:=
0.0
totalRequests
:=
int64
(
0
)
totalTokens
:=
int64
(
0
)
for
rows
.
Next
()
{
var
row
UserSpendingRankingItem
if
err
=
rows
.
Scan
(
&
row
.
UserID
,
&
row
.
Email
,
&
row
.
ActualCost
,
&
row
.
Requests
,
&
row
.
Tokens
,
&
totalActualCost
);
err
!=
nil
{
if
err
=
rows
.
Scan
(
&
row
.
UserID
,
&
row
.
Email
,
&
row
.
ActualCost
,
&
row
.
Requests
,
&
row
.
Tokens
,
&
totalActualCost
,
&
totalRequests
,
&
totalTokens
);
err
!=
nil
{
return
nil
,
err
}
ranking
=
append
(
ranking
,
row
)
...
...
@@ -2204,6 +2210,8 @@ func (r *usageLogRepository) GetUserSpendingRanking(ctx context.Context, startTi
return
&
UserSpendingRankingResponse
{
Ranking
:
ranking
,
TotalActualCost
:
totalActualCost
,
TotalRequests
:
totalRequests
,
TotalTokens
:
totalTokens
,
},
nil
}
...
...
backend/internal/repository/usage_log_repo_request_type_test.go
View file @
94e067a2
...
...
@@ -259,10 +259,10 @@ func TestUsageLogRepositoryGetUserSpendingRanking(t *testing.T) {
start
:=
time
.
Date
(
2025
,
1
,
1
,
0
,
0
,
0
,
0
,
time
.
UTC
)
end
:=
start
.
Add
(
24
*
time
.
Hour
)
rows
:=
sqlmock
.
NewRows
([]
string
{
"user_id"
,
"email"
,
"actual_cost"
,
"requests"
,
"tokens"
,
"total_actual_cost"
})
.
AddRow
(
int64
(
2
),
"beta@example.com"
,
12.5
,
int64
(
9
),
int64
(
900
),
40.0
)
.
AddRow
(
int64
(
1
),
"alpha@example.com"
,
12.5
,
int64
(
8
),
int64
(
800
),
40.0
)
.
AddRow
(
int64
(
3
),
"gamma@example.com"
,
4.25
,
int64
(
5
),
int64
(
300
),
40.0
)
rows
:=
sqlmock
.
NewRows
([]
string
{
"user_id"
,
"email"
,
"actual_cost"
,
"requests"
,
"tokens"
,
"total_actual_cost"
,
"total_requests"
,
"total_tokens"
})
.
AddRow
(
int64
(
2
),
"beta@example.com"
,
12.5
,
int64
(
9
),
int64
(
900
),
40.0
,
int64
(
30
),
int64
(
2600
)
)
.
AddRow
(
int64
(
1
),
"alpha@example.com"
,
12.5
,
int64
(
8
),
int64
(
800
),
40.0
,
int64
(
30
),
int64
(
2600
)
)
.
AddRow
(
int64
(
3
),
"gamma@example.com"
,
4.25
,
int64
(
5
),
int64
(
300
),
40.0
,
int64
(
30
),
int64
(
2600
)
)
mock
.
ExpectQuery
(
"WITH user_spend AS
\\
("
)
.
WithArgs
(
start
,
end
,
12
)
.
...
...
@@ -277,6 +277,8 @@ func TestUsageLogRepositoryGetUserSpendingRanking(t *testing.T) {
{
UserID
:
3
,
Email
:
"gamma@example.com"
,
ActualCost
:
4.25
,
Requests
:
5
,
Tokens
:
300
},
},
TotalActualCost
:
40.0
,
TotalRequests
:
30
,
TotalTokens
:
2600
,
},
got
)
require
.
NoError
(
t
,
mock
.
ExpectationsWereMet
())
}
...
...
frontend/src/components/charts/ModelDistributionChart.vue
View file @
94e067a2
...
...
@@ -127,7 +127,7 @@
>
{{
t
(
'
admin.dashboard.failedToLoad
'
)
}}
</div>
<div
v-else-if=
"rankingItems.length > 0 && rankingChartData"
class=
"flex items-center gap-6"
>
<div
v-else-if=
"ranking
Display
Items.length > 0 && rankingChartData"
class=
"flex items-center gap-6"
>
<div
class=
"h-48 w-48"
>
<Doughnut
:data=
"rankingChartData"
:options=
"rankingDoughnutOptions"
/>
</div>
...
...
@@ -143,21 +143,24 @@
</thead>
<tbody>
<tr
v-for=
"(item, index) in rankingItems"
:key=
"`$
{item.user_id}-${index}`"
class="cursor-pointer border-t border-gray-100 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-dark-700/40"
@click="emit('ranking-click', item)"
v-for=
"(item, index) in rankingDisplayItems"
:key=
"item.isOther ? 'others' : `$
{item.user_id}-${index}`"
class="border-t border-gray-100 transition-colors dark:border-gray-700"
:class="item.isOther
? 'bg-gray-50/70 dark:bg-dark-700/20'
: 'cursor-pointer hover:bg-gray-50 dark:hover:bg-dark-700/40'"
@click="item.isOther ? undefined : emit('ranking-click', item)"
>
<td
class=
"py-1.5"
>
<div
class=
"flex min-w-0 items-center gap-2"
>
<span
class=
"shrink-0 text-[11px] font-semibold text-gray-500 dark:text-gray-400"
>
#
{{
index
+
1
}}
{{
item
.
isOther
?
'
Σ
'
:
`#${
index + 1
}
`
}}
<
/span
>
<
span
class
=
"
block max-w-[140px] truncate font-medium text-gray-900 dark:text-white
"
:title=
"getRanking
User
Label(item)"
:
title
=
"
getRanking
Row
Label(item)
"
>
{{
getRanking
User
Label
(
item
)
}}
{{
getRanking
Row
Label
(
item
)
}}
<
/span
>
<
/div
>
<
/td
>
...
...
@@ -197,11 +200,14 @@ ChartJS.register(ArcElement, Tooltip, Legend)
const
{
t
}
=
useI18n
()
type
DistributionMetric
=
'
tokens
'
|
'
actual_cost
'
type
RankingDisplayItem
=
UserSpendingRankingItem
&
{
isOther
?:
boolean
}
const
props
=
withDefaults
(
defineProps
<
{
modelStats
:
ModelStat
[]
enableRankingView
?:
boolean
rankingItems
?:
UserSpendingRankingItem
[]
rankingTotalActualCost
?:
number
rankingTotalRequests
?:
number
rankingTotalTokens
?:
number
loading
?:
boolean
metric
?:
DistributionMetric
showMetricToggle
?:
boolean
...
...
@@ -211,6 +217,8 @@ const props = withDefaults(defineProps<{
enableRankingView
:
false
,
rankingItems
:
()
=>
[],
rankingTotalActualCost
:
0
,
rankingTotalRequests
:
0
,
rankingTotalTokens
:
0
,
loading
:
false
,
metric
:
'
tokens
'
,
showMetricToggle
:
false
,
...
...
@@ -266,14 +274,14 @@ const chartData = computed(() => {
const
rankingChartData
=
computed
(()
=>
{
if
(
!
props
.
rankingItems
?.
length
)
return
null
const
rankedTotal
=
props
.
rankingItems
.
reduce
((
sum
,
item
)
=>
sum
+
item
.
actual_cost
,
0
)
const
otherActualCost
=
Math
.
max
((
props
.
rankingTotalActualCost
||
0
)
-
rankedTotal
,
0
)
const
labels
=
props
.
rankingItems
.
map
((
item
,
index
)
=>
`#${index + 1
}
${getRankingUserLabel(item)
}
`
)
const
data
=
props
.
rankingItems
.
map
((
item
)
=>
item
.
actual_cost
)
const
backgroundColor
=
chartColors
.
slice
(
0
,
props
.
rankingItems
.
length
)
if
(
other
ActualCost
>
0.000001
)
{
if
(
other
RankingItem
.
value
)
{
labels
.
push
(
t
(
'
admin.dashboard.spendingRankingOther
'
))
data
.
push
(
otherActualCost
)
data
.
push
(
otherRankingItem
.
value
.
actual_cost
)
backgroundColor
.
push
(
'
#94a3b8
'
)
}
return
{
...
...
@@ -281,13 +289,43 @@ const rankingChartData = computed(() => {
datasets
:
[
{
data
,
backgroundColor
:
chartColors
.
slice
(
0
,
data
.
length
)
,
backgroundColor
,
borderWidth
:
0
}
]
}
}
)
const
otherRankingItem
=
computed
<
RankingDisplayItem
|
null
>
(()
=>
{
if
(
!
props
.
rankingItems
?.
length
)
return
null
const
rankedActualCost
=
props
.
rankingItems
.
reduce
((
sum
,
item
)
=>
sum
+
item
.
actual_cost
,
0
)
const
rankedRequests
=
props
.
rankingItems
.
reduce
((
sum
,
item
)
=>
sum
+
item
.
requests
,
0
)
const
rankedTokens
=
props
.
rankingItems
.
reduce
((
sum
,
item
)
=>
sum
+
item
.
tokens
,
0
)
const
otherActualCost
=
Math
.
max
((
props
.
rankingTotalActualCost
||
0
)
-
rankedActualCost
,
0
)
const
otherRequests
=
Math
.
max
((
props
.
rankingTotalRequests
||
0
)
-
rankedRequests
,
0
)
const
otherTokens
=
Math
.
max
((
props
.
rankingTotalTokens
||
0
)
-
rankedTokens
,
0
)
if
(
otherActualCost
<=
0.000001
&&
otherRequests
<=
0
&&
otherTokens
<=
0
)
return
null
return
{
user_id
:
0
,
email
:
''
,
actual_cost
:
otherActualCost
,
requests
:
otherRequests
,
tokens
:
otherTokens
,
isOther
:
true
}
}
)
const
rankingDisplayItems
=
computed
<
RankingDisplayItem
[]
>
(()
=>
{
if
(
!
props
.
rankingItems
?.
length
)
return
[]
return
otherRankingItem
.
value
?
[...
props
.
rankingItems
,
otherRankingItem
.
value
]
:
[...
props
.
rankingItems
]
}
)
const
doughnutOptions
=
computed
(()
=>
({
responsive
:
true
,
maintainAspectRatio
:
false
,
...
...
@@ -351,6 +389,11 @@ const getRankingUserLabel = (item: UserSpendingRankingItem): string => {
return
t
(
'
admin.redeem.userPrefix
'
,
{
id
:
item
.
user_id
}
)
}
const
getRankingRowLabel
=
(
item
:
RankingDisplayItem
):
string
=>
{
if
(
item
.
isOther
)
return
t
(
'
admin.dashboard.spendingRankingOther
'
)
return
getRankingUserLabel
(
item
)
}
const
formatCost
=
(
value
:
number
):
string
=>
{
if
(
value
>=
1000
)
{
return
(
value
/
1000
).
toFixed
(
2
)
+
'
K
'
...
...
frontend/src/components/charts/__tests__/ModelDistributionChart.spec.ts
View file @
94e067a2
...
...
@@ -5,6 +5,14 @@ import ModelDistributionChart from '../ModelDistributionChart.vue'
const
messages
:
Record
<
string
,
string
>
=
{
'
admin.dashboard.modelDistribution
'
:
'
Model Distribution
'
,
'
admin.dashboard.spendingRankingTitle
'
:
'
User Spending Ranking
'
,
'
admin.dashboard.viewModelDistribution
'
:
'
Model Distribution
'
,
'
admin.dashboard.viewSpendingRanking
'
:
'
User Spending Ranking
'
,
'
admin.dashboard.spendingRankingUser
'
:
'
User
'
,
'
admin.dashboard.spendingRankingRequests
'
:
'
Requests
'
,
'
admin.dashboard.spendingRankingTokens
'
:
'
Tokens
'
,
'
admin.dashboard.spendingRankingSpend
'
:
'
Spend
'
,
'
admin.dashboard.spendingRankingOther
'
:
'
Others
'
,
'
admin.dashboard.model
'
:
'
Model
'
,
'
admin.dashboard.requests
'
:
'
Requests
'
,
'
admin.dashboard.tokens
'
:
'
Tokens
'
,
...
...
@@ -13,6 +21,7 @@ const messages: Record<string, string> = {
'
admin.dashboard.metricTokens
'
:
'
By Tokens
'
,
'
admin.dashboard.metricActualCost
'
:
'
By Actual Cost
'
,
'
admin.dashboard.noDataAvailable
'
:
'
No data available
'
,
'
admin.redeem.userPrefix
'
:
'
User #{id}
'
,
}
vi
.
mock
(
'
vue-i18n
'
,
async
()
=>
{
...
...
@@ -116,4 +125,47 @@ describe('ModelDistributionChart', () => {
})
expect
(
label
).
toBe
(
'
model-b: $1.40 (87.5%)
'
)
})
it
(
'
renders Others in the spending ranking table and uses a dedicated chart color
'
,
async
()
=>
{
const
wrapper
=
mount
(
ModelDistributionChart
,
{
props
:
{
modelStats
:
[],
enableRankingView
:
true
,
rankingItems
:
[
{
user_id
:
1
,
email
:
'
alpha@example.com
'
,
actual_cost
:
12
,
requests
:
10
,
tokens
:
1000
},
{
user_id
:
2
,
email
:
'
beta@example.com
'
,
actual_cost
:
8
,
requests
:
6
,
tokens
:
600
},
],
rankingTotalActualCost
:
30
,
rankingTotalRequests
:
20
,
rankingTotalTokens
:
2000
,
},
global
:
{
stubs
:
{
LoadingSpinner
:
true
,
},
},
})
const
rankingButton
=
wrapper
.
findAll
(
'
button
'
).
find
((
button
)
=>
button
.
text
()
===
'
User Spending Ranking
'
)
expect
(
rankingButton
).
toBeTruthy
()
await
rankingButton
!
.
trigger
(
'
click
'
)
const
chartData
=
JSON
.
parse
(
wrapper
.
find
(
'
.chart-data
'
).
text
())
expect
(
chartData
.
labels
).
toEqual
([
'
#1 alpha@example.com
'
,
'
#2 beta@example.com
'
,
'
Others
'
,
])
expect
(
chartData
.
datasets
[
0
].
data
).
toEqual
([
12
,
8
,
10
])
expect
(
chartData
.
datasets
[
0
].
backgroundColor
[
0
]).
toBe
(
'
#3b82f6
'
)
expect
(
chartData
.
datasets
[
0
].
backgroundColor
[
2
]).
toBe
(
'
#94a3b8
'
)
expect
(
chartData
.
datasets
[
0
].
backgroundColor
[
2
]).
not
.
toBe
(
chartData
.
datasets
[
0
].
backgroundColor
[
0
])
const
rows
=
wrapper
.
findAll
(
'
tbody tr
'
)
expect
(
rows
).
toHaveLength
(
3
)
expect
(
rows
[
2
].
text
()).
toContain
(
'
Others
'
)
expect
(
rows
[
2
].
text
()).
toContain
(
'
4
'
)
expect
(
rows
[
2
].
text
()).
toContain
(
'
400
'
)
expect
(
rows
[
2
].
text
()).
toContain
(
'
$10.00
'
)
})
})
frontend/src/types/index.ts
View file @
94e067a2
...
...
@@ -1209,6 +1209,8 @@ export interface UserSpendingRankingItem {
export
interface
UserSpendingRankingResponse
{
ranking
:
UserSpendingRankingItem
[]
total_actual_cost
:
number
total_requests
:
number
total_tokens
:
number
start_date
:
string
end_date
:
string
}
...
...
frontend/src/views/admin/DashboardView.vue
View file @
94e067a2
...
...
@@ -241,6 +241,8 @@
:enable-ranking-view=
"true"
:ranking-items=
"rankingItems"
:ranking-total-actual-cost=
"rankingTotalActualCost"
:ranking-total-requests=
"rankingTotalRequests"
:ranking-total-tokens=
"rankingTotalTokens"
:loading=
"chartsLoading"
:ranking-loading=
"rankingLoading"
:ranking-error=
"rankingError"
...
...
@@ -334,6 +336,8 @@ const modelStats = ref<ModelStat[]>([])
const
userTrend
=
ref
<
UserUsageTrendPoint
[]
>
([])
const
rankingItems
=
ref
<
UserSpendingRankingItem
[]
>
([])
const
rankingTotalActualCost
=
ref
(
0
)
const
rankingTotalRequests
=
ref
(
0
)
const
rankingTotalTokens
=
ref
(
0
)
let
chartLoadSeq
=
0
let
usersTrendLoadSeq
=
0
let
rankingLoadSeq
=
0
...
...
@@ -347,7 +351,7 @@ const formatLocalDate = (date: Date): string => {
const
getTodayLocalDate
=
()
=>
formatLocalDate
(
new
Date
())
// Date range
const
granularity
=
ref
<
'
day
'
|
'
hour
'
>
(
'
day
'
)
const
granularity
=
ref
<
'
day
'
|
'
hour
'
>
(
'
hour
'
)
const
startDate
=
ref
(
getTodayLocalDate
())
const
endDate
=
ref
(
getTodayLocalDate
())
...
...
@@ -630,11 +634,15 @@ const loadUserSpendingRanking = async () => {
if
(
currentSeq
!==
rankingLoadSeq
)
return
rankingItems
.
value
=
response
.
ranking
||
[]
rankingTotalActualCost
.
value
=
response
.
total_actual_cost
||
0
rankingTotalRequests
.
value
=
response
.
total_requests
||
0
rankingTotalTokens
.
value
=
response
.
total_tokens
||
0
}
catch
(
error
)
{
if
(
currentSeq
!==
rankingLoadSeq
)
return
console
.
error
(
'
Error loading user spending ranking:
'
,
error
)
rankingItems
.
value
=
[]
rankingTotalActualCost
.
value
=
0
rankingTotalRequests
.
value
=
0
rankingTotalTokens
.
value
=
0
rankingError
.
value
=
true
}
finally
{
if
(
currentSeq
===
rankingLoadSeq
)
{
...
...
frontend/src/views/admin/UsageView.vue
View file @
94e067a2
...
...
@@ -122,7 +122,7 @@ type DistributionMetric = 'tokens' | 'actual_cost'
type
EndpointSource
=
'
inbound
'
|
'
upstream
'
|
'
path
'
const
route
=
useRoute
()
const
usageStats
=
ref
<
AdminUsageStatsResponse
|
null
>
(
null
);
const
usageLogs
=
ref
<
AdminUsageLog
[]
>
([]);
const
loading
=
ref
(
false
);
const
exporting
=
ref
(
false
)
const
trendData
=
ref
<
TrendDataPoint
[]
>
([]);
const
modelStats
=
ref
<
ModelStat
[]
>
([]);
const
groupStats
=
ref
<
GroupStat
[]
>
([]);
const
chartsLoading
=
ref
(
false
);
const
granularity
=
ref
<
'
day
'
|
'
hour
'
>
(
'
day
'
)
const
trendData
=
ref
<
TrendDataPoint
[]
>
([]);
const
modelStats
=
ref
<
ModelStat
[]
>
([]);
const
groupStats
=
ref
<
GroupStat
[]
>
([]);
const
chartsLoading
=
ref
(
false
);
const
granularity
=
ref
<
'
day
'
|
'
hour
'
>
(
'
hour
'
)
const
modelDistributionMetric
=
ref
<
DistributionMetric
>
(
'
tokens
'
)
const
groupDistributionMetric
=
ref
<
DistributionMetric
>
(
'
tokens
'
)
const
endpointDistributionMetric
=
ref
<
DistributionMetric
>
(
'
tokens
'
)
...
...
@@ -159,6 +159,7 @@ const formatLD = (d: Date) => {
return
`
${
year
}
-
${
month
}
-
${
day
}
`
}
const
getTodayLocalDate
=
()
=>
formatLD
(
new
Date
())
const
getGranularityForRange
=
(
start
:
string
,
end
:
string
):
'
day
'
|
'
hour
'
=>
start
===
end
?
'
hour
'
:
'
day
'
const
startDate
=
ref
(
getTodayLocalDate
());
const
endDate
=
ref
(
getTodayLocalDate
())
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
})
...
...
@@ -193,6 +194,7 @@ const applyRouteQueryFilters = () => {
start_date
:
startDate
.
value
,
end_date
:
endDate
.
value
}
granularity
.
value
=
getGranularityForRange
(
startDate
.
value
,
endDate
.
value
)
}
const
loadLogs
=
async
()
=>
{
...
...
@@ -258,7 +260,7 @@ const loadChartData = async () => {
}
const
applyFilters
=
()
=>
{
pagination
.
page
=
1
;
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
=
'
day
'
;
applyFilters
()
}
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
handlePageChange
=
(
p
:
number
)
=>
{
pagination
.
page
=
p
;
loadLogs
()
}
const
handlePageSizeChange
=
(
s
:
number
)
=>
{
pagination
.
page_size
=
s
;
pagination
.
page
=
1
;
loadLogs
()
}
const
cancelExport
=
()
=>
exportAbortController
?.
abort
()
...
...
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