Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
db48108d
authored
Apr 28, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(logs): enhance usage logs table with log type indicators and improve UI elements
parent
22ef5b2f
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
138 additions
and
88 deletions
+138
-88
web/default/src/components/data-table/column-header.tsx
+1
-1
web/default/src/components/status-badge.tsx
+13
-11
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
+0
-0
web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx
+19
-9
web/default/src/features/usage-logs/components/common-logs-stats.tsx
+6
-6
web/default/src/features/usage-logs/components/usage-logs-table.tsx
+54
-15
web/default/src/lib/format.ts
+0
-1
web/default/src/styles/theme.css
+45
-45
No files found.
web/default/src/components/data-table/column-header.tsx
View file @
db48108d
...
...
@@ -19,7 +19,7 @@ import {
type
DataTableColumnHeaderProps
<
TData
,
TValue
>
=
React
.
HTMLAttributes
<
HTMLDivElement
>
&
{
column
:
Column
<
TData
,
TValue
>
title
:
string
title
:
React
.
ReactNode
}
export
function
DataTableColumnHeader
<
TData
,
TValue
>
({
...
...
web/default/src/components/status-badge.tsx
View file @
db48108d
...
...
@@ -69,7 +69,7 @@ export interface StatusBadgeProps extends Omit<
children
?:
React
.
ReactNode
icon
?:
LucideIcon
pulse
?:
boolean
/**
@deprecated Dot is always shown in flat design
*/
/**
When false, hides the leading dot
*/
showDot
?:
boolean
variant
?:
StatusVariant
|
null
size
?:
'sm'
|
'md'
|
'lg'
|
null
...
...
@@ -87,7 +87,7 @@ export function StatusBadge({
variant
,
size
=
'sm'
,
pulse
=
false
,
showDot
:
_showDot
,
showDot
=
true
,
rounded
:
_rounded
,
copyable
=
true
,
copyText
,
...
...
@@ -110,7 +110,7 @@ export function StatusBadge({
onClick
?.(
e
)
}
const
content
=
children
??
(
label
?
<
span
>
{
label
}
</
span
>
:
null
)
const
content
=
children
??
(
label
?
<
span
className=
'truncate'
>
{
label
}
</
span
>
:
null
)
return
(
<
span
...
...
@@ -127,14 +127,16 @@ export function StatusBadge({
title=
{
copyable
?
`Click to copy: ${copyText || label || ''}`
:
undefined
}
{
...
props
}
>
<
span
className=
{
cn
(
'inline-block size-1.5 shrink-0 rounded-full'
,
dotColorMap
[
computedVariant
]
)
}
aria
-
hidden=
'true'
/>
{
Icon
&&
<
Icon
className=
'h-3 w-3'
/>
}
{
showDot
&&
(
<
span
className=
{
cn
(
'inline-block size-1.5 shrink-0 rounded-full'
,
dotColorMap
[
computedVariant
]
)
}
aria
-
hidden=
'true'
/>
)
}
{
Icon
&&
<
Icon
className=
'size-3 shrink-0'
/>
}
{
content
}
</
span
>
)
...
...
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
View file @
db48108d
This diff is collapsed.
Click to expand it.
web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx
View file @
db48108d
import
{
useState
,
useEffect
,
useCallback
,
type
ReactNode
}
from
'react'
import
{
useNavigate
,
getRouteApi
}
from
'@tanstack/react-router'
import
{
ChevronDown
,
Eye
,
EyeOff
,
RotateCcw
,
Search
}
from
'lucide-react'
import
{
useQueryClient
,
useIsFetching
}
from
'@tanstack/react-query'
import
{
ChevronDown
,
Eye
,
EyeOff
,
Loader2
,
RotateCcw
,
Search
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
import
{
cn
}
from
'@/lib/utils'
import
{
useIsAdmin
}
from
'@/hooks/use-admin'
...
...
@@ -33,9 +34,11 @@ export function CommonLogsFilterBar({
}:
CommonLogsFilterBarProps
)
{
const
{
t
}
=
useTranslation
()
const
navigate
=
useNavigate
()
const
queryClient
=
useQueryClient
()
const
searchParams
=
route
.
useSearch
()
const
isAdmin
=
useIsAdmin
()
const
{
sensitiveVisible
,
setSensitiveVisible
}
=
useUsageLogsContext
()
const
fetchingLogs
=
useIsFetching
({
queryKey
:
[
'logs'
]
})
const
[
expanded
,
setExpanded
]
=
useState
(
false
)
const
[
filters
,
setFilters
]
=
useState
<
CommonLogFilters
>
(()
=>
{
...
...
@@ -88,14 +91,15 @@ export function CommonLogsFilterBar({
navigate
({
to
:
'/usage-logs/$section'
,
params
:
{
section
:
'common'
},
search
:
(
prev
:
Record
<
string
,
unknown
>
)
=>
({
...
prev
,
search
:
{
...
filterParams
,
...(
logType
?
{
type
:
[
logType
]
}
:
{
type
:
undefined
}),
...(
logType
?
{
type
:
[
logType
]
}
:
{}),
page
:
1
,
}
)
,
},
})
},
[
filters
,
logType
,
navigate
])
queryClient
.
invalidateQueries
({
queryKey
:
[
'logs'
]
})
queryClient
.
invalidateQueries
({
queryKey
:
[
'usage-logs-stats'
]
})
},
[
filters
,
logType
,
navigate
,
queryClient
])
const
handleReset
=
useCallback
(()
=>
{
const
{
start
,
end
}
=
getDefaultTimeRange
()
...
...
@@ -112,7 +116,9 @@ export function CommonLogsFilterBar({
endTime
:
end
.
getTime
(),
},
})
},
[
navigate
])
queryClient
.
invalidateQueries
({
queryKey
:
[
'logs'
]
})
queryClient
.
invalidateQueries
({
queryKey
:
[
'usage-logs-stats'
]
})
},
[
navigate
,
queryClient
])
const
handleKeyDown
=
useCallback
(
(
e
:
React
.
KeyboardEvent
)
=>
{
...
...
@@ -267,8 +273,12 @@ export function CommonLogsFilterBar({
<
RotateCcw
className=
'size-3.5'
/>
{
t
(
'Reset'
)
}
</
Button
>
<
Button
size=
'sm'
className=
'h-8'
onClick=
{
handleApply
}
>
<
Search
className=
'size-3.5'
/>
<
Button
size=
'sm'
className=
'h-8'
onClick=
{
handleApply
}
disabled=
{
fetchingLogs
>
0
}
>
{
fetchingLogs
>
0
?
(
<
Loader2
className=
'size-3.5 animate-spin'
/>
)
:
(
<
Search
className=
'size-3.5'
/>
)
}
{
t
(
'Search'
)
}
</
Button
>
{
viewOptions
}
...
...
web/default/src/features/usage-logs/components/common-logs-stats.tsx
View file @
db48108d
...
...
@@ -42,19 +42,19 @@ export function CommonLogsStats() {
if
(
isLoading
)
{
return
(
<
div
className=
'flex items-center gap-
1.5
'
>
<
Skeleton
className=
'h-
6 w-[126
px] rounded-md'
/>
<
Skeleton
className=
'h-
6 w-[76
px] rounded-md'
/>
<
Skeleton
className=
'h-
6 w-[92
px] rounded-md'
/>
<
div
className=
'flex items-center gap-
2
'
>
<
Skeleton
className=
'h-
7 w-[150
px] rounded-md'
/>
<
Skeleton
className=
'h-
7 w-[100
px] rounded-md'
/>
<
Skeleton
className=
'h-
7 w-[120
px] rounded-md'
/>
</
div
>
)
}
const
tagClass
=
'inline-flex h-
6 items-center rounded-md border px-2.5
text-xs font-medium shadow-xs'
'inline-flex h-
7 items-center rounded-md border px-3 py-1
text-xs font-medium shadow-xs'
return
(
<
div
className=
'flex flex-wrap items-center gap-
1.5
'
>
<
div
className=
'flex flex-wrap items-center gap-
2
'
>
<
span
className=
{
cn
(
tagClass
,
...
...
web/default/src/features/usage-logs/components/usage-logs-table.tsx
View file @
db48108d
...
...
@@ -34,7 +34,7 @@ import {
MobileCardList
,
}
from
'@/components/data-table'
import
{
PageFooterPortal
}
from
'@/components/layout'
import
{
DEFAULT_LOGS_DATA
}
from
'../constants'
import
{
DEFAULT_LOGS_DATA
,
LOG_TYPE_ENUM
}
from
'../constants'
import
{
useColumnsByCategory
}
from
'../lib/columns'
import
{
fetchLogsByCategory
}
from
'../lib/utils'
import
type
{
LogCategory
}
from
'../types'
...
...
@@ -43,6 +43,20 @@ import { CommonLogsStats } from './common-logs-stats'
const
route
=
getRouteApi
(
'/_authenticated/usage-logs/$section'
)
const
logTypeBorderColor
:
Record
<
number
,
string
>
=
{
[
LOG_TYPE_ENUM
.
TOPUP
]:
'border-l-cyan-400 dark:border-l-cyan-500'
,
[
LOG_TYPE_ENUM
.
CONSUME
]:
'border-l-emerald-400 dark:border-l-emerald-500'
,
[
LOG_TYPE_ENUM
.
MANAGE
]:
'border-l-orange-400 dark:border-l-orange-500'
,
[
LOG_TYPE_ENUM
.
SYSTEM
]:
'border-l-purple-400 dark:border-l-purple-500'
,
[
LOG_TYPE_ENUM
.
ERROR
]:
'border-l-rose-400 dark:border-l-rose-500'
,
[
LOG_TYPE_ENUM
.
REFUND
]:
'border-l-blue-400 dark:border-l-blue-500'
,
}
const
logTypeRowTint
:
Record
<
number
,
string
>
=
{
[
LOG_TYPE_ENUM
.
ERROR
]:
'bg-rose-50/40 dark:bg-rose-950/20'
,
[
LOG_TYPE_ENUM
.
REFUND
]:
'bg-blue-50/30 dark:bg-blue-950/15'
,
}
interface
UsageLogsTableProps
{
logCategory
:
LogCategory
}
...
...
@@ -150,6 +164,42 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
ensurePageInRange
(
pageCount
)
},
[
pageCount
,
ensurePageInRange
])
const
isCommon
=
logCategory
===
'common'
const
renderRows
=
()
=>
{
const
rows
=
table
.
getRowModel
().
rows
if
(
rows
.
length
===
0
)
return
null
return
rows
.
map
((
row
)
=>
{
const
logType
=
(
row
.
original
as
Record
<
string
,
unknown
>
).
type
as
|
number
|
undefined
const
borderClass
=
isCommon
&&
logType
!=
null
?
logTypeBorderColor
[
logType
]
??
'border-l-transparent'
:
''
const
tintClass
=
isCommon
&&
logType
!=
null
?
(
logTypeRowTint
[
logType
]
??
''
)
:
''
return
(
<
TableRow
key=
{
row
.
id
}
className=
{
cn
(
'!border-l-[3px] transition-colors'
,
borderClass
,
tintClass
)
}
>
{
row
.
getVisibleCells
().
map
((
cell
)
=>
(
<
TableCell
key=
{
cell
.
id
}
className=
'py-2'
>
{
flexRender
(
cell
.
column
.
columnDef
.
cell
,
cell
.
getContext
())
}
</
TableCell
>
))
}
</
TableRow
>
)
})
}
return
(
<>
<
div
className=
'space-y-4'
>
...
...
@@ -184,9 +234,9 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
)
}
>
<
Table
>
<
TableHeader
>
<
TableHeader
className=
'bg-muted/30 sticky top-0 z-10'
>
{
table
.
getHeaderGroups
().
map
((
headerGroup
)
=>
(
<
TableRow
key=
{
headerGroup
.
id
}
>
<
TableRow
key=
{
headerGroup
.
id
}
className=
'border-l-[3px] border-l-transparent'
>
{
headerGroup
.
headers
.
map
((
header
)
=>
(
<
TableHead
key=
{
header
.
id
}
colSpan=
{
header
.
colSpan
}
>
{
header
.
isPlaceholder
...
...
@@ -212,18 +262,7 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
)
}
/>
)
:
(
table
.
getRowModel
().
rows
.
map
((
row
)
=>
(
<
TableRow
key=
{
row
.
id
}
>
{
row
.
getVisibleCells
().
map
((
cell
)
=>
(
<
TableCell
key=
{
cell
.
id
}
className=
'py-2'
>
{
flexRender
(
cell
.
column
.
columnDef
.
cell
,
cell
.
getContext
()
)
}
</
TableCell
>
))
}
</
TableRow
>
))
renderRows
()
)
}
</
TableBody
>
</
Table
>
...
...
web/default/src/lib/format.ts
View file @
db48108d
...
...
@@ -162,7 +162,6 @@ export function formatTokens(tokens: number): string {
* Format use time in seconds with appropriate unit
*/
export
function
formatUseTime
(
seconds
:
number
):
string
{
if
(
seconds
<
1
)
return
`
${(
seconds
*
1000
).
toFixed
(
0
)}
ms`
if
(
seconds
<
60
)
return
`
${
seconds
.
toFixed
(
1
)}
s`
const
minutes
=
Math
.
floor
(
seconds
/
60
)
const
remainingSeconds
=
seconds
%
60
...
...
web/default/src/styles/theme.css
View file @
db48108d
@custom-variant
dark
(
&
:
is
(.
dark
*
));
:root
{
--background
:
oklch
(
1
0
0
);
--foreground
:
oklch
(
0.1
29
0.042
264.695
);
--card
:
oklch
(
1
0
0
);
--card-foreground
:
oklch
(
0.1
29
0.042
264.695
);
--popover
:
oklch
(
1
0
0
);
--popover-foreground
:
oklch
(
0.1
29
0.042
264.695
);
--primary
:
oklch
(
0.2
08
0.042
265.755
);
--primary-foreground
:
oklch
(
0.98
4
0.003
247.858
);
--secondary
:
oklch
(
0.9
68
0.007
247.896
);
--secondary-foreground
:
oklch
(
0.2
08
0.042
265.755
);
--muted
:
oklch
(
0.9
68
0.007
247.896
);
--muted-foreground
:
oklch
(
0.
554
0.046
257.417
);
--accent
:
oklch
(
0.9
68
0.007
247.896
);
--accent-foreground
:
oklch
(
0.2
08
0.042
265.755
);
--background
:
oklch
(
0.994
0.002
247.858
);
--foreground
:
oklch
(
0.1
8
0.035
264.695
);
--card
:
oklch
(
0.997
0.002
247.858
);
--card-foreground
:
oklch
(
0.1
8
0.035
264.695
);
--popover
:
oklch
(
0.997
0.002
247.858
);
--popover-foreground
:
oklch
(
0.1
8
0.035
264.695
);
--primary
:
oklch
(
0.2
55
0.042
265.755
);
--primary-foreground
:
oklch
(
0.98
5
0.004
247.858
);
--secondary
:
oklch
(
0.9
74
0.004
247.896
);
--secondary-foreground
:
oklch
(
0.2
55
0.042
265.755
);
--muted
:
oklch
(
0.9
72
0.004
247.896
);
--muted-foreground
:
oklch
(
0.
49
0.04
257.417
);
--accent
:
oklch
(
0.9
72
0.004
247.896
);
--accent-foreground
:
oklch
(
0.2
55
0.042
265.755
);
--destructive
:
oklch
(
0.577
0.245
27.325
);
--border
:
oklch
(
0.92
9
0.013
255.508
);
--input
:
oklch
(
0.92
9
0.013
255.508
);
--ring
:
oklch
(
0.
704
0.04
256.788
);
--border
:
oklch
(
0.92
5
0.01
255.508
);
--input
:
oklch
(
0.92
5
0.01
255.508
);
--ring
:
oklch
(
0.
64
0.055
256.788
);
--chart-1
:
oklch
(
0.646
0.222
41.116
);
--chart-2
:
oklch
(
0.6
0.118
184.704
);
--chart-3
:
oklch
(
0.398
0.07
227.392
);
--chart-4
:
oklch
(
0.828
0.189
84.429
);
--chart-5
:
oklch
(
0.769
0.188
70.08
);
--radius
:
0.625rem
;
--sidebar
:
var
(
--background
);
--sidebar
:
oklch
(
0.991
0.002
247.858
);
--sidebar-foreground
:
var
(
--foreground
);
--sidebar-primary
:
var
(
--primary
);
--sidebar-primary-foreground
:
var
(
--primary-foreground
);
...
...
@@ -33,44 +33,44 @@
--sidebar-accent-foreground
:
var
(
--accent-foreground
);
--sidebar-border
:
var
(
--border
);
--sidebar-ring
:
var
(
--ring
);
--skeleton-base
:
oklch
(
0.94
3
0.003
264
);
--skeleton-highlight
:
oklch
(
0.98
5
0.001
264
);
--skeleton-base
:
oklch
(
0.94
8
0.004
264
);
--skeleton-highlight
:
oklch
(
0.98
8
0.002
264
);
}
.dark
{
--background
:
oklch
(
0.1
1
0
0
);
--foreground
:
oklch
(
0.9
84
0.003
247.858
);
--card
:
oklch
(
0.
13
0
0
);
--card-foreground
:
oklch
(
0.9
84
0.003
247.858
);
--popover
:
oklch
(
0.
15
0
0
);
--popover-foreground
:
oklch
(
0.9
84
0.003
247.858
);
--primary
:
oklch
(
0.
929
0.013
255.508
);
--primary-foreground
:
oklch
(
0.2
08
0.042
265.755
);
--secondary
:
oklch
(
0.2
0
0
);
--secondary-foreground
:
oklch
(
0.9
84
0.003
247.858
);
--muted
:
oklch
(
0.2
0
0
);
--muted-foreground
:
oklch
(
0.
704
0
0
);
--accent
:
oklch
(
0.2
0
0
);
--accent-foreground
:
oklch
(
0.9
84
0.003
247.858
);
--background
:
oklch
(
0.1
65
0.012
258
);
--foreground
:
oklch
(
0.9
2
0.008
247.858
);
--card
:
oklch
(
0.
205
0.012
258
);
--card-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--popover
:
oklch
(
0.
225
0.014
258
);
--popover-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--primary
:
oklch
(
0.
87
0.018
255.508
);
--primary-foreground
:
oklch
(
0.2
35
0.042
265.755
);
--secondary
:
oklch
(
0.2
55
0.012
258
);
--secondary-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--muted
:
oklch
(
0.2
45
0.012
258
);
--muted-foreground
:
oklch
(
0.
68
0.014
257.417
);
--accent
:
oklch
(
0.2
65
0.014
258
);
--accent-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--destructive
:
oklch
(
0.704
0.191
22.216
);
--border
:
oklch
(
1
0
0
/
12%
);
--input
:
oklch
(
1
0
0
/
16%
);
--ring
:
oklch
(
0.
65
0
0
);
--border
:
oklch
(
0.31
0.014
258
);
--input
:
oklch
(
0.34
0.014
258
);
--ring
:
oklch
(
0.
58
0.025
256.788
);
--chart-1
:
oklch
(
0.488
0.243
264.376
);
--chart-2
:
oklch
(
0.696
0.17
162.48
);
--chart-3
:
oklch
(
0.769
0.188
70.08
);
--chart-4
:
oklch
(
0.627
0.265
303.9
);
--chart-5
:
oklch
(
0.645
0.246
16.439
);
--sidebar
:
oklch
(
0.
205
0
0
);
--sidebar-foreground
:
oklch
(
0.9
85
0
0
);
--sidebar
:
oklch
(
0.
185
0.012
258
);
--sidebar-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--sidebar-primary
:
oklch
(
0.75
0.14
233
);
--sidebar-primary-foreground
:
oklch
(
0.29
0.06
243
);
--sidebar-accent
:
oklch
(
0.2
69
0
0
);
--sidebar-accent-foreground
:
oklch
(
0.9
85
0
0
);
--sidebar-border
:
oklch
(
1
0
0
/
10%
);
--sidebar-ring
:
oklch
(
0.5
56
0
0
);
--skeleton-base
:
oklch
(
0.
18
0
0
);
--skeleton-highlight
:
oklch
(
0.
26
0
0
);
--sidebar-accent
:
oklch
(
0.2
55
0.012
258
);
--sidebar-accent-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--sidebar-border
:
oklch
(
0.3
0.014
258
);
--sidebar-ring
:
oklch
(
0.5
2
0.02
256.788
);
--skeleton-base
:
oklch
(
0.
245
0.01
258
);
--skeleton-highlight
:
oklch
(
0.
32
0.014
258
);
}
@theme
inline
{
...
...
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