feat: complete Epic 1 production foundation
This commit is contained in:
+324
-52
@@ -3,8 +3,15 @@ var AGENTS = [];
|
||||
var currentWorkspaceId = null;
|
||||
var currentAgentId = null;
|
||||
var activeKeyKind = 'mcp_client';
|
||||
var selectedScopes = new Set(['read']);
|
||||
var selectedScopes = new Set(['read', 'write']);
|
||||
var search = '';
|
||||
var loadGeneration = 0;
|
||||
var modalGeneration = 0;
|
||||
var keyMutations = {};
|
||||
var onboardingQueryHydrated = false;
|
||||
var onboardingRevealWasCreated = false;
|
||||
var createReconciliationRequired = false;
|
||||
var revealedKeyGeneration = 0;
|
||||
|
||||
var SCOPES_BY_KIND = {
|
||||
mcp_client: ['read', 'write', 'deploy'],
|
||||
@@ -58,6 +65,7 @@ function mapAgentRecord(record) {
|
||||
slug: record.slug,
|
||||
displayName: record.display_name || record.slug || record.id,
|
||||
mcpEndpoint: record.mcp_endpoint || '',
|
||||
catalogRevision: Number(record.catalog_revision || 0),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,11 +75,13 @@ function currentWorkspace() {
|
||||
|
||||
async function loadKeys() {
|
||||
var workspace = currentWorkspace();
|
||||
currentWorkspaceId = workspace ? workspace.id : null;
|
||||
var workspaceId = workspace ? workspace.id : null;
|
||||
var generation = ++loadGeneration;
|
||||
currentWorkspaceId = workspaceId;
|
||||
|
||||
setTableLoading(true);
|
||||
|
||||
if (!currentWorkspaceId || !window.CrankApi) {
|
||||
if (!workspaceId || !window.CrankApi) {
|
||||
AGENTS = [];
|
||||
KEYS = [];
|
||||
currentAgentId = null;
|
||||
@@ -79,11 +89,14 @@ async function loadKeys() {
|
||||
setCreateButtonState();
|
||||
setTableLoading(false);
|
||||
renderTable(tKey('apikeys.error.api'));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var response = await window.CrankApi.listAgents(currentWorkspaceId);
|
||||
var response = await window.CrankApi.listAgents(workspaceId);
|
||||
if (generation !== loadGeneration || workspaceId !== (currentWorkspace() && currentWorkspace().id)) {
|
||||
return;
|
||||
}
|
||||
AGENTS = ((response && response.items) || []).map(mapAgentRecord);
|
||||
if (!AGENTS.length) {
|
||||
currentAgentId = null;
|
||||
@@ -92,21 +105,38 @@ async function loadKeys() {
|
||||
setCreateButtonState();
|
||||
setTableLoading(false);
|
||||
renderTable();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
if (!currentAgentId || !AGENTS.some(function(agent) { return agent.id === currentAgentId; })) {
|
||||
var onboardingParams = new URLSearchParams(window.location.search);
|
||||
var requestedAgentId = onboardingParams.get('onboarding') === '1' ? onboardingParams.get('agentId') : '';
|
||||
if (requestedAgentId && AGENTS.some(function(agent) { return agent.id === requestedAgentId; })) {
|
||||
currentAgentId = requestedAgentId;
|
||||
} else if (!currentAgentId || !AGENTS.some(function(agent) { return agent.id === currentAgentId; })) {
|
||||
currentAgentId = AGENTS[0].id;
|
||||
}
|
||||
var agentId = currentAgentId;
|
||||
renderAgentPicker();
|
||||
var keysResponse = await window.CrankApi.listAgentPlatformApiKeys(
|
||||
currentWorkspaceId,
|
||||
currentAgentId
|
||||
workspaceId,
|
||||
agentId
|
||||
);
|
||||
if (
|
||||
generation !== loadGeneration
|
||||
|| workspaceId !== (currentWorkspace() && currentWorkspace().id)
|
||||
|| agentId !== currentAgentId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
KEYS = ((keysResponse && keysResponse.items) || []).map(mapKeyRecord);
|
||||
setCreateButtonState();
|
||||
setTableLoading(false);
|
||||
renderTable();
|
||||
hydrateOnboardingKeyQuery();
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (generation !== loadGeneration || workspaceId !== (currentWorkspace() && currentWorkspace().id)) {
|
||||
return;
|
||||
}
|
||||
AGENTS = [];
|
||||
KEYS = [];
|
||||
currentAgentId = null;
|
||||
@@ -114,6 +144,7 @@ async function loadKeys() {
|
||||
setCreateButtonState();
|
||||
setTableLoading(false);
|
||||
renderTable(error.message || tKey('apikeys.error.load'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,48 +164,68 @@ function setTableLoading(on) {
|
||||
async function revokeKey(id) {
|
||||
if (!confirm(tKey('apikeys.confirm.revoke'))) return;
|
||||
if (!currentAgentId) return;
|
||||
if (keyMutations[id]) return;
|
||||
|
||||
try {
|
||||
var workspaceId = currentWorkspaceId;
|
||||
var agentId = currentAgentId;
|
||||
var key = KEYS.find(function(item) { return item.id === id; });
|
||||
await window.CrankApi.revokeAgentPlatformApiKey(currentWorkspaceId, currentAgentId, id);
|
||||
await loadKeys();
|
||||
if (window.CrankUi) {
|
||||
keyMutations[id] = true;
|
||||
renderTable();
|
||||
await window.CrankApi.revokeAgentPlatformApiKey(workspaceId, agentId, id);
|
||||
if (workspaceId === currentWorkspaceId && agentId === currentAgentId) {
|
||||
await loadKeys();
|
||||
}
|
||||
if (window.CrankUi && workspaceId === currentWorkspaceId && agentId === currentAgentId) {
|
||||
window.CrankUi.success(
|
||||
tfKey('apikeys.toast.revoke_message', { name: key ? key.name : '' }),
|
||||
tKey('apikeys.toast.revoke_title')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.CrankUi) {
|
||||
if (window.CrankUi && workspaceId === currentWorkspaceId && agentId === currentAgentId) {
|
||||
window.CrankUi.error(
|
||||
error.message || tKey('apikeys.toast.revoke_error_message'),
|
||||
tKey('apikeys.toast.revoke_error_title')
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
delete keyMutations[id];
|
||||
renderTable();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteKey(id) {
|
||||
if (!confirm(tKey('apikeys.confirm.delete'))) return;
|
||||
if (!currentAgentId) return;
|
||||
if (keyMutations[id]) return;
|
||||
|
||||
try {
|
||||
var workspaceId = currentWorkspaceId;
|
||||
var agentId = currentAgentId;
|
||||
var key = KEYS.find(function(item) { return item.id === id; });
|
||||
await window.CrankApi.deleteAgentPlatformApiKey(currentWorkspaceId, currentAgentId, id);
|
||||
await loadKeys();
|
||||
if (window.CrankUi) {
|
||||
keyMutations[id] = true;
|
||||
renderTable();
|
||||
await window.CrankApi.deleteAgentPlatformApiKey(workspaceId, agentId, id);
|
||||
if (workspaceId === currentWorkspaceId && agentId === currentAgentId) {
|
||||
await loadKeys();
|
||||
}
|
||||
if (window.CrankUi && workspaceId === currentWorkspaceId && agentId === currentAgentId) {
|
||||
window.CrankUi.success(
|
||||
tfKey('apikeys.toast.delete_message', { name: key ? key.name : '' }),
|
||||
tKey('apikeys.toast.delete_title')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.CrankUi) {
|
||||
if (window.CrankUi && workspaceId === currentWorkspaceId && agentId === currentAgentId) {
|
||||
window.CrankUi.error(
|
||||
error.message || tKey('apikeys.toast.delete_error_message'),
|
||||
tKey('apikeys.toast.delete_error_title')
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
delete keyMutations[id];
|
||||
renderTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,9 +238,84 @@ async function createKey(name, scopes) {
|
||||
return {
|
||||
rawKey: created.secret,
|
||||
record: mapKeyRecord(created.api_key),
|
||||
connection: created.connection || null,
|
||||
};
|
||||
}
|
||||
|
||||
function isOnboardingKeyQuery() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
return params.get('onboarding') === '1' && params.get('action') === 'create';
|
||||
}
|
||||
|
||||
function hydrateOnboardingKeyQuery() {
|
||||
if (!isOnboardingKeyQuery() || onboardingQueryHydrated) return;
|
||||
onboardingQueryHydrated = true;
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var selected = currentAgent();
|
||||
var expectedRevision = Number(params.get('agentRevision') || 0);
|
||||
if (!selected || (expectedRevision && selected.catalogRevision !== expectedRevision)) return;
|
||||
var hasActiveKey = KEYS.some(function(key) { return key.status === 'active' && key.keyKind === 'mcp_client'; });
|
||||
if (hasActiveKey) {
|
||||
showLostKeyRecovery();
|
||||
return;
|
||||
}
|
||||
activeKeyKind = 'mcp_client';
|
||||
renderKeyKindTabs();
|
||||
openModal();
|
||||
}
|
||||
|
||||
function showLostKeyRecovery() {
|
||||
var element = document.getElementById('onboarding-key-lost');
|
||||
if (element) element.hidden = false;
|
||||
}
|
||||
|
||||
function renderEphemeralConnection(connection) {
|
||||
var root = document.getElementById('onboarding-connection-config');
|
||||
var clients = document.getElementById('onboarding-connection-clients');
|
||||
var warning = document.getElementById('onboarding-clipboard-warning');
|
||||
if (!root || !clients || !warning) return;
|
||||
clients.replaceChildren();
|
||||
if (!connection || !connection.endpoint) {
|
||||
root.hidden = true;
|
||||
warning.hidden = true;
|
||||
return;
|
||||
}
|
||||
var endpoint = document.createElement('code');
|
||||
endpoint.textContent = connection.endpoint;
|
||||
clients.appendChild(endpoint);
|
||||
(connection.clients || []).forEach(function(client) {
|
||||
var block = document.createElement('div');
|
||||
block.style.marginTop = '10px';
|
||||
var label = document.createElement('strong');
|
||||
label.textContent = client.client;
|
||||
var pre = document.createElement('pre');
|
||||
pre.className = 'log-detail-block';
|
||||
pre.textContent = JSON.stringify(client.config, null, 2);
|
||||
var copy = document.createElement('button');
|
||||
copy.type = 'button';
|
||||
copy.className = 'btn-secondary';
|
||||
copy.textContent = tKey('apikeys.modal.copy_config');
|
||||
copy.addEventListener('click', async function() {
|
||||
var value = pre.textContent;
|
||||
copy.disabled = true;
|
||||
try {
|
||||
await copyEphemeralValue(value);
|
||||
wipeRevealedKey();
|
||||
} catch (_error) {
|
||||
showClipboardFailure();
|
||||
} finally {
|
||||
copy.disabled = false;
|
||||
}
|
||||
});
|
||||
block.appendChild(label);
|
||||
block.appendChild(pre);
|
||||
block.appendChild(copy);
|
||||
clients.appendChild(block);
|
||||
});
|
||||
root.hidden = false;
|
||||
warning.hidden = false;
|
||||
}
|
||||
|
||||
function currentAgent() {
|
||||
return AGENTS.find(function(agent) { return agent.id === currentAgentId; }) || null;
|
||||
}
|
||||
@@ -311,7 +437,7 @@ function renderTable(errorMessage) {
|
||||
|
||||
if (subtitle) {
|
||||
var active = visibleKeys.filter(function(key) { return key.status === 'active'; }).length;
|
||||
var revoked = visibleKeys.filter(function(key) { return key.status === 'revoked'; }).length;
|
||||
var revoked = visibleKeys.filter(function(key) { return key.status !== 'active'; }).length;
|
||||
subtitle.textContent = currentAgentId
|
||||
? tfKey('apikeys.active.subtitle', { active: active, revoked: revoked })
|
||||
: tKey('apikeys.agent.empty_hint');
|
||||
@@ -325,7 +451,7 @@ function renderTable(errorMessage) {
|
||||
errorCell.textContent = errorMessage;
|
||||
errorRow.appendChild(errorCell);
|
||||
tbody.appendChild(errorRow);
|
||||
renderKeyCards([], errorMessage);
|
||||
renderKeyCards([], errorMessage, visibleKeys.length);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -339,7 +465,7 @@ function renderTable(errorMessage) {
|
||||
: tKey('apikeys.agent.empty_hint');
|
||||
empty.appendChild(td);
|
||||
tbody.appendChild(empty);
|
||||
renderKeyCards(rows);
|
||||
renderKeyCards(rows, null, visibleKeys.length);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -361,33 +487,42 @@ function renderTable(errorMessage) {
|
||||
|
||||
var badge = document.createElement('span');
|
||||
badge.className = key.status === 'active' ? 'badge badge-active' : 'badge badge-revoked';
|
||||
badge.textContent = key.status === 'active' ? tKey('apikeys.status.active') : tKey('apikeys.status.revoked');
|
||||
badge.textContent = keyStatusLabel(key.status);
|
||||
node.querySelector('.col-status').appendChild(badge);
|
||||
|
||||
var actionsActive = node.querySelector('.actions-active');
|
||||
var actionsRevoked = node.querySelector('.actions-revoked');
|
||||
if (key.status === 'active') {
|
||||
actionsActive.querySelectorAll('button').forEach(function(button) {
|
||||
button.disabled = Boolean(keyMutations[key.id]);
|
||||
});
|
||||
actionsActive.querySelector('[title=\"Copy key prefix\"]').addEventListener('click', function() {
|
||||
copyPrefix(key.prefix);
|
||||
});
|
||||
actionsActive.querySelector('[title=\"Revoke key\"]').addEventListener('click', function() {
|
||||
revokeKey(key.id);
|
||||
});
|
||||
} else {
|
||||
} else if (key.status === 'revoked') {
|
||||
actionsActive.hidden = true;
|
||||
actionsRevoked.hidden = false;
|
||||
actionsRevoked.querySelectorAll('button').forEach(function(button) {
|
||||
button.disabled = Boolean(keyMutations[key.id]);
|
||||
});
|
||||
actionsRevoked.querySelector('[title=\"Delete\"]').addEventListener('click', function() {
|
||||
deleteKey(key.id);
|
||||
});
|
||||
} else {
|
||||
actionsActive.hidden = true;
|
||||
actionsRevoked.hidden = true;
|
||||
}
|
||||
|
||||
tbody.appendChild(node);
|
||||
});
|
||||
|
||||
renderKeyCards(rows);
|
||||
renderKeyCards(rows, null, visibleKeys.length);
|
||||
}
|
||||
|
||||
function renderKeyCards(rows, errorMessage) {
|
||||
function renderKeyCards(rows, errorMessage, visibleKeyCount) {
|
||||
var cardList = document.getElementById('keys-card-list');
|
||||
if (!cardList) return;
|
||||
|
||||
@@ -402,7 +537,7 @@ function renderKeyCards(rows, errorMessage) {
|
||||
cardList.appendChild(
|
||||
buildKeyCardMessage(
|
||||
currentAgentId
|
||||
? (visibleKeys.length ? tKey('apikeys.empty.search') : emptyTextForKind())
|
||||
? (visibleKeyCount ? tKey('apikeys.empty.search') : emptyTextForKind())
|
||||
: tKey('apikeys.agent.empty_hint'),
|
||||
false
|
||||
)
|
||||
@@ -431,9 +566,7 @@ function renderKeyCards(rows, errorMessage) {
|
||||
|
||||
var statusBadge = document.createElement('span');
|
||||
statusBadge.className = key.status === 'active' ? 'badge badge-active' : 'badge badge-revoked';
|
||||
statusBadge.textContent = key.status === 'active'
|
||||
? tKey('apikeys.status.active')
|
||||
: tKey('apikeys.status.revoked');
|
||||
statusBadge.textContent = keyStatusLabel(key.status);
|
||||
actions.appendChild(statusBadge);
|
||||
header.appendChild(headerMain);
|
||||
header.appendChild(actions);
|
||||
@@ -451,14 +584,14 @@ function renderKeyCards(rows, errorMessage) {
|
||||
if (key.status === 'active') {
|
||||
actionRow.appendChild(buildCardAction('apikeys.action.copy_prefix', function() {
|
||||
copyPrefix(key.prefix);
|
||||
}));
|
||||
}, false, Boolean(keyMutations[key.id])));
|
||||
actionRow.appendChild(buildCardAction('apikeys.action.revoke', function() {
|
||||
revokeKey(key.id);
|
||||
}, true));
|
||||
} else {
|
||||
}, true, Boolean(keyMutations[key.id])));
|
||||
} else if (key.status === 'revoked') {
|
||||
actionRow.appendChild(buildCardAction('apikeys.action.delete', function() {
|
||||
deleteKey(key.id);
|
||||
}, true));
|
||||
}, true, Boolean(keyMutations[key.id])));
|
||||
}
|
||||
card.appendChild(actionRow);
|
||||
|
||||
@@ -477,6 +610,12 @@ function buildKeyCardMessage(text, isError) {
|
||||
return card;
|
||||
}
|
||||
|
||||
function keyStatusLabel(status) {
|
||||
if (status === 'active') return tKey('apikeys.status.active');
|
||||
if (status === 'deleted') return tKey('apikeys.status.deleted');
|
||||
return tKey('apikeys.status.revoked');
|
||||
}
|
||||
|
||||
function emptyTextForKind() {
|
||||
return activeKeyKind === 'approval'
|
||||
? tKey('apikeys.empty.approval')
|
||||
@@ -497,25 +636,70 @@ function buildMetaItem(labelKey, valueText) {
|
||||
return item;
|
||||
}
|
||||
|
||||
function buildCardAction(labelKey, onClick, isDanger) {
|
||||
function buildCardAction(labelKey, onClick, isDanger, disabled) {
|
||||
var button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = isDanger ? 'btn-secondary' : 'btn-secondary';
|
||||
button.textContent = tKey(labelKey);
|
||||
button.disabled = Boolean(disabled);
|
||||
button.addEventListener('click', onClick);
|
||||
return button;
|
||||
}
|
||||
|
||||
var modal = document.getElementById('modal-create');
|
||||
|
||||
function wipeRevealedKey(expectedGeneration) {
|
||||
if (expectedGeneration && revealedKeyGeneration && revealedKeyGeneration !== expectedGeneration) {
|
||||
return;
|
||||
}
|
||||
var keyValue = document.getElementById('reveal-key-value');
|
||||
if (keyValue) {
|
||||
keyValue.textContent = '';
|
||||
}
|
||||
var clients = document.getElementById('onboarding-connection-clients');
|
||||
if (clients) clients.replaceChildren();
|
||||
var connection = document.getElementById('onboarding-connection-config');
|
||||
if (connection) connection.hidden = true;
|
||||
var warning = document.getElementById('onboarding-clipboard-warning');
|
||||
if (warning) warning.hidden = true;
|
||||
var copyButton = document.getElementById('copy-key-btn');
|
||||
if (copyButton) {
|
||||
copyButton.replaceChildren(
|
||||
buildIconSvg(
|
||||
(window.APP_BASE || '') + 'icons/general/copy.svg#icon',
|
||||
14,
|
||||
14
|
||||
)
|
||||
);
|
||||
}
|
||||
revealedKeyGeneration = 0;
|
||||
}
|
||||
|
||||
function resetCreateButton() {
|
||||
var button = document.getElementById('modal-confirm-btn');
|
||||
if (!button) return;
|
||||
button.disabled = !currentAgentId;
|
||||
button.textContent = tKey('apikeys.modal.create');
|
||||
}
|
||||
|
||||
function invalidateModalState() {
|
||||
modalGeneration += 1;
|
||||
wipeRevealedKey();
|
||||
resetCreateButton();
|
||||
createReconciliationRequired = false;
|
||||
var warning = document.getElementById('ambiguous-create-warning');
|
||||
if (warning) warning.hidden = true;
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
if (!currentAgentId) return;
|
||||
invalidateModalState();
|
||||
document.getElementById('modal-form-body').hidden = false;
|
||||
document.getElementById('modal-reveal-body').hidden = true;
|
||||
document.getElementById('modal-footer-create').hidden = false;
|
||||
document.getElementById('modal-footer-done').hidden = true;
|
||||
document.getElementById('new-key-name').value = '';
|
||||
selectedScopes = new Set([activeKeyKind === 'approval' ? 'approve' : 'read']);
|
||||
selectedScopes = new Set(activeKeyKind === 'approval' ? ['approve'] : ['read', 'write']);
|
||||
document.getElementById('modal-create-title').textContent = activeKeyKind === 'approval'
|
||||
? tKey('apikeys.modal.title_approval')
|
||||
: tKey('apikeys.modal.title_mcp');
|
||||
@@ -529,6 +713,7 @@ function openModal() {
|
||||
|
||||
function closeModal() {
|
||||
modal.classList.remove('open');
|
||||
invalidateModalState();
|
||||
}
|
||||
|
||||
document.getElementById('btn-create-key').addEventListener('click', openModal);
|
||||
@@ -558,14 +743,34 @@ document.getElementById('modal-confirm-btn').addEventListener('click', async fun
|
||||
try {
|
||||
button.disabled = true;
|
||||
button.textContent = tKey('apikeys.creating');
|
||||
var requestGeneration = ++modalGeneration;
|
||||
var workspaceId = currentWorkspaceId;
|
||||
var agentId = currentAgentId;
|
||||
var keyKind = activeKeyKind;
|
||||
var created = await createKey(name, Array.from(selectedScopes));
|
||||
if (
|
||||
requestGeneration !== modalGeneration
|
||||
|| workspaceId !== currentWorkspaceId
|
||||
|| agentId !== currentAgentId
|
||||
|| keyKind !== activeKeyKind
|
||||
|| !modal.classList.contains('open')
|
||||
) {
|
||||
wipeRevealedKey(requestGeneration);
|
||||
var reconciled = await loadKeys();
|
||||
if (reconciled) showLostKeyRecovery();
|
||||
return;
|
||||
}
|
||||
KEYS.unshift(created.record);
|
||||
document.getElementById('reveal-key-value').textContent = created.rawKey;
|
||||
revealedKeyGeneration = requestGeneration;
|
||||
onboardingRevealWasCreated = true;
|
||||
renderEphemeralConnection(keyKind === 'mcp_client' ? created.connection : null);
|
||||
document.getElementById('modal-form-body').hidden = true;
|
||||
document.getElementById('modal-reveal-body').hidden = false;
|
||||
document.getElementById('modal-footer-create').hidden = true;
|
||||
document.getElementById('modal-footer-done').hidden = false;
|
||||
renderTable();
|
||||
if (window.CrankOnboarding) window.CrankOnboarding.signalRefresh();
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.success(
|
||||
tKey('apikeys.toast.create_message'),
|
||||
@@ -573,38 +778,87 @@ document.getElementById('modal-confirm-btn').addEventListener('click', async fun
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
var ambiguous = !error.status || error.status === 408 || error.status >= 500;
|
||||
if (ambiguous
|
||||
&& workspaceId === currentWorkspaceId
|
||||
&& agentId === currentAgentId
|
||||
&& modal.classList.contains('open')) {
|
||||
createReconciliationRequired = true;
|
||||
var warning = document.getElementById('ambiguous-create-warning');
|
||||
if (warning) warning.hidden = false;
|
||||
await loadKeys();
|
||||
}
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.error(
|
||||
error.message || tKey('apikeys.toast.create_error_message'),
|
||||
tKey('apikeys.toast.create_error_title')
|
||||
ambiguous ? tKey('apikeys.ambiguous.body') : (error.message || tKey('apikeys.toast.create_error_message')),
|
||||
ambiguous ? tKey('apikeys.ambiguous.title') : tKey('apikeys.toast.create_error_title')
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = tKey('apikeys.modal.create');
|
||||
if (requestGeneration === modalGeneration && button && modal.classList.contains('open')) {
|
||||
button.disabled = createReconciliationRequired || !currentAgentId;
|
||||
button.textContent = createReconciliationRequired
|
||||
? tKey('apikeys.ambiguous.blocked')
|
||||
: tKey('apikeys.modal.create');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('modal-done-btn').addEventListener('click', closeModal);
|
||||
|
||||
document.getElementById('copy-key-btn').addEventListener('click', function() {
|
||||
var value = document.getElementById('reveal-key-value').textContent;
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(value).catch(function() {});
|
||||
function copyEphemeralValue(value) {
|
||||
if (!value || !navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') {
|
||||
return Promise.reject(new Error('clipboard unavailable'));
|
||||
}
|
||||
this.replaceChildren(
|
||||
buildIconSvg(
|
||||
(window.APP_BASE || '') + 'icons/general/check.svg#icon',
|
||||
13,
|
||||
13
|
||||
)
|
||||
);
|
||||
return navigator.clipboard.writeText(value);
|
||||
}
|
||||
|
||||
function showClipboardFailure() {
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.info(
|
||||
tKey('apikeys.toast.copy_message'),
|
||||
tKey('apikeys.toast.copy_title')
|
||||
window.CrankUi.error(
|
||||
tKey('apikeys.toast.copy_error_message'),
|
||||
tKey('apikeys.toast.copy_error_title')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('copy-key-btn').addEventListener('click', async function() {
|
||||
var value = document.getElementById('reveal-key-value').textContent;
|
||||
this.disabled = true;
|
||||
try {
|
||||
await copyEphemeralValue(value);
|
||||
this.replaceChildren(
|
||||
buildIconSvg(
|
||||
(window.APP_BASE || '') + 'icons/general/check.svg#icon',
|
||||
13,
|
||||
13
|
||||
)
|
||||
);
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.info(
|
||||
tKey('apikeys.toast.copy_message'),
|
||||
tKey('apikeys.toast.copy_title')
|
||||
);
|
||||
}
|
||||
wipeRevealedKey();
|
||||
} catch (_error) {
|
||||
showClipboardFailure();
|
||||
} finally {
|
||||
this.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('ambiguous-create-retry-btn').addEventListener('click', async function() {
|
||||
var button = this;
|
||||
button.disabled = true;
|
||||
var reconciled = await loadKeys();
|
||||
if (reconciled && currentAgentId && modal.classList.contains('open')) {
|
||||
createReconciliationRequired = false;
|
||||
document.getElementById('ambiguous-create-warning').hidden = true;
|
||||
resetCreateButton();
|
||||
document.getElementById('new-key-name').focus();
|
||||
}
|
||||
button.disabled = false;
|
||||
});
|
||||
|
||||
document.getElementById('key-search').addEventListener('input', function() {
|
||||
@@ -613,12 +867,14 @@ document.getElementById('key-search').addEventListener('input', function() {
|
||||
});
|
||||
|
||||
document.getElementById('agent-select').addEventListener('change', async function() {
|
||||
closeModal();
|
||||
currentAgentId = this.value || null;
|
||||
await loadKeys();
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-key-kind]').forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
closeModal();
|
||||
activeKeyKind = this.dataset.keyKind || 'mcp_client';
|
||||
search = '';
|
||||
document.getElementById('key-search').value = '';
|
||||
@@ -641,6 +897,22 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve());
|
||||
await loadKeys();
|
||||
window.addEventListener('crank:workspacechange', function() {
|
||||
keyMutations = {};
|
||||
closeModal();
|
||||
onboardingQueryHydrated = false;
|
||||
loadKeys();
|
||||
});
|
||||
window.addEventListener('crank:langchange', function() {
|
||||
closeModal();
|
||||
if (onboardingRevealWasCreated && isOnboardingKeyQuery()) showLostKeyRecovery();
|
||||
});
|
||||
window.addEventListener('pagehide', function() {
|
||||
closeModal();
|
||||
});
|
||||
window.addEventListener('pageshow', function(event) {
|
||||
if (event.persisted) {
|
||||
closeModal();
|
||||
if (onboardingRevealWasCreated && isOnboardingKeyQuery()) showLostKeyRecovery();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user