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
273 lines
6.8 KiB
JavaScript
273 lines
6.8 KiB
JavaScript
/**
|
|
* Component Configuration Registry
|
|
*
|
|
* Extensible registry for external tools and components.
|
|
* Each component defines its config schema, making it easy to:
|
|
* - Add new tools without code changes
|
|
* - Generate settings UI dynamically
|
|
* - Validate configurations
|
|
* - Store and retrieve settings consistently
|
|
*/
|
|
|
|
import { getConfig, getDssHost, getStorybookPort } from './config-loader.js';
|
|
|
|
/**
|
|
* Component Registry
|
|
* Add new components here to extend the settings system.
|
|
*/
|
|
export const componentRegistry = {
|
|
storybook: {
|
|
id: 'storybook',
|
|
name: 'Storybook',
|
|
description: 'Component documentation and playground',
|
|
icon: 'book',
|
|
category: 'documentation',
|
|
|
|
// Config schema - defines available settings
|
|
config: {
|
|
port: {
|
|
type: 'number',
|
|
label: 'Port',
|
|
default: 6006,
|
|
readonly: true, // Derived from server config
|
|
description: 'Storybook runs on this port',
|
|
},
|
|
theme: {
|
|
type: 'select',
|
|
label: 'Theme',
|
|
options: [
|
|
{ value: 'light', label: 'Light' },
|
|
{ value: 'dark', label: 'Dark' },
|
|
{ value: 'auto', label: 'Auto (System)' },
|
|
],
|
|
default: 'auto',
|
|
description: 'Storybook UI theme preference',
|
|
},
|
|
showDocs: {
|
|
type: 'boolean',
|
|
label: 'Show Docs Tab',
|
|
default: true,
|
|
description: 'Display the documentation tab in stories',
|
|
},
|
|
},
|
|
|
|
// Dynamic URL builder (uses nginx path-based routing)
|
|
getUrl() {
|
|
try {
|
|
const host = getDssHost();
|
|
const protocol = window.location.protocol;
|
|
// Admin configured path-based routing at /storybook/
|
|
return `${protocol}//${host}/storybook/`;
|
|
} catch {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
// Status check
|
|
async checkStatus() {
|
|
const url = this.getUrl();
|
|
if (!url) return { status: 'unknown', message: 'Configuration not loaded' };
|
|
|
|
try {
|
|
const response = await fetch(url, { mode: 'no-cors', cache: 'no-cache' });
|
|
return { status: 'available', message: 'Storybook is running' };
|
|
} catch {
|
|
return { status: 'unavailable', message: 'Storybook is not responding' };
|
|
}
|
|
},
|
|
},
|
|
|
|
figma: {
|
|
id: 'figma',
|
|
name: 'Figma',
|
|
description: 'Design file integration and token extraction',
|
|
icon: 'figma',
|
|
category: 'design',
|
|
|
|
config: {
|
|
apiKey: {
|
|
type: 'password',
|
|
label: 'API Token',
|
|
placeholder: 'figd_xxxxxxxxxx',
|
|
description: 'Your Figma Personal Access Token',
|
|
sensitive: true, // Never display actual value
|
|
},
|
|
fileKey: {
|
|
type: 'text',
|
|
label: 'Default File Key',
|
|
placeholder: 'Enter Figma file key',
|
|
description: 'Default Figma file to use for token extraction',
|
|
},
|
|
autoSync: {
|
|
type: 'boolean',
|
|
label: 'Auto-sync Tokens',
|
|
default: false,
|
|
description: 'Automatically sync tokens when file changes detected',
|
|
},
|
|
},
|
|
|
|
getUrl() {
|
|
return 'https://www.figma.com';
|
|
},
|
|
|
|
async checkStatus() {
|
|
// Check if API key is configured via backend
|
|
try {
|
|
const response = await fetch('/api/figma/health');
|
|
const data = await response.json();
|
|
if (data.configured) {
|
|
return { status: 'connected', message: `Connected as ${data.user || 'user'}` };
|
|
}
|
|
return { status: 'not_configured', message: 'API token not set' };
|
|
} catch {
|
|
return { status: 'error', message: 'Failed to check Figma status' };
|
|
}
|
|
},
|
|
},
|
|
|
|
// Future components can be added here
|
|
jira: {
|
|
id: 'jira',
|
|
name: 'Jira',
|
|
description: 'Issue tracking integration',
|
|
icon: 'clipboard',
|
|
category: 'project',
|
|
enabled: false, // Not yet implemented
|
|
|
|
config: {
|
|
baseUrl: {
|
|
type: 'url',
|
|
label: 'Jira URL',
|
|
placeholder: 'https://your-org.atlassian.net',
|
|
description: 'Your Jira instance URL',
|
|
},
|
|
projectKey: {
|
|
type: 'text',
|
|
label: 'Project Key',
|
|
placeholder: 'DS',
|
|
description: 'Default Jira project key',
|
|
},
|
|
},
|
|
|
|
getUrl() {
|
|
return localStorage.getItem('jira_base_url') || null;
|
|
},
|
|
|
|
async checkStatus() {
|
|
return { status: 'not_implemented', message: 'Coming soon' };
|
|
},
|
|
},
|
|
|
|
confluence: {
|
|
id: 'confluence',
|
|
name: 'Confluence',
|
|
description: 'Documentation wiki integration',
|
|
icon: 'file-text',
|
|
category: 'documentation',
|
|
enabled: false, // Not yet implemented
|
|
|
|
config: {
|
|
baseUrl: {
|
|
type: 'url',
|
|
label: 'Confluence URL',
|
|
placeholder: 'https://your-org.atlassian.net/wiki',
|
|
description: 'Your Confluence instance URL',
|
|
},
|
|
spaceKey: {
|
|
type: 'text',
|
|
label: 'Space Key',
|
|
placeholder: 'DS',
|
|
description: 'Default Confluence space key',
|
|
},
|
|
},
|
|
|
|
getUrl() {
|
|
return localStorage.getItem('confluence_base_url') || null;
|
|
},
|
|
|
|
async checkStatus() {
|
|
return { status: 'not_implemented', message: 'Coming soon' };
|
|
},
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Get all enabled components
|
|
*/
|
|
export function getEnabledComponents() {
|
|
return Object.values(componentRegistry).filter(c => c.enabled !== false);
|
|
}
|
|
|
|
/**
|
|
* Get components by category
|
|
*/
|
|
export function getComponentsByCategory(category) {
|
|
return Object.values(componentRegistry).filter(c => c.category === category && c.enabled !== false);
|
|
}
|
|
|
|
/**
|
|
* Get component by ID
|
|
*/
|
|
export function getComponent(id) {
|
|
return componentRegistry[id] || null;
|
|
}
|
|
|
|
/**
|
|
* Get component setting value
|
|
*/
|
|
export function getComponentSetting(componentId, settingKey) {
|
|
const storageKey = `dss_component_${componentId}_${settingKey}`;
|
|
const stored = localStorage.getItem(storageKey);
|
|
|
|
if (stored !== null) {
|
|
try {
|
|
return JSON.parse(stored);
|
|
} catch {
|
|
return stored;
|
|
}
|
|
}
|
|
|
|
// Return default value from schema
|
|
const component = getComponent(componentId);
|
|
if (component && component.config[settingKey]) {
|
|
const defaultValue = component.config[settingKey].default;
|
|
if (defaultValue !== undefined) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Set component setting value
|
|
*/
|
|
export function setComponentSetting(componentId, settingKey, value) {
|
|
const storageKey = `dss_component_${componentId}_${settingKey}`;
|
|
localStorage.setItem(storageKey, JSON.stringify(value));
|
|
}
|
|
|
|
/**
|
|
* Get all settings for a component
|
|
*/
|
|
export function getComponentSettings(componentId) {
|
|
const component = getComponent(componentId);
|
|
if (!component) return {};
|
|
|
|
const settings = {};
|
|
for (const key of Object.keys(component.config)) {
|
|
settings[key] = getComponentSetting(componentId, key);
|
|
}
|
|
return settings;
|
|
}
|
|
|
|
export default {
|
|
componentRegistry,
|
|
getEnabledComponents,
|
|
getComponentsByCategory,
|
|
getComponent,
|
|
getComponentSetting,
|
|
setComponentSetting,
|
|
getComponentSettings,
|
|
};
|