secubox-openwrt/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/internal.js
CyberMind-FR 89232f3736 feat(vhost-manager): add VHost templates synchronized with appstore
Created dynamic VHost template system similar to Network Modes profiles:

New Files:
- root/usr/share/vhost-manager/templates.json (7.7KB, 253 lines)
- root/usr/share/vhost-manager/README.md

Template Catalog (13 templates):
- LuCI UI, Netdata (Core/Monitoring)
- CrowdSec, AdGuard Home (Security/Network)
- NoDogSplash (Network - Captive Portal)
- Domoticz, Zigbee2MQTT (IoT & Home Automation)
- Lyrion Music Server (Media)
- LocalAI (AI & Machine Learning)
- Citadel, Mail-in-a-Box, Nextcloud (Productivity)
- ISPConfig (Hosting & Control Panels)

Features:
- Synchronized with SecuBox appstore applications
- 9 categories matching appstore structure
- Default domains (*.local), backend ports, SSL/auth requirements
- Dynamic loading via fetch from /usr/share/vhost-manager/templates.json
- Links to appstore app_id for integration

Code Changes:
- internal.js: Added loadTemplates() method with fetch API
- Replaced hardcoded SERVICES array with dynamic loading
- Version bump: 0.4.1-r3 → 0.5.0-r1

Benefits:
- Single source of truth for service configurations
- Easy to add new services (just edit JSON)
- Consistent with appstore and network-modes pattern
- Pre-configured templates reduce user errors
- SSL and WebSocket settings included

Access: https://192.168.8.191/cgi-bin/luci/admin/secubox/services/vhosts/internal

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 08:52:05 +01:00

123 lines
3.8 KiB
JavaScript

'use strict';
'require view';
'require vhost-manager/api as API';
'require secubox-theme/theme as Theme';
'require vhost-manager/ui as VHostUI';
'require request';
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: lang });
var SERVICES = [];
return view.extend({
load: function() {
return Promise.all([
API.listVHosts(),
this.loadTemplates()
]);
},
loadTemplates: function() {
return request.get('/usr/share/vhost-manager/templates.json').then(function(response) {
try {
var data = JSON.parse(response.responseText || '{}');
SERVICES = (data.templates || []).map(function(t) {
return {
id: t.id,
icon: t.icon,
name: t.name,
domain: t.domain,
backend: t.backend,
port: t.port,
category: t.category,
description: t.description,
app_id: t.app_id,
requires_ssl: t.requires_ssl,
requires_auth: t.requires_auth,
websocket_support: t.websocket_support,
notes: t.notes
};
});
return SERVICES;
} catch(e) {
console.error('Failed to parse vhost templates:', e);
return [];
}
}).catch(function(err) {
console.error('Failed to load vhost templates:', err);
return [];
});
},
render: function(data) {
var vhosts = data[0] || [];
var active = {};
vhosts.forEach(function(v) {
active[v.domain] = true;
});
return E('div', { 'class': 'vhost-page' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('vhost-manager/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('vhost-manager/dashboard.css') }),
VHostUI.renderTabs('internal'),
this.renderHeader(vhosts),
this.renderServices(active)
]);
},
renderHeader: function(vhosts) {
var configured = vhosts.filter(function(vhost) {
return SERVICES.some(function(s) { return s.domain === vhost.domain; });
}).length;
return E('div', { 'class': 'sh-page-header' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '🏠'),
_('Internal Service Catalog')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Pre-built recipes for publishing popular LAN services with SSL, auth, and redirects.'))
]),
E('div', { 'class': 'sh-stats-grid' }, [
this.renderStat(SERVICES.length, _('Templates')),
this.renderStat(configured, _('Configured'))
])
]);
},
renderStat: function(value, label) {
return E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value' }, value.toString()),
E('div', { 'class': 'sh-stat-label' }, label)
]);
},
renderServices: function(active) {
return E('div', { 'class': 'vhost-card-grid' },
SERVICES.map(function(service) {
var isActive = !!active[service.domain];
return E('div', { 'class': 'vhost-card' }, [
E('div', { 'class': 'vhost-card-title' }, [service.icon, service.name]),
E('div', { 'class': 'vhost-card-meta' }, service.category),
E('p', { 'class': 'vhost-card-meta' }, service.description),
E('div', { 'class': 'vhost-card-meta' }, _('Domain: %s').format(service.domain)),
E('div', { 'class': 'vhost-card-meta' }, _('Backend: %s').format(service.backend)),
E('div', { 'class': 'vhost-actions' }, [
E('span', { 'class': 'vhost-pill ' + (isActive ? 'success' : '') },
isActive ? _('Published') : _('Not configured')),
E('a', {
'class': 'sh-btn-secondary',
'href': L.url('admin', 'secubox', 'services', 'vhosts', 'vhosts')
}, isActive ? _('Manage') : _('Create'))
])
]);
})
);
}
});