diff --git a/apps/ui/html/logs.html b/apps/ui/html/logs.html index d8937a1..d881319 100644 --- a/apps/ui/html/logs.html +++ b/apps/ui/html/logs.html @@ -13,6 +13,7 @@ + diff --git a/apps/ui/html/usage.html b/apps/ui/html/usage.html index c653157..e00ea12 100644 --- a/apps/ui/html/usage.html +++ b/apps/ui/html/usage.html @@ -13,6 +13,7 @@ + diff --git a/apps/ui/index.html b/apps/ui/index.html index 795152b..5428888 100644 --- a/apps/ui/index.html +++ b/apps/ui/index.html @@ -319,12 +319,12 @@ -
-

No operations found

-

Try adjusting your search or filters.

+
+

No operations found

+

Try adjusting your search or filters.

-
+

Backend connection failed

diff --git a/apps/ui/js/agents.js b/apps/ui/js/agents.js index f7dd1bb..125c926 100644 --- a/apps/ui/js/agents.js +++ b/apps/ui/js/agents.js @@ -288,6 +288,12 @@ document.addEventListener('alpine:init', function() { } await this.reload(); + if (window.CrankUi) { + window.CrankUi.success( + this.form.display_name + ' was saved with ' + this.form.selectedOps.length + ' tool bindings.', + this.drawerMode === 'create' ? 'Agent created' : 'Agent updated' + ); + } this.closeDrawer(); } catch (error) { if (window.CrankUi) { @@ -301,8 +307,15 @@ document.addEventListener('alpine:init', function() { if (!confirm('Delete this agent? This cannot be undone.')) return; try { + var agent = this.agents.find(function(item) { return item.id === id; }); await window.CrankApi.deleteAgent(this.workspaceId, id); await this.reload(); + if (window.CrankUi) { + window.CrankUi.success( + (agent ? agent.display_name : 'Agent') + ' was deleted.', + 'Agent deleted' + ); + } } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || 'Failed to delete agent', 'Delete failed'); @@ -326,6 +339,17 @@ document.addEventListener('alpine:init', function() { await window.CrankApi.archiveAgent(this.workspaceId, agent.id); } await this.reload(); + if (window.CrankUi) { + var actionLabel = action === 'publish' + ? 'published' + : action === 'unpublish' + ? 'returned to draft' + : 'archived'; + window.CrankUi.success( + agent.display_name + ' was ' + actionLabel + '.', + 'Agent lifecycle updated' + ); + } } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || 'Failed to update agent lifecycle', 'Lifecycle update failed'); @@ -357,7 +381,11 @@ document.addEventListener('alpine:init', function() { }, copyEndpoint(agent) { - navigator.clipboard.writeText(this.mcpEndpoint(agent)).catch(function() {}); + var endpoint = this.mcpEndpoint(agent); + navigator.clipboard.writeText(endpoint).catch(function() {}); + if (window.CrankUi) { + window.CrankUi.info(endpoint, 'MCP endpoint copied'); + } }, statusClass(status) { diff --git a/apps/ui/js/api-keys.js b/apps/ui/js/api-keys.js index d8ac141..e551181 100644 --- a/apps/ui/js/api-keys.js +++ b/apps/ui/js/api-keys.js @@ -69,8 +69,15 @@ async function revokeKey(id) { if (!confirm('Revoke this key? All requests using it will fail immediately.')) return; try { + var key = KEYS.find(function(item) { return item.id === id; }); await window.CrankApi.revokePlatformApiKey(currentWorkspaceId, id); await loadKeys(); + if (window.CrankUi) { + window.CrankUi.success( + (key ? key.name : 'API key') + ' was revoked.', + 'Key revoked' + ); + } } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || 'Failed to revoke key', 'Revoke failed'); @@ -82,8 +89,15 @@ async function deleteKey(id) { if (!confirm('Delete this key? This cannot be undone.')) return; try { + var key = KEYS.find(function(item) { return item.id === id; }); await window.CrankApi.deletePlatformApiKey(currentWorkspaceId, id); await loadKeys(); + if (window.CrankUi) { + window.CrankUi.success( + (key ? key.name : 'API key') + ' was deleted.', + 'Key deleted' + ); + } } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || 'Failed to delete key', 'Delete failed'); @@ -229,6 +243,7 @@ modal.addEventListener('click', function(event) { }); document.getElementById('modal-confirm-btn').addEventListener('click', async function() { + var button = this; var name = document.getElementById('new-key-name').value.trim(); if (!name) { document.getElementById('new-key-name').focus(); @@ -242,6 +257,8 @@ document.getElementById('modal-confirm-btn').addEventListener('click', async fun } try { + button.disabled = true; + button.textContent = 'Creating…'; var created = await createKey(name, Array.from(selectedScopes)); KEYS.unshift(created.record); document.getElementById('reveal-key-value').textContent = created.rawKey; @@ -250,10 +267,16 @@ document.getElementById('modal-confirm-btn').addEventListener('click', async fun document.getElementById('modal-footer-create').style.display = 'none'; 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'); + } } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || 'Failed to create key', 'Key creation failed'); } + } finally { + button.disabled = false; + button.textContent = 'Create key'; } }); @@ -265,6 +288,9 @@ document.getElementById('copy-key-btn').addEventListener('click', function() { navigator.clipboard.writeText(value).catch(function() {}); } this.innerHTML = ''; + if (window.CrankUi) { + window.CrankUi.info('Store the raw key securely. It cannot be revealed again.', 'API key copied'); + } }); document.getElementById('key-search').addEventListener('input', function() { @@ -276,6 +302,9 @@ function copyPrefix(prefix) { if (navigator.clipboard) { navigator.clipboard.writeText(prefix).catch(function() {}); } + if (window.CrankUi) { + window.CrankUi.info(prefix, 'Key prefix copied'); + } } document.addEventListener('DOMContentLoaded', async function() { diff --git a/apps/ui/js/catalog.js b/apps/ui/js/catalog.js index 112fae2..d4c6f16 100644 --- a/apps/ui/js/catalog.js +++ b/apps/ui/js/catalog.js @@ -362,12 +362,19 @@ document.addEventListener('alpine:init', function() { async deleteOperation(id) { if (!confirm('Delete this operation? This cannot be undone.')) return; try { + var operation = this.operations.find(function(item) { return item.id === id; }); await window.CrankApi.deleteOperation(this.workspaceId, id); this.operations = this.operations.filter(function(operation) { return operation.id !== id; }); this.categoryOptions = Array.from(new Set(this.operations.map(function(operation) { return operation.category; }).filter(Boolean))).sort(); this.stats = computeStats(this.operations); + if (window.CrankUi) { + window.CrankUi.success( + (operation ? (operation.display_name || operation.name) : 'Operation') + ' was deleted.', + 'Operation deleted' + ); + } } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || 'Failed to delete operation', 'Delete failed'); diff --git a/apps/ui/js/logs.js b/apps/ui/js/logs.js index 9d0bbe0..0ee9ae0 100644 --- a/apps/ui/js/logs.js +++ b/apps/ui/js/logs.js @@ -86,27 +86,34 @@ document.addEventListener('DOMContentLoaded', function () { }; } - function renderEmpty(message) { + function renderEmpty(title, message) { logList.innerHTML = ''; var empty = document.createElement('div'); - empty.style.cssText = 'text-align:center;padding:40px;color:var(--text-muted);font-size:13.5px;'; - empty.textContent = message; + empty.className = 'empty-state'; + empty.innerHTML = + '
' + title + '
' + + '
' + message + '
'; logList.appendChild(empty); } function renderLogs() { if (state.loading && state.logs.length === 0) { - renderEmpty('Loading logs...'); + renderEmpty('Loading logs…', 'Fetching invocation history for the current workspace.'); return; } if (state.loadError) { - renderEmpty(state.loadError); + renderEmpty('Unable to load logs', state.loadError); return; } if (!state.logs.length) { - renderEmpty('No log entries match the current filter'); + renderEmpty( + 'No log entries found', + state.search || state.level !== 'all' + ? 'Try widening the period or clearing the current search and level filters.' + : 'Run tests or published MCP tools to start collecting invocation history.' + ); return; } @@ -323,6 +330,12 @@ document.addEventListener('DOMContentLoaded', function () { state.liveMode = !state.liveMode; setLiveState(); startPolling(); + if (window.CrankUi) { + window.CrankUi.info( + state.liveMode ? 'Polling logs every 4 seconds.' : 'Automatic polling is paused.', + state.liveMode ? 'Live mode enabled' : 'Live mode paused' + ); + } } document.querySelectorAll('.filter-chip[data-level]').forEach(function (button) { @@ -344,7 +357,13 @@ document.addEventListener('DOMContentLoaded', function () { } if (refreshBtn) { - refreshBtn.addEventListener('click', loadLogs); + refreshBtn.addEventListener('click', function () { + loadLogs().then(function () { + if (!state.loadError && window.CrankUi) { + window.CrankUi.info('The latest invocation records were loaded for the current workspace.', 'Logs refreshed'); + } + }); + }); } if (timeRangeSel) { diff --git a/apps/ui/js/usage.js b/apps/ui/js/usage.js index d220e3e..0288d67 100644 --- a/apps/ui/js/usage.js +++ b/apps/ui/js/usage.js @@ -76,9 +76,19 @@ document.addEventListener('DOMContentLoaded', function () { return 'Wk ' + (index + 1); } - function renderEmpty(message) { - chartWrap.innerHTML = '
' + message + '
'; - tableBody.innerHTML = '' + message + ''; + function renderEmpty(title, message) { + chartWrap.innerHTML = + '
' + + '
' + title + '
' + + '
' + message + '
' + + '
'; + tableBody.innerHTML = + '' + + '
' + + '
' + title + '
' + + '
' + message + '
' + + '
' + + ''; } function renderStats() { @@ -119,7 +129,11 @@ document.addEventListener('DOMContentLoaded', function () { chartWrap.innerHTML = ''; if (!timeline.length) { - chartWrap.innerHTML = '
No usage data for selected period
'; + chartWrap.innerHTML = + '
' + + '
No usage data yet
' + + '
Invocation metrics will appear here after tests or published tool calls in the selected period.
' + + '
'; return; } @@ -145,7 +159,13 @@ document.addEventListener('DOMContentLoaded', function () { tableBody.innerHTML = ''; if (!operations.length) { - tableBody.innerHTML = 'No operation usage data for selected period'; + tableBody.innerHTML = + '' + + '
' + + '
No operation breakdown yet
' + + '
The selected period has no invocation samples to break down by operation.
' + + '
' + + ''; return; } @@ -194,12 +214,12 @@ document.addEventListener('DOMContentLoaded', function () { function renderUsage() { if (state.loading && !state.usage) { - renderEmpty('Loading usage...'); + renderEmpty('Loading usage…', 'Aggregating invocation metrics for the selected workspace.'); return; } if (state.loadError) { - renderEmpty(state.loadError); + renderEmpty('Unable to load usage', state.loadError); return; } @@ -241,6 +261,9 @@ document.addEventListener('DOMContentLoaded', function () { function exportCsv() { if (!state.usage) { + if (window.CrankUi) { + window.CrankUi.info('Load usage data before exporting the CSV snapshot.', 'No usage data loaded'); + } return; } @@ -268,6 +291,9 @@ document.addEventListener('DOMContentLoaded', function () { link.download = 'crank-usage-' + state.period + '.csv'; link.click(); URL.revokeObjectURL(url); + if (window.CrankUi) { + window.CrankUi.success('The current usage snapshot was exported as CSV.', 'Usage exported'); + } } if (periodSelect) {