Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
250 lines
7.5 KiB
JavaScript
250 lines
7.5 KiB
JavaScript
/**
|
|
* ds-console-viewer.js
|
|
* Console log viewer with real-time streaming and filtering
|
|
*/
|
|
|
|
import toolBridge from '../../services/tool-bridge.js';
|
|
|
|
class DSConsoleViewer extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.logs = [];
|
|
this.currentFilter = 'all';
|
|
this.autoScroll = true;
|
|
this.refreshInterval = null;
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.render();
|
|
this.startAutoRefresh();
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
this.stopAutoRefresh();
|
|
}
|
|
|
|
render() {
|
|
const filteredLogs = this.currentFilter === 'all'
|
|
? this.logs
|
|
: this.logs.filter(log => log.level === this.currentFilter);
|
|
|
|
this.innerHTML = `
|
|
<div style="display: flex; flex-direction: column; height: 100%;">
|
|
<!-- Header Controls -->
|
|
<div style="
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 12px;
|
|
border-bottom: 1px solid var(--vscode-border);
|
|
background-color: var(--vscode-sidebar);
|
|
">
|
|
<div style="display: flex; gap: 8px;">
|
|
<button class="filter-btn ${this.currentFilter === 'all' ? 'active' : ''}" data-filter="all" style="
|
|
padding: 4px 12px;
|
|
background-color: ${this.currentFilter === 'all' ? 'var(--vscode-accent)' : 'transparent'};
|
|
border: 1px solid var(--vscode-border);
|
|
color: var(--vscode-text);
|
|
border-radius: 2px;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
">All</button>
|
|
<button class="filter-btn ${this.currentFilter === 'log' ? 'active' : ''}" data-filter="log" style="
|
|
padding: 4px 12px;
|
|
background-color: ${this.currentFilter === 'log' ? 'var(--vscode-accent)' : 'transparent'};
|
|
border: 1px solid var(--vscode-border);
|
|
color: var(--vscode-text);
|
|
border-radius: 2px;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
">Log</button>
|
|
<button class="filter-btn ${this.currentFilter === 'warn' ? 'active' : ''}" data-filter="warn" style="
|
|
padding: 4px 12px;
|
|
background-color: ${this.currentFilter === 'warn' ? 'var(--vscode-accent)' : 'transparent'};
|
|
border: 1px solid var(--vscode-border);
|
|
color: var(--vscode-text);
|
|
border-radius: 2px;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
">Warn</button>
|
|
<button class="filter-btn ${this.currentFilter === 'error' ? 'active' : ''}" data-filter="error" style="
|
|
padding: 4px 12px;
|
|
background-color: ${this.currentFilter === 'error' ? 'var(--vscode-accent)' : 'transparent'};
|
|
border: 1px solid var(--vscode-border);
|
|
color: var(--vscode-text);
|
|
border-radius: 2px;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
">Error</button>
|
|
</div>
|
|
<div style="display: flex; gap: 8px; align-items: center;">
|
|
<label style="font-size: 11px; display: flex; align-items: center; gap: 4px; cursor: pointer;">
|
|
<input type="checkbox" id="auto-scroll-toggle" ${this.autoScroll ? 'checked' : ''}>
|
|
Auto-scroll
|
|
</label>
|
|
<button id="clear-logs-btn" class="button" style="padding: 4px 12px; font-size: 11px;">
|
|
Clear
|
|
</button>
|
|
<button id="refresh-logs-btn" class="button" style="padding: 4px 12px; font-size: 11px;">
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Console Output -->
|
|
<div id="console-output" style="
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 8px;
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
font-size: 12px;
|
|
background-color: var(--vscode-bg);
|
|
">
|
|
${filteredLogs.length === 0 ? `
|
|
<div style="padding: 16px; text-align: center; color: var(--vscode-text-dim);">
|
|
No console logs${this.currentFilter !== 'all' ? ` (${this.currentFilter})` : ''}
|
|
</div>
|
|
` : filteredLogs.map(log => this.renderLogEntry(log)).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
this.setupEventListeners();
|
|
|
|
if (this.autoScroll) {
|
|
this.scrollToBottom();
|
|
}
|
|
}
|
|
|
|
renderLogEntry(log) {
|
|
const levelColors = {
|
|
log: 'var(--vscode-text)',
|
|
warn: '#ff9800',
|
|
error: '#f44336',
|
|
info: '#2196f3',
|
|
debug: 'var(--vscode-text-dim)'
|
|
};
|
|
|
|
const color = levelColors[log.level] || 'var(--vscode-text)';
|
|
|
|
return `
|
|
<div style="
|
|
padding: 4px 8px;
|
|
border-bottom: 1px solid var(--vscode-border);
|
|
display: flex;
|
|
gap: 12px;
|
|
">
|
|
<span style="color: var(--vscode-text-dim); min-width: 80px; flex-shrink: 0;">
|
|
${log.timestamp}
|
|
</span>
|
|
<span style="color: ${color}; font-weight: 600; min-width: 50px; flex-shrink: 0;">
|
|
[${log.level.toUpperCase()}]
|
|
</span>
|
|
<span style="color: var(--vscode-text); flex: 1; word-break: break-word;">
|
|
${this.escapeHtml(log.message)}
|
|
</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Filter buttons
|
|
this.querySelectorAll('.filter-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
this.currentFilter = e.target.dataset.filter;
|
|
this.render();
|
|
});
|
|
});
|
|
|
|
// Auto-scroll toggle
|
|
const autoScrollToggle = this.querySelector('#auto-scroll-toggle');
|
|
if (autoScrollToggle) {
|
|
autoScrollToggle.addEventListener('change', (e) => {
|
|
this.autoScroll = e.target.checked;
|
|
});
|
|
}
|
|
|
|
// Clear button
|
|
const clearBtn = this.querySelector('#clear-logs-btn');
|
|
if (clearBtn) {
|
|
clearBtn.addEventListener('click', () => {
|
|
this.logs = [];
|
|
this.render();
|
|
});
|
|
}
|
|
|
|
// Refresh button
|
|
const refreshBtn = this.querySelector('#refresh-logs-btn');
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', () => {
|
|
this.fetchLogs();
|
|
});
|
|
}
|
|
}
|
|
|
|
async fetchLogs() {
|
|
try {
|
|
const result = await toolBridge.getBrowserLogs(this.currentFilter, 100);
|
|
if (result && result.logs) {
|
|
this.logs = result.logs.map(log => ({
|
|
timestamp: new Date(log.timestamp).toLocaleTimeString(),
|
|
level: log.level || 'log',
|
|
message: log.message || JSON.stringify(log)
|
|
}));
|
|
this.render();
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch logs:', error);
|
|
this.addLog('error', `Failed to fetch logs: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
addLog(level, message) {
|
|
const now = new Date();
|
|
this.logs.push({
|
|
timestamp: now.toLocaleTimeString(),
|
|
level,
|
|
message
|
|
});
|
|
|
|
// Keep only last 100 logs
|
|
if (this.logs.length > 100) {
|
|
this.logs = this.logs.slice(-100);
|
|
}
|
|
|
|
this.render();
|
|
}
|
|
|
|
startAutoRefresh() {
|
|
// Fetch logs every 2 seconds
|
|
this.fetchLogs();
|
|
this.refreshInterval = setInterval(() => {
|
|
this.fetchLogs();
|
|
}, 2000);
|
|
}
|
|
|
|
stopAutoRefresh() {
|
|
if (this.refreshInterval) {
|
|
clearInterval(this.refreshInterval);
|
|
this.refreshInterval = null;
|
|
}
|
|
}
|
|
|
|
scrollToBottom() {
|
|
const output = this.querySelector('#console-output');
|
|
if (output) {
|
|
output.scrollTop = output.scrollHeight;
|
|
}
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
}
|
|
|
|
customElements.define('ds-console-viewer', DSConsoleViewer);
|
|
|
|
export default DSConsoleViewer;
|