Files
2026-06-23 21:03:14 +00:00

524 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const PAGE_SIZE_DESKTOP = 6;
const PAGE_SIZE_MOBILE = 4;
const MOBILE_BREAKPOINT = 960;
function getSortOptions() {
return [
{ value: 'created_desc', label: (window.t && t('sort.created_desc')) || 'Sort: Last created' },
{ value: 'created_asc', label: (window.t && t('sort.created_asc')) || 'Sort: Oldest first' },
{ value: 'name_asc', label: (window.t && t('sort.name_asc')) || 'Sort: Name AZ' },
{ value: 'name_desc', label: (window.t && t('sort.name_desc')) || 'Sort: Name ZA' },
];
}
function getTabDefs() {
return [
{ id: 'all', label: (window.t && t('tab.all')) || 'All' },
{ id: 'active', label: (window.t && t('tab.active')) || 'Active' },
{ id: 'draft', label: (window.t && t('tab.draft')) || 'Draft' },
{ id: 'error', label: (window.t && t('tab.error')) || 'Error' },
{ id: 'inactive', label: (window.t && t('tab.inactive')) || 'Inactive' },
];
}
function uiStatus(rawStatus) {
if (rawStatus === 'published') return 'active';
if (rawStatus === 'archived') return 'inactive';
if (rawStatus === 'testing') return 'active';
return rawStatus || 'draft';
}
function currentLocale() {
return localStorage.getItem('crank_lang') === 'ru' ? 'ru-RU' : 'en-US';
}
function mapOperation(item) {
var mapped = {
id: item.id,
name: item.name,
display_name: item.display_name,
protocol: item.protocol,
status: uiStatus(item.status),
raw_status: item.status,
category: item.category || 'general',
created_at: item.created_at,
updated_at: item.updated_at,
published_at: item.published_at,
target_url: item.target_url || '',
method: item.target_action || '',
usage_summary: item.usage_summary || {
calls_today: 0,
error_rate_pct: 0,
avg_latency_ms: 0,
},
agent_refs: item.agent_refs || [],
};
return window.localizeDemoOperation ? window.localizeDemoOperation(mapped) : mapped;
}
function emptyStats() {
return {
total: 0,
active: 0,
requestsToday: 0,
averageLatencyMs: 0,
};
}
function computeStats(operations) {
if (!operations.length) return emptyStats();
var requestsToday = operations.reduce(function(sum, operation) {
return sum + (operation.usage_summary ? operation.usage_summary.calls_today : 0);
}, 0);
var latencyBase = operations.filter(function(operation) {
return operation.usage_summary && operation.usage_summary.calls_today > 0;
});
var latencyWeight = latencyBase.reduce(function(sum, operation) {
return sum + operation.usage_summary.calls_today;
}, 0);
var weightedLatency = latencyBase.reduce(function(sum, operation) {
return sum + (operation.usage_summary.avg_latency_ms * operation.usage_summary.calls_today);
}, 0);
return {
total: operations.length,
active: operations.filter(function(operation) { return operation.status === 'active'; }).length,
requestsToday: requestsToday,
averageLatencyMs: latencyWeight > 0 ? Math.round(weightedLatency / latencyWeight) : 0,
};
}
document.addEventListener('alpine:init', function() {
Alpine.data('catalog', function() {
return {
operations: [],
loading: true,
loadError: '',
pageSize: PAGE_SIZE_DESKTOP,
search: '',
tab: 'all',
filterCategory: null,
filterAgent: null,
sort: 'created_desc',
page: 1,
openDropdown: null,
navOpen: false,
categoryOptions: [],
workspaceId: null,
stats: emptyStats(),
_opsVersion: 0,
_nonStatusViewCache: null,
_nonStatusViewKey: '',
_filteredCache: null,
_filteredKey: '',
_agentsByOpIdCache: null,
_agentsByOpIdCacheVersion: -1,
_activeAgentsCache: null,
_activeAgentsCacheVersion: -1,
async init() {
var self = this;
await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve());
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
this.workspaceId = workspace ? workspace.id : null;
var updatePageSize = function() {
self.pageSize = window.innerWidth <= MOBILE_BREAKPOINT ? PAGE_SIZE_MOBILE : PAGE_SIZE_DESKTOP;
self.page = 1;
};
updatePageSize();
window.addEventListener('resize', updatePageSize);
window.addEventListener('crank:langchange', function() {
self.sort = self.sort;
applyLang();
});
window.addEventListener('crank:workspacechange', async function(event) {
self.workspaceId = event.detail ? event.detail.id : null;
await self.reload();
});
document.addEventListener('click', function(event) {
if (!event.target.closest('.filter-dropdown, .sort-dropdown, .user-menu, .ws-switcher')) {
self.openDropdown = null;
}
if (!event.target.closest('.navbar')) {
self.navOpen = false;
}
});
await this.reload();
},
async reload() {
this.loading = true;
this.loadError = '';
try {
var response = await window.CrankApi.listOperations(this.workspaceId);
this.replaceOperations((response && response.items ? response.items : []).map(mapOperation));
this.categoryOptions = Array.from(new Set(this.operations.map(function(operation) {
return operation.category;
}).filter(Boolean))).sort();
this.stats = computeStats(this.operations);
} catch (error) {
this.replaceOperations([]);
this.categoryOptions = [];
this.stats = emptyStats();
this.loadError = error.message || 'Failed to load operations';
}
this.loading = false;
this.page = 1;
},
replaceOperations(operations) {
this.operations = operations;
this._opsVersion += 1;
this._nonStatusViewCache = null;
this._nonStatusViewKey = '';
this._filteredCache = null;
this._filteredKey = '';
this._agentsByOpIdCache = null;
this._agentsByOpIdCacheVersion = -1;
this._activeAgentsCache = null;
this._activeAgentsCacheVersion = -1;
},
_applyNonStatusFilters(operations) {
var filtered = operations;
if (this.search.trim()) {
var query = this.search.toLowerCase();
filtered = filtered.filter(function(operation) {
return operation.name.toLowerCase().includes(query)
|| operation.display_name.toLowerCase().includes(query)
|| operation.category.toLowerCase().includes(query)
|| (operation.target_url || '').toLowerCase().includes(query);
});
}
if (this.filterCategory) {
filtered = filtered.filter(function(operation) { return operation.category === this.filterCategory; }, this);
}
if (this.filterAgent) {
filtered = filtered.filter(function(operation) {
return (operation.agent_refs || []).some(function(agent) { return agent.agent_id === this.filterAgent; }, this);
}, this);
}
return filtered;
},
_nonStatusView() {
var key = [
this._opsVersion,
this.search.trim(),
this.filterCategory || '',
this.filterAgent || '',
].join('|');
if (this._nonStatusViewCache && this._nonStatusViewKey === key) {
return this._nonStatusViewCache;
}
var operations = this._applyNonStatusFilters(this.operations);
var tabCounts = {
all: operations.length,
active: 0,
draft: 0,
error: 0,
inactive: 0,
};
operations.forEach(function(operation) {
if (Object.prototype.hasOwnProperty.call(tabCounts, operation.status)) {
tabCounts[operation.status] += 1;
}
});
this._nonStatusViewKey = key;
this._nonStatusViewCache = {
operations: operations,
tabCounts: tabCounts,
};
return this._nonStatusViewCache;
},
get filtered() {
var nonStatusView = this._nonStatusView();
var key = [
this._nonStatusViewKey,
this.tab,
this.sort,
].join('|');
if (this._filteredCache && this._filteredKey === key) {
return this._filteredCache;
}
var operations = nonStatusView.operations;
if (this.tab !== 'all') {
operations = operations.filter(function(operation) {
return operation.status === this.tab;
}, this);
}
operations = operations.slice().sort(function(left, right) {
if (this.sort === 'created_desc') return new Date(right.created_at) - new Date(left.created_at);
if (this.sort === 'created_asc') return new Date(left.created_at) - new Date(right.created_at);
if (this.sort === 'name_asc') return left.name.localeCompare(right.name);
if (this.sort === 'name_desc') return right.name.localeCompare(left.name);
return 0;
}.bind(this));
this._filteredKey = key;
this._filteredCache = operations;
return this._filteredCache;
},
get totalFiltered() {
return this.filtered.length;
},
get totalPages() {
return Math.max(1, Math.ceil(this.totalFiltered / this.pageSize));
},
get paginated() {
var start = (this.page - 1) * this.pageSize;
return this.filtered.slice(start, start + this.pageSize);
},
get pageStart() {
return this.totalFiltered === 0 ? 0 : (this.page - 1) * this.pageSize + 1;
},
get pageEnd() {
return Math.min(this.page * this.pageSize, this.totalFiltered);
},
tabCount(tab) {
var tabCounts = this._nonStatusView().tabCounts;
return Object.prototype.hasOwnProperty.call(tabCounts, tab) ? tabCounts[tab] : 0;
},
get activeChips() {
var chips = [];
if (this.filterCategory) chips.push({ key: 'category', label: this.tfKey('ops.filter.category', { value: this.filterCategory }) });
if (this.filterAgent) {
var agent = this.activeAgents.find(function(item) { return item.agent_id === this.filterAgent; }, this);
chips.push({ key: 'agent', label: this.tfKey('ops.filter.agent', { value: agent ? agent.display_name : '' }) });
}
return chips;
},
get hasActiveFilters() {
return !!(this.filterCategory || this.filterAgent || this.search.trim());
},
clearChip(key) {
if (key === 'category') this.filterCategory = null;
if (key === 'agent') this.filterAgent = null;
this.page = 1;
},
clearAllFilters() {
this.filterCategory = null;
this.filterAgent = null;
this.search = '';
this.tab = 'all';
this.page = 1;
},
get agentsByOpId() {
if (this._agentsByOpIdCache && this._agentsByOpIdCacheVersion === this._opsVersion) {
return this._agentsByOpIdCache;
}
var map = {};
this.operations.forEach(function(operation) {
map[operation.id] = operation.agent_refs || [];
});
this._agentsByOpIdCacheVersion = this._opsVersion;
this._agentsByOpIdCache = map;
return this._agentsByOpIdCache;
},
get activeAgents() {
if (this._activeAgentsCache && this._activeAgentsCacheVersion === this._opsVersion) {
return this._activeAgentsCache;
}
var seen = {};
var agents = [];
this.operations.forEach(function(operation) {
(operation.agent_refs || []).forEach(function(agent) {
if (!seen[agent.agent_id]) {
seen[agent.agent_id] = true;
agents.push(agent);
}
});
});
this._activeAgentsCacheVersion = this._opsVersion;
this._activeAgentsCache = agents.sort(function(left, right) {
return left.display_name.localeCompare(right.display_name);
});
return this._activeAgentsCache;
},
get sortLabel() {
return getSortOptions().find(function(option) { return option.value === this.sort; }, this)?.label || 'Sort';
},
setTab(tab) {
this.tab = tab;
this.page = 1;
},
setSearch(value) {
this.search = value;
this.page = 1;
},
setCategory(value) {
this.filterCategory = this.filterCategory === value ? null : value;
this.openDropdown = null;
this.page = 1;
},
setSort(value) {
this.sort = value;
this.openDropdown = null;
},
setAgent(value) {
this.filterAgent = this.filterAgent === value ? null : value;
this.openDropdown = null;
this.page = 1;
},
clearCategory() {
this.filterCategory = null;
this.openDropdown = null;
this.page = 1;
},
clearAgent() {
this.filterAgent = null;
this.openDropdown = null;
this.page = 1;
},
toggleDropdown(name) {
this.openDropdown = this.openDropdown === name ? null : name;
},
goPage(page) {
if (page >= 1 && page <= this.totalPages) this.page = page;
},
editOperation(operation) {
window.location.href = ((window.CrankRoutes && window.CrankRoutes.wizard) || '/wizard/')
+ '?mode=edit&operationId=' + encodeURIComponent(operation.id);
},
async deleteOperation(id) {
if (!confirm(this.tKey('ops.delete.confirm'))) return;
try {
var operation = this.operations.find(function(item) { return item.id === id; });
await window.CrankApi.deleteOperation(this.workspaceId, id);
this.replaceOperations(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(
this.tfKey('ops.delete.success.message', {
name: operation ? (operation.display_name || operation.name) : ''
}),
this.tKey('ops.delete.success.title')
);
}
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(
error.message || this.tKey('ops.delete.error.message'),
this.tKey('ops.delete.error.title')
);
}
}
},
protocolLabel(operation) {
return operation.method ? ('REST · ' + operation.method) : 'REST';
},
protocolClass(operation) {
return 'badge badge-' + operation.protocol;
},
statusClass(operation) {
return 'status-badge status-' + operation.status;
},
statusLabel(operation) {
if (operation.status === 'active') return this.tKey('tab.active');
if (operation.status === 'inactive') return this.tKey('tab.inactive');
if (operation.status === 'error') return this.tKey('tab.error');
return this.tKey('tab.draft');
},
formatDate(dateStr) {
if (!dateStr) return '—';
return new Date(dateStr).toLocaleDateString(currentLocale(), { month: 'short', day: 'numeric', year: 'numeric' });
},
truncateUrl(url) {
if (!url) return '—';
return url.length > 42 ? url.slice(0, 42) + '…' : url;
},
formatMetricNumber(value) {
return new Intl.NumberFormat(currentLocale()).format(value || 0);
},
tKey(key) {
return window.t ? t(key) : key;
},
tfKey(key, vars) {
return window.tf ? tf(key, vars) : this.tKey(key);
},
get sortOptions() {
return getSortOptions();
},
get tabDefs() {
return getTabDefs();
},
handleNewOperation() {
window.location.href = (window.CrankRoutes && window.CrankRoutes.wizard) || '/wizard/';
},
handleOpenApiImport() {
if (window.CrankOpenApiImport) {
window.CrankOpenApiImport.open({
workspaceId: this.workspaceId,
onImported: this.reload.bind(this),
});
}
},
handleLogout() {
window.CrankAuth.logout();
},
};
});
});