| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293 |
- <script setup lang="ts">
- import { onMounted, ref } from 'vue'
- import type { ApiKeyDto } from '@agent-server/shared'
- import PageHeader from '../components/PageHeader.vue'
- import { admin, errorMessage } from '../lib/api'
- import { formatTime, toast } from '../lib/ui'
- const keys = ref<ApiKeyDto[]>([])
- const name = ref('')
- const created = ref<{ key: string; name: string } | null>(null)
- async function load() {
- keys.value = await admin.apiKeys().catch(err => (toast(errorMessage(err), 'error'), []))
- }
- async function create() {
- if (!name.value.trim()) return
- try {
- const r = await admin.createApiKey(name.value.trim())
- created.value = { key: r.key, name: r.apiKey.name }
- name.value = ''
- await load()
- } catch (err) {
- toast(errorMessage(err), 'error')
- }
- }
- async function toggle(k: ApiKeyDto) {
- await admin.setApiKeyEnabled(k.id, !k.enabled).catch(err => toast(errorMessage(err), 'error'))
- await load()
- }
- async function remove(k: ApiKeyDto) {
- if (!confirm(`删除 API Key「${k.name}」?使用它的平台将无法再调用。`)) return
- await admin.deleteApiKey(k.id).catch(err => toast(errorMessage(err), 'error'))
- await load()
- }
- async function copy(text: string) {
- try {
- await navigator.clipboard.writeText(text)
- toast('已复制', 'success')
- } catch {
- toast('复制失败,请手动选择复制', 'error')
- }
- }
- onMounted(load)
- </script>
- <template>
- <div class="h-full flex flex-col">
- <PageHeader title="API Keys" subtitle="每个接入的业务平台一个 Key,调用时放在 Authorization: Bearer 头中" />
- <div class="flex-1 overflow-y-auto p-6 flex flex-col gap-5 max-w-240">
- <form class="flex gap-2" @submit.prevent="create">
- <input v-model="name" class="field max-w-80" placeholder="平台名称,例如:家庭记账" aria-label="名称">
- <button type="submit" class="btn-primary" :disabled="!name.trim()"><span class="codicon codicon-add" />签发</button>
- </form>
- <div v-if="created" class="card p-4 flex flex-col gap-2 border-accent">
- <div class="font-600">「{{ created.name }}」的 API Key(只显示这一次,请立即保存)</div>
- <div class="flex gap-2">
- <code class="flex-1 font-mono text-[12.5px] p-2 rounded-md bg-code break-all select-all">{{ created.key }}</code>
- <button type="button" class="btn-secondary" @click="copy(created.key)"><span class="codicon codicon-copy" />复制</button>
- </div>
- <button type="button" class="btn-ghost self-start" @click="created = null">我已保存</button>
- </div>
- <table class="w-full border-collapse text-[12.5px]">
- <thead>
- <tr class="text-left text-fg-muted">
- <th class="py-2 font-500">名称</th><th class="font-500">前缀</th><th class="font-500">状态</th>
- <th class="font-500">创建时间</th><th class="font-500">最近使用</th><th />
- </tr>
- </thead>
- <tbody>
- <tr v-for="k in keys" :key="k.id" class="border-t border-t-solid border-border">
- <td class="py-2">{{ k.name }}</td>
- <td class="font-mono">{{ k.prefix }}…</td>
- <td><span class="badge" :class="k.enabled ? 'bg-success text-white' : 'bg-code text-fg-muted'">{{ k.enabled ? '启用' : '停用' }}</span></td>
- <td>{{ formatTime(k.createdAt) }}</td>
- <td>{{ formatTime(k.lastUsedAt) }}</td>
- <td class="text-right whitespace-nowrap">
- <button type="button" class="btn-ghost" @click="toggle(k)">{{ k.enabled ? '停用' : '启用' }}</button>
- <button type="button" class="icon-btn" title="删除" @click="remove(k)"><span class="codicon codicon-trash" /></button>
- </td>
- </tr>
- <tr v-if="!keys.length"><td colspan="6" class="py-4 hint">还没有 API Key</td></tr>
- </tbody>
- </table>
- </div>
- </div>
- </template>
|