feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,568 @@
/**
* ComponentPlayground
*
* Interactive development tool for LiveComponents.
*
* Features:
* - Component browser with search and filtering
* - Live component preview with auto-refresh
* - JSON state editor with validation
* - Action tester with parameter support
* - Performance metrics visualization
* - Template code generator
*
* Usage:
* import { ComponentPlayground } from './modules/livecomponent/ComponentPlayground';
*
* const playground = new ComponentPlayground('#playground-container');
* playground.init();
*/
export class ComponentPlayground {
/**
* @param {string} containerSelector - Container element selector
*/
constructor(containerSelector) {
this.container = document.querySelector(containerSelector);
if (!this.container) {
throw new Error(`Container not found: ${containerSelector}`);
}
// State
this.components = [];
this.selectedComponent = null;
this.componentMetadata = null;
this.currentState = {};
this.previewInstanceId = `playground-${Date.now()}`;
// UI Elements (will be created in init())
this.componentList = null;
this.componentSearch = null;
this.stateEditor = null;
this.previewContainer = null;
this.actionTester = null;
this.metricsDisplay = null;
// Performance tracking
this.metrics = {
renderTime: 0,
stateSize: 0,
actionExecutions: 0
};
}
/**
* Initialize playground
*/
async init() {
this.buildUI();
await this.loadComponents();
this.attachEventListeners();
}
/**
* Build playground UI structure
*/
buildUI() {
this.container.innerHTML = `
<div class="playground">
<!-- Header -->
<header class="playground__header">
<h1 class="playground__title">LiveComponent Playground</h1>
<p class="playground__subtitle">Interactive development tool for testing LiveComponents</p>
</header>
<!-- Main Layout -->
<div class="playground__layout">
<!-- Sidebar: Component Selector -->
<aside class="playground__sidebar">
<div class="playground__search">
<input
type="text"
id="component-search"
class="playground__search-input"
placeholder="Search components..."
autocomplete="off"
/>
</div>
<div id="component-list" class="playground__component-list">
<div class="playground__loading">Loading components...</div>
</div>
</aside>
<!-- Main Content -->
<main class="playground__main">
<!-- State Editor -->
<section class="playground__section">
<h2 class="playground__section-title">Component State</h2>
<div class="playground__state-editor">
<textarea
id="state-editor"
class="playground__textarea"
placeholder='{\n "property": "value"\n}'
rows="10"
></textarea>
<div class="playground__editor-actions">
<button id="apply-state" class="playground__button playground__button--primary">
Apply State
</button>
<button id="reset-state" class="playground__button">
Reset
</button>
<button id="format-json" class="playground__button">
Format JSON
</button>
</div>
<div id="state-validation" class="playground__validation"></div>
</div>
</section>
<!-- Live Preview -->
<section class="playground__section">
<h2 class="playground__section-title">Live Preview</h2>
<div id="preview-container" class="playground__preview">
<div class="playground__empty">
Select a component to preview
</div>
</div>
<div id="metrics-display" class="playground__metrics"></div>
</section>
<!-- Action Tester -->
<section class="playground__section">
<h2 class="playground__section-title">Actions</h2>
<div id="action-tester" class="playground__actions">
<div class="playground__empty">
Select a component to test actions
</div>
</div>
</section>
<!-- Code Generator -->
<section class="playground__section">
<h2 class="playground__section-title">Template Code</h2>
<div class="playground__code-generator">
<pre id="generated-code" class="playground__code"><code>Select a component to generate code</code></pre>
<button id="copy-code" class="playground__button">
Copy to Clipboard
</button>
</div>
</section>
</main>
</div>
</div>
`;
// Cache UI elements
this.componentList = this.container.querySelector('#component-list');
this.componentSearch = this.container.querySelector('#component-search');
this.stateEditor = this.container.querySelector('#state-editor');
this.previewContainer = this.container.querySelector('#preview-container');
this.actionTester = this.container.querySelector('#action-tester');
this.metricsDisplay = this.container.querySelector('#metrics-display');
}
/**
* Load all available components
*/
async loadComponents() {
try {
const response = await fetch('/playground/api/components');
const data = await response.json();
this.components = data.components || [];
this.renderComponentList(this.components);
} catch (error) {
this.componentList.innerHTML = `
<div class="playground__error">
Failed to load components: ${error.message}
</div>
`;
}
}
/**
* Render component list
*/
renderComponentList(components) {
if (components.length === 0) {
this.componentList.innerHTML = '<div class="playground__empty">No components found</div>';
return;
}
this.componentList.innerHTML = components.map(component => `
<div class="playground__component-item" data-component="${component.name}">
<div class="playground__component-name">${component.name}</div>
<div class="playground__component-meta">
<span class="playground__badge">${component.properties} props</span>
<span class="playground__badge">${component.actions} actions</span>
${component.has_cache ? '<span class="playground__badge playground__badge--cache">cached</span>' : ''}
</div>
</div>
`).join('');
}
/**
* Attach event listeners
*/
attachEventListeners() {
// Component selection
this.componentList.addEventListener('click', (e) => {
const item = e.target.closest('.playground__component-item');
if (item) {
this.selectComponent(item.dataset.component);
}
});
// Component search
this.componentSearch.addEventListener('input', (e) => {
this.filterComponents(e.target.value);
});
// State editor actions
this.container.querySelector('#apply-state').addEventListener('click', () => {
this.applyState();
});
this.container.querySelector('#reset-state').addEventListener('click', () => {
this.resetState();
});
this.container.querySelector('#format-json').addEventListener('click', () => {
this.formatJSON();
});
// Copy code
this.container.querySelector('#copy-code').addEventListener('click', () => {
this.copyCode();
});
}
/**
* Filter components by search term
*/
filterComponents(searchTerm) {
const term = searchTerm.toLowerCase().trim();
if (!term) {
this.renderComponentList(this.components);
return;
}
const filtered = this.components.filter(component =>
component.name.toLowerCase().includes(term) ||
component.class.toLowerCase().includes(term)
);
this.renderComponentList(filtered);
}
/**
* Select component
*/
async selectComponent(componentName) {
// Update active state
this.componentList.querySelectorAll('.playground__component-item').forEach(item => {
item.classList.remove('playground__component-item--active');
});
const selectedItem = this.componentList.querySelector(`[data-component="${componentName}"]`);
if (selectedItem) {
selectedItem.classList.add('playground__component-item--active');
}
this.selectedComponent = componentName;
// Load metadata
await this.loadComponentMetadata(componentName);
// Reset state
this.resetState();
// Render actions
this.renderActions();
// Generate code
this.updateGeneratedCode();
}
/**
* Load component metadata
*/
async loadComponentMetadata(componentName) {
try {
const response = await fetch(`/playground/api/component/${componentName}`);
const data = await response.json();
if (data.success) {
this.componentMetadata = data.data;
} else {
console.error('Failed to load metadata:', data.error);
}
} catch (error) {
console.error('Error loading metadata:', error);
}
}
/**
* Apply state from editor
*/
async applyState() {
const jsonText = this.stateEditor.value.trim();
const validationEl = this.container.querySelector('#state-validation');
// Validate JSON
try {
const state = jsonText ? JSON.parse(jsonText) : {};
this.currentState = state;
validationEl.innerHTML = '<span class="playground__success">✓ Valid JSON</span>';
// Preview component with new state
await this.previewComponent();
} catch (error) {
validationEl.innerHTML = `<span class="playground__error">✗ Invalid JSON: ${error.message}</span>`;
}
}
/**
* Reset state to default
*/
resetState() {
this.currentState = {};
this.stateEditor.value = '{}';
this.container.querySelector('#state-validation').innerHTML = '';
if (this.selectedComponent) {
this.previewComponent();
}
}
/**
* Format JSON in editor
*/
formatJSON() {
try {
const json = JSON.parse(this.stateEditor.value || '{}');
this.stateEditor.value = JSON.stringify(json, null, 2);
this.container.querySelector('#state-validation').innerHTML = '<span class="playground__success">✓ Formatted</span>';
} catch (error) {
this.container.querySelector('#state-validation').innerHTML = `<span class="playground__error">✗ Invalid JSON</span>`;
}
}
/**
* Preview component with current state
*/
async previewComponent() {
if (!this.selectedComponent) return;
this.previewContainer.innerHTML = '<div class="playground__loading">Loading preview...</div>';
try {
const startTime = performance.now();
const response = await fetch('/playground/api/preview', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
component_name: this.selectedComponent,
state: this.currentState,
instance_id: this.previewInstanceId
})
});
const data = await response.json();
const endTime = performance.now();
if (data.success) {
this.previewContainer.innerHTML = data.html;
// Update metrics
this.metrics.renderTime = data.render_time_ms;
this.metrics.stateSize = JSON.stringify(data.state).length;
this.updateMetrics();
// Initialize LiveComponent in preview
if (window.LiveComponent) {
window.LiveComponent.initComponent(this.previewContainer.firstElementChild);
}
} else {
this.previewContainer.innerHTML = `
<div class="playground__error">
Preview failed: ${data.error}
</div>
`;
}
} catch (error) {
this.previewContainer.innerHTML = `
<div class="playground__error">
Error: ${error.message}
</div>
`;
}
}
/**
* Render actions for selected component
*/
renderActions() {
if (!this.componentMetadata || !this.componentMetadata.actions) {
this.actionTester.innerHTML = '<div class="playground__empty">No actions available</div>';
return;
}
const actions = this.componentMetadata.actions.filter(action =>
!['onMount', 'onUpdated', 'onDestroy'].includes(action.name)
);
if (actions.length === 0) {
this.actionTester.innerHTML = '<div class="playground__empty">No actions available</div>';
return;
}
this.actionTester.innerHTML = actions.map(action => `
<div class="playground__action">
<button
class="playground__button playground__button--action"
data-action="${action.name}"
>
${action.name}()
</button>
${action.parameters.length > 0 ? `
<div class="playground__action-params">
${action.parameters.map(param => `
<label>
${param.name} (${param.type}):
<input type="text" data-param="${param.name}" placeholder="${param.type}" />
</label>
`).join('')}
</div>
` : ''}
</div>
`).join('');
// Attach action button listeners
this.actionTester.querySelectorAll('[data-action]').forEach(button => {
button.addEventListener('click', () => {
this.executeAction(button.dataset.action, button.closest('.playground__action'));
});
});
}
/**
* Execute component action
*/
async executeAction(actionName, actionElement) {
// Get parameters
const parameters = {};
if (actionElement) {
actionElement.querySelectorAll('[data-param]').forEach(input => {
const paramName = input.dataset.param;
let value = input.value;
// Try to parse as number or boolean
if (value === 'true') value = true;
else if (value === 'false') value = false;
else if (!isNaN(value) && value !== '') value = Number(value);
parameters[paramName] = value;
});
}
try {
const response = await fetch('/playground/api/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
component_id: `${this.selectedComponent}:${this.previewInstanceId}`,
action_name: actionName,
parameters: parameters,
current_state: this.currentState
})
});
const data = await response.json();
if (data.success) {
// Update current state
this.currentState = data.new_state;
this.stateEditor.value = JSON.stringify(this.currentState, null, 2);
// Update preview
this.previewContainer.innerHTML = data.html;
// Update metrics
this.metrics.actionExecutions++;
this.updateMetrics();
// Re-initialize LiveComponent
if (window.LiveComponent) {
window.LiveComponent.initComponent(this.previewContainer.firstElementChild);
}
} else {
alert(`Action failed: ${data.error}`);
}
} catch (error) {
alert(`Error executing action: ${error.message}`);
}
}
/**
* Update metrics display
*/
updateMetrics() {
this.metricsDisplay.innerHTML = `
<div class="playground__metrics-grid">
<div class="playground__metric">
<span class="playground__metric-label">Render Time</span>
<span class="playground__metric-value">${this.metrics.renderTime.toFixed(2)}ms</span>
</div>
<div class="playground__metric">
<span class="playground__metric-label">State Size</span>
<span class="playground__metric-value">${this.metrics.stateSize} bytes</span>
</div>
<div class="playground__metric">
<span class="playground__metric-label">Actions Executed</span>
<span class="playground__metric-value">${this.metrics.actionExecutions}</span>
</div>
</div>
`;
}
/**
* Update generated template code
*/
updateGeneratedCode() {
if (!this.selectedComponent) return;
const code = `<!-- Use in your template -->\n{{{ ${this.selectedComponent} }}}`;
this.container.querySelector('#generated-code code').textContent = code;
}
/**
* Copy generated code to clipboard
*/
async copyCode() {
const code = this.container.querySelector('#generated-code code').textContent;
try {
await navigator.clipboard.writeText(code);
const button = this.container.querySelector('#copy-code');
const originalText = button.textContent;
button.textContent = '✓ Copied!';
setTimeout(() => {
button.textContent = originalText;
}, 2000);
} catch (error) {
alert('Failed to copy code to clipboard');
}
}
}
export default ComponentPlayground;