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 A–Z' }, { value: 'name_desc', label: (window.t && t('sort.name_desc')) || 'Sort: Name Z–A' }, ]; } 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/'; }, handleLogout() { window.CrankAuth.logout(); }, }; }); });