Initial commit: Clean DSS implementation
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
This commit is contained in:
249
admin-ui/js/components/tools/ds-console-viewer.js
Normal file
249
admin-ui/js/components/tools/ds-console-viewer.js
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user