UNPKG

@api.global/typedserver

Version:

A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.

1,056 lines (984 loc) 61.2 kB
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; import { LitElement, html, css, property, state, customElement, DeesContextmenu } from './plugins.js'; import { sharedStyles, panelStyles, tableStyles, buttonStyles } from './sw-dash-styles.js'; /** * TypedRequest traffic monitoring panel for sw-dash * * Receives logs, stats, and methods via properties from parent (sw-dash-app). * Filtering is done locally. * Load more and clear operations dispatch events to parent. */ let SwDashRequests = (() => { let _classDecorators = [customElement('sw-dash-requests')]; let _classDescriptor; let _classExtraInitializers = []; let _classThis; let _classSuper = LitElement; let _logs_decorators; let _logs_initializers = []; let _logs_extraInitializers = []; let _totalCount_decorators; let _totalCount_initializers = []; let _totalCount_extraInitializers = []; let _stats_decorators; let _stats_initializers = []; let _stats_extraInitializers = []; let _methods_decorators; let _methods_initializers = []; let _methods_extraInitializers = []; let _directionFilter_decorators; let _directionFilter_initializers = []; let _directionFilter_extraInitializers = []; let _phaseFilter_decorators; let _phaseFilter_initializers = []; let _phaseFilter_extraInitializers = []; let _methodFilter_decorators; let _methodFilter_initializers = []; let _methodFilter_extraInitializers = []; let _searchText_decorators; let _searchText_initializers = []; let _searchText_extraInitializers = []; let _isLoadingMore_decorators; let _isLoadingMore_initializers = []; let _isLoadingMore_extraInitializers = []; let _modalOpen_decorators; let _modalOpen_initializers = []; let _modalOpen_extraInitializers = []; let _selectedGroup_decorators; let _selectedGroup_initializers = []; let _selectedGroup_extraInitializers = []; var SwDashRequests = class extends _classSuper { static { _classThis = this; } static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; _logs_decorators = [property({ type: Array })]; _totalCount_decorators = [property({ type: Number })]; _stats_decorators = [property({ type: Object })]; _methods_decorators = [property({ type: Array })]; _directionFilter_decorators = [state()]; _phaseFilter_decorators = [state()]; _methodFilter_decorators = [state()]; _searchText_decorators = [state()]; _isLoadingMore_decorators = [state()]; _modalOpen_decorators = [state()]; _selectedGroup_decorators = [state()]; __esDecorate(this, null, _logs_decorators, { kind: "accessor", name: "logs", static: false, private: false, access: { has: obj => "logs" in obj, get: obj => obj.logs, set: (obj, value) => { obj.logs = value; } }, metadata: _metadata }, _logs_initializers, _logs_extraInitializers); __esDecorate(this, null, _totalCount_decorators, { kind: "accessor", name: "totalCount", static: false, private: false, access: { has: obj => "totalCount" in obj, get: obj => obj.totalCount, set: (obj, value) => { obj.totalCount = value; } }, metadata: _metadata }, _totalCount_initializers, _totalCount_extraInitializers); __esDecorate(this, null, _stats_decorators, { kind: "accessor", name: "stats", static: false, private: false, access: { has: obj => "stats" in obj, get: obj => obj.stats, set: (obj, value) => { obj.stats = value; } }, metadata: _metadata }, _stats_initializers, _stats_extraInitializers); __esDecorate(this, null, _methods_decorators, { kind: "accessor", name: "methods", static: false, private: false, access: { has: obj => "methods" in obj, get: obj => obj.methods, set: (obj, value) => { obj.methods = value; } }, metadata: _metadata }, _methods_initializers, _methods_extraInitializers); __esDecorate(this, null, _directionFilter_decorators, { kind: "accessor", name: "directionFilter", static: false, private: false, access: { has: obj => "directionFilter" in obj, get: obj => obj.directionFilter, set: (obj, value) => { obj.directionFilter = value; } }, metadata: _metadata }, _directionFilter_initializers, _directionFilter_extraInitializers); __esDecorate(this, null, _phaseFilter_decorators, { kind: "accessor", name: "phaseFilter", static: false, private: false, access: { has: obj => "phaseFilter" in obj, get: obj => obj.phaseFilter, set: (obj, value) => { obj.phaseFilter = value; } }, metadata: _metadata }, _phaseFilter_initializers, _phaseFilter_extraInitializers); __esDecorate(this, null, _methodFilter_decorators, { kind: "accessor", name: "methodFilter", static: false, private: false, access: { has: obj => "methodFilter" in obj, get: obj => obj.methodFilter, set: (obj, value) => { obj.methodFilter = value; } }, metadata: _metadata }, _methodFilter_initializers, _methodFilter_extraInitializers); __esDecorate(this, null, _searchText_decorators, { kind: "accessor", name: "searchText", static: false, private: false, access: { has: obj => "searchText" in obj, get: obj => obj.searchText, set: (obj, value) => { obj.searchText = value; } }, metadata: _metadata }, _searchText_initializers, _searchText_extraInitializers); __esDecorate(this, null, _isLoadingMore_decorators, { kind: "accessor", name: "isLoadingMore", static: false, private: false, access: { has: obj => "isLoadingMore" in obj, get: obj => obj.isLoadingMore, set: (obj, value) => { obj.isLoadingMore = value; } }, metadata: _metadata }, _isLoadingMore_initializers, _isLoadingMore_extraInitializers); __esDecorate(this, null, _modalOpen_decorators, { kind: "accessor", name: "modalOpen", static: false, private: false, access: { has: obj => "modalOpen" in obj, get: obj => obj.modalOpen, set: (obj, value) => { obj.modalOpen = value; } }, metadata: _metadata }, _modalOpen_initializers, _modalOpen_extraInitializers); __esDecorate(this, null, _selectedGroup_decorators, { kind: "accessor", name: "selectedGroup", static: false, private: false, access: { has: obj => "selectedGroup" in obj, get: obj => obj.selectedGroup, set: (obj, value) => { obj.selectedGroup = value; } }, metadata: _metadata }, _selectedGroup_initializers, _selectedGroup_extraInitializers); __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); SwDashRequests = _classThis = _classDescriptor.value; if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); } static styles = [ sharedStyles, panelStyles, tableStyles, buttonStyles, css ` :host { display: block; } .requests-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-4); gap: var(--space-3); flex-wrap: wrap; } .filter-group { display: flex; align-items: center; gap: var(--space-2); } .filter-label { font-size: 12px; color: var(--text-tertiary); } .filter-select { background: var(--bg-secondary); border: 1px solid var(--border-default); border-radius: var(--radius-sm); padding: var(--space-1) var(--space-2); color: var(--text-primary); font-size: 12px; } .filter-select:focus { outline: none; border-color: var(--accent-primary); } .requests-list { display: flex; flex-direction: column; gap: var(--space-2); max-height: 600px; overflow-y: auto; } .request-card { background: var(--bg-secondary); border: 1px solid var(--border-default); border-radius: var(--radius-md); padding: var(--space-3); } .request-card.has-error { border-color: var(--accent-error); } .request-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--space-2); gap: var(--space-2); } .request-badges { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; } .badge { display: inline-flex; align-items: center; padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; } .badge.direction-outgoing { background: rgba(59, 130, 246, 0.15); color: #3b82f6; } .badge.direction-incoming { background: rgba(34, 197, 94, 0.15); color: var(--accent-success); } .badge.phase-request { background: rgba(251, 191, 36, 0.15); color: var(--accent-warning); } .badge.phase-response { background: rgba(99, 102, 241, 0.15); color: var(--accent-primary); } .badge.error { background: rgba(239, 68, 68, 0.15); color: var(--accent-error); } .method-name { font-size: 13px; font-weight: 600; color: var(--text-primary); font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; } .request-meta { display: flex; gap: var(--space-3); align-items: center; font-size: 11px; color: var(--text-tertiary); } .request-time { font-variant-numeric: tabular-nums; } .request-duration { color: var(--accent-success); } .request-duration.slow { color: var(--accent-warning); } .request-duration.very-slow { color: var(--accent-error); } .request-error { font-size: 12px; color: var(--accent-error); background: rgba(239, 68, 68, 0.1); padding: var(--space-2); border-radius: var(--radius-sm); margin-top: var(--space-2); } .stats-bar { display: flex; gap: var(--space-4); margin-bottom: var(--space-4); padding: var(--space-3); background: var(--bg-secondary); border-radius: var(--radius-md); border: 1px solid var(--border-default); flex-wrap: wrap; } .stat-item { display: flex; flex-direction: column; gap: var(--space-1); } .stat-value { font-size: 18px; font-weight: 600; color: var(--text-primary); font-variant-numeric: tabular-nums; } .stat-value.error { color: var(--accent-error); } .stat-label { font-size: 11px; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.5px; } .method-stats { margin-bottom: var(--space-4); } .method-stats-title { font-size: 12px; font-weight: 500; color: var(--text-secondary); margin-bottom: var(--space-2); } .method-stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--space-2); } .method-stat-card { background: var(--bg-tertiary); border-radius: var(--radius-sm); padding: var(--space-2); cursor: pointer; transition: background 0.15s ease; } .method-stat-card:hover { background: var(--bg-secondary); } .method-stat-card.active { background: rgba(99, 102, 241, 0.15); border: 1px solid var(--accent-primary); } .method-stat-name { font-size: 11px; font-weight: 500; color: var(--text-primary); font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; margin-bottom: var(--space-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .method-stat-details { display: flex; gap: var(--space-3); font-size: 10px; color: var(--text-tertiary); } .empty-state { text-align: center; padding: var(--space-6); color: var(--text-tertiary); } .clear-btn { background: rgba(239, 68, 68, 0.1); color: var(--accent-error); border: 1px solid transparent; } .clear-btn:hover { background: rgba(239, 68, 68, 0.2); border-color: var(--accent-error); } .pagination { display: flex; justify-content: center; align-items: center; gap: var(--space-2); margin-top: var(--space-4); } .page-info { font-size: 12px; color: var(--text-tertiary); } .correlation-id { font-size: 10px; color: var(--text-tertiary); font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; } /* Grouped request card */ .request-card .request-response-badges { display: flex; gap: var(--space-2); margin-top: var(--space-1); } .request-card .status-badge { font-size: 10px; padding: 2px 6px; border-radius: var(--radius-sm); } .status-badge.has-request { background: rgba(251, 191, 36, 0.15); color: var(--accent-warning); } .status-badge.has-response { background: rgba(99, 102, 241, 0.15); color: var(--accent-primary); } .status-badge.pending { background: rgba(156, 163, 175, 0.15); color: var(--text-tertiary); } .btn-show-payload { background: var(--bg-tertiary); border: 1px solid var(--border-default); color: var(--accent-primary); font-size: 11px; padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); cursor: pointer; margin-top: var(--space-2); } .btn-show-payload:hover { background: var(--accent-primary); color: white; } /* Modal styles */ .payload-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: var(--space-4); } .payload-modal { background: var(--bg-primary); border-radius: var(--radius-lg); border: 1px solid var(--border-default); width: 100%; max-width: 1400px; height: 90vh; display: flex; flex-direction: column; overflow: hidden; } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border-default); background: var(--bg-secondary); } .modal-title { font-size: 14px; font-weight: 600; color: var(--text-primary); font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; } .modal-subtitle { font-size: 11px; color: var(--text-tertiary); margin-top: var(--space-1); } .modal-close { background: transparent; border: none; color: var(--text-tertiary); font-size: 24px; cursor: pointer; padding: var(--space-1); line-height: 1; } .modal-close:hover { color: var(--text-primary); } .modal-body { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; flex: 1; overflow: hidden; background: var(--border-default); } .payload-panel { background: var(--bg-primary); display: flex; flex-direction: column; overflow: hidden; } .payload-panel-header { padding: var(--space-2) var(--space-3); background: var(--bg-secondary); border-bottom: 1px solid var(--border-default); font-size: 12px; font-weight: 600; display: flex; align-items: center; gap: var(--space-2); } .payload-panel-header .badge { font-size: 10px; } .payload-panel-content { flex: 1; overflow: auto; padding: var(--space-3); } .payload-json { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 12px; color: var(--text-secondary); white-space: pre-wrap; word-break: break-all; line-height: 1.5; } .payload-empty { color: var(--text-tertiary); font-style: italic; font-size: 12px; padding: var(--space-4); text-align: center; } .payload-meta { font-size: 11px; color: var(--text-tertiary); padding: var(--space-2) var(--space-3); border-top: 1px solid var(--border-default); background: var(--bg-tertiary); } .payload-error { background: rgba(239, 68, 68, 0.1); color: var(--accent-error); padding: var(--space-2) var(--space-3); font-size: 12px; border-bottom: 1px solid rgba(239, 68, 68, 0.2); } ` ]; #logs_accessor_storage = __runInitializers(this, _logs_initializers, []); // Received from parent (sw-dash-app) get logs() { return this.#logs_accessor_storage; } set logs(value) { this.#logs_accessor_storage = value; } #totalCount_accessor_storage = (__runInitializers(this, _logs_extraInitializers), __runInitializers(this, _totalCount_initializers, 0)); get totalCount() { return this.#totalCount_accessor_storage; } set totalCount(value) { this.#totalCount_accessor_storage = value; } #stats_accessor_storage = (__runInitializers(this, _totalCount_extraInitializers), __runInitializers(this, _stats_initializers, null)); get stats() { return this.#stats_accessor_storage; } set stats(value) { this.#stats_accessor_storage = value; } #methods_accessor_storage = (__runInitializers(this, _stats_extraInitializers), __runInitializers(this, _methods_initializers, [])); get methods() { return this.#methods_accessor_storage; } set methods(value) { this.#methods_accessor_storage = value; } #directionFilter_accessor_storage = (__runInitializers(this, _methods_extraInitializers), __runInitializers(this, _directionFilter_initializers, 'all')); // Local state for filtering get directionFilter() { return this.#directionFilter_accessor_storage; } set directionFilter(value) { this.#directionFilter_accessor_storage = value; } #phaseFilter_accessor_storage = (__runInitializers(this, _directionFilter_extraInitializers), __runInitializers(this, _phaseFilter_initializers, 'all')); get phaseFilter() { return this.#phaseFilter_accessor_storage; } set phaseFilter(value) { this.#phaseFilter_accessor_storage = value; } #methodFilter_accessor_storage = (__runInitializers(this, _phaseFilter_extraInitializers), __runInitializers(this, _methodFilter_initializers, '')); get methodFilter() { return this.#methodFilter_accessor_storage; } set methodFilter(value) { this.#methodFilter_accessor_storage = value; } #searchText_accessor_storage = (__runInitializers(this, _methodFilter_extraInitializers), __runInitializers(this, _searchText_initializers, '')); get searchText() { return this.#searchText_accessor_storage; } set searchText(value) { this.#searchText_accessor_storage = value; } #isLoadingMore_accessor_storage = (__runInitializers(this, _searchText_extraInitializers), __runInitializers(this, _isLoadingMore_initializers, false)); get isLoadingMore() { return this.#isLoadingMore_accessor_storage; } set isLoadingMore(value) { this.#isLoadingMore_accessor_storage = value; } #modalOpen_accessor_storage = (__runInitializers(this, _isLoadingMore_extraInitializers), __runInitializers(this, _modalOpen_initializers, false)); // Modal state get modalOpen() { return this.#modalOpen_accessor_storage; } set modalOpen(value) { this.#modalOpen_accessor_storage = value; } #selectedGroup_accessor_storage = (__runInitializers(this, _modalOpen_extraInitializers), __runInitializers(this, _selectedGroup_initializers, null)); get selectedGroup() { return this.#selectedGroup_accessor_storage; } set selectedGroup(value) { this.#selectedGroup_accessor_storage = value; } handleDirectionFilterChange(e) { this.directionFilter = e.target.value; // Local filtering - no HTTP request } handlePhaseFilterChange(e) { this.phaseFilter = e.target.value; // Local filtering - no HTTP request } handleMethodFilterChange(e) { this.methodFilter = e.target.value; // Local filtering - no HTTP request } setMethodFilter(method) { // Toggle: clicking the same method clears the filter this.methodFilter = this.methodFilter === method ? '' : method; } handleSearch(e) { this.searchText = e.target.value.toLowerCase(); } handleClear() { if (!confirm('Are you sure you want to clear the request logs? This cannot be undone.')) { return; } // Dispatch event to parent to clear via DeesComms this.dispatchEvent(new CustomEvent('clear-requests', { bubbles: true, composed: true, })); } loadMore() { if (this.isLoadingMore || this.logs.length === 0) return; this.isLoadingMore = true; const oldestLog = this.logs[this.logs.length - 1]; // Dispatch event to parent to load more via DeesComms this.dispatchEvent(new CustomEvent('load-more-requests', { detail: { before: oldestLog.timestamp, method: this.methodFilter || undefined, }, bubbles: true, composed: true, })); // Reset loading state after a short delay (parent will update logs prop) setTimeout(() => { this.isLoadingMore = false; }, 1000); } openPayloadModal(group) { this.selectedGroup = group; this.modalOpen = true; } handleContextMenu(event, group) { // Build full message object for copying const fullMessage = { correlationId: group.correlationId, method: group.method, timestamp: group.timestamp, durationMs: group.durationMs, request: group.request ? { direction: group.request.direction, phase: group.request.phase, timestamp: group.request.timestamp, payload: group.request.payload, } : null, response: group.response ? { direction: group.response.direction, phase: group.response.phase, timestamp: group.response.timestamp, durationMs: group.response.durationMs, payload: group.response.payload, error: group.response.error, } : null, }; DeesContextmenu.openContextMenuWithOptions(event, [ { name: 'Copy Full Message', iconName: 'copy', action: async () => { await navigator.clipboard.writeText(JSON.stringify(fullMessage, null, 2)); }, }, { name: 'Copy Request Payload', iconName: 'upload', disabled: !group.request, action: async () => { if (group.request) { await navigator.clipboard.writeText(JSON.stringify(group.request.payload, null, 2)); } }, }, { name: 'Copy Response Payload', iconName: 'download', disabled: !group.response, action: async () => { if (group.response) { await navigator.clipboard.writeText(JSON.stringify(group.response.payload, null, 2)); } }, }, { divider: true }, { name: 'Copy Correlation ID', iconName: 'hash', action: async () => { await navigator.clipboard.writeText(group.correlationId); }, }, { name: 'Copy Method Name', iconName: 'tag', action: async () => { await navigator.clipboard.writeText(group.method); }, }, { divider: true }, { name: 'Filter by Method', iconName: 'filter', action: async () => { this.setMethodFilter(group.method); }, }, { name: 'Show Payload', iconName: 'eye', action: async () => { this.openPayloadModal(group); }, }, ]); } closeModal() { this.modalOpen = false; this.selectedGroup = null; } handleModalOverlayClick(e) { if (e.target.classList.contains('payload-modal-overlay')) { this.closeModal(); } } handleKeydown = (__runInitializers(this, _selectedGroup_extraInitializers), (e) => { if (e.key === 'Escape' && this.modalOpen) { this.closeModal(); } }); connectedCallback() { super.connectedCallback(); document.addEventListener('keydown', this.handleKeydown); } disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('keydown', this.handleKeydown); } formatTimestamp(ts) { const date = new Date(ts); return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }); } getDurationClass(durationMs) { if (!durationMs) return ''; if (durationMs > 5000) return 'very-slow'; if (durationMs > 1000) return 'slow'; return ''; } formatDuration(durationMs) { if (!durationMs) return ''; if (durationMs < 1000) return `${durationMs}ms`; return `${(durationMs / 1000).toFixed(2)}s`; } /** * Filter logs locally based on direction, phase, method, and search text */ getFilteredLogs() { let result = this.logs; // Apply direction filter if (this.directionFilter !== 'all') { result = result.filter(l => l.direction === this.directionFilter); } // Apply phase filter if (this.phaseFilter !== 'all') { result = result.filter(l => l.phase === this.phaseFilter); } // Apply method filter if (this.methodFilter) { result = result.filter(l => l.method === this.methodFilter); } // Apply search if (this.searchText) { result = result.filter(l => l.method.toLowerCase().includes(this.searchText) || l.correlationId.toLowerCase().includes(this.searchText) || (l.error && l.error.toLowerCase().includes(this.searchText)) || JSON.stringify(l.payload).toLowerCase().includes(this.searchText)); } return result; } /** * Group filtered logs by correlationId to show request/response pairs together */ getGroupedLogs() { const filtered = this.getFilteredLogs(); const groups = new Map(); for (const log of filtered) { let group = groups.get(log.correlationId); if (!group) { group = { correlationId: log.correlationId, method: log.method, timestamp: log.timestamp, hasError: false, }; groups.set(log.correlationId, group); } if (log.phase === 'request') { group.request = log; // Update timestamp to the earliest (request time) if (log.timestamp < group.timestamp) { group.timestamp = log.timestamp; } } else if (log.phase === 'response') { group.response = log; if (log.durationMs !== undefined) { group.durationMs = log.durationMs; } } if (log.error) { group.hasError = true; } } // Convert to array and sort by timestamp (newest first) return Array.from(groups.values()).sort((a, b) => b.timestamp - a.timestamp); } /** * Render the payload modal */ renderModal() { if (!this.modalOpen || !this.selectedGroup) { return null; } const group = this.selectedGroup; return html ` <div class="payload-modal-overlay" @click="${this.handleModalOverlayClick}"> <div class="payload-modal"> <div class="modal-header"> <div> <div class="modal-title">${group.method}</div> <div class="modal-subtitle"> Correlation ID: ${group.correlationId} ${group.durationMs !== undefined ? html ` | Duration: ${this.formatDuration(group.durationMs)}` : ''} </div> </div> <button class="modal-close" @click="${this.closeModal}">&times;</button> </div> <div class="modal-body"> <!-- Request Panel (Left) --> <div class="payload-panel"> <div class="payload-panel-header"> <span class="badge phase-request">REQUEST</span> ${group.request ? html ` <span class="badge direction-${group.request.direction}">${group.request.direction}</span> ` : ''} </div> ${group.request ? html ` <div class="payload-meta"> Timestamp: ${this.formatTimestamp(group.request.timestamp)} </div> <div class="payload-panel-content"> <pre class="payload-json">${JSON.stringify(group.request.payload, null, 2)}</pre> </div> ` : html ` <div class="payload-empty">No request data captured</div> `} </div> <!-- Response Panel (Right) --> <div class="payload-panel"> <div class="payload-panel-header"> <span class="badge phase-response">RESPONSE</span> ${group.response ? html ` <span class="badge direction-${group.response.direction}">${group.response.direction}</span> ` : ''} </div> ${group.response?.error ? html ` <div class="payload-error">Error: ${group.response.error}</div> ` : ''} ${group.response ? html ` <div class="payload-meta"> Timestamp: ${this.formatTimestamp(group.response.timestamp)} ${group.response.durationMs !== undefined ? html ` | Duration: ${this.formatDuration(group.response.durationMs)}` : ''} </div> <div class="payload-panel-content"> <pre class="payload-json">${JSON.stringify(group.response.payload, null, 2)}</pre> </div> ` : html ` <div class="payload-empty">No response yet (pending)</div> `} </div> </div> </div> </div> `; } render() { const groupedLogs = this.getGroupedLogs(); return html ` ${this.renderModal()} <!-- Stats Bar --> <div class="stats-bar"> <div class="stat-item"> <span class="stat-value">${this.stats?.totalRequests ?? 0}</span> <span class="stat-label">Total Requests</span> </div> <div class="stat-item"> <span class="stat-value">${this.stats?.totalResponses ?? 0}</span> <span class="stat-label">Total Responses</span> </div> <div class="stat-item"> <span class="stat-value ${(this.stats?.errorCount ?? 0) > 0 ? 'error' : ''}">${this.stats?.errorCount ?? 0}</span> <span class="stat-label">Errors</span> </div> <div class="stat-item"> <span class="stat-value">${this.stats?.avgDurationMs ?? 0}ms</span> <span class="stat-label">Avg Duration</span> </div> <div class="stat-item"> <span class="stat-value">${groupedLogs.length}</span> <span class="stat-label">Showing</span> </div> </div> <!-- Method Stats --> ${this.stats && Object.keys(this.stats.methodCounts).length > 0 ? html ` <div class="method-stats"> <div class="method-stats-title">Methods</div> <div class="method-stats-grid"> ${Object.entries(this.stats.methodCounts).slice(0, 8).map(([method, data]) => html ` <div class="method-stat-card ${this.methodFilter === method ? 'active' : ''}" @click="${() => this.setMethodFilter(method)}" > <div class="method-stat-name" title="${method}">${method}</div> <div class="method-stat-details"> <span>${data.requests} req</span> <span>${data.responses} res</span> ${data.errors > 0 ? html `<span style="color: var(--accent-error)">${data.errors} err</span>` : ''} <span>${data.avgDurationMs}ms avg</span> </div> </div> `)} </div> </div> ` : ''} <!-- Filters --> <div class="requests-header"> <div class="filter-group"> <span class="filter-label">Direction:</span> <select class="filter-select" @change="${this.handleDirectionFilterChange}"> <option value="all">All</option> <option value="outgoing">Outgoing</option> <option value="incoming">Incoming</option> </select> <span class="filter-label">Phase:</span> <select class="filter-select" @change="${this.handlePhaseFilterChange}"> <option value="all">All</option> <option value="request">Request</option> <option value="response">Response</option> </select> <span class="filter-label">Method:</span> <select class="filter-select" .value="${this.methodFilter}" @change="${this.handleMethodFilterChange}"> <option value="">All Methods</option> ${this.methods.map(m => html `<option value="${m}" ?selected="${this.methodFilter === m}">${m}</option>`)} </select> <input type="text" class="search-input" placeholder="Search..." .value="${this.searchText}" @input="${this.handleSearch}" style="width: 150px;" > </div> <button class="btn clear-btn" @click="${this.handleClear}">Clear Logs</button> </div> <!-- Request List (Grouped by correlationId) --> ${this.logs.length === 0 ? html ` <div class="empty-state">No request logs found. Traffic will appear here as TypedRequests are made.</div> ` : groupedLogs.length === 0 ? html ` <div class="empty-state">No logs match filter</div> ` : html ` <div class="requests-list"> ${groupedLogs.map(group => html ` <div class="request-card ${group.hasError ? 'has-error' : ''}" @contextmenu="${(e) => this.handleContextMenu(e, group)}" > <div class="request-header"> <div> <div class="request-badges"> ${group.request ? html ` <span class="badge direction-${group.request.direction}">${group.request.direction}</span> ` : ''} ${group.hasError ? html `<span class="badge error">error</span>` : ''} </div> <div class="method-name">${group.method}</div> <div class="correlation-id">${group.correlationId}</div> <div class="request-response-badges"> <span class="status-badge ${group.request ? 'has-request' : 'pending'}"> ${group.request ? 'REQ' : 'REQ pending'} </span> <span class="status-badge ${group.response ? 'has-response' : 'pending'}"> ${group.response ? 'RES' : 'RES pending'} </span> </div> </div> <div class="request-meta"> <span class="request-time">${this.formatTimestamp(group.timestamp)}</span> ${group.durationMs !== undefined ? html ` <span class="request-duration ${this.getDurationClass(group.durationMs)}"> ${this.formatDuration(group.durationMs)} </span> ` : ''} </div> </div> ${group.response?.error ? html ` <div class="request-error">${group.response.error}</div> ` : ''} <button class="btn-show-payload" @click="${() => this.openPayloadModal(group)}"> Show Payload </button> </div> `)} </div> ${this.logs.length < this.totalCount ? html ` <div class="pagination"> <button class="btn btn-secondary" @click="${this.loadMore}" ?disabled="${this.isLoadingMore}"> ${this.isLoadingMore ? 'Loading...' : 'Load More'} </button> <span class="page-info">${this.logs.length} of ${this.totalCount} logs</span> </div> ` : ''} `} `; } static { __runInitializers(_classThis, _classExtraInitializers); } }; return SwDashRequests = _classThis; })(); export { SwDashRequests }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3ctZGFzaC1yZXF1ZXN0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX3N3ZGFzaC9zdy1kYXNoLXJlcXVlc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxhQUFhLEVBQUUsZUFBZSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRXRHLE9BQU8sRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQXFDM0Y7Ozs7OztHQU1HO0lBRVUsY0FBYzs0QkFEMUIsYUFBYSxDQUFDLGtCQUFrQixDQUFDOzs7O3NCQUNFLFVBQVU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OEJBQWxCLFNBQVEsV0FBVTs7OztnQ0F1YjNDLFFBQVEsQ0FBQyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQztzQ0FDekIsUUFBUSxDQUFDLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDO2lDQUMxQixRQUFRLENBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUM7bUNBQzFCLFFBQVEsQ0FBQyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQzsyQ0FHekIsS0FBSyxFQUFFO3VDQUNQLEtBQUssRUFBRTt3Q0FDUCxLQUFLLEVBQUU7c0NBQ1AsS0FBSyxFQUFFO3lDQUNQLEtBQUssRUFBRTtxQ0FHUCxLQUFLLEVBQUU7eUNBQ1AsS0FBSyxFQUFFO1lBZG1CLGlLQUFTLElBQUksNkJBQUosSUFBSSxtRkFBK0I7WUFDM0MsbUxBQVMsVUFBVSw2QkFBVixVQUFVLCtGQUFLO1lBQ3hCLG9LQUFTLEtBQUssNkJBQUwsS0FBSyxxRkFBbUM7WUFDbEQsMEtBQVMsT0FBTyw2QkFBUCxPQUFPLHlGQUFnQjtZ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