// UnitForge Templates JavaScript // Handles the templates browser functionality class TemplatesBrowser { constructor() { this.templates = []; this.filteredTemplates = []; this.currentCategory = "all"; this.currentTemplate = null; this.init(); } init() { this.setupEventListeners(); this.loadTemplates(); } setupEventListeners() { // Search input const searchInput = document.getElementById("searchInput"); if (searchInput) { searchInput.addEventListener( "keyup", this.debounce(this.filterTemplates.bind(this), 300), ); } // Template form changes document.addEventListener("change", (e) => { if ( e.target.matches( "#templateForm input, #templateForm select, #templateForm textarea", ) ) { this.updatePreview(); } }); document.addEventListener("input", (e) => { if (e.target.matches("#templateForm input, #templateForm textarea")) { this.updatePreview(); } }); } async loadTemplates() { const loadingState = document.getElementById("loadingState"); const templatesGrid = document.getElementById("templatesGrid"); try { // Temporarily use static JSON file for testing const response = await fetch("/static/templates.json"); this.templates = await response.json(); this.filteredTemplates = [...this.templates]; if (loadingState) loadingState.classList.add("d-none"); if (templatesGrid) templatesGrid.classList.remove("d-none"); this.renderTemplates(); this.updateCategoryCounts(); } catch (error) { if (loadingState) { loadingState.innerHTML = `

Failed to load templates

${error.message}

`; } unitforge.showToast("Failed to load templates", "error"); } } renderTemplates() { const grid = document.getElementById("templatesGrid"); const noResults = document.getElementById("noResults"); if (!grid) return; if (this.filteredTemplates.length === 0) { grid.classList.add("d-none"); if (noResults) noResults.classList.remove("d-none"); return; } if (noResults) noResults.classList.add("d-none"); grid.classList.remove("d-none"); grid.innerHTML = this.filteredTemplates .map((template) => this.createTemplateCard(template)) .join(""); } createTemplateCard(template) { const tags = template.tags .map((tag) => `${this.escapeHtml(tag)}`) .join(""); const requiredParams = template.parameters.filter((p) => p.required).length; const totalParams = template.parameters.length; return `
${this.escapeHtml(template.name)}
${template.unit_type}
${template.category}

${this.escapeHtml(template.description)}

${requiredParams}/${totalParams} parameters
${tags ? `
${tags}
` : ""}
`; } getUnitTypeIcon(unitType) { const icons = { service: "play-circle", timer: "clock", socket: "plug", mount: "hdd", target: "bullseye", path: "folder", }; return icons[unitType] || "file"; } updateCategoryCounts() { const categories = { all: this.templates.length, "Web Services": 0, "Database Services": 0, "System Maintenance": 0, "Container Services": 0, "Network Services": 0, }; this.templates.forEach((template) => { if (categories.hasOwnProperty(template.category)) { categories[template.category]++; } }); // Update count badges const categoryMappings = { all: "count-all", "Web Services": "count-web", "Database Services": "count-database", "System Maintenance": "count-maintenance", "Container Services": "count-container", "Network Services": "count-network", }; Object.keys(categories).forEach((category) => { const elementId = categoryMappings[category]; if (elementId) { const countElement = document.getElementById(elementId); if (countElement) { countElement.textContent = categories[category]; } } }); } filterTemplates() { const searchTerm = document .getElementById("searchInput") .value.toLowerCase(); this.filteredTemplates = this.templates.filter((template) => { const matchesSearch = !searchTerm || template.name.toLowerCase().includes(searchTerm) || template.description.toLowerCase().includes(searchTerm) || template.tags.some((tag) => tag.toLowerCase().includes(searchTerm)); const matchesCategory = this.currentCategory === "all" || template.category === this.currentCategory; return matchesSearch && matchesCategory; }); this.renderTemplates(); } filterByCategory(category) { this.currentCategory = category; // Update active tab document.querySelectorAll("#categoryTabs .nav-link").forEach((tab) => { tab.classList.remove("active"); }); const tabMappings = { all: "tab-all", "Web Services": "tab-web", "Database Services": "tab-database", "System Maintenance": "tab-maintenance", "Container Services": "tab-container", "Network Services": "tab-network", }; const activeTab = document.getElementById(tabMappings[category]); if (activeTab) { activeTab.classList.add("active"); } this.filterTemplates(); } clearSearch() { const searchInput = document.getElementById("searchInput"); if (searchInput) { searchInput.value = ""; } this.currentCategory = "all"; // Reset active tab document.querySelectorAll("#categoryTabs .nav-link").forEach((tab) => { tab.classList.remove("active"); }); document.getElementById("tab-all").classList.add("active"); this.filterTemplates(); } async openTemplate(templateName) { try { this.currentTemplate = await unitforge.getTemplate(templateName); this.showTemplateModal(); } catch (error) { unitforge.showToast(`Failed to load template: ${error.message}`, "error"); } } showTemplateModal() { if (!this.currentTemplate) return; const modal = document.getElementById("templateModal"); const title = document.getElementById("templateModalTitle"); const info = document.getElementById("templateInfo"); const parametersDiv = document.getElementById("templateParameters"); const previewFilename = document.getElementById("previewFilename"); // Update modal title if (title) { title.innerHTML = ` ${this.escapeHtml(this.currentTemplate.name)} `; } // Update template info if (info) { info.innerHTML = `
Name:
${this.escapeHtml(this.currentTemplate.name)}
Type:
${this.currentTemplate.unit_type}
Category:
${this.escapeHtml(this.currentTemplate.category)}
Description:
${this.escapeHtml(this.currentTemplate.description)}
${ this.currentTemplate.tags.length > 0 ? `
Tags:
${this.currentTemplate.tags .map( (tag) => `${this.escapeHtml(tag)}`, ) .join("")}
` : "" } `; } // Update filename if (previewFilename) { const defaultName = this.getDefaultName(); previewFilename.textContent = `${defaultName}.${this.currentTemplate.unit_type}`; } // Generate parameters form if (parametersDiv) { parametersDiv.innerHTML = this.generateParametersForm(); } // Show modal const bsModal = new bootstrap.Modal(modal); bsModal.show(); // Update preview this.updatePreview(); } generateParametersForm() { if (!this.currentTemplate || !this.currentTemplate.parameters) { return '

No parameters required for this template.

'; } return this.currentTemplate.parameters .map((param) => { const isRequired = param.required; const fieldId = `param-${param.name}`; let inputHtml = ""; switch (param.type) { case "boolean": inputHtml = ` `; break; case "choice": const options = param.choices .map( (choice) => ``, ) .join(""); inputHtml = ` `; break; case "integer": inputHtml = ` `; break; default: // string, list inputHtml = ` `; } return `
${this.escapeHtml(param.description)}
${inputHtml} ${param.example ? `
Example: ${this.escapeHtml(param.example)}
` : ""}
`; }) .join(""); } getDefaultName() { const nameParam = this.currentTemplate.parameters.find( (p) => p.name === "name", ); if (nameParam && nameParam.example) { return nameParam.example; } return this.currentTemplate.name.toLowerCase().replace(/[^a-z0-9]/g, ""); } getFormParameters() { const parameters = {}; if (!this.currentTemplate || !this.currentTemplate.parameters) { return parameters; } this.currentTemplate.parameters.forEach((param) => { const element = document.getElementById(`param-${param.name}`); if (element && element.value.trim()) { let value = element.value.trim(); // Type conversion switch (param.type) { case "boolean": value = value === "true"; break; case "integer": value = parseInt(value); if (isNaN(value)) value = param.default || 0; break; case "list": value = value .split(",") .map((v) => v.trim()) .filter((v) => v); break; } parameters[param.name] = value; } }); return parameters; } async updatePreview() { if (!this.currentTemplate) return; const preview = document.getElementById("templatePreview"); const validation = document.getElementById("validationResults"); if (!preview) return; const parameters = this.getFormParameters(); // Check if required parameters are missing const missingRequired = this.currentTemplate.parameters .filter((p) => p.required && !parameters.hasOwnProperty(p.name)) .map((p) => p.name); if (missingRequired.length > 0) { preview.innerHTML = `# Please fill in required parameters: ${missingRequired.join(", ")}`; if (validation) validation.classList.add("d-none"); return; } try { const result = await unitforge.generateFromTemplate( this.currentTemplate.name, parameters, `${parameters.name || "generated"}.${this.currentTemplate.unit_type}`, ); preview.innerHTML = `${this.escapeHtml(result.content)}`; // Update filename const filenameElement = document.getElementById("previewFilename"); if (filenameElement) { filenameElement.textContent = result.filename; } // Show validation results if (validation && result.validation) { validation.innerHTML = unitforge.formatValidationResults( result.validation, ); validation.classList.remove("d-none"); } } catch (error) { preview.innerHTML = `# Error generating preview: ${error.message}`; if (validation) validation.classList.add("d-none"); } } async validateTemplate() { const parameters = this.getFormParameters(); const validation = document.getElementById("validationResults"); if (!validation) return; validation.innerHTML = `
Validating template...
`; try { const result = await unitforge.generateFromTemplate( this.currentTemplate.name, parameters, `${parameters.name || "generated"}.${this.currentTemplate.unit_type}`, ); validation.innerHTML = unitforge.formatValidationResults( result.validation, ); if (result.validation.valid) { unitforge.showToast("Template validation passed!", "success"); } else { unitforge.showToast( `Validation found ${result.validation.errors.length} error(s)`, "warning", ); } } catch (error) { validation.innerHTML = `
Validation failed: ${error.message}
`; unitforge.showToast("Validation failed", "error"); } } async generateAndDownload() { const generateBtn = document.getElementById("generateBtn"); const originalText = generateBtn.innerHTML; generateBtn.innerHTML = 'Generating...'; generateBtn.disabled = true; try { const parameters = this.getFormParameters(); const result = await unitforge.generateFromTemplate( this.currentTemplate.name, parameters, `${parameters.name || "generated"}.${this.currentTemplate.unit_type}`, ); // Download the file unitforge.downloadTextFile(result.content, result.filename); unitforge.showToast(`Downloaded ${result.filename}`, "success"); // Close modal const modal = bootstrap.Modal.getInstance( document.getElementById("templateModal"), ); if (modal) { modal.hide(); } } catch (error) { unitforge.showToast(`Generation failed: ${error.message}`, "error"); } finally { generateBtn.innerHTML = originalText; generateBtn.disabled = false; } } openInEditor() { const parameters = this.getFormParameters(); // Store template data in session storage for the editor sessionStorage.setItem( "templateData", JSON.stringify({ template: this.currentTemplate.name, parameters: parameters, }), ); // Open editor in new tab/window or navigate window.open("/editor", "_blank"); } async copyPreviewToClipboard() { const preview = document.getElementById("templatePreview"); if (preview) { const content = preview.textContent; await unitforge.copyToClipboard(content); } } escapeHtml(text) { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } } // Global functions for HTML onclick handlers function filterByCategory(category) { templatesBrowser.filterByCategory(category); } function filterTemplates() { templatesBrowser.filterTemplates(); } function clearSearch() { templatesBrowser.clearSearch(); } function openTemplate(templateName) { templatesBrowser.openTemplate(templateName); } function validateTemplate() { templatesBrowser.validateTemplate(); } function generateAndDownload() { templatesBrowser.generateAndDownload(); } function openInEditor() { templatesBrowser.openInEditor(); } function copyPreviewToClipboard() { templatesBrowser.copyPreviewToClipboard(); } // Initialize the templates browser when DOM is ready let templatesBrowser; document.addEventListener("DOMContentLoaded", function () { templatesBrowser = new TemplatesBrowser(); // Export for use in other modules window.TemplatesBrowser = TemplatesBrowser; window.templatesBrowser = templatesBrowser; });