secubox-openwrt/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js
CyberMind-FR b884b12970 feat: Integrate global CyberMood theme into core modules
Migrated three core modules to use the global secubox-theme package:

Modules Updated:
- luci-app-secubox (dashboard, modules views)
- luci-app-network-modes (overview view)
- luci-app-system-hub (overview, services views)

Changes Per Module:
- Replaced module-specific theme imports with 'require secubox-theme/theme as Theme'
- Updated CSS imports to use global secubox-theme.css bundle
- Initialized global theme with Theme.init({ theme: 'dark', language: 'en' })
- Removed redundant theme.getTheme() calls from load() functions
- Added global theme CSS link tags to view renders

Benefits:
- Unified CyberMood design system across all modules
- Access to 100+ CSS variables (colors, spacing, effects)
- Theme switching support (dark, light, cyberpunk)
- Multi-language support (en, fr, de, es) via Theme.t()
- Reduced CSS duplication
- Consistent UI components (cards, buttons, badges)

Deployment:
- Created deploy-modules-with-theme.sh for batch deployment
- All modules successfully deployed to router 192.168.8.191
- Verified HTTP access to updated JavaScript files

Testing:
-  SecuBox dashboard loads with global theme
-  Network-modes overview uses theme CSS
-  System-hub views integrate theme properly
-  All 27 view files deployed successfully

Next Steps:
- Modules can now use Theme.createCard(), createButton(), createBadge()
- Translation keys available for internationalization
- Theme variants switchable via Theme.apply('dark'|'light'|'cyberpunk')

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-28 17:16:37 +01:00

315 lines
8.7 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require dom';
'require system-hub/api as API';
'require secubox-theme/theme as Theme';
// Initialize global theme
Theme.init({ theme: 'dark', language: 'en' });
return view.extend({
services: [],
currentFilter: 'all',
searchQuery: '',
load: function() {
return API.listServices();
},
render: function(data) {
this.services = data || [];
var container = E('div', { 'class': 'system-hub-services' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/services.css') }),
// Header with stats
this.renderHeader(),
// Filter tabs
this.renderFilterTabs(),
// Search box
this.renderSearchBox(),
// Services grid
E('div', { 'class': 'sh-services-grid', 'id': 'services-grid' })
]);
// Initial render
this.updateServicesGrid();
return container;
},
renderHeader: function() {
var stats = this.getStats();
return E('div', { 'class': 'sh-page-header' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '⚙️'),
'System Services'
]),
E('p', { 'class': 'sh-page-subtitle' },
'Manage system services: start, stop, restart, enable, or disable')
]),
E('div', { 'class': 'sh-stats-grid' }, [
E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value', 'style': 'color: #22c55e;' }, stats.running),
E('div', { 'class': 'sh-stat-label' }, 'Running')
]),
E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value', 'style': 'color: #ef4444;' }, stats.stopped),
E('div', { 'class': 'sh-stat-label' }, 'Stopped')
]),
E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value', 'style': 'color: #6366f1;' }, stats.enabled),
E('div', { 'class': 'sh-stat-label' }, 'Enabled')
]),
E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value' }, stats.total),
E('div', { 'class': 'sh-stat-label' }, 'Total')
])
])
]);
},
renderFilterTabs: function() {
var self = this;
var stats = this.getStats();
return E('div', { 'class': 'sh-filter-tabs' }, [
this.createFilterTab('all', '📋', 'All Services', stats.total),
this.createFilterTab('running', '▶️', 'Running', stats.running),
this.createFilterTab('stopped', '⏹️', 'Stopped', stats.stopped),
this.createFilterTab('enabled', '✓', 'Enabled', stats.enabled),
this.createFilterTab('disabled', '✗', 'Disabled', stats.disabled)
]);
},
createFilterTab: function(filter, icon, label, count) {
var self = this;
var isActive = this.currentFilter === filter;
return E('div', {
'class': 'sh-filter-tab' + (isActive ? ' active' : ''),
'click': function() {
self.currentFilter = filter;
self.updateFilterTabs();
self.updateServicesGrid();
}
}, [
E('span', { 'class': 'sh-tab-icon' }, icon),
E('span', { 'class': 'sh-tab-label' }, label + ' (' + count + ')')
]);
},
renderSearchBox: function() {
var self = this;
return E('div', { 'style': 'margin-bottom: 24px;' }, [
E('input', {
'type': 'text',
'class': 'cbi-input-text',
'placeholder': '🔍 Search services...',
'style': 'width: 100%; padding: 12px 16px; border-radius: 8px; border: 1px solid var(--sh-border); background: var(--sh-bg-card); color: var(--sh-text-primary); font-size: 14px;',
'input': function(ev) {
self.searchQuery = ev.target.value.toLowerCase();
self.updateServicesGrid();
}
})
]);
},
updateFilterTabs: function() {
var tabs = document.querySelectorAll('.sh-filter-tab');
tabs.forEach(function(tab, index) {
var filters = ['all', 'running', 'stopped', 'enabled', 'disabled'];
if (filters[index] === this.currentFilter) {
tab.classList.add('active');
} else {
tab.classList.remove('active');
}
}.bind(this));
},
updateServicesGrid: function() {
var grid = document.getElementById('services-grid');
if (!grid) return;
var filtered = this.getFilteredServices();
if (filtered.length === 0) {
dom.content(grid, [
E('div', { 'class': 'sh-empty-state' }, [
E('div', { 'class': 'sh-empty-icon' }, '📭'),
E('div', { 'class': 'sh-empty-text' },
this.searchQuery ? 'No services match your search' : 'No services found')
])
]);
return;
}
dom.content(grid, filtered.map(this.renderServiceCard, this));
},
getFilteredServices: function() {
return this.services.filter(function(service) {
// Apply filter
var matchesFilter = true;
switch (this.currentFilter) {
case 'running':
matchesFilter = service.running;
break;
case 'stopped':
matchesFilter = !service.running;
break;
case 'enabled':
matchesFilter = service.enabled;
break;
case 'disabled':
matchesFilter = !service.enabled;
break;
}
// Apply search
var matchesSearch = !this.searchQuery ||
service.name.toLowerCase().includes(this.searchQuery);
return matchesFilter && matchesSearch;
}.bind(this));
},
renderServiceCard: function(service) {
var statusClass = service.running ? 'ok' : 'error';
var statusIcon = service.running ? '▶️' : '⏹️';
var statusText = service.running ? 'Running' : 'Stopped';
var enabledIcon = service.enabled ? '✓' : '✗';
var enabledText = service.enabled ? 'Enabled' : 'Disabled';
return E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('h3', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '⚙️'),
service.name
]),
E('div', { 'class': 'sh-card-badge sh-status-badge sh-status-' + statusClass }, [
statusIcon + ' ' + statusText
])
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 8px; margin-bottom: 16px;' }, [
E('span', { 'style': 'font-size: 16px;' }, enabledIcon),
E('span', { 'style': 'font-weight: 600; color: var(--sh-text-secondary);' },
'Autostart: ' + enabledText)
]),
E('div', { 'style': 'display: flex; gap: 8px; flex-wrap: wrap;' },
this.renderActionButtons(service)
)
])
]);
},
renderActionButtons: function(service) {
var buttons = [];
// Start button (only if stopped)
if (!service.running) {
buttons.push(E('button', {
'class': 'sh-btn sh-btn-success',
'click': L.bind(this.performAction, this, service.name, 'start')
}, [
E('span', {}, '▶️'),
E('span', {}, 'Start')
]));
}
// Stop button (only if running)
if (service.running) {
buttons.push(E('button', {
'class': 'sh-btn sh-btn-danger',
'click': L.bind(this.performAction, this, service.name, 'stop')
}, [
E('span', {}, '⏹️'),
E('span', {}, 'Stop')
]));
}
// Restart button
buttons.push(E('button', {
'class': 'sh-btn sh-btn-warning',
'click': L.bind(this.performAction, this, service.name, 'restart')
}, [
E('span', {}, '🔄'),
E('span', {}, 'Restart')
]));
// Enable/Disable button
if (service.enabled) {
buttons.push(E('button', {
'class': 'sh-btn sh-btn-secondary',
'click': L.bind(this.performAction, this, service.name, 'disable')
}, [
E('span', {}, '✗'),
E('span', {}, 'Disable')
]));
} else {
buttons.push(E('button', {
'class': 'sh-btn sh-btn-primary',
'click': L.bind(this.performAction, this, service.name, 'enable')
}, [
E('span', {}, '✓'),
E('span', {}, 'Enable')
]));
}
return buttons;
},
getStats: function() {
var stats = {
total: this.services.length,
running: 0,
stopped: 0,
enabled: 0,
disabled: 0
};
this.services.forEach(function(service) {
if (service.running) stats.running++;
else stats.stopped++;
if (service.enabled) stats.enabled++;
else stats.disabled++;
});
return stats;
},
performAction: function(service, action) {
ui.showModal(_('Performing Action'), [
E('p', { 'class': 'spinning' }, _('Executing %s on service %s...').format(action, service))
]);
API.serviceAction(service, action).then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', '✓ ' + result.message), 'info');
window.location.reload();
} else {
ui.addNotification(null, E('p', '✗ ' + result.message), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Action failed: ') + err.message), 'error');
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});