209 lines
6.1 KiB
JavaScript
209 lines
6.1 KiB
JavaScript
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('agents', () => ({
|
|
agents: [],
|
|
operations: [],
|
|
loading: true,
|
|
|
|
agentSearch: '',
|
|
openDropdown: null,
|
|
|
|
// drawer state
|
|
drawerOpen: false,
|
|
drawerMode: 'create', // 'create' | 'edit'
|
|
editingId: null,
|
|
|
|
// form fields
|
|
form: {
|
|
display_name: '',
|
|
slug: '',
|
|
description: '',
|
|
status: 'active',
|
|
selectedOps: [],
|
|
},
|
|
|
|
// operations picker
|
|
opSearch: '',
|
|
slugManuallyEdited: false,
|
|
|
|
async init() {
|
|
const [agentsRes, opsRes] = await Promise.all([
|
|
fetch((window.DATA_URL||'data/')+'agents.json'),
|
|
fetch((window.DATA_URL||'data/')+'operations.json'),
|
|
]);
|
|
this.agents = await agentsRes.json();
|
|
this.operations = await opsRes.json();
|
|
this.loading = false;
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && this.drawerOpen) this.closeDrawer();
|
|
});
|
|
|
|
document.addEventListener('click', () => { this.openDropdown = null; });
|
|
},
|
|
|
|
// ── computed ──
|
|
get filteredAgents() {
|
|
const q = this.agentSearch.toLowerCase().trim();
|
|
if (!q) return this.agents;
|
|
return this.agents.filter(a =>
|
|
a.display_name.toLowerCase().includes(q) ||
|
|
a.slug.toLowerCase().includes(q) ||
|
|
(a.description || '').toLowerCase().includes(q)
|
|
);
|
|
},
|
|
|
|
get totalCalls() {
|
|
return this.agents.reduce((s, a) => s + (a.calls_today || 0), 0);
|
|
},
|
|
get activeCount() {
|
|
return this.agents.filter(a => a.status === 'active').length;
|
|
},
|
|
get totalOpsExposed() {
|
|
return this.agents.reduce((s, a) => s + (a.operation_count || 0), 0);
|
|
},
|
|
|
|
get filteredOps() {
|
|
const q = this.opSearch.toLowerCase();
|
|
if (!q) return this.operations;
|
|
return this.operations.filter(op =>
|
|
op.name.toLowerCase().includes(q) ||
|
|
op.display_name.toLowerCase().includes(q)
|
|
);
|
|
},
|
|
|
|
// ── drawer ──
|
|
openCreate() {
|
|
this.drawerMode = 'create';
|
|
this.editingId = null;
|
|
this.form = { display_name: '', slug: '', description: '', status: 'active', selectedOps: [] };
|
|
this.opSearch = '';
|
|
this.slugManuallyEdited = false;
|
|
this.drawerOpen = true;
|
|
},
|
|
|
|
openEdit(agent) {
|
|
this.drawerMode = 'edit';
|
|
this.editingId = agent.id;
|
|
this.form = {
|
|
display_name: agent.display_name,
|
|
slug: agent.slug,
|
|
description: agent.description,
|
|
status: agent.status,
|
|
selectedOps: [...(agent.operation_ids || [])],
|
|
};
|
|
this.opSearch = '';
|
|
this.slugManuallyEdited = true;
|
|
this.drawerOpen = true;
|
|
},
|
|
|
|
closeDrawer() {
|
|
this.drawerOpen = false;
|
|
},
|
|
|
|
// ── slug auto-derive ──
|
|
onNameInput(val) {
|
|
this.form.display_name = val;
|
|
if (!this.slugManuallyEdited) {
|
|
this.form.slug = val.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
}
|
|
},
|
|
|
|
onSlugInput(val) {
|
|
this.form.slug = val.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
this.slugManuallyEdited = true;
|
|
},
|
|
|
|
// ── operations picker ──
|
|
toggleOp(opId) {
|
|
const idx = this.form.selectedOps.indexOf(opId);
|
|
if (idx === -1) this.form.selectedOps.push(opId);
|
|
else this.form.selectedOps.splice(idx, 1);
|
|
},
|
|
isOpSelected(opId) {
|
|
return this.form.selectedOps.includes(opId);
|
|
},
|
|
|
|
// ── save ──
|
|
saveAgent() {
|
|
if (!this.form.display_name.trim() || !this.form.slug.trim()) return;
|
|
|
|
const ws = (localStorage.getItem('crank_workspace') || 'acme-workspace');
|
|
const endpoint = '/mcp/v1/' + ws + '/' + this.form.slug;
|
|
|
|
if (this.drawerMode === 'create') {
|
|
this.agents.unshift({
|
|
id: 'ag-' + Date.now(),
|
|
slug: this.form.slug,
|
|
display_name: this.form.display_name,
|
|
description: this.form.description,
|
|
status: this.form.status,
|
|
operation_count: this.form.selectedOps.length,
|
|
operation_ids: [...this.form.selectedOps],
|
|
key_count: 0,
|
|
created_at: new Date().toISOString(),
|
|
created_by: 'AT',
|
|
last_called_at: null,
|
|
calls_today: 0,
|
|
mcp_endpoint: endpoint,
|
|
});
|
|
} else {
|
|
const idx = this.agents.findIndex(a => a.id === this.editingId);
|
|
if (idx !== -1) {
|
|
this.agents[idx] = Object.assign({}, this.agents[idx], {
|
|
display_name: this.form.display_name,
|
|
slug: this.form.slug,
|
|
description: this.form.description,
|
|
status: this.form.status,
|
|
operation_count: this.form.selectedOps.length,
|
|
operation_ids: [...this.form.selectedOps],
|
|
mcp_endpoint: endpoint,
|
|
});
|
|
}
|
|
}
|
|
this.closeDrawer();
|
|
},
|
|
|
|
// ── delete ──
|
|
deleteAgent(id) {
|
|
this.agents = this.agents.filter(a => a.id !== id);
|
|
},
|
|
|
|
// ── helpers ──
|
|
mcpEndpoint(agent) {
|
|
if (agent.mcp_endpoint) return agent.mcp_endpoint;
|
|
const ws = localStorage.getItem('crank_workspace') || 'acme-workspace';
|
|
return '/mcp/v1/' + ws + '/' + agent.slug;
|
|
},
|
|
|
|
copyEndpoint(agent) {
|
|
navigator.clipboard.writeText(this.mcpEndpoint(agent)).catch(() => {});
|
|
},
|
|
|
|
statusClass(status) {
|
|
return 'agent-status-badge agent-status-' + status;
|
|
},
|
|
|
|
protocolBadge(op) {
|
|
if (op.protocol === 'rest') return 'badge badge-rest';
|
|
if (op.protocol === 'graphql') return 'badge badge-graphql';
|
|
return 'badge badge-grpc';
|
|
},
|
|
|
|
protocolLabel(op) {
|
|
if (op.protocol === 'rest') return 'REST';
|
|
if (op.protocol === 'graphql') return 'GQL';
|
|
return 'gRPC';
|
|
},
|
|
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '—';
|
|
return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
},
|
|
|
|
formatCalls(n) {
|
|
if (!n) return '0';
|
|
return n >= 1000 ? (n / 1000).toFixed(1) + 'k' : String(n);
|
|
},
|
|
}));
|
|
});
|