feat: localize api keys page
This commit is contained in:
+52
-26
@@ -5,11 +5,19 @@ var search = '';
|
||||
|
||||
var SCOPES = ['read', 'write', 'deploy'];
|
||||
var SCOPE_DESC = {
|
||||
read: 'Initialize MCP sessions and list tools',
|
||||
write: 'Execute published tools through MCP',
|
||||
deploy: 'Reserved for deploy-scoped automation',
|
||||
read: 'apikeys.scope.read',
|
||||
write: 'apikeys.scope.write',
|
||||
deploy: 'apikeys.scope.deploy',
|
||||
};
|
||||
|
||||
function tKey(key) {
|
||||
return window.t ? t(key) : key;
|
||||
}
|
||||
|
||||
function tfKey(key, vars) {
|
||||
return window.tf ? tf(key, vars) : tKey(key);
|
||||
}
|
||||
|
||||
function mapKeyRecord(record) {
|
||||
var apiKey = record.api_key || {};
|
||||
return {
|
||||
@@ -18,7 +26,7 @@ function mapKeyRecord(record) {
|
||||
prefix: apiKey.prefix || '',
|
||||
scopes: apiKey.scopes || [],
|
||||
created: apiKey.created_at ? apiKey.created_at.slice(0, 10) : '—',
|
||||
lastUsed: apiKey.last_used_at ? apiKey.last_used_at.slice(0, 10) : 'Never',
|
||||
lastUsed: apiKey.last_used_at ? apiKey.last_used_at.slice(0, 10) : tKey('apikeys.last_used.never'),
|
||||
status: apiKey.status || 'active',
|
||||
};
|
||||
}
|
||||
@@ -36,7 +44,7 @@ async function loadKeys() {
|
||||
if (!currentWorkspaceId || !window.CrankApi) {
|
||||
KEYS = [];
|
||||
setTableLoading(false);
|
||||
renderTable('Workspace or API is unavailable');
|
||||
renderTable(tKey('apikeys.error.api'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,7 +56,7 @@ async function loadKeys() {
|
||||
} catch (error) {
|
||||
KEYS = [];
|
||||
setTableLoading(false);
|
||||
renderTable(error.message || 'Failed to load API keys');
|
||||
renderTable(error.message || tKey('apikeys.error.load'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,13 +68,13 @@ function setTableLoading(on) {
|
||||
var td = document.createElement('td');
|
||||
td.colSpan = 7;
|
||||
td.style.cssText = 'text-align:center;padding:36px;color:var(--text-muted);';
|
||||
td.textContent = 'Loading…';
|
||||
td.textContent = tKey('apikeys.loading');
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
async function revokeKey(id) {
|
||||
if (!confirm('Revoke this key? All requests using it will fail immediately.')) return;
|
||||
if (!confirm(tKey('apikeys.confirm.revoke'))) return;
|
||||
|
||||
try {
|
||||
var key = KEYS.find(function(item) { return item.id === id; });
|
||||
@@ -74,19 +82,22 @@ async function revokeKey(id) {
|
||||
await loadKeys();
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.success(
|
||||
(key ? key.name : 'API key') + ' was revoked.',
|
||||
'Key revoked'
|
||||
tfKey('apikeys.toast.revoke_message', { name: key ? key.name : '' }),
|
||||
tKey('apikeys.toast.revoke_title')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.error(error.message || 'Failed to revoke key', 'Revoke failed');
|
||||
window.CrankUi.error(
|
||||
error.message || tKey('apikeys.toast.revoke_error_message'),
|
||||
tKey('apikeys.toast.revoke_error_title')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteKey(id) {
|
||||
if (!confirm('Delete this key? This cannot be undone.')) return;
|
||||
if (!confirm(tKey('apikeys.confirm.delete'))) return;
|
||||
|
||||
try {
|
||||
var key = KEYS.find(function(item) { return item.id === id; });
|
||||
@@ -94,13 +105,16 @@ async function deleteKey(id) {
|
||||
await loadKeys();
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.success(
|
||||
(key ? key.name : 'API key') + ' was deleted.',
|
||||
'Key deleted'
|
||||
tfKey('apikeys.toast.delete_message', { name: key ? key.name : '' }),
|
||||
tKey('apikeys.toast.delete_title')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.error(error.message || 'Failed to delete key', 'Delete failed');
|
||||
window.CrankUi.error(
|
||||
error.message || tKey('apikeys.toast.delete_error_message'),
|
||||
tKey('apikeys.toast.delete_error_title')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,7 +140,7 @@ function renderScopes() {
|
||||
input.dataset.scope = scope;
|
||||
if (selectedScopes.has(scope)) input.checked = true;
|
||||
node.querySelector('.scope-checkbox-name').textContent = scope;
|
||||
node.querySelector('.scope-checkbox-desc').textContent = SCOPE_DESC[scope];
|
||||
node.querySelector('.scope-checkbox-desc').textContent = tKey(SCOPE_DESC[scope]);
|
||||
input.addEventListener('change', function() {
|
||||
if (this.checked) selectedScopes.add(this.dataset.scope);
|
||||
else selectedScopes.delete(this.dataset.scope);
|
||||
@@ -148,7 +162,7 @@ function renderTable(errorMessage) {
|
||||
if (subtitle) {
|
||||
var active = KEYS.filter(function(key) { return key.status === 'active'; }).length;
|
||||
var revoked = KEYS.filter(function(key) { return key.status === 'revoked'; }).length;
|
||||
subtitle.textContent = active + ' active · ' + revoked + ' revoked';
|
||||
subtitle.textContent = tfKey('apikeys.active.subtitle', { active: active, revoked: revoked });
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
@@ -167,7 +181,7 @@ function renderTable(errorMessage) {
|
||||
var td = document.createElement('td');
|
||||
td.colSpan = 7;
|
||||
td.style.cssText = 'text-align:center;padding:36px;color:var(--text-muted);';
|
||||
td.textContent = KEYS.length ? 'No keys match your search' : 'No API keys yet';
|
||||
td.textContent = KEYS.length ? tKey('apikeys.empty.search') : tKey('apikeys.empty.none');
|
||||
empty.appendChild(td);
|
||||
tbody.appendChild(empty);
|
||||
return;
|
||||
@@ -191,7 +205,7 @@ function renderTable(errorMessage) {
|
||||
|
||||
var badge = document.createElement('span');
|
||||
badge.className = key.status === 'active' ? 'badge badge-active' : 'badge badge-revoked';
|
||||
badge.textContent = key.status === 'active' ? 'Active' : 'Revoked';
|
||||
badge.textContent = key.status === 'active' ? tKey('apikeys.status.active') : tKey('apikeys.status.revoked');
|
||||
node.querySelector('.col-status').appendChild(badge);
|
||||
|
||||
var actionsActive = node.querySelector('.actions-active');
|
||||
@@ -251,14 +265,17 @@ document.getElementById('modal-confirm-btn').addEventListener('click', async fun
|
||||
}
|
||||
if (!selectedScopes.size) {
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.info('Select at least one scope before creating a key.', 'Missing scope');
|
||||
window.CrankUi.info(
|
||||
tKey('apikeys.toast.scope_missing_message'),
|
||||
tKey('apikeys.toast.scope_missing_title')
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
button.disabled = true;
|
||||
button.textContent = 'Creating…';
|
||||
button.textContent = tKey('apikeys.creating');
|
||||
var created = await createKey(name, Array.from(selectedScopes));
|
||||
KEYS.unshift(created.record);
|
||||
document.getElementById('reveal-key-value').textContent = created.rawKey;
|
||||
@@ -268,15 +285,21 @@ document.getElementById('modal-confirm-btn').addEventListener('click', async fun
|
||||
document.getElementById('modal-footer-done').style.display = '';
|
||||
renderTable();
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.success('Copy the key now. It will not be shown again.', 'API key created');
|
||||
window.CrankUi.success(
|
||||
tKey('apikeys.toast.create_message'),
|
||||
tKey('apikeys.toast.create_title')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.error(error.message || 'Failed to create key', 'Key creation failed');
|
||||
window.CrankUi.error(
|
||||
error.message || tKey('apikeys.toast.create_error_message'),
|
||||
tKey('apikeys.toast.create_error_title')
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = 'Create key';
|
||||
button.textContent = tKey('apikeys.modal.create');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -289,7 +312,10 @@ document.getElementById('copy-key-btn').addEventListener('click', function() {
|
||||
}
|
||||
this.innerHTML = '<svg width="13" height="13"><use href="' + (window.APP_BASE || '') + 'icons/general/check.svg#icon"/></svg>';
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.info('Store the raw key securely. It cannot be revealed again.', 'API key copied');
|
||||
window.CrankUi.info(
|
||||
tKey('apikeys.toast.copy_message'),
|
||||
tKey('apikeys.toast.copy_title')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -303,7 +329,7 @@ function copyPrefix(prefix) {
|
||||
navigator.clipboard.writeText(prefix).catch(function() {});
|
||||
}
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.info(prefix, 'Key prefix copied');
|
||||
window.CrankUi.info(prefix, tKey('apikeys.toast.prefix_title'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user