Fix agent creation UI: scope selector + agent widget on main screen
1. Fixed 'Scoped' agent creation: - Scope selector now appears when 'Scoped' is selected - Loads available scopes from entries - Shows checkboxes for each scope - Select All/None button - Validates at least one scope selected 2. Fixed token display: - Uses result.credential (was result.token) - Uses result.scopes (was result.scope) 3. Added agent widget to main screen: - Shows active agent count - Click to open agent management - Only appears when agents exist 4. Removed Admin option from create/edit (agents can't manage agents)
This commit is contained in:
parent
d3e9f89bc0
commit
2410a2d128
|
|
@ -499,6 +499,7 @@
|
|||
'<button onclick="showNewEntry()" class="btn btn-primary btn-sm">+ New</button>' +
|
||||
'</div></div>' +
|
||||
'<div id="listStats" class="list-stats"></div>' +
|
||||
'<div id="agentWidget" class="agent-widget hidden"></div>' +
|
||||
'<div class="main-content">' +
|
||||
'<div id="listPane" class="split-list"></div>' +
|
||||
'<div id="detailPane" class="split-detail"><div class="split-detail-empty">Select an entry</div></div>' +
|
||||
|
|
@ -542,6 +543,7 @@
|
|||
entries = await api('GET', '/api/entries');
|
||||
history.replaceState({list: true}, '', '/app/');
|
||||
renderEntryList();
|
||||
loadAgentWidget(); // Load agents count
|
||||
if (autoImport && entries.length === 0) {
|
||||
location.href = '/app/import.html';
|
||||
}
|
||||
|
|
@ -554,6 +556,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function loadAgentWidget() {
|
||||
try {
|
||||
var agents = await api('GET', '/api/agents');
|
||||
var widget = document.getElementById('agentWidget');
|
||||
if (!agents || agents.length === 0) {
|
||||
widget.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
var active = agents.filter(function(a) { return a.status === 'active'; }).length;
|
||||
widget.innerHTML = '<div onclick="showAgents()" style="cursor:pointer;display:flex;align-items:center;gap:0.5rem;padding:0.5rem 1rem;background:var(--surface-1);border-bottom:1px solid var(--border)">' +
|
||||
'<span style="font-size:1.2rem">🤖</span>' +
|
||||
'<span>' + active + ' active agent' + (active !== 1 ? 's' : '') + '</span>' +
|
||||
'<span style="margin-left:auto;color:var(--muted);font-size:0.75rem">Click to manage</span>' +
|
||||
'</div>';
|
||||
widget.classList.remove('hidden');
|
||||
} catch(e) {
|
||||
// Silently fail - agents not critical
|
||||
}
|
||||
}
|
||||
|
||||
function domainFrom(url) {
|
||||
try { return new URL(url).hostname.replace(/^www\./, ''); } catch(e) { return url; }
|
||||
}
|
||||
|
|
@ -1311,18 +1333,78 @@
|
|||
'</div>' +
|
||||
'<div class="form-group mb-3">' +
|
||||
'<label class="form-label">Access level</label>' +
|
||||
'<select id="agentAccess" class="form-input">' +
|
||||
'<select id="agentAccess" class="form-input" onchange="toggleScopeSelector()">' +
|
||||
'<option value="scoped">Scoped (only assigned entries)</option>' +
|
||||
'<option value="all">Full read access (all entries)</option>' +
|
||||
'<option value="admin">Admin (full access + manage agents)</option>' +
|
||||
'</select>' +
|
||||
'</div>' +
|
||||
'<div id="scopeSelector" class="form-group mb-3" style="display:none">' +
|
||||
'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.5rem">' +
|
||||
'<label class="form-label" style="margin:0">Select scopes</label>' +
|
||||
'<button onclick="toggleAllScopes()" class="btn btn-ghost btn-sm">Select All / None</button>' +
|
||||
'</div>' +
|
||||
'<div id="scopeList" style="max-height:200px;overflow-y:auto;border:1px solid var(--border);border-radius:0.5rem;padding:0.75rem;background:var(--surface-1)">' +
|
||||
'<p class="text-muted">Loading scopes...</p>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="modal-actions">' +
|
||||
'<button onclick="doCreateAgent()" class="btn btn-primary">Create</button>' +
|
||||
'<button onclick="showAgents()" class="btn btn-ghost">Back</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
document.getElementById('agentName').focus();
|
||||
loadAvailableScopes();
|
||||
toggleScopeSelector(); // Show/hide based on initial selection
|
||||
}
|
||||
|
||||
var availableScopes = [];
|
||||
|
||||
async function loadAvailableScopes() {
|
||||
try {
|
||||
var entries = await api('GET', '/api/entries');
|
||||
var scopeSet = new Set();
|
||||
entries.forEach(function(e) {
|
||||
if (e.scopes && e.scopes !== '0000') {
|
||||
e.scopes.split(',').forEach(function(s) {
|
||||
if (s.trim()) scopeSet.add(s.trim());
|
||||
});
|
||||
}
|
||||
});
|
||||
availableScopes = Array.from(scopeSet).sort();
|
||||
renderScopeList();
|
||||
} catch(e) {
|
||||
document.getElementById('scopeList').innerHTML = '<p class="text-red">Failed to load scopes</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderScopeList() {
|
||||
var container = document.getElementById('scopeList');
|
||||
if (availableScopes.length === 0) {
|
||||
container.innerHTML = '<p class="text-muted">No custom scopes found. Create entries with scopes first.</p>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
availableScopes.forEach(function(scope) {
|
||||
html += '<label style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;cursor:pointer">' +
|
||||
'<input type="checkbox" name="agentScope" value="' + scope + '" style="accent-color:var(--text)">' +
|
||||
'<code style="font-size:0.8rem">' + scope + '</code>' +
|
||||
'</label>';
|
||||
});
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function toggleScopeSelector() {
|
||||
var access = document.getElementById('agentAccess').value;
|
||||
var selector = document.getElementById('scopeSelector');
|
||||
selector.style.display = access === 'scoped' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function toggleAllScopes() {
|
||||
var checkboxes = document.querySelectorAll('input[name="agentScope"]');
|
||||
var allChecked = Array.from(checkboxes).every(function(cb) { return cb.checked; });
|
||||
checkboxes.forEach(function(cb) {
|
||||
cb.checked = !allChecked;
|
||||
});
|
||||
}
|
||||
|
||||
async function doCreateAgent() {
|
||||
|
|
@ -1330,11 +1412,22 @@
|
|||
if (!name) { toast('Name is required', 'error'); return; }
|
||||
var access = document.getElementById('agentAccess').value;
|
||||
|
||||
var scopes = '0000'; // Default scope
|
||||
if (access === 'scoped') {
|
||||
var selected = document.querySelectorAll('input[name="agentScope"]:checked');
|
||||
if (selected.length === 0) {
|
||||
toast('Select at least one scope, or choose "Full read access"', 'error');
|
||||
return;
|
||||
}
|
||||
scopes = Array.from(selected).map(function(cb) { return cb.value; }).join(',');
|
||||
}
|
||||
|
||||
try {
|
||||
var result = await api('POST', '/api/agents', {
|
||||
name: name,
|
||||
all_access: access === 'all' || access === 'admin',
|
||||
admin: access === 'admin'
|
||||
scopes: scopes,
|
||||
all_access: access === 'all',
|
||||
admin: false
|
||||
});
|
||||
showAgentToken(result);
|
||||
} catch (err) {
|
||||
|
|
@ -1346,10 +1439,10 @@
|
|||
var content = document.getElementById('modalContent');
|
||||
content.innerHTML = '<div class="modal-body">' +
|
||||
'<h3 class="modal-title">Agent Created</h3>' +
|
||||
'<p class="mb-2"><strong>' + escapeHtml(result.name) + '</strong> — scope <code>' + escapeHtml(result.scope) + '</code></p>' +
|
||||
'<p class="mb-2"><strong>' + escapeHtml(result.name) + '</strong> — scopes: <code>' + escapeHtml(result.scopes) + '</code></p>' +
|
||||
'<p class="text-subtle mb-3">Copy this token now. It will never be shown again.</p>' +
|
||||
'<div class="token-display" style="background:var(--surface-1);border:1px solid var(--border);border-radius:0.5rem;padding:1rem;font-family:JetBrains Mono,monospace;font-size:0.8rem;word-break:break-all;user-select:all;cursor:pointer" onclick="navigator.clipboard.writeText(this.textContent);toast(\'Token copied\')">' +
|
||||
escapeHtml(result.token) +
|
||||
escapeHtml(result.credential) +
|
||||
'</div>' +
|
||||
'<p class="text-subtle mt-2" style="font-size:0.75rem">Click to copy. Use this token as the Bearer in CLI: <code>clavitor-cli init <token></code></p>' +
|
||||
'<div class="modal-actions mt-4">' +
|
||||
|
|
@ -1377,7 +1470,6 @@
|
|||
'<select id="editAgentAccess" class="form-input">' +
|
||||
'<option value="scoped"' + (!agent.all_access ? ' selected' : '') + '>Scoped</option>' +
|
||||
'<option value="all"' + (agent.all_access && !agent.admin ? ' selected' : '') + '>Full read access</option>' +
|
||||
'<option value="admin"' + (agent.admin ? ' selected' : '') + '>Admin</option>' +
|
||||
'</select>' +
|
||||
'</div>' +
|
||||
'<div class="modal-actions">' +
|
||||
|
|
|
|||
Loading…
Reference in New Issue