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,429 @@
/**
* Nested Component Handler
*
* Manages parent-child relationships for nested LiveComponents on the client-side.
* Coordinates event bubbling, state synchronization, and lifecycle management.
*
* Features:
* - Parent-child relationship tracking
* - Event bubbling from child to parent
* - Child lifecycle coordination
* - State synchronization between parent and children
* - Automatic cleanup on component destruction
*
* Architecture:
* - Scans DOM for nested components (data-parent-component attribute)
* - Registers hierarchies with LiveComponentManager
* - Intercepts events to enable bubbling
* - Coordinates updates between parents and children
*/
export class NestedComponentHandler {
constructor(liveComponentManager) {
this.liveComponentManager = liveComponentManager;
// Registry: componentId → { parentId, childIds, depth }
this.hierarchyRegistry = new Map();
// Parent → [Children] mapping
this.childrenRegistry = new Map();
// Event bubbling callbacks: componentId → [callbacks]
this.bubbleCallbacks = new Map();
}
/**
* Initialize nested component system
* Scans DOM for nested components and registers hierarchies
*/
init() {
this.scanNestedComponents();
console.log(`[NestedComponents] Initialized with ${this.hierarchyRegistry.size} components`);
}
/**
* Scan DOM for nested components
* Looks for data-parent-component attribute to establish parent-child relationships
*/
scanNestedComponents() {
// Find all components with parent
const nestedComponents = document.querySelectorAll('[data-parent-component]');
nestedComponents.forEach(element => {
const componentId = element.dataset.liveComponent;
const parentId = element.dataset.parentComponent;
const depth = parseInt(element.dataset.nestingDepth) || 1;
if (componentId && parentId) {
this.registerHierarchy(componentId, parentId, depth);
}
});
// Register root components (no parent)
const rootComponents = document.querySelectorAll('[data-live-component]:not([data-parent-component])');
rootComponents.forEach(element => {
const componentId = element.dataset.liveComponent;
if (componentId && !this.hierarchyRegistry.has(componentId)) {
this.registerRoot(componentId);
}
});
}
/**
* Register root component (no parent)
*
* @param {string} componentId - Component ID
*/
registerRoot(componentId) {
this.hierarchyRegistry.set(componentId, {
parentId: null,
childIds: [],
depth: 0,
path: [componentId]
});
console.log(`[NestedComponents] Registered root: ${componentId}`);
}
/**
* Register component hierarchy
*
* @param {string} componentId - Child component ID
* @param {string} parentId - Parent component ID
* @param {number} depth - Nesting depth
*/
registerHierarchy(componentId, parentId, depth = 1) {
// Get parent's path
const parentHierarchy = this.hierarchyRegistry.get(parentId);
const parentPath = parentHierarchy ? parentHierarchy.path : [parentId];
// Create hierarchy entry
this.hierarchyRegistry.set(componentId, {
parentId,
childIds: [],
depth,
path: [...parentPath, componentId]
});
// Add to parent's children
if (!this.childrenRegistry.has(parentId)) {
this.childrenRegistry.set(parentId, []);
}
const children = this.childrenRegistry.get(parentId);
if (!children.includes(componentId)) {
children.push(componentId);
}
console.log(`[NestedComponents] Registered child: ${componentId} (parent: ${parentId}, depth: ${depth})`);
}
/**
* Register dynamic nested component at runtime
*
* @param {string} componentId - Component ID
* @param {string} parentId - Parent component ID
*/
registerDynamicChild(componentId, parentId) {
const parentHierarchy = this.hierarchyRegistry.get(parentId);
if (!parentHierarchy) {
console.warn(`[NestedComponents] Cannot register child - parent not found: ${parentId}`);
return;
}
const depth = parentHierarchy.depth + 1;
this.registerHierarchy(componentId, parentId, depth);
}
/**
* Get component hierarchy
*
* @param {string} componentId - Component ID
* @returns {Object|null} Hierarchy object or null
*/
getHierarchy(componentId) {
return this.hierarchyRegistry.get(componentId) || null;
}
/**
* Get parent component ID
*
* @param {string} componentId - Component ID
* @returns {string|null} Parent ID or null if root
*/
getParentId(componentId) {
const hierarchy = this.getHierarchy(componentId);
return hierarchy ? hierarchy.parentId : null;
}
/**
* Get child component IDs
*
* @param {string} componentId - Parent component ID
* @returns {Array<string>} Array of child IDs
*/
getChildIds(componentId) {
return this.childrenRegistry.get(componentId) || [];
}
/**
* Check if component has children
*
* @param {string} componentId - Component ID
* @returns {boolean} True if has children
*/
hasChildren(componentId) {
const children = this.getChildIds(componentId);
return children.length > 0;
}
/**
* Check if component is root
*
* @param {string} componentId - Component ID
* @returns {boolean} True if root component
*/
isRoot(componentId) {
const hierarchy = this.getHierarchy(componentId);
return hierarchy ? hierarchy.parentId === null : true;
}
/**
* Get nesting depth
*
* @param {string} componentId - Component ID
* @returns {number} Nesting depth (0 for root)
*/
getDepth(componentId) {
const hierarchy = this.getHierarchy(componentId);
return hierarchy ? hierarchy.depth : 0;
}
/**
* Get all ancestors (parent, grandparent, etc.)
*
* @param {string} componentId - Component ID
* @returns {Array<string>} Array of ancestor IDs (parent first, root last)
*/
getAncestors(componentId) {
const hierarchy = this.getHierarchy(componentId);
if (!hierarchy || !hierarchy.path) {
return [];
}
// Path includes current component, remove it
const ancestors = [...hierarchy.path];
ancestors.pop();
// Return in reverse order (parent first, root last)
return ancestors.reverse();
}
/**
* Bubble event up through component hierarchy
*
* Dispatches custom event to each ancestor until stopped or root reached.
*
* @param {string} sourceId - Component that dispatched the event
* @param {string} eventName - Event name
* @param {Object} payload - Event payload
* @returns {boolean} True if bubbled to root, false if stopped
*/
bubbleEvent(sourceId, eventName, payload) {
console.log(`[NestedComponents] Bubbling event: ${eventName} from ${sourceId}`, payload);
let currentId = sourceId;
let bubbled = 0;
while (true) {
const parentId = this.getParentId(currentId);
// Reached root
if (parentId === null) {
console.log(`[NestedComponents] Event bubbled to root (${bubbled} levels)`);
return true;
}
// Get parent element
const parentElement = document.querySelector(`[data-live-component="${parentId}"]`);
if (!parentElement) {
console.warn(`[NestedComponents] Parent element not found: ${parentId}`);
return false;
}
// Dispatch custom event to parent
const bubbleEvent = new CustomEvent(`livecomponent:child:${eventName}`, {
detail: {
sourceId,
eventName,
payload,
currentLevel: bubbled
},
bubbles: false, // We handle bubbling manually
cancelable: true
});
const dispatched = parentElement.dispatchEvent(bubbleEvent);
// Event was cancelled - stop bubbling
if (!dispatched) {
console.log(`[NestedComponents] Event bubbling stopped at ${parentId}`);
return false;
}
// Check for registered callbacks
const callbacks = this.bubbleCallbacks.get(parentId);
if (callbacks) {
for (const callback of callbacks) {
const shouldContinue = callback(sourceId, eventName, payload);
if (shouldContinue === false) {
console.log(`[NestedComponents] Event bubbling stopped by callback at ${parentId}`);
return false;
}
}
}
// Move to next level
currentId = parentId;
bubbled++;
}
}
/**
* Register callback for child events
*
* @param {string} parentId - Parent component ID
* @param {Function} callback - Callback function (sourceId, eventName, payload) => boolean
*/
onChildEvent(parentId, callback) {
if (!this.bubbleCallbacks.has(parentId)) {
this.bubbleCallbacks.set(parentId, []);
}
this.bubbleCallbacks.get(parentId).push(callback);
console.log(`[NestedComponents] Registered child event callback for ${parentId}`);
}
/**
* Sync state from parent to children
*
* Useful for broadcasting shared state to all children.
*
* @param {string} parentId - Parent component ID
* @param {Object} sharedState - State to share with children
*/
syncStateToChildren(parentId, sharedState) {
const childIds = this.getChildIds(parentId);
console.log(`[NestedComponents] Syncing state to ${childIds.length} children of ${parentId}`);
childIds.forEach(childId => {
const childElement = document.querySelector(`[data-live-component="${childId}"]`);
if (!childElement) return;
// Dispatch state sync event
childElement.dispatchEvent(new CustomEvent('livecomponent:parent:state-sync', {
detail: { parentId, sharedState }
}));
// Update child state if applicable
// Child components can listen to this event and update accordingly
});
}
/**
* Update all children when parent changes
*
* @param {string} parentId - Parent component ID
* @param {Object} updates - Updates to apply to children
*/
async updateChildren(parentId, updates) {
const childIds = this.getChildIds(parentId);
console.log(`[NestedComponents] Updating ${childIds.length} children of ${parentId}`);
// Update children in parallel for performance
const updatePromises = childIds.map(async childId => {
const childElement = document.querySelector(`[data-live-component="${childId}"]`);
if (!childElement) return;
// Trigger child component update via LiveComponent action
// This depends on how your components handle updates
// For now, just dispatch an event
childElement.dispatchEvent(new CustomEvent('livecomponent:parent:update', {
detail: { parentId, updates }
}));
});
await Promise.all(updatePromises);
}
/**
* Unregister component and cleanup
*
* @param {string} componentId - Component to unregister
*/
unregister(componentId) {
// Remove from hierarchy registry
const hierarchy = this.hierarchyRegistry.get(componentId);
this.hierarchyRegistry.delete(componentId);
// Remove from parent's children
if (hierarchy && hierarchy.parentId) {
const siblings = this.childrenRegistry.get(hierarchy.parentId);
if (siblings) {
const index = siblings.indexOf(componentId);
if (index !== -1) {
siblings.splice(index, 1);
}
}
}
// Remove children registry
this.childrenRegistry.delete(componentId);
// Remove bubble callbacks
this.bubbleCallbacks.delete(componentId);
console.log(`[NestedComponents] Unregistered: ${componentId}`);
}
/**
* Get hierarchy statistics
*
* @returns {Object} Statistics
*/
getStats() {
let rootCount = 0;
let maxDepth = 0;
this.hierarchyRegistry.forEach(hierarchy => {
if (hierarchy.parentId === null) {
rootCount++;
}
maxDepth = Math.max(maxDepth, hierarchy.depth);
});
return {
total_components: this.hierarchyRegistry.size,
root_components: rootCount,
child_components: this.hierarchyRegistry.size - rootCount,
max_nesting_depth: maxDepth,
parents_with_children: this.childrenRegistry.size
};
}
/**
* Destroy nested component handler
*/
destroy() {
this.hierarchyRegistry.clear();
this.childrenRegistry.clear();
this.bubbleCallbacks.clear();
console.log('[NestedComponents] Destroyed');
}
}
export default NestedComponentHandler;