Split MCP and approval agent keys
This commit is contained in:
+81
-8
@@ -2,14 +2,20 @@ var KEYS = [];
|
||||
var AGENTS = [];
|
||||
var currentWorkspaceId = null;
|
||||
var currentAgentId = null;
|
||||
var activeKeyKind = 'mcp_client';
|
||||
var selectedScopes = new Set(['read']);
|
||||
var search = '';
|
||||
|
||||
var SCOPES = ['read', 'write', 'deploy'];
|
||||
var SCOPES_BY_KIND = {
|
||||
mcp_client: ['read', 'write', 'deploy'],
|
||||
approval: ['approve', 'deny'],
|
||||
};
|
||||
var SCOPE_DESC = {
|
||||
read: 'apikeys.scope.read',
|
||||
write: 'apikeys.scope.write',
|
||||
deploy: 'apikeys.scope.deploy',
|
||||
approve: 'apikeys.scope.approve',
|
||||
deny: 'apikeys.scope.deny',
|
||||
};
|
||||
|
||||
function tKey(key) {
|
||||
@@ -35,6 +41,7 @@ function mapKeyRecord(record) {
|
||||
return {
|
||||
id: apiKey.id,
|
||||
agentId: apiKey.agent_id || null,
|
||||
keyKind: apiKey.key_kind || 'mcp_client',
|
||||
name: apiKey.name,
|
||||
prefix: apiKey.prefix || '',
|
||||
scopes: apiKey.scopes || [],
|
||||
@@ -173,6 +180,7 @@ async function deleteKey(id) {
|
||||
async function createKey(name, scopes) {
|
||||
var created = await window.CrankApi.createAgentPlatformApiKey(currentWorkspaceId, currentAgentId, {
|
||||
name: name,
|
||||
key_kind: activeKeyKind,
|
||||
scopes: scopes,
|
||||
});
|
||||
return {
|
||||
@@ -221,13 +229,19 @@ function setCreateButtonState() {
|
||||
var button = document.getElementById('btn-create-key');
|
||||
if (!button) return;
|
||||
button.disabled = !currentAgentId;
|
||||
var label = document.getElementById('btn-create-key-label');
|
||||
if (label) {
|
||||
label.textContent = activeKeyKind === 'approval'
|
||||
? tKey('apikeys.new_approval')
|
||||
: tKey('apikeys.new_mcp');
|
||||
}
|
||||
}
|
||||
|
||||
function renderScopes() {
|
||||
var el = document.getElementById('scope-checkboxes');
|
||||
var tmpl = document.getElementById('tmpl-scope-checkbox');
|
||||
el.innerHTML = '';
|
||||
SCOPES.forEach(function(scope) {
|
||||
(SCOPES_BY_KIND[activeKeyKind] || []).forEach(function(scope) {
|
||||
var node = tmpl.content.cloneNode(true);
|
||||
var input = node.querySelector('input');
|
||||
input.dataset.scope = scope;
|
||||
@@ -242,11 +256,49 @@ function renderScopes() {
|
||||
});
|
||||
}
|
||||
|
||||
function renderKeyKindTabs() {
|
||||
document.querySelectorAll('[data-key-kind]').forEach(function(button) {
|
||||
var kind = button.dataset.keyKind;
|
||||
button.classList.toggle('active', kind === activeKeyKind);
|
||||
button.setAttribute('aria-selected', kind === activeKeyKind ? 'true' : 'false');
|
||||
});
|
||||
var hint = document.getElementById('key-kind-hint');
|
||||
if (hint) {
|
||||
hint.textContent = activeKeyKind === 'approval'
|
||||
? tKey('apikeys.kind.approval_hint')
|
||||
: tKey('apikeys.kind.mcp_hint');
|
||||
}
|
||||
renderScopeReference();
|
||||
setCreateButtonState();
|
||||
}
|
||||
|
||||
function renderScopeReference() {
|
||||
var grid = document.getElementById('scope-reference-grid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '';
|
||||
(SCOPES_BY_KIND[activeKeyKind] || []).forEach(function(scope) {
|
||||
var item = document.createElement('div');
|
||||
var title = document.createElement('div');
|
||||
title.style.cssText = 'font-size:12.5px;font-weight:600;color:var(--text-primary);margin-bottom:4px;';
|
||||
var badge = document.createElement('span');
|
||||
badge.className = 'badge badge-scope';
|
||||
badge.textContent = scope;
|
||||
title.appendChild(badge);
|
||||
var description = document.createElement('div');
|
||||
description.style.cssText = 'font-size:12px;color:var(--text-muted);line-height:1.55;';
|
||||
description.textContent = tKey(SCOPE_DESC[scope]);
|
||||
item.appendChild(title);
|
||||
item.appendChild(description);
|
||||
grid.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(errorMessage) {
|
||||
var q = search.toLowerCase();
|
||||
var tbody = document.getElementById('keys-tbody');
|
||||
var cardList = document.getElementById('keys-card-list');
|
||||
var rows = KEYS.filter(function(key) {
|
||||
var visibleKeys = KEYS.filter(function(key) { return key.keyKind === activeKeyKind; });
|
||||
var rows = visibleKeys.filter(function(key) {
|
||||
return !q || key.name.toLowerCase().includes(q) || key.prefix.toLowerCase().includes(q);
|
||||
});
|
||||
var subtitle = document.getElementById('keys-summary-subtitle');
|
||||
@@ -257,8 +309,8 @@ 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;
|
||||
var active = visibleKeys.filter(function(key) { return key.status === 'active'; }).length;
|
||||
var revoked = visibleKeys.filter(function(key) { return key.status === 'revoked'; }).length;
|
||||
subtitle.textContent = currentAgentId
|
||||
? tfKey('apikeys.active.subtitle', { active: active, revoked: revoked })
|
||||
: tKey('apikeys.agent.empty_hint');
|
||||
@@ -282,7 +334,7 @@ function renderTable(errorMessage) {
|
||||
td.colSpan = 7;
|
||||
td.style.cssText = 'text-align:center;padding:36px;color:var(--text-muted);';
|
||||
td.textContent = currentAgentId
|
||||
? (KEYS.length ? tKey('apikeys.empty.search') : tKey('apikeys.empty.none'))
|
||||
? (visibleKeys.length ? tKey('apikeys.empty.search') : emptyTextForKind())
|
||||
: tKey('apikeys.agent.empty_hint');
|
||||
empty.appendChild(td);
|
||||
tbody.appendChild(empty);
|
||||
@@ -349,7 +401,7 @@ function renderKeyCards(rows, errorMessage) {
|
||||
cardList.appendChild(
|
||||
buildKeyCardMessage(
|
||||
currentAgentId
|
||||
? (KEYS.length ? tKey('apikeys.empty.search') : tKey('apikeys.empty.none'))
|
||||
? (visibleKeys.length ? tKey('apikeys.empty.search') : emptyTextForKind())
|
||||
: tKey('apikeys.agent.empty_hint'),
|
||||
false
|
||||
)
|
||||
@@ -424,6 +476,12 @@ function buildKeyCardMessage(text, isError) {
|
||||
return card;
|
||||
}
|
||||
|
||||
function emptyTextForKind() {
|
||||
return activeKeyKind === 'approval'
|
||||
? tKey('apikeys.empty.approval')
|
||||
: tKey('apikeys.empty.none');
|
||||
}
|
||||
|
||||
function buildMetaItem(labelKey, valueText) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'resource-meta-item';
|
||||
@@ -456,7 +514,11 @@ function openModal() {
|
||||
document.getElementById('modal-footer-create').hidden = false;
|
||||
document.getElementById('modal-footer-done').hidden = true;
|
||||
document.getElementById('new-key-name').value = '';
|
||||
selectedScopes = new Set(['read']);
|
||||
selectedScopes = new Set([activeKeyKind === 'approval' ? 'approve' : 'read']);
|
||||
document.getElementById('modal-create-title').textContent = activeKeyKind === 'approval'
|
||||
? tKey('apikeys.modal.title_approval')
|
||||
: tKey('apikeys.modal.title_mcp');
|
||||
document.getElementById('approval-key-warning').hidden = activeKeyKind !== 'approval';
|
||||
renderScopes();
|
||||
modal.classList.add('open');
|
||||
setTimeout(function() {
|
||||
@@ -554,6 +616,16 @@ document.getElementById('agent-select').addEventListener('change', async functio
|
||||
await loadKeys();
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-key-kind]').forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
activeKeyKind = this.dataset.keyKind || 'mcp_client';
|
||||
search = '';
|
||||
document.getElementById('key-search').value = '';
|
||||
renderKeyKindTabs();
|
||||
renderTable();
|
||||
});
|
||||
});
|
||||
|
||||
function copyPrefix(prefix) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(prefix).catch(function() {});
|
||||
@@ -564,6 +636,7 @@ function copyPrefix(prefix) {
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
renderKeyKindTabs();
|
||||
await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve());
|
||||
await loadKeys();
|
||||
window.addEventListener('crank:workspacechange', function() {
|
||||
|
||||
Reference in New Issue
Block a user