- utils/date.js: add formatRelative(iso, {future,nullText}); migrate 6 sites
(SourceRow, SubscriptionsTab, SourceHealthDot, SchedulerStatusBar +
thin adapters in BackupRunsTable/SystemActivityTab for their '—' null text).
PostCard (30d->absolute) and CredentialCard (mo/y buckets) intentionally
keep bespoke formatters.
- utils/toast.js: toast(opts) wraps the globalThis.window?.__fcToast?.(...)
incantation; migrate 63 call sites across 24 files.
- utils/downloadStatus.js: single source for the download-event status enum
-> label/color/icon; collapse the 3 duplicate maps (DownloadStatChips,
DownloadsFilterPopover, DownloadEventRow).
Net -33 lines. Platform metadata was already centralized in platformColor.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78 lines
2.2 KiB
Vue
78 lines
2.2 KiB
Vue
<template>
|
|
<div class="fc-extkey-bar">
|
|
<span class="fc-extkey-bar__label">Extension API key:</span>
|
|
<code class="fc-extkey-bar__value">{{ store.extensionKey || '…' }}</code>
|
|
<v-btn size="small" variant="text" @click="copyKey" :disabled="!store.extensionKey">
|
|
Copy
|
|
</v-btn>
|
|
<v-btn size="small" variant="outlined" color="warning" @click="confirmRotate">
|
|
Rotate
|
|
</v-btn>
|
|
<v-dialog v-model="showRotateConfirm" max-width="420">
|
|
<v-card>
|
|
<v-card-title>Rotate extension API key?</v-card-title>
|
|
<v-card-text>
|
|
The old key stops working immediately. Update your browser extension
|
|
with the new key after rotating.
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer />
|
|
<v-btn variant="text" @click="showRotateConfirm = false">Cancel</v-btn>
|
|
<v-btn color="warning" variant="flat" @click="doRotate">Rotate</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { toast } from '../../utils/toast.js'
|
|
import { onMounted, ref } from 'vue'
|
|
import { useCredentialsStore } from '../../stores/credentials.js'
|
|
import { copyText } from '../../utils/clipboard.js'
|
|
|
|
const store = useCredentialsStore()
|
|
const showRotateConfirm = ref(false)
|
|
|
|
onMounted(() => store.loadKey())
|
|
|
|
async function copyKey() {
|
|
if (!store.extensionKey) return
|
|
try {
|
|
await copyText(store.extensionKey)
|
|
toast({ text: 'Copied', type: 'success' })
|
|
} catch {
|
|
toast({ text: 'Copy failed', type: 'error' })
|
|
}
|
|
}
|
|
|
|
function confirmRotate() { showRotateConfirm.value = true }
|
|
|
|
async function doRotate() {
|
|
showRotateConfirm.value = false
|
|
await store.rotateKey()
|
|
toast({ text: 'Key rotated', type: 'success' })
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-extkey-bar {
|
|
display: flex; align-items: center; gap: 0.75rem;
|
|
padding: 0.75rem 1rem; margin-bottom: 1rem;
|
|
background: rgb(var(--v-theme-surface));
|
|
border: 1px solid rgb(var(--v-theme-on-surface-variant) / 0.18);
|
|
border-radius: 6px;
|
|
}
|
|
.fc-extkey-bar__label {
|
|
color: rgb(var(--v-theme-on-surface-variant));
|
|
font-size: 0.85rem;
|
|
}
|
|
.fc-extkey-bar__value {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.85rem;
|
|
flex: 0 1 auto;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
</style>
|