feat: localize api keys page

This commit is contained in:
a.tolmachev
2026-04-02 23:04:10 +03:00
parent 12b43542a9
commit 2d7c43c438
3 changed files with 194 additions and 59 deletions
+32 -31
View File
@@ -48,12 +48,12 @@
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" style="display:none">
<div class="ws-dropdown-label">Your workspaces</div>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
<div class="ws-dropdown-footer">
<button class="ws-dropdown-create" onclick="window.location.href='/workspace-setup?mode=create'">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>
Create workspace
<span data-i18n="nav.create_workspace">Create workspace</span>
</button>
</div>
</div>
@@ -75,7 +75,7 @@
<span>⌘K</span>
</div>
<button class="nav-icon-btn" title="Notifications">
<button class="nav-icon-btn" data-i18n-title="nav.notifications" title="Notifications">
<svg width="15" height="15"><use href="icons/general/bell.svg#icon"/></svg>
</button>
@@ -116,12 +116,12 @@
<div class="page-header">
<div class="page-header-text">
<h1 class="page-title" data-i18n="apikeys.title">API Keys</h1>
<p class="page-subtitle">Keys authenticate external MCP clients and workspace automation against Crank.</p>
<p class="page-subtitle" data-i18n="apikeys.subtitle">Keys authenticate external MCP clients and workspace automation against Crank.</p>
</div>
<div class="page-header-actions">
<button class="btn-primary" id="btn-create-key" type="button">
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M7.75 2a.75.75 0 01.75.75V7h4.25a.75.75 0 010 1.5H8.5v4.25a.75.75 0 01-1.5 0V8.5H2.75a.75.75 0 010-1.5H7V2.75A.75.75 0 017.75 2z"/></svg>
New key
<span data-i18n="apikeys.new">Create key</span>
</button>
</div>
</div>
@@ -132,19 +132,20 @@
<path d="M8 11V8M8 5.5V5"/>
</svg>
<div>
<strong>Keys are only shown once.</strong> Copy and store them securely immediately after creation — Crank stores only a hash. `Last used` updates after successful MCP authentication.
<strong data-i18n="apikeys.callout.title">Keys are only shown once.</strong>
<span data-i18n="apikeys.callout.body">Copy and store them securely immediately after creation — Crank stores only a hash. `Last used` updates after successful MCP authentication.</span>
</div>
</div>
<div class="section-card">
<div class="section-card-header">
<div>
<div class="section-card-title">Active keys</div>
<div class="section-card-title" data-i18n="apikeys.active.title">Active keys</div>
<div class="section-card-subtitle">3 active · 1 revoked</div>
</div>
<div class="filter-bar-search" style="max-width: 220px; margin: 0;">
<svg width="13" height="13" style="position:absolute; left:9px; top:50%; transform:translateY(-50%); color:var(--text-muted);" viewBox="0 0 16 16" fill="currentColor"><path d="M10.68 11.74a6 6 0 01-7.922-8.982 6 6 0 018.982 7.922l3.04 3.04a.749.749 0 01-.326 1.275.749.749 0 01-.734-.215zM11.5 7a4.499 4.499 0 11-8.997 0A4.499 4.499 0 0111.5 7z"/></svg>
<input type="text" id="key-search" placeholder="Filter keys…" autocomplete="off">
<input type="text" id="key-search" data-i18n-ph="apikeys.search" placeholder="Filter keys…" autocomplete="off">
</div>
</div>
@@ -152,12 +153,12 @@
<table class="data-table" id="keys-table">
<thead>
<tr>
<th>Name</th>
<th>Key prefix</th>
<th>Scopes</th>
<th>Created</th>
<th>Last used</th>
<th>Status</th>
<th data-i18n="apikeys.th.name">Name</th>
<th data-i18n="apikeys.th.prefix">Key prefix</th>
<th data-i18n="apikeys.th.scopes">Scopes</th>
<th data-i18n="apikeys.th.created">Created</th>
<th data-i18n="apikeys.th.last">Last used</th>
<th data-i18n="apikeys.th.status">Status</th>
<th></th>
</tr>
</thead>
@@ -169,26 +170,26 @@
<div class="section-card">
<div class="section-card-header">
<div class="section-card-title">Scope reference</div>
<div class="section-card-title" data-i18n="apikeys.scope_ref">Scope reference</div>
</div>
<div class="section-card-body" style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;">
<div>
<div style="font-size:12.5px;font-weight:600;color:var(--text-primary);margin-bottom:4px;display:flex;align-items:center;gap:6px;">
<span class="badge badge-scope">read</span>
</div>
<div style="font-size:12px;color:var(--text-muted);line-height:1.55;">Initialize MCP sessions, ping the server, and list tools for a workspace agent.</div>
<div style="font-size:12px;color:var(--text-muted);line-height:1.55;" data-i18n="apikeys.scope.read">Initialize MCP sessions, ping the server, and list tools for a workspace agent.</div>
</div>
<div>
<div style="font-size:12.5px;font-weight:600;color:var(--text-primary);margin-bottom:4px;">
<span class="badge badge-scope">write</span>
</div>
<div style="font-size:12px;color:var(--text-muted);line-height:1.55;">Execute `tools/call` requests against published agent toolsets.</div>
<div style="font-size:12px;color:var(--text-muted);line-height:1.55;" data-i18n="apikeys.scope.write">Execute `tools/call` requests against published agent toolsets.</div>
</div>
<div>
<div style="font-size:12.5px;font-weight:600;color:var(--text-primary);margin-bottom:4px;">
<span class="badge badge-scope">deploy</span>
</div>
<div style="font-size:12px;color:var(--text-muted);line-height:1.55;">Reserved for deploy-scoped automation. Today it also permits MCP read/write flows.</div>
<div style="font-size:12px;color:var(--text-muted);line-height:1.55;" data-i18n="apikeys.scope.deploy">Reserved for deploy-scoped automation. Today it also permits MCP read/write flows.</div>
</div>
</div>
</div>
@@ -199,7 +200,7 @@
<div class="modal-overlay" id="modal-create">
<div class="modal">
<div class="modal-header">
<span class="modal-title">Create API key</span>
<span class="modal-title" data-i18n="apikeys.modal.title">Create API key</span>
<button class="modal-close" id="modal-close-btn" type="button">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
<line x1="1" y1="1" x2="11" y2="11"/><line x1="11" y1="1" x2="1" y2="11"/>
@@ -208,12 +209,12 @@
</div>
<div class="modal-body" id="modal-form-body">
<div class="field-group">
<label class="field-label">Key name</label>
<input class="field-input" id="new-key-name" type="text" placeholder="e.g. Production, CI pipeline" autocomplete="off">
<div class="field-hint">A descriptive label to identify the key. Only visible to admins.</div>
<label class="field-label" data-i18n="apikeys.modal.name">Key name</label>
<input class="field-input" id="new-key-name" type="text" data-i18n-ph="apikeys.modal.name_placeholder" placeholder="e.g. Production, CI pipeline" autocomplete="off">
<div class="field-hint" data-i18n="apikeys.modal.name_hint">A descriptive label to identify the key. Only visible to admins.</div>
</div>
<div class="field-group" style="margin-bottom:0;">
<label class="field-label">Scopes</label>
<label class="field-label" data-i18n="apikeys.modal.scopes">Scopes</label>
<div style="display:flex;flex-direction:column;gap:8px;margin-top:2px;" id="scope-checkboxes">
</div>
</div>
@@ -223,11 +224,11 @@
<svg class="callout-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="var(--amber)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<polygon points="8,1.5 15.5,14.5 0.5,14.5" fill="none"/><path d="M8 6v4M8 11.5v.5"/>
</svg>
<div><strong>Copy this key now.</strong> It won't be shown again.</div>
<div><strong data-i18n="apikeys.modal.reveal_title">Copy this key now.</strong> <span data-i18n="apikeys.modal.reveal_body">It won't be shown again.</span></div>
</div>
<div class="key-reveal">
<span class="key-mask" id="reveal-key-value">mcp_prod_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span>
<button class="key-copy" id="copy-key-btn" type="button" title="Copy to clipboard">
<button class="key-copy" id="copy-key-btn" type="button" data-i18n-title="apikeys.modal.copy" title="Copy to clipboard">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<rect x="5" y="5" width="9" height="9" rx="1.5"/>
<path d="M11 5V3.5A1.5 1.5 0 009.5 2H3.5A1.5 1.5 0 002 3.5v6A1.5 1.5 0 003.5 11H5"/>
@@ -236,11 +237,11 @@
</div>
</div>
<div class="modal-footer" id="modal-footer-create">
<button class="btn-secondary" id="modal-cancel-btn" type="button">Cancel</button>
<button class="btn-primary" id="modal-confirm-btn" type="button">Create key</button>
<button class="btn-secondary" id="modal-cancel-btn" type="button" data-i18n="apikeys.modal.cancel">Cancel</button>
<button class="btn-primary" id="modal-confirm-btn" type="button" data-i18n="apikeys.modal.create">Create key</button>
</div>
<div class="modal-footer" id="modal-footer-done" style="display:none;">
<button class="btn-primary" id="modal-done-btn" type="button">Done — I've copied the key</button>
<button class="btn-primary" id="modal-done-btn" type="button" data-i18n="apikeys.modal.done">Done — I've copied the key</button>
</div>
</div>
</div>
@@ -267,11 +268,11 @@
<td class="col-status"></td>
<td class="col-actions">
<span class="actions-active">
<button class="btn-icon" title="Copy key prefix"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="5" width="9" height="9" rx="1.5"/><path d="M11 5V3.5A1.5 1.5 0 009.5 2H3.5A1.5 1.5 0 002 3.5v6A1.5 1.5 0 003.5 11H5"/></svg></button>
<button class="btn-icon danger" title="Revoke key"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6.5"/><path d="M5.5 10.5l5-5M10.5 10.5l-5-5"/></svg></button>
<button class="btn-icon" data-i18n-title="apikeys.action.copy_prefix" title="Copy key prefix"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="5" width="9" height="9" rx="1.5"/><path d="M11 5V3.5A1.5 1.5 0 009.5 2H3.5A1.5 1.5 0 002 3.5v6A1.5 1.5 0 003.5 11H5"/></svg></button>
<button class="btn-icon danger" data-i18n-title="apikeys.action.revoke" title="Revoke key"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6.5"/><path d="M5.5 10.5l5-5M10.5 10.5l-5-5"/></svg></button>
</span>
<span class="actions-revoked" style="display:none;">
<button class="btn-icon" title="Delete"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2h4M2.5 4h11M5 4l.5 9h5l.5-9"/></svg></button>
<button class="btn-icon" data-i18n-title="apikeys.action.delete" title="Delete"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2h4M2.5 4h11M5 4l.5 9h5l.5-9"/></svg></button>
</span>
</td>
</tr>