mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 }) {
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium">{key.name}</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<code className="text-xs text-text-muted font-mono">{key.key}</code>
|
||||
<code className="text-xs text-text-muted font-mono">
|
||||
{visibleKeys.has(key.id) ? key.key : maskKey(key.key)}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => toggleKeyVisibility(key.id)}
|
||||
className="p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded text-text-muted hover:text-primary opacity-0 group-hover:opacity-100 transition-all"
|
||||
title={visibleKeys.has(key.id) ? "Hide key" : "Show key"}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">
|
||||
{visibleKeys.has(key.id) ? "visibility_off" : "visibility"}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => copy(key.key, key.id)}
|
||||
className="p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded text-text-muted hover:text-primary opacity-0 group-hover:opacity-100 transition-all"
|
||||
|
||||
Reference in New Issue
Block a user