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,288 @@
/**
* Lightweight DOM Patcher for LiveComponent Fragments
*
* Efficiently patches specific DOM fragments without full re-render.
* Optimized for LiveComponent use case with minimal overhead.
*
* Features:
* - Smart element matching by tag and data-lc-fragment attribute
* - Attribute diffing and patching
* - Text content updates
* - Child node reconciliation
* - Preserves focus and scroll position where possible
*
* Philosophy:
* - Keep it simple and focused
* - No external dependencies
* - Framework-compliant modern JavaScript
*/
export class DomPatcher {
/**
* Patch a specific fragment within a container
*
* @param {HTMLElement} container - Container element to search in
* @param {string} fragmentName - Fragment name (data-lc-fragment value)
* @param {string} newHtml - New HTML for the fragment
* @returns {boolean} - True if fragment was patched, false if not found
*/
patchFragment(container, fragmentName, newHtml) {
// Find existing fragment element
const existingElement = container.querySelector(`[data-lc-fragment="${fragmentName}"]`);
if (!existingElement) {
console.warn(`[DomPatcher] Fragment not found: ${fragmentName}`);
return false;
}
// Parse new HTML into element
const temp = document.createElement('div');
temp.innerHTML = newHtml;
const newElement = temp.firstElementChild;
if (!newElement) {
console.warn(`[DomPatcher] Invalid HTML for fragment: ${fragmentName}`);
return false;
}
// Verify fragment name matches
if (newElement.getAttribute('data-lc-fragment') !== fragmentName) {
console.warn(`[DomPatcher] Fragment name mismatch: expected ${fragmentName}, got ${newElement.getAttribute('data-lc-fragment')}`);
return false;
}
// Patch the element in place
this.patchElement(existingElement, newElement);
return true;
}
/**
* Patch multiple fragments at once
*
* @param {HTMLElement} container - Container element
* @param {Object.<string, string>} fragments - Map of fragment names to HTML
* @returns {Object.<string, boolean>} - Map of fragment names to success status
*/
patchFragments(container, fragments) {
const results = {};
for (const [fragmentName, html] of Object.entries(fragments)) {
results[fragmentName] = this.patchFragment(container, fragmentName, html);
}
return results;
}
/**
* Patch an element with new content
*
* Core patching logic that efficiently updates only what changed.
*
* @param {HTMLElement} oldElement - Existing element to patch
* @param {HTMLElement} newElement - New element with updated content
*/
patchElement(oldElement, newElement) {
// 1. Patch attributes
this.patchAttributes(oldElement, newElement);
// 2. Patch child nodes
this.patchChildren(oldElement, newElement);
}
/**
* Patch element attributes
*
* Only updates attributes that actually changed.
*
* @param {HTMLElement} oldElement - Existing element
* @param {HTMLElement} newElement - New element
*/
patchAttributes(oldElement, newElement) {
// Get all attributes from both elements
const oldAttrs = new Map();
const newAttrs = new Map();
for (const attr of oldElement.attributes) {
oldAttrs.set(attr.name, attr.value);
}
for (const attr of newElement.attributes) {
newAttrs.set(attr.name, attr.value);
}
// Remove attributes that no longer exist
for (const [name, value] of oldAttrs) {
if (!newAttrs.has(name)) {
oldElement.removeAttribute(name);
}
}
// Add or update attributes
for (const [name, value] of newAttrs) {
if (oldAttrs.get(name) !== value) {
oldElement.setAttribute(name, value);
}
}
}
/**
* Patch child nodes
*
* Reconciles child nodes between old and new elements.
* Uses simple key-based matching for efficiency.
*
* @param {HTMLElement} oldElement - Existing element
* @param {HTMLElement} newElement - New element
*/
patchChildren(oldElement, newElement) {
const oldChildren = Array.from(oldElement.childNodes);
const newChildren = Array.from(newElement.childNodes);
const maxLength = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < maxLength; i++) {
const oldChild = oldChildren[i];
const newChild = newChildren[i];
if (!oldChild && newChild) {
// New child added - append it
oldElement.appendChild(newChild.cloneNode(true));
} else if (oldChild && !newChild) {
// Child removed - remove it
oldElement.removeChild(oldChild);
} else if (oldChild && newChild) {
// Both exist - patch or replace
if (this.shouldPatch(oldChild, newChild)) {
if (oldChild.nodeType === Node.ELEMENT_NODE) {
this.patchElement(oldChild, newChild);
} else if (oldChild.nodeType === Node.TEXT_NODE) {
if (oldChild.nodeValue !== newChild.nodeValue) {
oldChild.nodeValue = newChild.nodeValue;
}
}
} else {
// Different node types or tags - replace
oldElement.replaceChild(newChild.cloneNode(true), oldChild);
}
}
}
}
/**
* Determine if two nodes should be patched or replaced
*
* Nodes should be patched if they are compatible (same type and tag).
*
* @param {Node} oldNode - Existing node
* @param {Node} newNode - New node
* @returns {boolean} - True if nodes should be patched
*/
shouldPatch(oldNode, newNode) {
// Different node types - replace
if (oldNode.nodeType !== newNode.nodeType) {
return false;
}
// Text nodes can always be patched
if (oldNode.nodeType === Node.TEXT_NODE) {
return true;
}
// Element nodes - check if same tag
if (oldNode.nodeType === Node.ELEMENT_NODE) {
if (oldNode.tagName !== newNode.tagName) {
return false;
}
// Check for special keys that indicate identity
const oldKey = oldNode.getAttribute('data-lc-key') || oldNode.getAttribute('id');
const newKey = newNode.getAttribute('data-lc-key') || newNode.getAttribute('id');
// If both have keys, they must match
if (oldKey && newKey) {
return oldKey === newKey;
}
// Otherwise, assume they match (same tag is enough)
return true;
}
// Other node types - replace
return false;
}
/**
* Preserve focus state before patching
*
* Returns a function to restore focus after patching.
*
* @param {HTMLElement} container - Container being patched
* @returns {Function} - Restore function
*/
preserveFocus(container) {
const activeElement = document.activeElement;
// Check if focused element is within container
if (!activeElement || !container.contains(activeElement)) {
return () => {}; // No-op restore
}
// Get selector for focused element
const selector = this.getElementSelector(activeElement);
const selectionStart = activeElement.selectionStart;
const selectionEnd = activeElement.selectionEnd;
// Return restore function
return () => {
try {
if (selector) {
const element = container.querySelector(selector);
if (element && element.focus) {
element.focus();
// Restore selection for input/textarea
if (element.setSelectionRange &&
typeof selectionStart === 'number' &&
typeof selectionEnd === 'number') {
element.setSelectionRange(selectionStart, selectionEnd);
}
}
}
} catch (e) {
// Focus restoration failed - not critical
console.debug('[DomPatcher] Could not restore focus:', e);
}
};
}
/**
* Get a selector for an element
*
* Tries to create a unique selector using ID, name, or data attributes.
*
* @param {HTMLElement} element - Element to get selector for
* @returns {string|null} - CSS selector or null
*/
getElementSelector(element) {
if (element.id) {
return `#${element.id}`;
}
if (element.name) {
return `[name="${element.name}"]`;
}
const lcKey = element.getAttribute('data-lc-key');
if (lcKey) {
return `[data-lc-key="${lcKey}"]`;
}
// Fallback - not guaranteed to be unique
return element.tagName.toLowerCase();
}
}
// Create singleton instance
export const domPatcher = new DomPatcher();
export default domPatcher;