fix: system-hub v0.0.2 - Move stub functions to views (baseclass.extend limitation)
Root Cause: - baseclass.extend() in LuCI only preserves RPC methods (rpc.declare) - Non-RPC functions passed to baseclass.extend() are filtered out - This caused TypeError: api.callGetComponents is not a function Solution: - Simplified api.js to contain ONLY RPC methods (10 methods) - Moved all stub functions and helpers directly into the views that use them - Each view now defines its own local functions for planned features API Changes (api.js): - Removed all stub functions (callGetComponents, callManageComponent, etc.) - Removed all helper functions (formatUptime, formatBytes, getComponentIcon, getHealthStatus) - Kept only RPC methods: getStatus, getSystemInfo, getHealth, listServices, serviceAction, getLogs, backupConfig, restoreConfig, reboot, getStorage View Changes: - components.js: Added local getComponents(), getComponentIcon(), manageComponent() - health.js: Added local getHealthStatus(), formatBytes(), inline report stub - remote.js: Added local getRemoteConfig(), inline session stub - settings.js: Fixed to use api.getStatus() instead of api.callStatus() All 9 views now functional with proper stub data for planned features. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fb899321cb
commit
6e0182ad35
@ -6,9 +6,12 @@
|
|||||||
* System Hub API
|
* System Hub API
|
||||||
* Package: luci-app-system-hub
|
* Package: luci-app-system-hub
|
||||||
* RPCD object: luci.system-hub
|
* RPCD object: luci.system-hub
|
||||||
* Version: 0.0.2
|
* Version: 0.0.2-debug
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Debug log to verify correct version is loaded
|
||||||
|
console.log('🔧 System Hub API v0.0.2-debug loaded at', new Date().toISOString());
|
||||||
|
|
||||||
var callStatus = rpc.declare({
|
var callStatus = rpc.declare({
|
||||||
object: 'luci.system-hub',
|
object: 'luci.system-hub',
|
||||||
method: 'status',
|
method: 'status',
|
||||||
@ -72,125 +75,8 @@ var callGetStorage = rpc.declare({
|
|||||||
expect: { storage: [] }
|
expect: { storage: [] }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper: Format uptime seconds to human-readable string
|
|
||||||
function formatUptime(seconds) {
|
|
||||||
if (!seconds) return '0s';
|
|
||||||
var d = Math.floor(seconds / 86400);
|
|
||||||
var h = Math.floor((seconds % 86400) / 3600);
|
|
||||||
var m = Math.floor((seconds % 3600) / 60);
|
|
||||||
if (d > 0) return d + 'd ' + h + 'h';
|
|
||||||
if (h > 0) return h + 'h ' + m + 'm';
|
|
||||||
return m + 'm';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Format bytes to human-readable size
|
|
||||||
function formatBytes(bytes) {
|
|
||||||
if (!bytes) return '0 B';
|
|
||||||
var k = 1024;
|
|
||||||
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
||||||
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Get component icon (stub for planned feature)
|
|
||||||
function getComponentIcon(icon) {
|
|
||||||
return icon || '📦';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Get health status info based on score
|
|
||||||
function getHealthStatus(score) {
|
|
||||||
if (score >= 90) return { status: 'excellent', label: 'Excellent', color: '#22c55e' };
|
|
||||||
if (score >= 75) return { status: 'good', label: 'Bon', color: '#3b82f6' };
|
|
||||||
if (score >= 50) return { status: 'warning', label: 'Attention', color: '#f59e0b' };
|
|
||||||
return { status: 'critical', label: 'Critique', color: '#ef4444' };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub: Get components (planned feature - returns mock data)
|
|
||||||
function stubGetComponents() {
|
|
||||||
return Promise.resolve({
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
id: 'netdata',
|
|
||||||
name: 'Netdata',
|
|
||||||
description: 'Real-time performance monitoring',
|
|
||||||
status: 'installed',
|
|
||||||
running: true,
|
|
||||||
icon: '📊',
|
|
||||||
color: '#00C851',
|
|
||||||
web_port: 19999
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'crowdsec',
|
|
||||||
name: 'CrowdSec',
|
|
||||||
description: 'Collaborative security engine',
|
|
||||||
status: 'installed',
|
|
||||||
running: true,
|
|
||||||
icon: '🛡️',
|
|
||||||
color: '#0091EA',
|
|
||||||
web_port: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'netifyd',
|
|
||||||
name: 'Netifyd',
|
|
||||||
description: 'Deep packet inspection',
|
|
||||||
status: 'planned',
|
|
||||||
roadmap_date: 'Q1 2026'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub: Manage component (planned feature)
|
|
||||||
function stubManageComponent(id, action) {
|
|
||||||
return Promise.resolve({
|
|
||||||
success: true,
|
|
||||||
message: 'Component ' + id + ' ' + action + ' - Feature coming soon'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub: Get remote access config (planned feature)
|
|
||||||
function stubGetRemote() {
|
|
||||||
return Promise.resolve({
|
|
||||||
rustdesk_enabled: false,
|
|
||||||
rustdesk_installed: false,
|
|
||||||
rustdesk_id: null,
|
|
||||||
allow_unattended: false,
|
|
||||||
require_approval: true,
|
|
||||||
notify_on_connect: true,
|
|
||||||
support: {
|
|
||||||
provider: 'CyberMind.fr',
|
|
||||||
email: 'support@cybermind.fr',
|
|
||||||
phone: '+33 1 23 45 67 89',
|
|
||||||
website: 'https://cybermind.fr'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub: Start remote session (planned feature)
|
|
||||||
function stubStartRemoteSession(type) {
|
|
||||||
return Promise.resolve({
|
|
||||||
success: false,
|
|
||||||
error: 'Remote session feature not yet implemented'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub: Get scheduled tasks (planned feature)
|
|
||||||
function stubGetSchedules() {
|
|
||||||
return Promise.resolve({
|
|
||||||
schedules: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub: Generate health report (planned feature)
|
|
||||||
function stubGenerateReport() {
|
|
||||||
return Promise.resolve({
|
|
||||||
success: false,
|
|
||||||
error: 'Report generation not yet implemented'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseclass.extend({
|
return baseclass.extend({
|
||||||
// Main RPC methods (camelCase)
|
// RPC methods - exposed via ubus
|
||||||
getStatus: callStatus,
|
getStatus: callStatus,
|
||||||
getSystemInfo: callGetSystemInfo,
|
getSystemInfo: callGetSystemInfo,
|
||||||
getHealth: callGetHealth,
|
getHealth: callGetHealth,
|
||||||
@ -200,21 +86,5 @@ return baseclass.extend({
|
|||||||
backupConfig: callBackupConfig,
|
backupConfig: callBackupConfig,
|
||||||
restoreConfig: callRestoreConfig,
|
restoreConfig: callRestoreConfig,
|
||||||
reboot: callReboot,
|
reboot: callReboot,
|
||||||
getStorage: callGetStorage,
|
getStorage: callGetStorage
|
||||||
|
|
||||||
// RPC methods (call* prefix for view compatibility)
|
|
||||||
callStatus: callStatus,
|
|
||||||
callGetHealth: callGetHealth,
|
|
||||||
callGetComponents: stubGetComponents,
|
|
||||||
callManageComponent: stubManageComponent,
|
|
||||||
callGetRemote: stubGetRemote,
|
|
||||||
callStartRemoteSession: stubStartRemoteSession,
|
|
||||||
callGetSchedules: stubGetSchedules,
|
|
||||||
callGenerateReport: stubGenerateReport,
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
formatUptime: formatUptime,
|
|
||||||
formatBytes: formatBytes,
|
|
||||||
getComponentIcon: getComponentIcon,
|
|
||||||
getHealthStatus: getHealthStatus
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,9 +5,57 @@
|
|||||||
|
|
||||||
var api = L.require('system-hub.api');
|
var api = L.require('system-hub.api');
|
||||||
|
|
||||||
|
// Stub: Get components (planned feature - returns mock data)
|
||||||
|
function getComponents() {
|
||||||
|
return Promise.resolve({
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
id: 'netdata',
|
||||||
|
name: 'Netdata',
|
||||||
|
description: 'Real-time performance monitoring',
|
||||||
|
status: 'installed',
|
||||||
|
running: true,
|
||||||
|
icon: '📊',
|
||||||
|
color: '#00C851',
|
||||||
|
web_port: 19999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'crowdsec',
|
||||||
|
name: 'CrowdSec',
|
||||||
|
description: 'Collaborative security engine',
|
||||||
|
status: 'installed',
|
||||||
|
running: true,
|
||||||
|
icon: '🛡️',
|
||||||
|
color: '#0091EA',
|
||||||
|
web_port: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'netifyd',
|
||||||
|
name: 'Netifyd',
|
||||||
|
description: 'Deep packet inspection',
|
||||||
|
status: 'planned',
|
||||||
|
roadmap_date: 'Q1 2026'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Get component icon
|
||||||
|
function getComponentIcon(icon) {
|
||||||
|
return icon || '📦';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stub: Manage component (planned feature)
|
||||||
|
function manageComponent(id, action) {
|
||||||
|
return Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
message: 'Component ' + id + ' ' + action + ' - Feature coming soon'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return api.callGetComponents();
|
return getComponents();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
@ -52,7 +100,7 @@ return view.extend({
|
|||||||
return E('div', { 'class': 'sh-component-card', 'style': '--component-color: ' + c.color }, [
|
return E('div', { 'class': 'sh-component-card', 'style': '--component-color: ' + c.color }, [
|
||||||
E('div', { 'class': 'sh-component-header' }, [
|
E('div', { 'class': 'sh-component-header' }, [
|
||||||
E('div', { 'class': 'sh-component-info' }, [
|
E('div', { 'class': 'sh-component-info' }, [
|
||||||
E('div', { 'class': 'sh-component-icon' }, api.getComponentIcon(c.icon)),
|
E('div', { 'class': 'sh-component-icon' }, getComponentIcon(c.icon)),
|
||||||
E('div', {}, [
|
E('div', {}, [
|
||||||
E('div', { 'class': 'sh-component-name' }, c.name),
|
E('div', { 'class': 'sh-component-name' }, c.name),
|
||||||
E('div', { 'class': 'sh-component-desc' }, c.description)
|
E('div', { 'class': 'sh-component-desc' }, c.description)
|
||||||
@ -80,7 +128,7 @@ return view.extend({
|
|||||||
|
|
||||||
renderRoadmapItem: function(c) {
|
renderRoadmapItem: function(c) {
|
||||||
return E('div', { 'class': 'sh-roadmap-item' }, [
|
return E('div', { 'class': 'sh-roadmap-item' }, [
|
||||||
E('div', { 'class': 'sh-roadmap-icon' }, api.getComponentIcon(c.icon)),
|
E('div', { 'class': 'sh-roadmap-icon' }, getComponentIcon(c.icon)),
|
||||||
E('div', { 'class': 'sh-roadmap-info' }, [
|
E('div', { 'class': 'sh-roadmap-info' }, [
|
||||||
E('div', { 'class': 'sh-roadmap-name' }, c.name),
|
E('div', { 'class': 'sh-roadmap-name' }, c.name),
|
||||||
E('div', { 'class': 'sh-roadmap-desc' }, c.description)
|
E('div', { 'class': 'sh-roadmap-desc' }, c.description)
|
||||||
@ -95,7 +143,7 @@ return view.extend({
|
|||||||
E('div', { 'class': 'spinning' })
|
E('div', { 'class': 'spinning' })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.callManageComponent(id, action).then(function(result) {
|
manageComponent(id, action).then(function(result) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
ui.addNotification(null, E('p', {}, '✅ ' + result.message), 'success');
|
ui.addNotification(null, E('p', {}, '✅ ' + result.message), 'success');
|
||||||
|
|||||||
@ -5,14 +5,31 @@
|
|||||||
|
|
||||||
var api = L.require('system-hub.api');
|
var api = L.require('system-hub.api');
|
||||||
|
|
||||||
|
// Helper: Get health status info based on score
|
||||||
|
function getHealthStatus(score) {
|
||||||
|
if (score >= 90) return { status: 'excellent', label: 'Excellent', color: '#22c55e' };
|
||||||
|
if (score >= 75) return { status: 'good', label: 'Bon', color: '#3b82f6' };
|
||||||
|
if (score >= 50) return { status: 'warning', label: 'Attention', color: '#f59e0b' };
|
||||||
|
return { status: 'critical', label: 'Critique', color: '#ef4444' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Format bytes to human-readable size
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
if (!bytes) return '0 B';
|
||||||
|
var k = 1024;
|
||||||
|
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return api.callGetHealth();
|
return api.getHealth();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var health = data;
|
var health = data;
|
||||||
var healthInfo = api.getHealthStatus(health.score || 0);
|
var healthInfo = getHealthStatus(health.score || 0);
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var view = E('div', { 'class': 'system-hub-dashboard' }, [
|
var view = E('div', { 'class': 'system-hub-dashboard' }, [
|
||||||
@ -42,8 +59,8 @@ return view.extend({
|
|||||||
E('div', { 'class': 'sh-card-body' }, [
|
E('div', { 'class': 'sh-card-body' }, [
|
||||||
E('div', { 'class': 'sh-health-grid' }, [
|
E('div', { 'class': 'sh-health-grid' }, [
|
||||||
this.renderDetailedMetric('🔲', 'CPU', health.cpu?.usage || 0, health.cpu?.status, 'Load: ' + (health.cpu?.load_1m || 'N/A')),
|
this.renderDetailedMetric('🔲', 'CPU', health.cpu?.usage || 0, health.cpu?.status, 'Load: ' + (health.cpu?.load_1m || 'N/A')),
|
||||||
this.renderDetailedMetric('💾', 'Mémoire', health.memory?.usage || 0, health.memory?.status, api.formatBytes((health.memory?.used_kb || 0) * 1024) + ' utilisés'),
|
this.renderDetailedMetric('💾', 'Mémoire', health.memory?.usage || 0, health.memory?.status, formatBytes((health.memory?.used_kb || 0) * 1024) + ' utilisés'),
|
||||||
this.renderDetailedMetric('💿', 'Stockage', health.disk?.usage || 0, health.disk?.status, api.formatBytes((health.disk?.used_kb || 0) * 1024) + ' utilisés'),
|
this.renderDetailedMetric('💿', 'Stockage', health.disk?.usage || 0, health.disk?.status, formatBytes((health.disk?.used_kb || 0) * 1024) + ' utilisés'),
|
||||||
this.renderDetailedMetric('🌡️', 'Température', health.temperature?.value || 0, health.temperature?.status, 'Zone 0: CPU'),
|
this.renderDetailedMetric('🌡️', 'Température', health.temperature?.value || 0, health.temperature?.status, 'Zone 0: CPU'),
|
||||||
this.renderDetailedMetric('🌐', 'Réseau WAN', health.network?.wan_up ? 100 : 0, health.network?.status, health.network?.wan_up ? 'Connecté' : 'Déconnecté'),
|
this.renderDetailedMetric('🌐', 'Réseau WAN', health.network?.wan_up ? 100 : 0, health.network?.status, health.network?.wan_up ? 'Connecté' : 'Déconnecté'),
|
||||||
this.renderDetailedMetric('⚙️', 'Services', ((health.services?.running || 0) / ((health.services?.running || 0) + (health.services?.failed || 0)) * 100) || 0,
|
this.renderDetailedMetric('⚙️', 'Services', ((health.services?.running || 0) / ((health.services?.running || 0) + (health.services?.failed || 0)) * 100) || 0,
|
||||||
@ -101,14 +118,11 @@ return view.extend({
|
|||||||
E('div', { 'class': 'spinning' })
|
E('div', { 'class': 'spinning' })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.callGenerateReport().then(function(result) {
|
// Stub: Report generation not yet implemented
|
||||||
|
setTimeout(function() {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
if (result.success) {
|
ui.addNotification(null, E('p', {}, '⚠️ Report generation feature coming soon'), 'info');
|
||||||
ui.addNotification(null, E('p', {}, '✅ Rapport généré: ' + result.file), 'success');
|
}, 1000);
|
||||||
} else {
|
|
||||||
ui.addNotification(null, E('p', {}, '❌ Erreur lors de la génération'), 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSaveApply: null,
|
handleSaveApply: null,
|
||||||
|
|||||||
@ -5,9 +5,27 @@
|
|||||||
|
|
||||||
var api = L.require('system-hub.api');
|
var api = L.require('system-hub.api');
|
||||||
|
|
||||||
|
// Stub: Get remote access config (planned feature)
|
||||||
|
function getRemoteConfig() {
|
||||||
|
return Promise.resolve({
|
||||||
|
rustdesk_enabled: false,
|
||||||
|
rustdesk_installed: false,
|
||||||
|
rustdesk_id: null,
|
||||||
|
allow_unattended: false,
|
||||||
|
require_approval: true,
|
||||||
|
notify_on_connect: true,
|
||||||
|
support: {
|
||||||
|
provider: 'CyberMind.fr',
|
||||||
|
email: 'support@cybermind.fr',
|
||||||
|
phone: '+33 1 23 45 67 89',
|
||||||
|
website: 'https://cybermind.fr'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return api.callGetRemote();
|
return getRemoteConfig();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
@ -135,14 +153,11 @@ return view.extend({
|
|||||||
E('div', { 'class': 'spinning' })
|
E('div', { 'class': 'spinning' })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.callStartRemoteSession(type).then(function(result) {
|
// Stub: Remote session not yet implemented
|
||||||
|
setTimeout(function() {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
if (result.success) {
|
ui.addNotification(null, E('p', {}, '⚠️ Remote session feature coming soon'), 'info');
|
||||||
ui.addNotification(null, E('p', {}, '✅ Session démarrée - ID: ' + (result.id || 'N/A')), 'success');
|
}, 1000);
|
||||||
} else {
|
|
||||||
ui.addNotification(null, E('p', {}, '❌ ' + (result.error || 'Erreur')), 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSaveApply: null,
|
handleSaveApply: null,
|
||||||
|
|||||||
@ -9,8 +9,8 @@ var api = L.require('system-hub.api');
|
|||||||
return view.extend({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
api.callStatus(),
|
api.getStatus(),
|
||||||
api.callGetSchedules()
|
Promise.resolve({ schedules: [] }) // Stub: No schedules yet
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user