feat(keys): add optional ID column on /keys page

Expose API key ID in the keys table column settings so users can show
or hide it. Hidden by default; bump column-settings version so existing
preferences also keep ID hidden until toggled on.
This commit is contained in:
bestony
2026-07-14 19:18:53 +08:00
parent 69bc6a87dd
commit 2157ee344b
4 changed files with 36 additions and 7 deletions
@@ -85,6 +85,7 @@ export default {
editKey: 'Edit API Key',
deleteKey: 'Delete API Key',
deleteConfirmMessage: "Are you sure you want to delete '{name}'? This action cannot be undone.",
id: 'ID',
apiKey: 'API Key',
group: 'Group',
currentConcurrency: 'Current Concurrency',
@@ -85,6 +85,7 @@ export default {
editKey: '编辑密钥',
deleteKey: '删除密钥',
deleteConfirmMessage: "确定要删除 '{name}' 吗?此操作无法撤销。",
id: 'ID',
apiKey: 'API 密钥',
group: '分组',
currentConcurrency: '当前并发',
+9 -3
View File
@@ -90,6 +90,10 @@
default-sort-order="desc"
@sort="handleSort"
>
<template #cell-id="{ value }">
<span class="font-mono text-xs text-gray-500 dark:text-gray-400">#{{ value }}</span>
</template>
<template #cell-key="{ value, row }">
<div class="flex items-center gap-2">
<code class="code text-xs">
@@ -1173,6 +1177,7 @@ const { copyToClipboard: clipboardCopy } = useClipboard()
const allColumns = computed<Column[]>(() => [
{ key: 'name', label: t('common.name'), sortable: true },
{ key: 'id', label: t('keys.id'), sortable: true },
{ key: 'key', label: t('keys.apiKey'), sortable: false },
{ key: 'group', label: t('keys.group'), sortable: false },
{ key: 'current_concurrency', label: t('keys.currentConcurrency'), sortable: true },
@@ -1187,12 +1192,13 @@ const allColumns = computed<Column[]>(() => [
])
const ALWAYS_VISIBLE_COLUMNS = new Set(['name', 'actions'])
const DEFAULT_HIDDEN_COLUMNS = ['rate_limit', 'last_used_at', 'last_used_ip']
const DEFAULT_HIDDEN_COLUMNS = ['id', 'rate_limit', 'last_used_at', 'last_used_ip']
const HIDDEN_COLUMNS_KEY = 'api-key-hidden-columns'
const COLUMN_SETTINGS_VERSION_KEY = 'api-key-column-settings-version'
const COLUMN_SETTINGS_VERSION = 2
const COLUMN_SETTINGS_VERSION = 3
const VERSION_NEW_HIDDEN_COLUMNS: Record<number, string[]> = {
2: ['last_used_ip']
2: ['last_used_ip'],
3: ['id']
}
const toggleableColumns = computed(() =>
@@ -42,6 +42,7 @@ const messages: Record<string, string> = {
'keys.created': 'Created',
'keys.expiresAt': 'Expires',
'keys.group': 'Group',
'keys.id': 'ID',
'keys.currentConcurrency': 'Current Concurrency',
'keys.lastUsedAt': 'Last Used',
'keys.lastUsedIP': 'Last Used IP',
@@ -162,6 +163,12 @@ const DataTableStub = {
Sort Current Concurrency
</button>
<div v-for="row in data" :key="row.id">
<div
v-if="columns.some((col) => col.key === 'id')"
data-test="key-id"
>
<slot name="cell-id" :value="row.id" :row="row" />
</div>
<slot name="cell-name" :value="row.name" :row="row" />
<div data-test="current-concurrency">
<slot name="cell-current_concurrency" :value="row.current_concurrency" :row="row" />
@@ -295,6 +302,7 @@ describe('user KeysView column settings', () => {
expect(visibleColumnKeys(wrapper)).not.toContain('rate_limit')
expect(visibleColumnKeys(wrapper)).not.toContain('last_used_at')
expect(visibleColumnKeys(wrapper)).not.toContain('last_used_ip')
expect(visibleColumnKeys(wrapper)).not.toContain('id')
})
it('shows a hidden column when toggled and persists the preference', async () => {
@@ -306,9 +314,21 @@ describe('user KeysView column settings', () => {
expect(visibleColumnKeys(wrapper)).toContain('rate_limit')
expect(localStorage.getItem('api-key-hidden-columns')).toBe(
JSON.stringify(['last_used_at', 'last_used_ip'])
JSON.stringify(['id', 'last_used_at', 'last_used_ip'])
)
expect(localStorage.getItem('api-key-column-settings-version')).toBe('2')
expect(localStorage.getItem('api-key-column-settings-version')).toBe('3')
})
it('shows the API key ID column when toggled', async () => {
const wrapper = await mountView()
await wrapper.get('button[title="Column Settings"]').trigger('click')
await getButtonByText(wrapper, 'ID').trigger('click')
await nextTick()
expect(visibleColumnKeys(wrapper)).toContain('id')
expect(wrapper.get('[data-test="key-id"]').text()).toBe('#1')
expect(visibleColumnMeta(wrapper).find((column) => column.key === 'id')?.sortable).toBe(true)
})
it('shows the last used IP column when toggled', async () => {
@@ -347,9 +367,9 @@ describe('user KeysView column settings', () => {
'actions',
])
expect(localStorage.getItem('api-key-hidden-columns')).toBe(
JSON.stringify(['group', 'created_at', 'last_used_ip'])
JSON.stringify(['group', 'created_at', 'last_used_ip', 'id'])
)
expect(localStorage.getItem('api-key-column-settings-version')).toBe('2')
expect(localStorage.getItem('api-key-column-settings-version')).toBe('3')
})
it('does not include always-visible columns in the toggleable menu', async () => {
@@ -360,6 +380,7 @@ describe('user KeysView column settings', () => {
const columnMenuText = wrapper.text()
expect(columnMenuText).toContain('API Key')
expect(columnMenuText).toContain('ID')
expect(columnMenuText).toContain('Current Concurrency')
expect(columnMenuText).toContain('Rate Limit')
expect(columnMenuText).toContain('Last Used IP')