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
9100e15e
authored
Jun 19, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add channel sensitive info toggle
parent
4206d7fd
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
129 additions
and
37 deletions
+129
-37
web/default/src/components/data-table/toolbar/toolbar.tsx
+16
-4
web/default/src/features/channels/components/channel-card.tsx
+15
-2
web/default/src/features/channels/components/channels-columns.tsx
+35
-15
web/default/src/features/channels/components/channels-provider.tsx
+5
-0
web/default/src/features/channels/components/channels-table.tsx
+46
-13
web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx
+12
-3
No files found.
web/default/src/components/data-table/toolbar/toolbar.tsx
View file @
9100e15e
...
...
@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
*/
import
*
as
React
from
'react'
import
{
useState
,
type
ReactNode
}
from
'react'
import
{
type
Table
}
from
'@tanstack/react-table'
import
type
{
Table
}
from
'@tanstack/react-table'
import
{
useDebounce
}
from
'@/hooks'
import
{
ChevronDown
,
Loader2
,
X
as
Cross2Icon
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
...
...
@@ -77,6 +77,11 @@ export type DataTableToolbarProps<TData> = {
*/
additionalSearch
?:
ReactNode
/**
* Extra controls displayed immediately after the filter chips, before the
* right-aligned action cluster.
*/
afterFilters
?:
ReactNode
/**
* Whether non-table filters (e.g. `additionalSearch` or `expandable`
* inputs) are currently active. Controls Reset button visibility
* when no column filters are set.
...
...
@@ -282,11 +287,15 @@ export function DataTableToolbar<TData>(props: DataTableToolbarProps<TData>) {
// Reset: outline text-only for form mode (always visible, disabled when
// nothing to reset); ghost text + X for filter-as-you-type mode (only
// visible when active filters exist).
const
resetButton
=
hasSearch
?
(
let
resetButton
:
ReactNode
=
null
if
(
hasSearch
)
{
resetButton
=
(
<
Button
variant=
'outline'
onClick=
{
handleReset
}
disabled=
{
!
isFiltered
}
>
{
t
(
'Reset'
)
}
</
Button
>
)
:
isFiltered
?
(
)
}
else
if
(
isFiltered
)
{
resetButton
=
(
<
Button
variant=
'ghost'
onClick=
{
handleReset
}
...
...
@@ -295,7 +304,8 @@ export function DataTableToolbar<TData>(props: DataTableToolbarProps<TData>) {
{
t
(
'Reset'
)
}
<
Cross2Icon
/>
</
Button
>
)
:
null
)
}
const
searchButton
=
hasSearch
?
(
<
Button
onClick=
{
props
.
onSearch
}
disabled=
{
props
.
searchLoading
}
>
...
...
@@ -341,6 +351,7 @@ export function DataTableToolbar<TData>(props: DataTableToolbarProps<TData>) {
{
props
.
customSearch
!==
undefined
?
props
.
customSearch
:
searchInput
}
{
props
.
additionalSearch
}
{
filterChips
}
{
props
.
afterFilters
}
<
div
className=
'ms-auto flex shrink-0 items-center gap-1.5 sm:gap-2'
>
{
expandToggle
}
</
div
>
...
...
@@ -376,6 +387,7 @@ export function DataTableToolbar<TData>(props: DataTableToolbarProps<TData>) {
{
props
.
customSearch
!==
undefined
?
props
.
customSearch
:
searchInput
}
{
props
.
additionalSearch
}
{
filterChips
}
{
props
.
afterFilters
}
{
expanded
&&
hasExpandable
&&
props
.
expandable
}
<
div
className=
'ms-auto flex shrink-0 items-center gap-1.5 sm:gap-2'
>
...
...
web/default/src/features/channels/components/channel-card.tsx
View file @
9100e15e
...
...
@@ -24,6 +24,9 @@ import { CHANNEL_STATUS } from '../constants'
import
{
isTagAggregateRow
,
parseGroupsList
}
from
'../lib'
import
type
{
Channel
}
from
'../types'
import
{
ChannelRowActionsLayoutContext
}
from
'./channel-row-actions-context'
import
{
useChannels
}
from
'./channels-provider'
const
SENSITIVE_MASK
=
'••••'
/**
* Bespoke channel card for the card view. Reuses every column's existing cell
...
...
@@ -35,6 +38,7 @@ import { ChannelRowActionsLayoutContext } from './channel-row-actions-context'
*/
export
function
ChannelCard
({
row
}:
{
row
:
Row
<
Channel
>
})
{
const
{
t
}
=
useTranslation
()
const
{
sensitiveVisible
}
=
useChannels
()
const
isTagRow
=
isTagAggregateRow
(
row
.
original
)
const
cells
=
row
.
getAllCells
()
...
...
@@ -99,7 +103,11 @@ export function ChannelCard({ row }: { row: Row<Channel> }) {
{
/* Left column */
}
<
div
className=
'flex min-w-0 flex-1 flex-col gap-3 overflow-hidden'
>
<
div
className=
'min-w-0 text-sm'
>
{
!
isTagRow
&&
<
div
className=
{
labelClass
}
>
#
{
row
.
original
.
id
}
</
div
>
}
{
!
isTagRow
&&
(
<
div
className=
{
labelClass
}
>
#
{
sensitiveVisible
?
row
.
original
.
id
:
SENSITIVE_MASK
}
</
div
>
)
}
{
nameCell
}
</
div
>
<
div
className=
'min-w-0'
>
...
...
@@ -138,7 +146,12 @@ export function ChannelCard({ row }: { row: Row<Channel> }) {
{
groups
.
length
>
0
?
(
<
div
className=
'-ml-1.5 flex flex-wrap gap-1'
>
{
groups
.
map
((
g
)
=>
(
<
GroupBadge
key=
{
g
}
group=
{
g
}
size=
'sm'
/>
<
GroupBadge
key=
{
g
}
group=
{
g
}
label=
{
sensitiveVisible
?
undefined
:
SENSITIVE_MASK
}
size=
'sm'
/>
))
}
</
div
>
)
:
(
...
...
web/default/src/features/channels/components/channels-columns.tsx
View file @
9100e15e
...
...
@@ -285,6 +285,7 @@ function WeightCell({ channel }: { channel: Channel }) {
* notation (e.g. "$28万"); the precise value stays available in the tooltip.
*/
const
MAX_INLINE_BALANCE_CHARS
=
8
const
SENSITIVE_MASK
=
'••••'
/**
* Balance cell component with click to update
...
...
@@ -292,6 +293,7 @@ const MAX_INLINE_BALANCE_CHARS = 8
function
BalanceCell
({
channel
}:
{
channel
:
Channel
})
{
const
{
t
,
i18n
}
=
useTranslation
()
const
queryClient
=
useQueryClient
()
const
{
sensitiveVisible
}
=
useChannels
()
const
isTagRow
=
isTagAggregateRow
(
channel
)
const
balance
=
channel
.
balance
||
0
const
usedQuota
=
channel
.
used_quota
||
0
...
...
@@ -320,6 +322,8 @@ function BalanceCell({ channel }: { channel: Channel }) {
:
remainingFull
const
usedLabel
=
`
${
t
(
'Used:'
)}
${
usedFull
}
`
const
remainingLabel
=
`
${
t
(
'Remaining:'
)}
${
remainingFull
}
`
const
maskedUsedLabel
=
`
${
t
(
'Used:'
)}
${
SENSITIVE_MASK
}
`
const
maskedRemainingLabel
=
`
${
t
(
'Remaining:'
)}
${
SENSITIVE_MASK
}
`
// Tag row: only show cumulative used quota
if
(
isTagRow
)
{
...
...
@@ -329,7 +333,11 @@ function BalanceCell({ channel }: { channel: Channel }) {
<
TooltipTrigger
render=
{
<
StatusBadge
label=
{
`${t('Used:')} ${usedDisplay}`
}
label=
{
sensitiveVisible
?
`${t('Used:')} ${usedDisplay}`
:
maskedUsedLabel
}
variant=
'neutral'
size=
'sm'
copyable=
{
false
}
...
...
@@ -339,7 +347,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
}
/>
<
TooltipContent
>
<
p
>
{
u
sedLabel
}
</
p
>
<
p
>
{
sensitiveVisible
?
usedLabel
:
maskedU
sedLabel
}
</
p
>
</
TooltipContent
>
</
Tooltip
>
</
TooltipProvider
>
...
...
@@ -376,12 +384,20 @@ function BalanceCell({ channel }: { channel: Channel }) {
await
handleUpdateChannelBalance
(
channel
.
id
,
queryClient
)
setIsUpdating
(
false
)
}
let
remainingBadgeLabel
=
remainingDisplay
if
(
isUpdating
)
{
let
remainingBadgeLabel
=
sensitiveVisible
?
remainingDisplay
:
SENSITIVE_MASK
if
(
sensitiveVisible
&&
isUpdating
)
{
remainingBadgeLabel
=
t
(
'Updating...'
)
}
else
if
(
channel
.
type
===
57
)
{
}
else
if
(
sensitiveVisible
&&
channel
.
type
===
57
)
{
remainingBadgeLabel
=
t
(
'Account Info'
)
}
let
remainingTooltipLabel
=
remainingLabel
if
(
!
sensitiveVisible
)
{
remainingTooltipLabel
=
maskedRemainingLabel
}
else
if
(
channel
.
type
===
57
)
{
remainingTooltipLabel
=
t
(
'Click to view Codex usage'
)
}
let
remainingBadgeVariant
:
StatusBadgeProps
[
'variant'
]
=
variant
if
(
channel
.
type
===
57
)
{
remainingBadgeVariant
=
'info'
...
...
@@ -396,7 +412,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
<
TooltipTrigger
render=
{
<
StatusBadge
label=
{
usedDisplay
}
label=
{
sensitiveVisible
?
usedDisplay
:
SENSITIVE_MASK
}
variant=
'neutral'
size=
'sm'
copyable=
{
false
}
...
...
@@ -406,7 +422,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
}
/>
<
TooltipContent
>
<
p
>
{
u
sedLabel
}
</
p
>
<
p
>
{
sensitiveVisible
?
usedLabel
:
maskedU
sedLabel
}
</
p
>
</
TooltipContent
>
</
Tooltip
>
<
Tooltip
>
...
...
@@ -424,11 +440,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
}
/>
<
TooltipContent
>
<
p
>
{
channel
.
type
===
57
?
t
(
'Click to view Codex usage'
)
:
remainingLabel
}
</
p
>
<
p
>
{
remainingTooltipLabel
}
</
p
>
{
channel
.
type
!==
57
&&
<
p
>
{
t
(
'Click to update balance'
)
}
</
p
>
}
</
TooltipContent
>
</
Tooltip
>
...
...
@@ -439,6 +451,8 @@ function BalanceCell({ channel }: { channel: Channel }) {
onOpenChange=
{
setCodexUsageOpen
}
channelName=
{
channel
.
name
}
channelId=
{
channel
.
id
}
channelDisplayName=
{
sensitiveVisible
?
undefined
:
SENSITIVE_MASK
}
channelDisplayId=
{
sensitiveVisible
?
undefined
:
SENSITIVE_MASK
}
response=
{
codexUsageResponse
}
onRefresh=
{
async
()
=>
{
if
(
isUpdating
)
{
...
...
@@ -472,6 +486,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
*/
export
function
useChannelsColumns
():
ColumnDef
<
Channel
>
[]
{
const
{
t
,
i18n
}
=
useTranslation
()
const
{
sensitiveVisible
}
=
useChannels
()
const
locale
=
i18n
.
resolvedLanguage
||
i18n
.
language
return
[
// Checkbox column
...
...
@@ -513,7 +528,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
meta
:
{
mobileHidden
:
true
},
cell
:
({
row
})
=>
{
const
id
=
row
.
getValue
(
'id'
)
as
number
return
<
TableId
value=
{
id
}
/>
return
<
TableId
value=
{
sensitiveVisible
?
id
:
SENSITIVE_MASK
}
/>
},
size
:
80
,
},
...
...
@@ -570,7 +585,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
<
div
className=
'flex flex-col gap-1'
>
<
div
className=
'flex items-center gap-1.5'
>
<
TruncatedText
text=
{
name
}
text=
{
sensitiveVisible
?
name
:
SENSITIVE_MASK
}
className=
'font-medium'
maxWidth=
'max-w-[180px]'
/>
...
...
@@ -929,7 +944,12 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
return
(
<
BadgeListCell
items=
{
groupArray
.
map
((
g
)
=>
(
<
GroupBadge
key=
{
g
}
group=
{
g
}
size=
'sm'
/>
<
GroupBadge
key=
{
g
}
group=
{
g
}
label=
{
sensitiveVisible
?
undefined
:
SENSITIVE_MASK
}
size=
'sm'
/>
))
}
/>
)
...
...
web/default/src/features/channels/components/channels-provider.tsx
View file @
9100e15e
...
...
@@ -53,6 +53,8 @@ type ChannelsContextType = {
setEnableTagMode
:
(
enabled
:
boolean
)
=>
void
idSort
:
boolean
setIdSort
:
(
enabled
:
boolean
)
=>
void
sensitiveVisible
:
boolean
setSensitiveVisible
:
(
visible
:
boolean
)
=>
void
upstream
:
UpstreamUpdateState
}
...
...
@@ -78,6 +80,7 @@ export function ChannelsProvider({ children }: { children: React.ReactNode }) {
const
[
idSort
,
setIdSort
]
=
useState
(()
=>
{
return
localStorage
.
getItem
(
'channels-id-sort'
)
===
'true'
})
const
[
sensitiveVisible
,
setSensitiveVisible
]
=
useState
(
true
)
const
queryClient
=
useQueryClient
()
const
refreshChannels
=
useCallback
(
async
()
=>
{
...
...
@@ -98,6 +101,8 @@ export function ChannelsProvider({ children }: { children: React.ReactNode }) {
setEnableTagMode
,
idSort
,
setIdSort
,
sensitiveVisible
,
setSensitiveVisible
,
upstream
,
}
}
>
...
...
web/default/src/features/channels/components/channels-table.tsx
View file @
9100e15e
...
...
@@ -19,17 +19,20 @@ For commercial licensing, please contact support@quantumnous.com
import
{
useState
,
useMemo
}
from
'react'
import
{
useQuery
}
from
'@tanstack/react-query'
import
{
getRouteApi
}
from
'@tanstack/react-router'
import
{
type
OnChangeFn
,
type
SortingState
,
type
Row
,
}
from
'@tanstack/react-table'
import
type
{
OnChangeFn
,
SortingState
,
Row
}
from
'@tanstack/react-table'
import
{
Eye
,
EyeOff
}
from
'lucide-react'
import
{
useMediaQuery
}
from
'@/hooks'
import
{
useTranslation
}
from
'react-i18next'
import
{
getLobeIcon
}
from
'@/lib/lobe-icon'
import
{
useTableUrlState
}
from
'@/hooks/use-table-url-state'
import
{
Button
}
from
'@/components/ui/button'
import
{
Input
}
from
'@/components/ui/input'
import
{
Tooltip
,
TooltipContent
,
TooltipTrigger
,
}
from
'@/components/ui/tooltip'
import
{
DISABLED_ROW_DESKTOP
,
DISABLED_ROW_MOBILE
,
DataTablePage
,
...
...
@@ -77,7 +80,12 @@ function isDisabledChannelRow(channel: Channel) {
export
function
ChannelsTable
()
{
const
{
t
}
=
useTranslation
()
const
{
enableTagMode
,
idSort
}
=
useChannels
()
const
{
enableTagMode
,
idSort
,
sensitiveVisible
,
setSensitiveVisible
,
}
=
useChannels
()
const
isMobile
=
useMediaQuery
(
'(max-width: 640px)'
)
// Table state
...
...
@@ -343,7 +351,10 @@ export function ChannelsTable() {
const
groupFilterOptions
=
[
{
label
:
t
(
'All Groups'
),
value
:
'all'
},
...
groupOptions
,
...
groupOptions
.
map
((
option
)
=>
({
...
option
,
label
:
sensitiveVisible
?
option
.
label
:
'••••'
,
})),
]
return
(
...
...
@@ -398,14 +409,36 @@ export function ChannelsTable() {
singleSelect
:
true
,
},
],
afterFilters
:
(
<
Tooltip
>
<
TooltipTrigger
render=
{
<
Button
variant=
'ghost'
size=
'icon'
onClick=
{
()
=>
setSensitiveVisible
(
!
sensitiveVisible
)
}
aria
-
label=
{
sensitiveVisible
?
t
(
'Hide'
)
:
t
(
'Show'
)
}
className=
'text-muted-foreground hover:text-foreground size-8'
/>
}
>
{
sensitiveVisible
?
<
Eye
/>
:
<
EyeOff
/>
}
</
TooltipTrigger
>
<
TooltipContent
>
{
sensitiveVisible
?
t
(
'Hide'
)
:
t
(
'Show'
)
}
</
TooltipContent
>
</
Tooltip
>
),
}
}
getRowClassName=
{
(
row
,
{
isMobile
})
=>
isDisabledChannelRow
(
row
.
original
)
?
isMobile
?
DISABLED_ROW_MOBILE
:
DISABLED_ROW_DESKTOP
:
undefined
getRowClassName=
{
(
row
,
{
isMobile
})
=>
{
if
(
!
isDisabledChannelRow
(
row
.
original
))
{
return
undefined
}
if
(
isMobile
)
{
return
DISABLED_ROW_MOBILE
}
return
DISABLED_ROW_DESKTOP
}
}
bulkActions=
{
<
DataTableBulkActions
table=
{
table
}
/>
}
/>
)
...
...
web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx
View file @
9100e15e
...
...
@@ -139,6 +139,8 @@ type CodexUsageDialogProps = {
onOpenChange
:
(
open
:
boolean
)
=>
void
channelName
?:
string
channelId
?:
number
channelDisplayName
?:
string
channelDisplayId
?:
string
response
:
CodexUsageDialogData
|
null
onRefresh
?:
()
=>
void
|
Promise
<
void
>
isRefreshing
?:
boolean
...
...
@@ -885,6 +887,8 @@ export function CodexUsageDialog({
onOpenChange
,
channelName
,
channelId
,
channelDisplayName
,
channelDisplayId
,
response
,
onRefresh
,
isRefreshing
,
...
...
@@ -931,9 +935,14 @@ export function CodexUsageDialog({
?
String
(
resetCredits
)
:
'-'
const
canResetCodexUsage
=
Number
(
resetCredits
)
>
0
const
channelLabel
=
`
${
channelName
||
'-'
}${
channelId
?
` (#
${
channelId
}
)`
:
''
}
`
const
channelLabelName
=
channelDisplayName
??
channelName
??
'-'
let
channelLabelId
=
''
if
(
channelDisplayId
!=
null
)
{
channelLabelId
=
` (#
${
channelDisplayId
}
)`
}
else
if
(
channelId
)
{
channelLabelId
=
` (#
${
channelId
}
)`
}
const
channelLabel
=
`
${
channelLabelName
}${
channelLabelId
}
`
const
{
fiveHourWindow
,
weeklyWindow
}
=
resolveRateLimitWindows
(
payload
)
const
errorMessage
=
...
...
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