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) {