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
260 lines
8.1 KiB
JavaScript
260 lines
8.1 KiB
JavaScript
/**
|
|
* ds-admin-settings.js
|
|
* Admin settings panel for DSS configuration
|
|
* Allows configuration of hostname, port, and local/remote setup
|
|
*/
|
|
|
|
import { useAdminStore } from '../../stores/admin-store.js';
|
|
|
|
export default class AdminSettings extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.adminStore = useAdminStore();
|
|
this.state = this.adminStore.getState();
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.render();
|
|
this.setupEventListeners();
|
|
this.unsubscribe = this.adminStore.subscribe(() => {
|
|
this.state = this.adminStore.getState();
|
|
this.updateUI();
|
|
});
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
if (this.unsubscribe) this.unsubscribe();
|
|
}
|
|
|
|
render() {
|
|
this.innerHTML = `
|
|
<div style="padding: 24px; max-width: 600px;">
|
|
<h2 style="margin-bottom: 24px; font-size: 20px;">DSS Settings</h2>
|
|
|
|
<!-- Hostname Setting -->
|
|
<div style="margin-bottom: 24px;">
|
|
<label style="display: block; margin-bottom: 8px; font-weight: 500;">
|
|
Hostname
|
|
</label>
|
|
<input
|
|
id="hostname-input"
|
|
type="text"
|
|
value="${this.state.hostname}"
|
|
style="
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border: 1px solid var(--vscode-input-border);
|
|
background: var(--vscode-input-background);
|
|
color: var(--vscode-foreground);
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
"
|
|
placeholder="localhost or IP address"
|
|
/>
|
|
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-top: 4px;">
|
|
Default: localhost
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Port Setting -->
|
|
<div style="margin-bottom: 24px;">
|
|
<label style="display: block; margin-bottom: 8px; font-weight: 500;">
|
|
Storybook Port
|
|
</label>
|
|
<input
|
|
id="port-input"
|
|
type="number"
|
|
value="${this.state.port}"
|
|
style="
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border: 1px solid var(--vscode-input-border);
|
|
background: var(--vscode-input-background);
|
|
color: var(--vscode-foreground);
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
"
|
|
placeholder="6006"
|
|
min="1"
|
|
max="65535"
|
|
/>
|
|
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-top: 4px;">
|
|
Default: 6006 (Storybook standard port)
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DSS Setup Type -->
|
|
<div style="margin-bottom: 24px;">
|
|
<label style="display: block; margin-bottom: 12px; font-weight: 500;">
|
|
DSS Setup Type
|
|
</label>
|
|
<div style="display: flex; gap: 16px;">
|
|
<label style="display: flex; align-items: center; cursor: pointer;">
|
|
<input
|
|
type="radio"
|
|
name="setup-type"
|
|
value="local"
|
|
${this.state.isRemote ? '' : 'checked'}
|
|
style="margin-right: 8px;"
|
|
/>
|
|
<span>Local</span>
|
|
</label>
|
|
<label style="display: flex; align-items: center; cursor: pointer;">
|
|
<input
|
|
type="radio"
|
|
name="setup-type"
|
|
value="remote"
|
|
${this.state.isRemote ? 'checked' : ''}
|
|
style="margin-right: 8px;"
|
|
/>
|
|
<span>Remote (Headless)</span>
|
|
</label>
|
|
</div>
|
|
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-top: 8px;">
|
|
<strong>Local:</strong> Uses browser devtools and local services<br/>
|
|
<strong>Remote:</strong> Uses headless tools and MCP providers
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Current Configuration Display -->
|
|
<div style="
|
|
background: var(--vscode-sidebar);
|
|
border: 1px solid var(--vscode-border);
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
margin-bottom: 24px;
|
|
">
|
|
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 8px;">CURRENT STORYBOOK URL:</div>
|
|
<div style="
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
word-break: break-all;
|
|
color: var(--vscode-foreground);
|
|
" id="storybook-url-display">
|
|
${this.getStorybookUrlDisplay()}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div style="display: flex; gap: 8px;">
|
|
<button
|
|
id="save-btn"
|
|
style="
|
|
padding: 8px 16px;
|
|
background: var(--vscode-button-background);
|
|
color: var(--vscode-button-foreground);
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
"
|
|
>
|
|
Save Settings
|
|
</button>
|
|
<button
|
|
id="reset-btn"
|
|
style="
|
|
padding: 8px 16px;
|
|
background: var(--vscode-button-secondaryBackground);
|
|
color: var(--vscode-button-secondaryForeground);
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
"
|
|
>
|
|
Reset to Defaults
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
setupEventListeners() {
|
|
const hostnameInput = this.querySelector('#hostname-input');
|
|
const portInput = this.querySelector('#port-input');
|
|
const setupTypeRadios = this.querySelectorAll('input[name="setup-type"]');
|
|
const saveBtn = this.querySelector('#save-btn');
|
|
const resetBtn = this.querySelector('#reset-btn');
|
|
|
|
// Update on input (but don't save immediately)
|
|
hostnameInput.addEventListener('change', () => {
|
|
this.adminStore.setHostname(hostnameInput.value);
|
|
});
|
|
|
|
portInput.addEventListener('change', () => {
|
|
const port = parseInt(portInput.value);
|
|
if (port > 0 && port <= 65535) {
|
|
this.adminStore.setPort(port);
|
|
}
|
|
});
|
|
|
|
setupTypeRadios.forEach(radio => {
|
|
radio.addEventListener('change', (e) => {
|
|
this.adminStore.setRemote(e.target.value === 'remote');
|
|
});
|
|
});
|
|
|
|
saveBtn.addEventListener('click', () => {
|
|
this.showNotification('Settings saved successfully!');
|
|
console.log('[AdminSettings] Settings saved:', this.adminStore.getState());
|
|
});
|
|
|
|
resetBtn.addEventListener('click', () => {
|
|
if (confirm('Reset all settings to defaults?')) {
|
|
this.adminStore.reset();
|
|
this.render();
|
|
this.setupEventListeners();
|
|
this.showNotification('Settings reset to defaults');
|
|
}
|
|
});
|
|
}
|
|
|
|
updateUI() {
|
|
const hostnameInput = this.querySelector('#hostname-input');
|
|
const portInput = this.querySelector('#port-input');
|
|
const setupTypeRadios = this.querySelectorAll('input[name="setup-type"]');
|
|
const urlDisplay = this.querySelector('#storybook-url-display');
|
|
|
|
if (hostnameInput) hostnameInput.value = this.state.hostname;
|
|
if (portInput) portInput.value = this.state.port;
|
|
|
|
setupTypeRadios.forEach(radio => {
|
|
radio.checked = (radio.value === 'remote') === this.state.isRemote;
|
|
});
|
|
|
|
if (urlDisplay) {
|
|
urlDisplay.textContent = this.getStorybookUrlDisplay();
|
|
}
|
|
}
|
|
|
|
getStorybookUrlDisplay() {
|
|
return this.adminStore.getStorybookUrl('default');
|
|
}
|
|
|
|
showNotification(message) {
|
|
const notification = document.createElement('div');
|
|
notification.textContent = message;
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
background: var(--vscode-notifications-background);
|
|
color: var(--vscode-foreground);
|
|
padding: 12px 16px;
|
|
border-radius: 4px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
z-index: 1000;
|
|
animation: slideIn 0.3s ease;
|
|
`;
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.style.animation = 'slideOut 0.3s ease';
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
}
|
|
|
|
customElements.define('ds-admin-settings', AdminSettings);
|