From 7d879d1901ce8e2f7984ceeb439a8f2a51079277 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Fri, 22 May 2026 17:10:36 +0800 Subject: [PATCH] feat: add show/hide and copy for API keys on dev page - Display client key and server key on Settings app cards with toggle visibility and copy button - Hide API keys table in Desa Mandiri Keys tab behind toggle + copy - Add eye toggle to password inputs in Add App and Edit API Config modals - Backend now returns apiKey and clientApiKey in apps list endpoint --- src/app.ts | 2 + src/frontend/routes/dev.tsx | 168 ++++++++++++++++++++++++++++++++---- 2 files changed, 152 insertions(+), 18 deletions(-) diff --git a/src/app.ts b/src/app.ts index d3cbf13..65b47ce 100644 --- a/src/app.ts +++ b/src/app.ts @@ -370,6 +370,8 @@ export function createApp() { errors: app.bugs.length, active: app.active, urlApi: app.urlApi, + apiKey: app.apiKey ?? '', + clientApiKey: app.clientApiKey ?? '', hasClientApiKey: !!app.clientApiKey, })) }, { diff --git a/src/frontend/routes/dev.tsx b/src/frontend/routes/dev.tsx index 944cd99..a1b0936 100644 --- a/src/frontend/routes/dev.tsx +++ b/src/frontend/routes/dev.tsx @@ -8,6 +8,7 @@ import { Button, Card, Center, + CopyButton, Container, Divider, Group, @@ -59,6 +60,8 @@ import { TbCode, TbDatabase, TbDots, + TbEye, + TbEyeOff, TbFileText, TbKey, TbLayoutDashboard, @@ -1474,6 +1477,8 @@ interface AppEntry { id: string name: string urlApi: string | null + apiKey: string + clientApiKey: string status: string active: boolean hasClientApiKey: boolean @@ -1501,10 +1506,24 @@ function SettingsPanel() { const [keyOpened, { open: openKey, close: closeKey }] = useDisclosure(false) const [generatedKey, setGeneratedKey] = useState('') const [keyCopied, setKeyCopied] = useState(false) + const [generatedKeyVisible, setGeneratedKeyVisible] = useState(false) + const [addKeyVisible, setAddKeyVisible] = useState(false) + const [apiConfigKeyVisible, setApiConfigKeyVisible] = useState(false) + const [visibleAppKeys, setVisibleAppKeys] = useState>(new Set()) + + const toggleAppKeyVisibility = (appId: string) => { + setVisibleAppKeys((prev) => { + const next = new Set(prev) + if (next.has(appId)) next.delete(appId) + else next.add(appId) + return next + }) + } const openApiModal = (app: AppEntry) => { setApiTarget(app) setApiForm({ urlApi: app.urlApi ?? '', apiKey: '' }) + setApiConfigKeyVisible(false) openApi() } @@ -1562,6 +1581,7 @@ function SettingsPanel() { qc.invalidateQueries({ queryKey: ['apps'] }) setGeneratedKey(res.clientApiKey) setKeyCopied(false) + setGeneratedKeyVisible(false) openKey() }, }) @@ -1626,6 +1646,54 @@ function SettingsPanel() { : No client key } + {app.clientApiKey && ( + <> + Client Key (untuk mobile app mengakses monitoring): + + + {visibleAppKeys.has(app.id) ? app.clientApiKey : '•'.repeat(32)} + + + toggleAppKeyVisibility(app.id)}> + {visibleAppKeys.has(app.id) ? : } + + + + {({ copy }) => ( + + + + + + )} + + + + )} + {app.apiKey && ( + <> + Server Key (untuk monitoring mengakses API external): + + + {visibleAppKeys.has(`server-${app.id}`) ? app.apiKey : '•'.repeat(32)} + + + toggleAppKeyVisibility(`server-${app.id}`)}> + {visibleAppKeys.has(`server-${app.id}`) ? : } + + + + {({ copy }) => ( + + + + + + )} + + + + )} @@ -1659,7 +1727,19 @@ function SettingsPanel() { setNewApp((p) => ({ ...p, id: e.target.value }))} required /> setNewApp((p) => ({ ...p, name: e.target.value }))} required /> setNewApp((p) => ({ ...p, urlApi: e.target.value }))} /> - setNewApp((p) => ({ ...p, apiKey: e.target.value }))} /> + setNewApp((p) => ({ ...p, apiKey: e.target.value }))} + rightSection={ + setAddKeyVisible((v) => !v)}> + {addKeyVisible ? : } + + } + /> @@ -1671,21 +1751,28 @@ function SettingsPanel() { Copy this key now — it will not be shown again after you close this dialog. - - {generatedKey} - - - + {generatedKeyVisible ? generatedKey : '•'.repeat(48)} + + + setGeneratedKeyVisible((v) => !v)}> + {generatedKeyVisible ? : } + + + + + + {({ copy }) => ( + + )} + @@ -1695,14 +1782,26 @@ function SettingsPanel() { setApiForm((p) => ({ ...p, urlApi: e.target.value }))} /> - setApiForm((p) => ({ ...p, apiKey: e.target.value }))} /> + setApiForm((p) => ({ ...p, apiKey: e.target.value }))} + rightSection={ + setApiConfigKeyVisible((v) => !v)}> + {apiConfigKeyVisible ? : } + + } + />