/** * javascript/templates.js * * Templates panel: reusable container configs saved as XML (Unraid * Docker-template-compatible schema — see ajax/templates.php's header * comment for why). "Use template" hands off to containers.js's Create * Container modal, pre-filled; templates are themselves created from * that same modal's "Save as template" checkbox, not from here. */ (function () { 'use strict'; const P = window.Podman; let allTemplates = []; function iconHtml(t) { if (t.icon) { return ''; } return '
' + P.escapeHtml(t.name.slice(0, 1).toUpperCase()) + '
'; } function cardHtml(t) { const overview = t.overview && t.overview.length > 110 ? t.overview.slice(0, 107) + '…' : (t.overview || ''); return '' + '
' + iconHtml(t) + '
' + '
' + P.escapeHtml(t.name) + '
' + '
' + P.escapeHtml(t.image) + '
' + (overview ? '
' + P.escapeHtml(overview) + '
' : '') + '
' + '
' + '' + '' + '' + '
'; } function render() { const grid = P.el('templates-grid'); grid.innerHTML = allTemplates.length ? allTemplates.map(cardHtml).join('') : '
No templates yet — save one from the "New Container" form, or import an XML template.
'; } function load() { const container = P.el('podman-panel-templates'); if (!P.el('templates-grid')) { container.innerHTML = '' + '
' + '
' + 'Reusable container configs' + '' + '
' + '
' + '
'; P.el('templates-import-btn').addEventListener('click', openImportModal); P.el('templates-grid').addEventListener('click', handleCardClick); } return P.get('templates', 'list').then(function (data) { allTemplates = data; render(); }).catch(function (err) { P.el('templates-grid').innerHTML = '
' + P.escapeHtml(err.message) + '
'; }); } function handleCardClick(e) { const btn = e.target.closest('button[data-action]'); if (!btn) return; const name = btn.closest('.podman-template-card').dataset.name; if (btn.dataset.action === 'use') { btn.disabled = true; P.get('templates', 'get', { name: name }).then(function (config) { btn.disabled = false; P.openCreateContainerModal(config); }).catch(function (err) { btn.disabled = false; P.toast('Could not load template: ' + err.message, 'error'); }); return; } if (btn.dataset.action === 'export') { btn.disabled = true; P.get('templates', 'export', { name: name }).then(function (data) { btn.disabled = false; const blob = new Blob([data.xml], { type: 'application/xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = name + '.xml'; a.click(); URL.revokeObjectURL(url); }).catch(function (err) { btn.disabled = false; P.toast('Export failed: ' + err.message, 'error'); }); return; } if (btn.dataset.action === 'delete') { if (!confirm('Delete template "' + name + '"? This does not affect any running containers.')) return; btn.disabled = true; P.post('templates', 'remove', { name: name }).then(load).catch(function (err) { btn.disabled = false; P.toast('Delete failed: ' + err.message, 'error'); }); } } function openImportModal() { const backdrop = document.createElement('div'); backdrop.className = 'podman-modal-backdrop'; backdrop.innerHTML = '' + ''; (document.querySelector('.podman-plugin') || document.body).appendChild(backdrop); let localTemplates = []; function renderLocalList(filter) { const list = backdrop.querySelector('#ti-local-list'); const visible = filter ? localTemplates.filter(function (t) { return t.name.toLowerCase().indexOf(filter) !== -1; }) : localTemplates; if (!visible.length) { list.innerHTML = '
' + (localTemplates.length ? 'No match.' : 'None found.') + '
'; return; } list.innerHTML = visible.map(function (t) { return '
' + '' + P.escapeHtml(t.name) + '' + '' + P.escapeHtml(t.image) + '' + '' + '
'; }).join(''); } P.get('templates', 'list_local').then(function (data) { localTemplates = data; renderLocalList(''); }).catch(function () { backdrop.querySelector('#ti-local-list').innerHTML = '
Could not read local templates.
'; }); backdrop.querySelector('#ti-local-search').addEventListener('input', function (e) { renderLocalList(e.target.value.trim().toLowerCase()); }); backdrop.querySelector('#ti-local-list').addEventListener('click', function (e) { const btn = e.target.closest('[data-action="import-local"]'); if (!btn) return; const file = btn.closest('.podman-local-template-item').dataset.file; btn.disabled = true; P.post('templates', 'import_local', { file: file }).then(function () { close(); return load(); }).catch(function (err) { btn.disabled = false; showError(err.message); }); }); backdrop.querySelector('#ti-xml').focus(); function close() { backdrop.remove(); } function showError(message) { let box = backdrop.querySelector('.podman-modal-error'); if (!box) { box = document.createElement('div'); box.className = 'podman-modal-error'; backdrop.querySelector('.podman-modal-body').appendChild(box); } box.textContent = message; } function submit() { const xml = backdrop.querySelector('#ti-xml').value.trim(); if (!xml) { showError('Paste a template XML first.'); return; } const submitBtn = backdrop.querySelector('[data-role="submit"]'); submitBtn.disabled = true; P.post('templates', 'import', { xml: xml }).then(function () { close(); return load(); }).catch(function (err) { submitBtn.disabled = false; showError(err.message); }); } backdrop.querySelector('[data-role="cancel"]').addEventListener('click', close); backdrop.querySelector('[data-role="submit"]').addEventListener('click', submit); backdrop.querySelector('form').addEventListener('submit', function (e) { e.preventDefault(); submit(); }); backdrop.addEventListener('click', function (e) { if (e.target === backdrop) close(); }); document.addEventListener('keydown', function onKey(e) { if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); } }); } P.registerPanel('templates', { init: load, refresh: load }); })();