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:
203
admin-ui/js/components/metrics/ds-frontpage.js
Normal file
203
admin-ui/js/components/metrics/ds-frontpage.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* ds-frontpage.js
|
||||
* Front page component for team workdesks
|
||||
* Refactored: Shadow DOM, extracted styles, uses ds-metric-card
|
||||
*/
|
||||
|
||||
import contextStore from '../../stores/context-store.js';
|
||||
import './ds-metric-card.js'; // Import the new reusable component
|
||||
|
||||
export default class Frontpage extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' }); // Enable Shadow DOM
|
||||
this.state = {
|
||||
teamName: 'Team',
|
||||
metrics: {}
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
|
||||
// Subscribe to context changes
|
||||
this.unsubscribe = contextStore.subscribe(({ state }) => {
|
||||
this.state.teamId = state.teamId;
|
||||
const teamNames = {
|
||||
'ui': 'UI Team',
|
||||
'ux': 'UX Team',
|
||||
'qa': 'QA Team',
|
||||
'admin': 'Admin'
|
||||
};
|
||||
this.state.teamName = teamNames[state.teamId] || 'Team';
|
||||
this.updateTeamName();
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this.unsubscribe) this.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
font-family: var(--vscode-font-family);
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
.container {
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.description {
|
||||
margin: 0 0 32px 0;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 14px;
|
||||
}
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.quick-actions {
|
||||
background: var(--vscode-sidebar-background);
|
||||
border: 1px solid var(--vscode-widget-border);
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
}
|
||||
h2 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.action-btn {
|
||||
padding: 12px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
transition: background-color 0.1s;
|
||||
}
|
||||
.action-btn:hover {
|
||||
background: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
.action-btn:focus-visible {
|
||||
outline: 2px solid var(--vscode-focusBorder);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<div>
|
||||
<h1 id="team-name">Team Dashboard</h1>
|
||||
<p class="description">
|
||||
Overview of design system adoption and metrics for your team
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Metrics Cards using reusable component -->
|
||||
<div class="metrics-grid">
|
||||
<ds-metric-card
|
||||
title="Adoption Rate"
|
||||
value="68%"
|
||||
subtitle="of team using DS"
|
||||
color="#4caf50">
|
||||
</ds-metric-card>
|
||||
|
||||
<ds-metric-card
|
||||
title="Components"
|
||||
value="45/65"
|
||||
subtitle="in use"
|
||||
color="#2196f3">
|
||||
</ds-metric-card>
|
||||
|
||||
<ds-metric-card
|
||||
title="Tokens"
|
||||
value="187"
|
||||
subtitle="managed"
|
||||
color="#ff9800">
|
||||
</ds-metric-card>
|
||||
|
||||
<ds-metric-card
|
||||
title="Last Update"
|
||||
value="2 hours"
|
||||
subtitle="ago"
|
||||
color="#9c27b0">
|
||||
</ds-metric-card>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="quick-actions">
|
||||
<h2>Quick Actions</h2>
|
||||
<div class="actions-grid">
|
||||
<button class="action-btn" data-action="components" type="button">
|
||||
📦 View Components
|
||||
</button>
|
||||
<button class="action-btn" data-action="tokens" type="button">
|
||||
🎨 Manage Tokens
|
||||
</button>
|
||||
<button class="action-btn" data-action="icons" type="button">
|
||||
✨ View Icons
|
||||
</button>
|
||||
<button class="action-btn" data-action="jira" type="button">
|
||||
🐛 Jira Issues
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.updateTeamName();
|
||||
}
|
||||
|
||||
updateTeamName() {
|
||||
// Select from Shadow DOM
|
||||
const teamNameEl = this.shadowRoot.querySelector('#team-name');
|
||||
if (teamNameEl) {
|
||||
teamNameEl.textContent = `${this.state.teamName} Dashboard`;
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Listen within Shadow DOM
|
||||
const buttons = this.shadowRoot.querySelectorAll('.action-btn');
|
||||
buttons.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const action = btn.dataset.action;
|
||||
this.handleQuickAction(action);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleQuickAction(action) {
|
||||
console.log(`Quick action triggered: ${action}`);
|
||||
// Events bubble out of Shadow DOM if composed: true
|
||||
this.dispatchEvent(new CustomEvent('quick-action', {
|
||||
detail: { action },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-frontpage', Frontpage);
|
||||
84
admin-ui/js/components/metrics/ds-metric-card.js
Normal file
84
admin-ui/js/components/metrics/ds-metric-card.js
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* ds-metric-card.js
|
||||
* Reusable web component for displaying dashboard metrics
|
||||
* Encapsulates styling and layout for consistency across dashboards
|
||||
*/
|
||||
|
||||
export default class MetricCard extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['title', 'value', 'subtitle', 'color', 'trend'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (oldValue !== newValue) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const title = this.getAttribute('title') || '';
|
||||
const value = this.getAttribute('value') || '0';
|
||||
const subtitle = this.getAttribute('subtitle') || '';
|
||||
const color = this.getAttribute('color') || 'var(--vscode-textLink-foreground)';
|
||||
|
||||
// Trend implementation (optional enhancement)
|
||||
const trend = this.getAttribute('trend'); // e.g., "up", "down"
|
||||
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
.card {
|
||||
background: var(--vscode-sidebar-background);
|
||||
border: 1px solid var(--vscode-widget-border);
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-top: 3px solid var(--card-color, ${color});
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
||||
.card:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.header {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 11px;
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
color: var(--card-color, ${color});
|
||||
}
|
||||
.subtitle {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
<div class="card" style="--card-color: ${color}">
|
||||
<div class="header">${title}</div>
|
||||
<div class="value">${value}</div>
|
||||
<div class="subtitle">${subtitle}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-metric-card', MetricCard);
|
||||
204
admin-ui/js/components/metrics/ds-metrics-dashboard.js
Normal file
204
admin-ui/js/components/metrics/ds-metrics-dashboard.js
Normal file
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* ds-metrics-dashboard.js
|
||||
* Metrics dashboard for design system adoption and health
|
||||
* Shows key metrics like component adoption rate, token usage, etc.
|
||||
*/
|
||||
|
||||
import store from '../../stores/app-store.js';
|
||||
|
||||
export default class MetricsDashboard extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
this.metrics = {
|
||||
adoptionRate: 0,
|
||||
componentsUsed: 0,
|
||||
totalComponents: 0,
|
||||
tokensCovered: 0,
|
||||
teamsActive: 0,
|
||||
averageUpdateFreq: 'N/A'
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.loadMetrics();
|
||||
}
|
||||
|
||||
async loadMetrics() {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
this.render();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/discovery/stats');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
if (json.status === 'success' && json.data) {
|
||||
const stats = json.data;
|
||||
|
||||
// Map backend field names to component properties
|
||||
this.metrics = {
|
||||
adoptionRate: stats.adoption_percentage || 0,
|
||||
componentsUsed: stats.components_in_use || 0,
|
||||
totalComponents: stats.total_components || 0,
|
||||
tokensCovered: stats.tokens_count || 0,
|
||||
teamsActive: stats.active_projects || 0,
|
||||
averageUpdateFreq: stats.avg_update_days
|
||||
? `${stats.avg_update_days} days`
|
||||
: 'N/A'
|
||||
};
|
||||
} else {
|
||||
throw new Error(json.message || 'Invalid response format');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load metrics:', error);
|
||||
this.error = error.message;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.isLoading) {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 24px; height: 100%; display: flex; align-items: center; justify-content: center;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 14px; color: var(--vscode-text-dim);">Loading metrics...</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.error) {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 24px; height: 100%; display: flex; align-items: center; justify-content: center;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 14px; color: var(--vscode-error); margin-bottom: 12px;">
|
||||
Failed to load metrics: ${this.error}
|
||||
</div>
|
||||
<button
|
||||
onclick="document.querySelector('ds-metrics-dashboard').loadMetrics()"
|
||||
style="
|
||||
padding: 8px 16px;
|
||||
background: var(--vscode-button);
|
||||
color: var(--vscode-button-fg);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
this.innerHTML = `
|
||||
<div style="padding: 24px; height: 100%; overflow-y: auto;">
|
||||
<h1 style="margin-bottom: 8px; font-size: 24px;">Design System Metrics</h1>
|
||||
<p style="color: var(--vscode-text-dim); margin-bottom: 32px;">
|
||||
Track adoption, health, and usage of your design system
|
||||
</p>
|
||||
|
||||
<!-- Metrics Grid -->
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin-bottom: 32px;">
|
||||
${this.renderMetricCard('Adoption Rate', `${this.metrics.adoptionRate}%`, '#4caf50', 'Percentage of team using DS')}
|
||||
${this.renderMetricCard('Components in Use', this.metrics.componentsUsed, '#2196f3', `of ${this.metrics.totalComponents} total`)}
|
||||
${this.renderMetricCard('Design Tokens', this.metrics.tokensCovered, '#ff9800', 'Total tokens managed')}
|
||||
${this.renderMetricCard('Active Projects', this.metrics.teamsActive, '#9c27b0', 'Projects in system')}
|
||||
</div>
|
||||
|
||||
<!-- Activity Timeline -->
|
||||
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 20px; margin-bottom: 24px;">
|
||||
<h2 style="margin-top: 0; margin-bottom: 16px; font-size: 16px;">Recent Activity</h2>
|
||||
<div style="font-size: 12px;">
|
||||
<div style="padding: 8px 0; border-bottom: 1px solid var(--vscode-border);">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
|
||||
<span style="font-weight: 500;">Component Library Updated</span>
|
||||
<span style="color: var(--vscode-text-dim);">2 hours ago</span>
|
||||
</div>
|
||||
<div style="color: var(--vscode-text-dim); font-size: 11px;">Added 3 new components to Button family</div>
|
||||
</div>
|
||||
<div style="padding: 8px 0; border-bottom: 1px solid var(--vscode-border);">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
|
||||
<span style="font-weight: 500;">Tokens Synchronized</span>
|
||||
<span style="color: var(--vscode-text-dim);">6 hours ago</span>
|
||||
</div>
|
||||
<div style="color: var(--vscode-text-dim); font-size: 11px;">Synced 42 color tokens from Figma</div>
|
||||
</div>
|
||||
<div style="padding: 8px 0;">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
|
||||
<span style="font-weight: 500;">Team Onboarded</span>
|
||||
<span style="color: var(--vscode-text-dim);">1 day ago</span>
|
||||
</div>
|
||||
<div style="color: var(--vscode-text-dim); font-size: 11px;">Marketing team completed DS training</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Health Indicators -->
|
||||
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 20px;">
|
||||
<h2 style="margin-top: 0; margin-bottom: 16px; font-size: 16px;">System Health</h2>
|
||||
<div style="font-size: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span>Component Coverage</span>
|
||||
<div style="width: 150px; height: 6px; background: var(--vscode-bg); border-radius: 3px; overflow: hidden;">
|
||||
<div style="width: 69%; height: 100%; background: #4caf50;"></div>
|
||||
</div>
|
||||
<span>69%</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span>Token Coverage</span>
|
||||
<div style="width: 150px; height: 6px; background: var(--vscode-bg); border-radius: 3px; overflow: hidden;">
|
||||
<div style="width: 85%; height: 100%; background: #2196f3;"></div>
|
||||
</div>
|
||||
<span>85%</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>Documentation</span>
|
||||
<div style="width: 150px; height: 6px; background: var(--vscode-bg); border-radius: 3px; overflow: hidden;">
|
||||
<div style="width: 92%; height: 100%; background: #ff9800;"></div>
|
||||
</div>
|
||||
<span>92%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderMetricCard(title, value, color, subtitle) {
|
||||
return `
|
||||
<div style="
|
||||
background: var(--vscode-sidebar);
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
border-top: 3px solid ${color};
|
||||
">
|
||||
<div style="color: var(--vscode-text-dim); font-size: 11px; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px;">
|
||||
${title}
|
||||
</div>
|
||||
<div style="font-size: 32px; font-weight: 600; margin-bottom: 4px; color: ${color};">
|
||||
${value}
|
||||
</div>
|
||||
<div style="color: var(--vscode-text-dim); font-size: 11px;">
|
||||
${subtitle}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-metrics-dashboard', MetricsDashboard);
|
||||
Reference in New Issue
Block a user