feat: add clientApiKey per-app for mobile bug submission
This commit is contained in:
@@ -53,11 +53,13 @@ import {
|
||||
TbApps,
|
||||
TbBug,
|
||||
TbChevronRight,
|
||||
TbCopy,
|
||||
TbCircleFilled,
|
||||
TbCode,
|
||||
TbDatabase,
|
||||
TbDots,
|
||||
TbFileText,
|
||||
TbKey,
|
||||
TbLayoutDashboard,
|
||||
TbLayoutSidebarLeftCollapse,
|
||||
TbLayoutSidebarLeftExpand,
|
||||
@@ -1469,6 +1471,7 @@ interface AppEntry {
|
||||
urlApi: string | null
|
||||
status: string
|
||||
active: boolean
|
||||
hasClientApiKey: boolean
|
||||
}
|
||||
|
||||
function SettingsPanel() {
|
||||
@@ -1489,6 +1492,11 @@ function SettingsPanel() {
|
||||
const [apiTarget, setApiTarget] = useState<AppEntry | null>(null)
|
||||
const [apiForm, setApiForm] = useState({ urlApi: '', apiKey: '' })
|
||||
|
||||
// ── Generated key modal ──
|
||||
const [keyOpened, { open: openKey, close: closeKey }] = useDisclosure(false)
|
||||
const [generatedKey, setGeneratedKey] = useState('')
|
||||
const [keyCopied, setKeyCopied] = useState(false)
|
||||
|
||||
const openApiModal = (app: AppEntry) => {
|
||||
setApiTarget(app)
|
||||
setApiForm({ urlApi: app.urlApi ?? '', apiKey: '' })
|
||||
@@ -1542,6 +1550,31 @@ function SettingsPanel() {
|
||||
},
|
||||
})
|
||||
|
||||
const generateKeyMutation = useMutation({
|
||||
mutationFn: (id: string) => fetch(`/api/apps/${id}/generate-key`, { method: 'POST', credentials: 'include' }).then((r) => r.json()),
|
||||
onSuccess: (res) => {
|
||||
if (res.error) { notifications.show({ color: 'red', title: 'Error', message: res.error }); return }
|
||||
qc.invalidateQueries({ queryKey: ['apps'] })
|
||||
setGeneratedKey(res.clientApiKey)
|
||||
setKeyCopied(false)
|
||||
openKey()
|
||||
},
|
||||
})
|
||||
|
||||
const confirmGenerateKey = (app: AppEntry) => {
|
||||
if (app.hasClientApiKey) {
|
||||
modals.openConfirmModal({
|
||||
title: 'Regenerate Client API Key',
|
||||
children: <Text size="sm">This will invalidate the existing key for <strong>{app.name}</strong>. Any mobile apps using the old key will stop working.</Text>,
|
||||
labels: { confirm: 'Regenerate', cancel: 'Cancel' },
|
||||
confirmProps: { color: 'orange' },
|
||||
onConfirm: () => generateKeyMutation.mutate(app.id),
|
||||
})
|
||||
} else {
|
||||
generateKeyMutation.mutate(app.id)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDeactivate = (app: AppEntry) => modals.openConfirmModal({
|
||||
title: 'Deactivate Application',
|
||||
children: <Text size="sm">Are you sure you want to deactivate <strong>{app.name}</strong>? It will no longer appear in the monitoring list.</Text>,
|
||||
@@ -1583,6 +1616,10 @@ function SettingsPanel() {
|
||||
? <Text size="xs" c="dimmed">{app.urlApi}</Text>
|
||||
: <Badge color="orange" variant="dot" size="xs">URL API not set</Badge>
|
||||
}
|
||||
{app.hasClientApiKey
|
||||
? <Badge color="teal" variant="dot" size="xs">Client key set</Badge>
|
||||
: <Badge color="red" variant="dot" size="xs">No client key</Badge>
|
||||
}
|
||||
</Group>
|
||||
</Box>
|
||||
</Group>
|
||||
@@ -1592,6 +1629,9 @@ function SettingsPanel() {
|
||||
<Button size="xs" variant="light" color="teal" leftSection={<TbServer size={14} />} onClick={() => openApiModal(app)}>
|
||||
Edit API Config
|
||||
</Button>
|
||||
<Button size="xs" variant="light" color="violet" leftSection={<TbKey size={14} />} onClick={() => confirmGenerateKey(app)} loading={generateKeyMutation.isPending}>
|
||||
{app.hasClientApiKey ? 'Regenerate Key' : 'Generate Key'}
|
||||
</Button>
|
||||
<Button size="xs" variant="light" color="red" onClick={() => confirmDeactivate(app)} loading={deleteMutation.isPending}>
|
||||
Deactivate
|
||||
</Button>
|
||||
@@ -1622,6 +1662,30 @@ function SettingsPanel() {
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{/* ── Generated Key Modal ── */}
|
||||
<Modal opened={keyOpened} onClose={closeKey} title="Client API Key Generated" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" c="dimmed">Copy this key now — it will not be shown again after you close this dialog.</Text>
|
||||
<Box
|
||||
p="sm"
|
||||
style={{ background: 'var(--mantine-color-dark-6)', borderRadius: 6, fontFamily: 'monospace', fontSize: 13, wordBreak: 'break-all' }}
|
||||
>
|
||||
{generatedKey}
|
||||
</Box>
|
||||
<Group justify="flex-end">
|
||||
<Button
|
||||
variant="light"
|
||||
color={keyCopied ? 'green' : 'blue'}
|
||||
leftSection={<TbCopy size={14} />}
|
||||
onClick={() => { navigator.clipboard.writeText(generatedKey); setKeyCopied(true) }}
|
||||
>
|
||||
{keyCopied ? 'Copied!' : 'Copy to Clipboard'}
|
||||
</Button>
|
||||
<Button variant="subtle" color="gray" onClick={closeKey}>Close</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{/* ── API Config Modal ── */}
|
||||
<Modal opened={apiOpened} onClose={closeApi} title={`API Config — ${apiTarget?.name}`} radius="md">
|
||||
<Stack gap="sm">
|
||||
|
||||
Reference in New Issue
Block a user