Files
RenewCMS/web/src/components/ApiKeysTable.vue
T

191 lines
4.5 KiB
Vue

<script setup lang="ts">
import { t } from '@/i18n'
import { computed } from 'vue'
import ApiKeyStatusBadge from '@/components/ApiKeyStatusBadge.vue'
import { API_KEY_SKELETON_ROWS, type ApiKey } from '@/composables/useApiKeys'
import {
creatorLabel,
displayPrefix,
expiresLabel,
formatKeyDate,
lastUsedLabel,
} from '@/lib/apiKeys'
const props = defineProps<{
keys: ApiKey[]
loading: boolean
showCreatedBy: boolean
}>()
const emit = defineEmits<{ revoke: [key: ApiKey] }>()
type Row = ApiKey | { skeleton: true; id: number }
const skeletonRows: Row[] = Array.from({ length: API_KEY_SKELETON_ROWS }, (_, index) => ({
skeleton: true,
id: index,
}))
const data = computed<Row[]>(() => (props.loading ? skeletonRows : props.keys))
function fixed(width: string) {
return { meta: { class: { th: width, td: width } } }
}
const columns = computed(() => [
{
id: 'name',
get header() {
return t('Name')
},
},
{
id: 'prefix',
get header() {
return t('Key')
},
...fixed('w-30.5'),
},
{
id: 'status',
get header() {
return t('Status')
},
...fixed('w-22'),
},
...(props.showCreatedBy
? [
{
id: 'created_by',
get header() {
return t('Created by')
},
...fixed('w-54.75'),
},
]
: []),
{
id: 'created_at',
get header() {
return t('Created')
},
...fixed('w-28'),
},
{
id: 'last_used_at',
get header() {
return t('Last used')
},
...fixed('w-28'),
},
{
id: 'expires_at',
get header() {
return t('Expires')
},
...fixed('w-28'),
},
{ id: 'actions', header: '', ...fixed('w-24.5') },
])
const meta = {
class: {
tr: (row: { original: Row }) => {
const original = row.original
return 'skeleton' in original || original.status !== 'revoked' ? '' : 'bg-muted'
},
},
}
function key(row: { original: Row }) {
return row.original as ApiKey
}
function isSkeleton(row: { original: Row }) {
return 'skeleton' in row.original
}
function isRevoked(row: { original: Row }) {
return !isSkeleton(row) && key(row).status === 'revoked'
}
function mutedClass(row: { original: Row }) {
return isRevoked(row) ? 'text-muted' : 'text-toned'
}
</script>
<template>
<div class="border border-default bg-default" :aria-busy="loading ? 'true' : undefined">
<span v-if="loading" role="status" class="sr-only">{{ t('Loading API keys') }}</span>
<UTable
:data="data"
:columns="columns"
:meta="meta"
:ui="{
base: 'table-fixed',
thead: 'bg-elevated [&>tr]:border-t-0',
separator: 'hidden',
th: 'h-9 px-1.5 py-0 first:pl-3 last:pr-3',
td: 'h-12 px-1.5 py-0 first:pl-3 last:pr-3',
tbody: 'divide-y-0',
tr: 'border-t border-default',
}"
>
<template #name-cell="{ row }">
<USkeleton v-if="isSkeleton(row)" class="h-3 w-[60%] rounded-none bg-accented" />
<span
v-else
class="block truncate font-medium"
:class="isRevoked(row) ? 'text-muted' : 'text-highlighted'"
>{{ key(row).name }}</span
>
</template>
<template #prefix-cell="{ row }">
<USkeleton v-if="isSkeleton(row)" class="h-3 w-[70%] rounded-none bg-accented" />
<span v-else class="font-mono text-xs" :class="mutedClass(row)">{{
displayPrefix(key(row).prefix)
}}</span>
</template>
<template #status-cell="{ row }">
<USkeleton v-if="isSkeleton(row)" class="h-5 w-14 rounded-none bg-accented" />
<ApiKeyStatusBadge v-else :status="key(row).status" />
</template>
<template #created_by-cell="{ row }">
<USkeleton v-if="isSkeleton(row)" class="h-3 w-[60%] rounded-none bg-accented" />
<span v-else class="block truncate" :class="mutedClass(row)">{{
creatorLabel(key(row))
}}</span>
</template>
<template #created_at-cell="{ row }">
<span v-if="!isSkeleton(row)" :class="mutedClass(row)">{{
formatKeyDate(key(row).created_at, '')
}}</span>
</template>
<template #last_used_at-cell="{ row }">
<span v-if="!isSkeleton(row)" :class="mutedClass(row)">{{
lastUsedLabel(key(row))
}}</span>
</template>
<template #expires_at-cell="{ row }">
<span v-if="!isSkeleton(row)" :class="mutedClass(row)">{{
expiresLabel(key(row))
}}</span>
</template>
<template #actions-cell="{ row }">
<span v-if="!isSkeleton(row) && !isRevoked(row)" class="flex justify-end">
<UButton
color="neutral"
variant="outline"
size="xs"
class="h-7 border border-accented bg-default px-2.5 text-xs font-medium text-highlighted ring-0"
@click="emit('revoke', key(row))"
>
{{ t('Revoke') }}
</UButton>
</span>
</template>
</UTable>
</div>
</template>