From d1ce98ca8a4efc0608fcdede471fae579a4d3894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=AAn=20To=C3=A1n?= Date: Sat, 28 Feb 2026 11:07:51 +0700 Subject: [PATCH] feat: add API key visibility toggle in Endpoint dashboard (#214) - Added eye icon button to show/hide individual API keys - Keys hidden by default on page load for security - Copy button always copies full key regardless of visibility state - Implemented per-key visibility state with React useState - Added maskKey helper to display first 8 characters + "..." - Clean up visibility state when keys are deleted Improves security and UX when managing API keys in the dashboard. --- CHANGELOG.md | 5 +++ .../dashboard/endpoint/EndpointPageClient.js | 35 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d9d3e6..e21b252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased + +## Features +- Added API key visibility toggle (eye icon) to Endpoint dashboard page for improved UX and security. + # v0.2.66 (2026-02-06) ## Features diff --git a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js index b504937..836a0ec 100644 --- a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js +++ b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js @@ -50,6 +50,8 @@ export default function APIPageClient({ machineId }) { const [tunnelStatus, setTunnelStatus] = useState(null); const [showDisableModal, setShowDisableModal] = useState(false); const [showEnableModal, setShowEnableModal] = useState(false); + // API key visibility toggle state + const [visibleKeys, setVisibleKeys] = useState(new Set()); const { copied, copy } = useCopyToClipboard(); @@ -352,6 +354,12 @@ export default function APIPageClient({ machineId }) { const res = await fetch(`/api/keys/${id}`, { method: "DELETE" }); if (res.ok) { setKeys(keys.filter((k) => k.id !== id)); + // Clean up visibility state + setVisibleKeys(prev => { + const next = new Set(prev); + next.delete(id); + return next; + }); } } catch (error) { console.log("Error deleting key:", error); @@ -373,6 +381,20 @@ export default function APIPageClient({ machineId }) { } }; + const maskKey = (fullKey) => { + if (!fullKey) return ""; + return fullKey.length > 8 ? fullKey.slice(0, 8) + "..." : fullKey; + }; + + const toggleKeyVisibility = (keyId) => { + setVisibleKeys(prev => { + const next = new Set(prev); + if (next.has(keyId)) next.delete(keyId); + else next.add(keyId); + return next; + }); + }; + const [baseUrl, setBaseUrl] = useState("/v1"); // Hydration fix: Only access window on client side @@ -506,7 +528,18 @@ export default function APIPageClient({ machineId }) {

{key.name}

- {key.key} + + {visibleKeys.has(key.id) ? key.key : maskKey(key.key)} + +