secubox-openwrt/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/diagnostics.js
CyberMind-FR 31a87c5d7a feat(structure): reorganize luci-app packages into package/secubox/ + appstore migration
Major structural reorganization and feature additions:

## Folder Reorganization
- Move 17 luci-app-* packages to package/secubox/ (except luci-app-secubox core hub)
- Update all tooling to support new structure:
  - secubox-tools/quick-deploy.sh: search both locations
  - secubox-tools/validate-modules.sh: validate both directories
  - secubox-tools/fix-permissions.sh: fix permissions in both locations
  - .github/workflows/test-validate.yml: build from both paths
- Update README.md links to new package/secubox/ paths

## AppStore Migration (Complete)
- Add catalog entries for all remaining luci-app packages:
  - network-tweaks.json: Network optimization tools
  - secubox-bonus.json: Documentation & demos hub
- Total: 24 apps in AppStore catalog (22 existing + 2 new)
- New category: 'documentation' for docs/demos/tutorials

## VHost Manager v2.0 Enhancements
- Add profile activation system for Internal Services and Redirects
- Implement createVHost() API wrapper for template-based deployment
- Fix Virtual Hosts view rendering with proper LuCI patterns
- Fix RPCD backend shell script errors (remove invalid local declarations)
- Extend backend validation for nginx return directives (redirect support)
- Add section_id parameter for named VHost profiles
- Add Remove button to Redirects page for feature parity
- Update README to v2.0 with comprehensive feature documentation

## Network Tweaks Dashboard
- Close button added to component details modal

Files changed: 340+ (336 renames with preserved git history)
Packages affected: 19 luci-app, 2 secubox-app, 1 theme, 4 tools

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 14:59:38 +01:00

432 lines
15 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require dom';
'require ui';
'require fs';
'require secubox-theme/theme as Theme';
'require system-hub/api as API';
'require system-hub/theme-assets as ThemeAssets';
'require system-hub/nav as HubNav';
var shLang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: shLang });
return view.extend({
load: function() {
return Promise.all([
API.listDiagnostics(),
API.listDiagnosticProfiles()
]);
},
render: function(data) {
this.currentArchives = (data && data[0] && data[0].archives) || [];
this.profiles = (data && data[1] && data[1].profiles) || [];
this.selectedProfile = null;
var archives = this.currentArchives;
var view = E('div', { 'class': 'system-hub-dashboard' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
ThemeAssets.stylesheet('common.css'),
ThemeAssets.stylesheet('dashboard.css'),
HubNav.renderTabs('diagnostics'),
// Profile Selector
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '📋'),
'Profils de Diagnostic'
])
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', {
'class': 'profile-grid',
'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1rem;'
}, this.renderProfileButtons()),
E('div', {
'id': 'profile-description',
'class': 'sh-info-box',
'style': 'display: none; padding: 0.75rem; background: rgba(66, 153, 225, 0.1); border-left: 3px solid #4299e1; border-radius: 4px;'
})
])
]),
// Collect Diagnostics
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🔍'), 'Collecte de Diagnostics' ])
]),
E('div', { 'class': 'sh-card-body' }, [
this.renderToggle('📋', 'Inclure les Logs', 'System log, kernel log, composants', true, 'diag_logs'),
this.renderToggle('⚙️', 'Inclure la Configuration', 'network, wireless, firewall, dhcp', true, 'diag_config'),
this.renderToggle('🌐', 'Inclure Infos Réseau', 'Interfaces, routes, connexions, ARP', true, 'diag_network'),
this.renderToggle('🔐', 'Anonymiser les données', 'Masquer mots de passe et clés', true, 'diag_anonymize'),
E('div', { 'class': 'sh-btn-group' }, [
E('button', {
'class': 'sh-btn sh-btn-primary',
'click': L.bind(this.collectDiagnostics, this)
}, [ '📦 Générer Archive' ]),
E('button', {
'class': 'sh-btn sh-btn-success',
'click': L.bind(this.uploadDiagnostics, this)
}, [ '☁️ Envoyer au Support' ])
])
])
]),
// Quick Tests
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🧪'), 'Tests Rapides' ]),
E('div', { 'id': 'test-profile-info', 'style': 'font-size: 12px; color: #888; display: none;' })
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', {
'id': 'quick-tests-grid',
'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;'
}, this.renderTestButtons(null))
])
]),
// Recent Archives
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '📁'), 'Archives Récentes' ])
]),
E('div', { 'class': 'sh-card-body', 'id': 'archives-list' }, this.renderArchiveList(archives))
]),
// Test Results
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '📊'), 'Résultats des Tests' ])
]),
E('div', { 'class': 'sh-card-body', 'id': 'test-results' }, [
E('div', { 'style': 'text-align: center; padding: 40px; color: #707080;' }, [
E('div', { 'style': 'font-size: 40px; margin-bottom: 12px;' }, '🧪'),
E('div', {}, 'Lancez un test pour voir les résultats')
])
])
])
]);
return view;
},
renderToggle: function(icon, label, desc, enabled, id) {
return E('div', { 'class': 'sh-toggle' }, [
E('div', { 'class': 'sh-toggle-info' }, [
E('span', { 'class': 'sh-toggle-icon' }, icon),
E('div', {}, [
E('div', { 'class': 'sh-toggle-label' }, label),
E('div', { 'class': 'sh-toggle-desc' }, desc)
])
]),
E('div', {
'class': 'sh-toggle-switch' + (enabled ? ' active' : ''),
'id': id,
'click': function(ev) { ev.target.classList.toggle('active'); }
})
]);
},
renderArchiveList: function(archives) {
if (!archives.length) {
return [
E('div', { 'style': 'text-align:center; color:#707080; padding:24px;' }, 'Aucune archive disponible')
];
}
return archives.map(this.renderArchiveItem, this);
},
renderArchiveItem: function(archive) {
var name = archive.name || '';
var size = API.formatBytes(archive.size || 0);
var date = archive.created_at || '';
return E('div', { 'style': 'display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #1a1a24; border-radius: 8px; margin-bottom: 10px;' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 12px;' }, [
E('span', { 'style': 'font-size: 20px;' }, '📦'),
E('div', {}, [
E('div', { 'style': 'font-size: 13px; font-weight: 600;' }, name),
E('div', { 'style': 'font-size: 10px; color: #707080;' }, size + ' ' + date)
])
]),
E('div', { 'style': 'display: flex; gap: 8px;' }, [
E('button', {
'class': 'sh-btn',
'style': 'padding: 6px 10px; font-size: 10px;',
'click': L.bind(this.downloadArchive, this, name)
}, '📥 Télécharger'),
E('button', {
'class': 'sh-btn',
'style': 'padding: 6px 10px; font-size: 10px; background:#321616;',
'click': L.bind(this.deleteArchive, this, name)
}, '🗑 Supprimer')
])
]);
},
collectDiagnostics: function() {
var includeLogs = document.getElementById('diag_logs')?.classList.contains('active') || false;
var includeConfig = document.getElementById('diag_config')?.classList.contains('active') || false;
var includeNetwork = document.getElementById('diag_network')?.classList.contains('active') || false;
var anonymize = document.getElementById('diag_anonymize')?.classList.contains('active') || false;
ui.showModal(_('Collecte Diagnostics'), [
E('p', {}, 'Collecte des informations de diagnostic...'),
E('div', { 'class': 'spinning' })
]);
API.collectDiagnostics(includeLogs, includeConfig, includeNetwork, anonymize, this.selectedProfile || 'manual').then(L.bind(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', {}, ' Archive créée: ' + result.file + ' (' + API.formatBytes(result.size) + ')'), 'success');
this.refreshArchives();
} else {
ui.addNotification(null, E('p', {}, ' Erreur lors de la collecte'), 'error');
}
}, this)).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
},
uploadDiagnostics: function() {
var archives = this.currentArchives || [];
if (!archives.length) {
ui.addNotification(null, E('p', {}, 'Aucune archive à envoyer'), 'warning');
return;
}
var latest = archives[0];
ui.showModal(_('Upload Support'), [
E('p', {}, 'Envoi de ' + latest.name + ''),
E('div', { 'class': 'spinning' })
]);
API.uploadDiagnostics(latest.name).then(function(result) {
ui.hideModal();
if (result && result.success) {
ui.addNotification(null, E('p', {}, ' Archive envoyée au support (' + (result.status || 'OK') + ')'), 'info');
} else {
ui.addNotification(null, E('p', {}, ' Upload impossible: ' + ((result && result.error) || 'Erreur inconnue')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
},
runTest: function(type) {
var resultsDiv = document.getElementById('test-results');
resultsDiv.innerHTML = '<div style="text-align: center; padding: 20px;"><div class="spinning"></div><div style="margin-top: 12px;">Test en cours...</div></div>';
API.runDiagnosticTest(type).then(function(result) {
var color = result.success ? '#10b981' : '#ef4444';
var bg = result.success ? 'rgba(16,185,129,0.2)' : 'rgba(239,68,68,0.2)';
var textColor = result.success ? '#059669' : '#dc2626';
var icon = result.success ? '✅' : '❌';
resultsDiv.innerHTML = '';
resultsDiv.appendChild(E('div', { 'style': 'padding: 20px; border-radius: 8px; border: 2px solid ' + color + '; background: ' + bg }, [
E('div', { 'style': 'display:flex; align-items:center; gap:12px; margin-bottom: 16px;' }, [
E('span', { 'style': 'font-size:28px;' }, icon),
E('div', { 'style': 'font-weight:700; font-size:16px; color:' + textColor + ';' }, (result.test || type) + ' - ' + (result.success ? 'Réussi' : 'Échec'))
]),
E('pre', {
'style': 'margin:0; padding:14px; font-size:13px; line-height:1.6; white-space:pre-wrap; background:rgba(0,0,0,0.3); border-radius:6px; color:#e0e0e0; font-family:monospace; overflow-x:auto;'
}, result.output || 'Aucune sortie')
]));
}).catch(function(err) {
resultsDiv.innerHTML = '';
resultsDiv.appendChild(E('div', { 'class': 'sh-alert error' }, [
E('div', { 'class': 'sh-alert-title' }, 'Erreur'),
E('div', {}, err.message || err)
]));
});
},
downloadArchive: function(name) {
ui.showModal(_('Téléchargement…'), [
E('p', {}, 'Préparation de ' + name)
]);
API.downloadDiagnostic(name).then(function(result) {
ui.hideModal();
if (!result.success || !result.data) {
ui.addNotification(null, E('p', {}, '❌ Téléchargement impossible'), 'error');
return;
}
var link = document.createElement('a');
link.href = 'data:application/gzip;base64,' + result.data;
link.download = result.name || name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
},
deleteArchive: function(name) {
if (!confirm(_('Supprimer ') + name + ' ?')) return;
API.deleteDiagnostic(name).then(L.bind(function(result) {
if (result.success) {
ui.addNotification(null, E('p', {}, '🗑️ Archive supprimée'), 'info');
this.refreshArchives();
} else {
ui.addNotification(null, E('p', {}, '❌ Suppression impossible'), 'error');
}
}, this));
},
refreshArchives: function() {
API.listDiagnostics().then(L.bind(function(data) {
this.currentArchives = data.archives || [];
var list = document.getElementById('archives-list');
if (!list) return;
list.innerHTML = '';
(this.renderArchiveList(this.currentArchives)).forEach(function(node) {
list.appendChild(node);
});
}, this));
},
// Mapping des tests avec leurs métadonnées
testDefinitions: {
'connectivity': { icon: '🌐', label: 'Test Connectivité', desc: 'Ping WAN & DNS' },
'dns': { icon: '📡', label: 'Test DNS', desc: 'Résolution de noms' },
'latency': { icon: '⚡', label: 'Test Latence', desc: 'Ping vers Google' },
'disk': { icon: '💾', label: 'Test Disque', desc: 'Lecture/Écriture' },
'firewall': { icon: '🔒', label: 'Test Firewall', desc: 'Règles actives' },
'wifi': { icon: '📶', label: 'Test WiFi', desc: 'Signal et clients' }
},
renderTestButtons: function(profileTests) {
var self = this;
var testsToShow = [];
if (profileTests) {
// Filtrer selon les tests du profil
testsToShow = profileTests.split(',').map(function(t) { return t.trim(); });
} else {
// Afficher tous les tests par défaut
testsToShow = ['connectivity', 'dns', 'latency', 'disk', 'firewall', 'wifi'];
}
return testsToShow.map(function(testType) {
var testDef = self.testDefinitions[testType];
if (!testDef) return null;
return E('button', {
'class': 'sh-btn',
'style': 'flex-direction: column; height: auto; padding: 16px;',
'click': function() { self.runTest(testType); }
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 8px;' }, testDef.icon),
E('span', { 'style': 'font-weight: 600;' }, testDef.label),
E('span', { 'style': 'font-size: 10px; color: #707080;' }, testDef.desc)
]);
}).filter(function(btn) { return btn !== null; });
},
updateQuickTests: function(profile) {
var grid = document.getElementById('quick-tests-grid');
var profileInfo = document.getElementById('test-profile-info');
if (!grid) return;
// Vider la grille
grid.innerHTML = '';
// Rendre les nouveaux boutons
var buttons = this.renderTestButtons(profile ? profile.tests : null);
buttons.forEach(function(btn) {
grid.appendChild(btn);
});
// Mettre à jour l'info du profil
if (profileInfo) {
if (profile && profile.tests) {
var testCount = profile.tests.split(',').length;
profileInfo.textContent = '📋 ' + testCount + ' test(s) recommandé(s) pour ce profil';
profileInfo.style.display = 'block';
} else {
profileInfo.style.display = 'none';
}
}
},
renderProfileButtons: function() {
var self = this;
return (this.profiles || []).map(function(profile) {
return E('button', {
'class': 'sh-btn sh-btn-secondary profile-btn',
'data-profile': profile.name,
'style': 'display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1rem;',
'click': L.bind(self.selectProfile, self, profile.name)
}, [
E('span', { 'style': 'font-size: 1.5rem;' }, profile.icon || '📋'),
E('span', {}, profile.label || profile.name)
]);
});
},
selectProfile: function(profileName) {
var self = this;
// Highlight selected button
document.querySelectorAll('.profile-btn').forEach(function(btn) {
if (btn.dataset.profile === profileName) {
btn.classList.add('active');
btn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
btn.style.color = 'white';
} else {
btn.classList.remove('active');
btn.style.background = '';
btn.style.color = '';
}
});
// Load profile config and update toggles
API.getDiagnosticProfile(profileName).then(function(profile) {
self.selectedProfile = profileName;
// Update toggles to match profile
self.setToggle('diag_logs', profile.include_logs);
self.setToggle('diag_config', profile.include_config);
self.setToggle('diag_network', profile.include_network);
self.setToggle('diag_anonymize', profile.anonymize);
// Show description
var descBox = document.getElementById('profile-description');
if (descBox) {
descBox.style.display = 'block';
descBox.textContent = '📝 ' + (profile.description || '');
}
// Update quick tests to show only relevant tests
self.updateQuickTests(profile);
});
},
setToggle: function(id, value) {
var toggle = document.getElementById(id);
if (toggle) {
if (value == 1 || value === true) {
toggle.classList.add('active');
} else {
toggle.classList.remove('active');
}
}
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});