/** * ds-network-monitor.js * Network request monitoring and debugging * * REFACTORED: DSS-compliant version using DSBaseTool + table-template.js * - Extends DSBaseTool for Shadow DOM, AbortController, and standardized lifecycle * - Uses table-template.js for DSS-compliant table rendering (NO inline events/styles) * - Event delegation pattern for all interactions * - Logger utility instead of console.* * * Reference: .knowledge/dss-coding-standards.json */ import DSBaseTool from '../base/ds-base-tool.js'; import toolBridge from '../../services/tool-bridge.js'; import { ComponentHelpers } from '../../utils/component-helpers.js'; import { logger } from '../../utils/logger.js'; import { createTableView, setupTableEvents, createStatsCard } from '../../templates/table-template.js'; class DSNetworkMonitor extends DSBaseTool { constructor() { super(); this.requests = []; this.filteredRequests = []; this.filterUrl = ''; this.filterType = 'all'; this.autoRefresh = false; this.refreshInterval = null; } connectedCallback() { super.connectedCallback(); this.loadRequests(); } disconnectedCallback() { if (this.refreshInterval) { clearInterval(this.refreshInterval); } super.disconnectedCallback(); } /** * Render the component (required by DSBaseTool) */ render() { this.shadowRoot.innerHTML = `
Initializing...
`; } /** * Setup event listeners (required by DSBaseTool) */ setupEventListeners() { // EVENT-002: Event delegation this.delegateEvents('.network-monitor-container', 'click', (action, e) => { if (action === 'refresh') { this.loadRequests(); } }); // Filter input with debounce const filterInput = this.$('#network-filter'); if (filterInput) { const debouncedFilter = ComponentHelpers.debounce((term) => { this.filterUrl = term.toLowerCase(); this.applyFilters(); }, 300); this.bindEvent(filterInput, 'input', (e) => debouncedFilter(e.target.value)); } // Type filter const typeFilter = this.$('#network-type-filter'); if (typeFilter) { this.bindEvent(typeFilter, 'change', (e) => { this.filterType = e.target.value; this.applyFilters(); }); } // Auto-refresh toggle const autoRefreshToggle = this.$('#auto-refresh-toggle'); if (autoRefreshToggle) { this.bindEvent(autoRefreshToggle, 'change', (e) => { this.autoRefresh = e.target.checked; if (this.autoRefresh) { this.refreshInterval = setInterval(() => this.loadRequests(), 2000); logger.debug('[DSNetworkMonitor] Auto-refresh enabled'); } else { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; logger.debug('[DSNetworkMonitor] Auto-refresh disabled'); } } }); } } async loadRequests() { const content = this.$('#network-content'); if (!content) return; // Only show loading on first load if (this.requests.length === 0) { content.innerHTML = '
Loading network requests...
'; } try { const result = await toolBridge.getNetworkRequests(null, 100); if (result && result.requests) { this.requests = result.requests; this.updateTypeFilter(); this.applyFilters(); logger.debug('[DSNetworkMonitor] Loaded requests', { count: this.requests.length }); } else { this.requests = []; content.innerHTML = '
🌐
No network requests captured
'; } } catch (error) { logger.error('[DSNetworkMonitor] Failed to load network requests', error); content.innerHTML = ComponentHelpers.renderError('Failed to load network requests', error); } } updateTypeFilter() { const typeFilter = this.$('#network-type-filter'); if (!typeFilter) return; const types = this.getResourceTypes(); const currentValue = typeFilter.value; typeFilter.innerHTML = ` ${types.map(type => ``).join('')} `; } applyFilters() { let filtered = [...this.requests]; // Filter by URL if (this.filterUrl) { filtered = filtered.filter(req => req.url.toLowerCase().includes(this.filterUrl) || req.method.toLowerCase().includes(this.filterUrl) ); } // Filter by type if (this.filterType !== 'all') { filtered = filtered.filter(req => req.resourceType === this.filterType); } this.filteredRequests = filtered; this.renderRequests(); } getResourceTypes() { if (!this.requests) return []; const types = new Set(this.requests.map(r => r.resourceType).filter(Boolean)); return Array.from(types).sort(); } getStatusColor(status) { if (status >= 200 && status < 300) return 'success'; if (status >= 300 && status < 400) return 'info'; if (status >= 400 && status < 500) return 'warning'; if (status >= 500) return 'error'; return 'info'; } renderRequests() { const content = this.$('#network-content'); if (!content) return; if (!this.filteredRequests || this.filteredRequests.length === 0) { content.innerHTML = `
🔍
${this.filterUrl ? 'No requests match your filter' : 'No network requests captured yet'}
`; return; } // Render info count const infoHtml = `
Showing ${this.filteredRequests.length} of ${this.requests.length} requests ${this.autoRefresh ? '• Auto-refreshing every 2s' : ''}
`; // Use table-template.js for DSS-compliant rendering const { html: tableHtml, styles: tableStyles } = createTableView({ columns: [ { header: 'Method', key: 'method', width: '80px', align: 'left' }, { header: 'Status', key: 'status', width: '80px', align: 'left' }, { header: 'URL', key: 'url', align: 'left' }, { header: 'Type', key: 'resourceType', width: '100px', align: 'left' }, { header: 'Time', key: 'timing', width: '80px', align: 'left' } ], rows: this.filteredRequests, renderCell: (col, row) => this.renderCell(col, row), renderDetails: (row) => this.renderDetails(row), emptyMessage: 'No network requests', emptyIcon: '🌐' }); // Adopt table styles this.adoptStyles(tableStyles); // Render table content.innerHTML = infoHtml + tableHtml + '
💡 Click any row to view full request details
'; // Setup table event handlers setupTableEvents(this.shadowRoot); logger.debug('[DSNetworkMonitor] Rendered requests', { count: this.filteredRequests.length }); } renderCell(col, row) { const method = row.method || 'GET'; const status = row.status || '-'; const statusColor = this.getStatusColor(status); const resourceType = row.resourceType || 'other'; const url = row.url || 'Unknown URL'; const timing = row.timing ? `${Math.round(row.timing)}ms` : '-'; switch (col.key) { case 'method': const methodColor = method === 'GET' ? 'info' : method === 'POST' ? 'success' : 'warning'; return `${this.escapeHtml(method)}`; case 'status': return `${this.escapeHtml(String(status))}`; case 'url': return `${this.escapeHtml(url)}`; case 'resourceType': return `${this.escapeHtml(resourceType)}`; case 'timing': return `${timing}`; default: return this.escapeHtml(String(row[col.key] || '-')); } } renderDetails(row) { const method = row.method || 'GET'; const status = row.status || '-'; const url = row.url || 'Unknown URL'; const resourceType = row.resourceType || 'other'; return `
URL: ${this.escapeHtml(url)}
Method: ${this.escapeHtml(method)}
Status: ${this.escapeHtml(String(status))}
Type: ${this.escapeHtml(resourceType)}
${row.headers ? `
Headers:
${this.escapeHtml(JSON.stringify(row.headers, null, 2))}
` : ''} `; } } customElements.define('ds-network-monitor', DSNetworkMonitor); export default DSNetworkMonitor;