mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 06:41:46 +00:00
fix(admin): collapse long scope lists with toggle in MCP Access panel
Show first 6 scope badges per session with a clickable "+N more" pill that expands to all scopes; a "show less" pill collapses them again. Also fix column alignment to items-start so Owner/Created stay at the top of tall rows.
This commit is contained in:
@@ -26,13 +26,23 @@ interface AdminMcpToken {
|
|||||||
username: string
|
username: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SCOPES_PREVIEW = 6
|
||||||
|
|
||||||
export default function AdminMcpTokensPanel() {
|
export default function AdminMcpTokensPanel() {
|
||||||
const [sessions, setSessions] = useState<AdminOAuthSession[]>([])
|
const [sessions, setSessions] = useState<AdminOAuthSession[]>([])
|
||||||
const [sessionsLoading, setSessionsLoading] = useState(true)
|
const [sessionsLoading, setSessionsLoading] = useState(true)
|
||||||
const [tokens, setTokens] = useState<AdminMcpToken[]>([])
|
const [tokens, setTokens] = useState<AdminMcpToken[]>([])
|
||||||
const [tokensLoading, setTokensLoading] = useState(true)
|
const [tokensLoading, setTokensLoading] = useState(true)
|
||||||
|
const [expandedScopes, setExpandedScopes] = useState<Set<number>>(new Set())
|
||||||
const [revokeConfirmId, setRevokeConfirmId] = useState<number | null>(null)
|
const [revokeConfirmId, setRevokeConfirmId] = useState<number | null>(null)
|
||||||
const [deleteConfirmId, setDeleteConfirmId] = useState<number | null>(null)
|
const [deleteConfirmId, setDeleteConfirmId] = useState<number | null>(null)
|
||||||
|
|
||||||
|
const toggleScopes = (id: number) =>
|
||||||
|
setExpandedScopes(prev => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
next.has(id) ? next.delete(id) : next.add(id)
|
||||||
|
return next
|
||||||
|
})
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { t, locale } = useTranslation()
|
const { t, locale } = useTranslation()
|
||||||
|
|
||||||
@@ -99,35 +109,54 @@ export default function AdminMcpTokensPanel() {
|
|||||||
<span className="text-right">{t('admin.oauthSessions.created')}</span>
|
<span className="text-right">{t('admin.oauthSessions.created')}</span>
|
||||||
<span></span>
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
{sessions.map((session, i) => (
|
{sessions.map((session, i) => {
|
||||||
<div key={session.id}
|
const expanded = expandedScopes.has(session.id)
|
||||||
className="grid grid-cols-[1fr_auto_auto_auto] items-center gap-x-6 px-4 py-3"
|
const visible = expanded ? session.scopes : session.scopes.slice(0, SCOPES_PREVIEW)
|
||||||
style={{ borderBottom: i < sessions.length - 1 ? '1px solid var(--border-primary)' : undefined }}>
|
const hidden = session.scopes.length - SCOPES_PREVIEW
|
||||||
<div className="min-w-0">
|
return (
|
||||||
<p className="text-sm font-medium truncate" style={{ color: 'var(--text-primary)' }}>{session.client_name}</p>
|
<div key={session.id}
|
||||||
<div className="flex flex-wrap gap-1 mt-1">
|
className="grid grid-cols-[1fr_auto_auto_auto] items-start gap-x-6 px-4 py-3"
|
||||||
{session.scopes.map(scope => (
|
style={{ borderBottom: i < sessions.length - 1 ? '1px solid var(--border-primary)' : undefined }}>
|
||||||
<span key={scope} className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-mono"
|
<div className="min-w-0">
|
||||||
style={{ background: 'var(--bg-secondary)', color: 'var(--text-tertiary)', border: '1px solid var(--border-primary)' }}>
|
<p className="text-sm font-medium truncate" style={{ color: 'var(--text-primary)' }}>{session.client_name}</p>
|
||||||
{scope}
|
<div className="flex flex-wrap gap-1 mt-1.5">
|
||||||
</span>
|
{visible.map(scope => (
|
||||||
))}
|
<span key={scope} className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-mono"
|
||||||
|
style={{ background: 'var(--bg-secondary)', color: 'var(--text-tertiary)', border: '1px solid var(--border-primary)' }}>
|
||||||
|
{scope}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{!expanded && hidden > 0 && (
|
||||||
|
<button onClick={() => toggleScopes(session.id)}
|
||||||
|
className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium transition-colors hover:opacity-80"
|
||||||
|
style={{ background: 'var(--bg-secondary)', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)' }}>
|
||||||
|
+{hidden} more
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{expanded && hidden > 0 && (
|
||||||
|
<button onClick={() => toggleScopes(session.id)}
|
||||||
|
className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium transition-colors hover:opacity-80"
|
||||||
|
style={{ background: 'var(--bg-secondary)', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)' }}>
|
||||||
|
show less
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 text-sm pt-0.5" style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
<User className="w-3.5 h-3.5 flex-shrink-0" />
|
||||||
|
<span className="whitespace-nowrap">{session.username}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs whitespace-nowrap text-right pt-0.5" style={{ color: 'var(--text-tertiary)' }}>
|
||||||
|
{new Date(session.created_at).toLocaleDateString(locale)}
|
||||||
|
</span>
|
||||||
|
<button onClick={() => setRevokeConfirmId(session.id)}
|
||||||
|
className="p-1.5 rounded-lg transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
|
||||||
|
style={{ color: 'var(--text-tertiary)' }} title={t('common.delete')}>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
)
|
||||||
<User className="w-3.5 h-3.5 flex-shrink-0" />
|
})}
|
||||||
<span className="whitespace-nowrap">{session.username}</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-xs whitespace-nowrap text-right" style={{ color: 'var(--text-tertiary)' }}>
|
|
||||||
{new Date(session.created_at).toLocaleDateString(locale)}
|
|
||||||
</span>
|
|
||||||
<button onClick={() => setRevokeConfirmId(session.id)}
|
|
||||||
className="p-1.5 rounded-lg transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
|
|
||||||
style={{ color: 'var(--text-tertiary)' }} title={t('common.delete')}>
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user