/** * NovaCPX Feature Manager * Loaded by admin panel's features page */ window.FeaturesManager = { async load() { const res = await Nova.api('features', 'list'); if (!res?.success) return '

Failed to load features.

'; const grouped = res.data; const categoryIcons = { 'Web Server':'🌐', 'PHP':'βš™οΈ', 'Database':'πŸ—„οΈ', 'Email':'πŸ“§', 'DNS':'πŸ”', 'FTP':'πŸ“', 'SSL':'πŸ”’', 'Security':'πŸ›‘οΈ', 'Containers':'🐳', 'IP Management':'🌍', 'Monitoring':'πŸ“Š', 'Backup':'πŸ’Ύ', 'CDN & Performance':'⚑', 'Development':'πŸ‘¨β€πŸ’»', 'One-Click Apps':'πŸš€', 'Applications':'πŸ“¦', 'Billing':'πŸ’³', 'Reseller':'πŸͺ', 'Notifications':'πŸ””', 'Compliance':'βœ…', }; return `

Feature Manager

${Object.entries(grouped).map(([cat, feats]) => `
${categoryIcons[cat] || 'πŸ”§'}

${cat}

${feats.length}
${feats.map(f => `
${f.name}
${f.description}
${f.min_ram_mb > 0 ? `
Requires ${f.min_ram_mb}MB RAM
` : ''}
${f.installed ? `` : `` } ${f.installed ? `Installed` : `Not installed`}
`).join('')}
`).join('')}
`; }, async toggle(id, enable) { const res = await Nova.api('features', 'toggle', { method: 'POST', body: { id, enable } }); if (res?.data?.action === 'install_required') { Nova.confirm(`"${res.data.feature.name}" must be installed first. Install now?`, () => this.install(id)); return; } Nova.toast(res?.message || 'Updated', res?.success ? 'success' : 'error'); }, async install(id) { const res = await Nova.api('features', 'install', { method: 'POST', body: { id } }); if (!res?.success) { Nova.toast(res?.message || 'Install failed', 'error'); return; } const logDiv = `
Starting installation…
`; const ov = Nova.modal('Installing Feature', logDiv); const poll = setInterval(async () => { const logRes = await Nova.api('features', 'install-log', { params: { id } }); const logEl = document.getElementById(`install-log-${id}`); if (logEl) logEl.innerHTML = (logRes?.data?.log || '').split('\n').map(l => `
${l}
`).join(''); if (!logRes?.data?.running) { clearInterval(poll); if (logRes?.data?.installed) { Nova.toast('Feature installed successfully', 'success'); ov.remove(); document.getElementById('page-content').innerHTML = await this.load(); this.bindSearch(); } } }, 2000); }, bindSearch() { const search = document.getElementById('feat-search'); const catFilter = document.getElementById('feat-cat-filter'); if (!search || !catFilter) return; const filter = () => { const q = search.value.toLowerCase(); const cat = catFilter.value; document.querySelectorAll('.feat-card').forEach(c => { const match = (!q || c.dataset.name.includes(q)) && (!cat || c.dataset.cat === cat); c.closest('div').style.display = match ? '' : 'none'; }); document.querySelectorAll('.feat-category').forEach(sec => { const visible = [...sec.querySelectorAll('.feat-card')].some(c => c.closest('div').style.display !== 'none'); sec.style.display = visible ? '' : 'none'; }); }; search.addEventListener('input', filter); catFilter.addEventListener('change', filter); }, };