Ajout page d'accueil avec grille de cartes OS + recherche globale
This commit is contained in:
+69
-11
@@ -73,6 +73,23 @@
|
||||
.dot-orange { width: 6px; height: 6px; border-radius: 50%; background: #f59e0b; flex-shrink: 0; }
|
||||
.breadcrumb { font-size: 0.75rem; color: #94a3b8; display: flex; gap: 6px; align-items: center; margin-bottom: 2px; }
|
||||
.breadcrumb .current { color: #0f172a; font-weight: 500; }
|
||||
.os-card {
|
||||
border-radius: 10px; border: 1px solid #e2e8f0;
|
||||
background: white; cursor: pointer;
|
||||
transition: all 0.15s; padding: 16px;
|
||||
}
|
||||
.os-card:hover { border-color: #0f172a; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
|
||||
.os-card .icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.os-card .stat { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.os-card .sublabel { font-size: 0.6875rem; color: #94a3b8; }
|
||||
.placeholder-card { opacity: 0.6; }
|
||||
.placeholder-card:hover { opacity: 0.8; }
|
||||
.home-btn {
|
||||
padding: 5px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 500;
|
||||
cursor: pointer; transition: all 0.12s; color: #64748b;
|
||||
display: flex; align-items: center; gap: 5px;
|
||||
}
|
||||
.home-btn:hover { background: #f1f5f9; color: #0f172a; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-[#f8fafc] text-[#0f172a]">
|
||||
@@ -101,33 +118,74 @@
|
||||
</aside>
|
||||
|
||||
<!-- MAIN -->
|
||||
<main class="flex-1 p-6 max-w-5xl">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<div class="breadcrumb" id="breadcrumb"></div>
|
||||
<h1 class="text-xl font-bold" id="os-title"></h1>
|
||||
<p class="text-xs text-[#64748b] mt-0.5" id="os-sub"></p>
|
||||
<main class="flex-1 p-6 max-w-5xl" id="main-content">
|
||||
|
||||
<!-- HEADER (visible on both views) -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="home-btn" id="home-btn" onclick="showHome()" style="display:none">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/></svg>
|
||||
Accueil
|
||||
</div>
|
||||
<div>
|
||||
<div class="breadcrumb" id="breadcrumb"></div>
|
||||
<h1 class="text-xl font-bold" id="os-title"></h1>
|
||||
<p class="text-xs text-[#64748b] mt-0.5" id="os-sub"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<svg class="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-[#94a3b8]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
<input class="search-input" id="search" placeholder="Chercher une commande..." oninput="filterCommands(this.value)">
|
||||
<input class="search-input" id="search" placeholder="Chercher une commande..." oninput="if(currentSection)filterCommands(this.value);else globalSearch(this.value)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1 flex-wrap mb-3" id="tabs"></div>
|
||||
<!-- SECTION TABS (hidden on home) -->
|
||||
<div class="flex gap-1 flex-wrap mb-3" id="tabs" style="display:none"></div>
|
||||
|
||||
<div class="flex gap-3 items-center mb-3 text-xs text-[#64748b]">
|
||||
<!-- STATS BAR (hidden on home) -->
|
||||
<div class="flex gap-3 items-center mb-3 text-xs text-[#64748b]" id="stats-bar" style="display:none">
|
||||
<span id="shown">0</span>
|
||||
<span>commandes</span>
|
||||
<span>·</span>
|
||||
<span id="filter-info">Aucun filtre</span>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-[#e2e8f0] rounded-xl overflow-hidden">
|
||||
<!-- COMMANDS LIST (hidden on home) -->
|
||||
<div class="bg-white border border-[#e2e8f0] rounded-xl overflow-hidden" id="cmds-container" style="display:none">
|
||||
<div id="cmds"></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center text-[0.6875rem] text-[#94a3b8]">
|
||||
<!-- HOME CONTENT -->
|
||||
<div id="home-content">
|
||||
|
||||
<!-- Hero -->
|
||||
<div class="mb-8 text-center">
|
||||
<h2 class="text-2xl font-bold mb-2">SysCmd</h2>
|
||||
<p class="text-sm text-[#64748b] max-w-xl mx-auto">
|
||||
Référence de commandes multi-plateforme. Cliquez sur un OS pour voir ses commandes.
|
||||
</p>
|
||||
<div class="mt-3 flex justify-center gap-2 text-xs text-[#94a3b8]">
|
||||
<span class="flex items-center gap-1"><span class="dot-green inline-block"></span> Données disponibles</span>
|
||||
<span class="flex items-center gap-1"><span class="dot-orange inline-block"></span> Catégorie</span>
|
||||
<span class="flex items-center gap-1"><span class="dot-gray inline-block"></span> À venir</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global search results -->
|
||||
<div id="global-search-results" class="mb-6" style="display:none">
|
||||
<div class="bg-white border border-[#e2e8f0] rounded-xl overflow-hidden">
|
||||
<div id="global-cmds"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STATS ROW -->
|
||||
<div class="grid grid-cols-4 gap-3 mb-6 text-center" id="stats-row"></div>
|
||||
|
||||
<!-- CARD GRID -->
|
||||
<div id="card-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3"></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center text-[0.6875rem] text-[#94a3b8]" id="footer-hint">
|
||||
Cliquez sur une commande pour la copier
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* SYSCMD — arbre avec sections en sous-niveaux */
|
||||
/* SYSCMD — arbre avec sections en sous-niveaux + page d'accueil */
|
||||
function t(fr, en) { return { fr: fr, en: en }; }
|
||||
|
||||
let currentNode = null;
|
||||
let currentSection = null;
|
||||
|
||||
const TREE = {
|
||||
id: 'root', label: t('OS', 'OS'),
|
||||
@@ -35,26 +36,25 @@ const TREE = {
|
||||
]
|
||||
};
|
||||
|
||||
// ── SECTIONS ──
|
||||
const SEC = {};
|
||||
|
||||
SEC.sys = { label: t('syst\u00e8me', 'system'), commands: [
|
||||
SEC.sys = { label: t('système', 'system'), commands: [
|
||||
{ d: t('Version du noyau', 'Kernel version'), c: 'uname -r' },
|
||||
{ d: t('Version OS', 'OS version'), c: 'lsb_release -a' },
|
||||
{ d: t('Memoire dispo', 'Memory available'), c: 'free -h' },
|
||||
{ d: t("Nom d'hote", 'Hostname'), c: 'hostnamectl' },
|
||||
{ d: t('Mémoire dispo', 'Memory available'), c: 'free -h' },
|
||||
{ d: t("Nom d'hôte", 'Hostname'), c: 'hostnamectl' },
|
||||
{ d: t('Uptime', 'Uptime'), c: 'uptime' },
|
||||
{ d: t('Infos CPU', 'CPU info'), c: 'lscpu' },
|
||||
{ d: t('Date / heure', 'Date / time'), c: 'timedatectl' },
|
||||
]};
|
||||
|
||||
SEC.apt = { label: t('APT', 'APT'), commands: [
|
||||
{ d: t('Synchro depots', 'Sync repos'), c: 'sudo apt update' },
|
||||
{ d: t('Mise a jour', 'Full upgrade'), c: 'sudo apt full-upgrade -y' },
|
||||
{ d: t('Synchro dépôts', 'Sync repos'), c: 'sudo apt update' },
|
||||
{ d: t('Mise à jour', 'Full upgrade'), c: 'sudo apt full-upgrade -y' },
|
||||
{ d: t('Installer', 'Install'), c: 'sudo apt install [package]' },
|
||||
{ d: t('Purge', 'Purge'), c: 'sudo apt purge [package]' },
|
||||
{ d: t('Chercher', 'Search'), c: 'apt search [keyword]' },
|
||||
{ d: t('Paquets installes', 'Installed'), c: 'apt list --installed' },
|
||||
{ d: t('Paquets installés', 'Installed'), c: 'apt list --installed' },
|
||||
{ d: t('Fichiers fournis', 'Package files'), c: 'dpkg -L [package]' },
|
||||
]};
|
||||
|
||||
@@ -75,7 +75,7 @@ SEC.systemd = { label: t('systemd', 'systemd'), commands: [
|
||||
{ d: t('Purger logs', 'Purge logs'), c: 'sudo journalctl --vacuum-time=7d' },
|
||||
]};
|
||||
|
||||
SEC['win-sys'] = { label: t('systeme', 'system'), commands: [
|
||||
SEC['win-sys'] = { label: t('système', 'system'), commands: [
|
||||
{ d: t('(CMD) Version', '(CMD) Version'), c: 'ver' },
|
||||
{ d: t('(CMD) systeminfo', '(CMD) systeminfo'), c: 'systeminfo' },
|
||||
{ d: t('(PS) Get-ComputerInfo', '(PS) Get-ComputerInfo'), c: 'Get-ComputerInfo' },
|
||||
@@ -93,7 +93,7 @@ SEC['win-process'] = { label: t('processus', 'processes'), commands: [
|
||||
{ d: t('(PS) Get-Process', '(PS) Get-Process'), c: 'Get-Process' },
|
||||
]};
|
||||
|
||||
SEC['win-network'] = { label: t('reseau', 'network'), commands: [
|
||||
SEC['win-network'] = { label: t('réseau', 'network'), commands: [
|
||||
{ d: t('(CMD) ipconfig', '(CMD) ipconfig'), c: 'ipconfig /all' },
|
||||
{ d: t('(CMD) netstat', '(CMD) netstat'), c: 'netstat -an | findstr LISTEN' },
|
||||
{ d: t('(PS) Test-NetConnection', '(PS) Test-NetConnection'), c: 'Test-NetConnection [ip] -Port [port]' },
|
||||
@@ -151,109 +151,140 @@ SEC['solaris-zones'] = { label: t('Zones', 'Zones'), commands: [
|
||||
{ d: t('Console', 'Console'), c: 'sudo zlogin -C [zone]' },
|
||||
]};
|
||||
|
||||
// ── COLLECT ALL COMMANDS (for global search) ──
|
||||
var ALL_COMMANDS = [];
|
||||
(function() {
|
||||
var seen = {};
|
||||
for (var k in SEC) {
|
||||
if (SEC.hasOwnProperty(k)) {
|
||||
SEC[k].commands.forEach(function(cmd) {
|
||||
var key = cmd.c;
|
||||
if (!seen[key]) { seen[key] = true; ALL_COMMANDS.push(cmd); }
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// ── HELPERS ──
|
||||
function findNode(tree, id) {
|
||||
if (tree.id === id) return tree;
|
||||
for (var i = 0; i < (tree.children || []).length; i++) {
|
||||
var f = findNode(tree.children[i], id);
|
||||
if (f) return f;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectLeafNodes(node, out) {
|
||||
if (node.children) {
|
||||
for (var i = 0; i < node.children.length; i++) collectLeafNodes(node.children[i], out);
|
||||
} else {
|
||||
out.push(node);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function countNodeCmds(node) {
|
||||
if (node.sectionIds) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < node.sectionIds.length; i++) {
|
||||
if (SEC[node.sectionIds[i]]) n += SEC[node.sectionIds[i]].commands.length;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── RENDER TREE ──
|
||||
function renderTree() {
|
||||
const el = document.getElementById('tree');
|
||||
var el = document.getElementById('tree');
|
||||
el.innerHTML = '';
|
||||
renderNode(el, TREE, 0);
|
||||
}
|
||||
|
||||
function renderNode(container, node, depth) {
|
||||
const isBranch = node.children && node.children.length > 0;
|
||||
const isVersion = !!node.sectionIds;
|
||||
const isEmpty = !isBranch && !isVersion;
|
||||
|
||||
const div = document.createElement('div');
|
||||
var isBranch = node.children && node.children.length > 0;
|
||||
var isVersion = !!node.sectionIds;
|
||||
var div = document.createElement('div');
|
||||
|
||||
if (depth === 0) {
|
||||
const label = document.createElement('div');
|
||||
var label = document.createElement('div');
|
||||
label.className = 'group-label';
|
||||
label.textContent = node.label.fr;
|
||||
div.appendChild(label);
|
||||
} else {
|
||||
const row = document.createElement('div');
|
||||
let cls = 'sidebar-link';
|
||||
var row = document.createElement('div');
|
||||
var cls = 'sidebar-link';
|
||||
if (depth === 2) cls += ' sidebar-link-3';
|
||||
if (depth === 3) cls += ' sidebar-link-4';
|
||||
if (node === currentNode) cls += ' active';
|
||||
row.className = cls;
|
||||
|
||||
// Toggle or spacer
|
||||
if (isBranch) {
|
||||
const tgl = document.createElement('span');
|
||||
var tgl = document.createElement('span');
|
||||
tgl.className = 'tree-toggle open';
|
||||
tgl.innerHTML = '<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 18l6-6-6-6"/></svg>';
|
||||
row.appendChild(tgl);
|
||||
} else {
|
||||
const sp = document.createElement('span');
|
||||
var sp = document.createElement('span');
|
||||
sp.style.cssText = 'width:16px;flex-shrink:0;display:inline-block';
|
||||
row.appendChild(sp);
|
||||
}
|
||||
|
||||
// Dot
|
||||
const dot = document.createElement('span');
|
||||
if (isVersion || (node._parent && node._parent.sectionIds)) dot.className = 'dot-green';
|
||||
var dot = document.createElement('span');
|
||||
if (isVersion || node._parent) dot.className = 'dot-green';
|
||||
else if (isBranch) dot.className = 'dot-orange';
|
||||
else dot.className = 'dot-gray';
|
||||
row.appendChild(dot);
|
||||
|
||||
// Label
|
||||
const lbl = document.createElement('span');
|
||||
var lbl = document.createElement('span');
|
||||
lbl.textContent = node.label.fr;
|
||||
row.appendChild(lbl);
|
||||
|
||||
// Count (only for non-section leaves that have data)
|
||||
let cnt = 0;
|
||||
if (node.sectionIds) {
|
||||
cnt = node.sectionIds.reduce(function(a, id) { return a + (SEC[id] ? SEC[id].commands.length : 0); }, 0);
|
||||
}
|
||||
var cnt = countNodeCmds(node) || (node._parent && node._parent.sectionIds ? SEC[node._sectionId] ? SEC[node._sectionId].commands.length : 0 : 0);
|
||||
if (cnt > 0) {
|
||||
const b = document.createElement('span');
|
||||
var b = document.createElement('span');
|
||||
b.className = 'ml-auto text-[0.625rem] text-[#94a3b8]';
|
||||
b.textContent = '' + cnt;
|
||||
row.appendChild(b);
|
||||
}
|
||||
|
||||
// Click
|
||||
row.onclick = function(e) {
|
||||
if (isBranch && (e.target.closest('.tree-toggle') || e.target.closest('[data-toggle]'))) {
|
||||
const tg = row.querySelector('.tree-toggle');
|
||||
var tg = row.querySelector('.tree-toggle');
|
||||
if (tg) { tg.classList.toggle('open'); row.nextElementSibling.classList.toggle('open'); e.stopPropagation(); }
|
||||
return;
|
||||
}
|
||||
if (isBranch) return; // only leaf nodes select
|
||||
selectNode(node);
|
||||
};
|
||||
|
||||
div.appendChild(row);
|
||||
}
|
||||
|
||||
// Enfants
|
||||
if (isBranch) {
|
||||
const kids = document.createElement('div');
|
||||
var kids = document.createElement('div');
|
||||
kids.className = 'tree-children open';
|
||||
for (const ch of node.children) {
|
||||
ch._parent = node;
|
||||
renderNode(kids, ch, depth + 1);
|
||||
for (var i = 0; i < node.children.length; i++) {
|
||||
node.children[i]._parent = node;
|
||||
renderNode(kids, node.children[i], depth + 1);
|
||||
}
|
||||
div.appendChild(kids);
|
||||
}
|
||||
|
||||
// Si c'est une version avec sectionIds, on ajoute les sections comme enfants
|
||||
if (isVersion && node.sectionIds) {
|
||||
const kids = document.createElement('div');
|
||||
var kids = document.createElement('div');
|
||||
kids.className = 'tree-children';
|
||||
|
||||
// On déplie si c'est le noeud actif
|
||||
if (node === currentNode || (currentNode && currentNode._parent === node)) {
|
||||
kids.className = 'tree-children open';
|
||||
}
|
||||
|
||||
node.sectionIds.forEach(function(sid) {
|
||||
const sec = SEC[sid];
|
||||
if (!sec) return;
|
||||
const snode = { id: node.id + '-' + sid, label: sec.label, _sectionId: sid, _parent: node };
|
||||
for (var j = 0; j < node.sectionIds.length; j++) {
|
||||
var sid = node.sectionIds[j];
|
||||
var sec = SEC[sid];
|
||||
if (!sec) continue;
|
||||
var snode = { id: node.id + '-' + sid, label: sec.label, _sectionId: sid, _parent: node };
|
||||
renderNode(kids, snode, depth + 1);
|
||||
});
|
||||
}
|
||||
div.appendChild(kids);
|
||||
}
|
||||
|
||||
@@ -263,85 +294,192 @@ function renderNode(container, node, depth) {
|
||||
// ── SELECT NODE ──
|
||||
function selectNode(node) {
|
||||
currentNode = node;
|
||||
currentSection = null;
|
||||
renderTree();
|
||||
|
||||
const titleEl = document.getElementById('os-title');
|
||||
const subEl = document.getElementById('os-sub');
|
||||
const cmdsEl = document.getElementById('cmds');
|
||||
const searchEl = document.getElementById('search');
|
||||
searchEl.value = '';
|
||||
document.getElementById('search').value = '';
|
||||
hideHome();
|
||||
document.getElementById('home-btn').style.display = 'flex';
|
||||
|
||||
var titleEl = document.getElementById('os-title');
|
||||
var subEl = document.getElementById('os-sub');
|
||||
var cmdsEl = document.getElementById('cmds');
|
||||
|
||||
// Cas 1: c'est une section (a _sectionId)
|
||||
if (node._sectionId) {
|
||||
const sec = SEC[node._sectionId];
|
||||
var sec = SEC[node._sectionId];
|
||||
if (sec) {
|
||||
titleEl.textContent = sec.label.fr;
|
||||
titleEl.textContent = node._parent.label.fr + ' > ' + sec.label.fr;
|
||||
subEl.textContent = sec.commands.length + ' commandes';
|
||||
showCmds(sec.commands);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Cas 2: c'est une version ou OS avec sectionIds (on affiche la première section)
|
||||
if (node.sectionIds && node.sectionIds.length > 0) {
|
||||
const sid = node.sectionIds[0];
|
||||
const sec = SEC[sid];
|
||||
var sid = node.sectionIds[0];
|
||||
var sec = SEC[sid];
|
||||
if (sec) {
|
||||
titleEl.textContent = node.label.fr;
|
||||
subEl.textContent = sec.commands.length + ' commandes';
|
||||
subEl.textContent = node.sectionIds.length + ' sections · ' + countNodeCmds(node) + ' commandes';
|
||||
showCmds(sec.commands);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Cas 3: pas de données
|
||||
titleEl.textContent = node.label.fr;
|
||||
subEl.textContent = 'Aucune donnee';
|
||||
subEl.textContent = 'Aucune donnée';
|
||||
cmdsEl.innerHTML = '<div class="empty-state"><h3>' + node.label.fr + '</h3><p>Pas encore de commandes pour cet OS.</p></div>';
|
||||
}
|
||||
|
||||
function showCmds(commands) {
|
||||
const el = document.getElementById('cmds');
|
||||
const sv = document.getElementById('search').value.trim().toLowerCase();
|
||||
// ── SHOW/HIDE HOME ──
|
||||
function showHome() {
|
||||
currentSection = null;
|
||||
currentNode = null;
|
||||
renderTree();
|
||||
document.getElementById('search').value = '';
|
||||
document.getElementById('filter-info').textContent = 'Aucun filtre';
|
||||
document.getElementById('home-content').style.display = 'block';
|
||||
document.getElementById('tabs').style.display = 'none';
|
||||
document.getElementById('stats-bar').style.display = 'none';
|
||||
document.getElementById('cmds-container').style.display = 'none';
|
||||
document.getElementById('home-btn').style.display = 'none';
|
||||
document.getElementById('os-title').textContent = 'SysCmd';
|
||||
document.getElementById('os-sub').textContent = '';
|
||||
document.getElementById('breadcrumb').innerHTML = '';
|
||||
document.getElementById('footer-hint').textContent = 'Cliquez sur un OS pour explorer ses commandes';
|
||||
renderHome();
|
||||
}
|
||||
|
||||
let cmds = commands;
|
||||
function hideHome() {
|
||||
document.getElementById('home-content').style.display = 'none';
|
||||
document.getElementById('tabs').style.display = 'flex';
|
||||
document.getElementById('stats-bar').style.display = 'flex';
|
||||
document.getElementById('cmds-container').style.display = 'block';
|
||||
document.getElementById('footer-hint').textContent = 'Cliquez sur une commande pour la copier';
|
||||
}
|
||||
|
||||
// ── RENDER HOME ──
|
||||
function renderHome() {
|
||||
renderStats();
|
||||
renderCards();
|
||||
}
|
||||
|
||||
function renderStats() {
|
||||
var totalCmds = ALL_COMMANDS.length;
|
||||
var totalSections = 0;
|
||||
for (var k in SEC) if (SEC.hasOwnProperty(k)) totalSections++;
|
||||
var totalOS = 0;
|
||||
var leaves = collectLeafNodes(TREE, []);
|
||||
for (var i = 0; i < leaves.length; i++) if (leaves[i].sectionIds) totalOS++;
|
||||
|
||||
var el = document.getElementById('stats-row');
|
||||
el.innerHTML =
|
||||
'<div class="bg-white border border-[#e2e8f0] rounded-xl p-3"><div class="stat-value text-[#0f172a]">' + totalCmds + '</div><div class="text-xs text-[#94a3b8]">commandes</div></div>' +
|
||||
'<div class="bg-white border border-[#e2e8f0] rounded-xl p-3"><div class="stat-value text-[#0f172a]">' + totalSections + '</div><div class="text-xs text-[#94a3b8]">sections</div></div>' +
|
||||
'<div class="bg-white border border-[#e2e8f0] rounded-xl p-3"><div class="stat-value text-[#0f172a]">' + totalOS + '</div><div class="text-xs text-[#94a3b8]">OS renseignés</div></div>' +
|
||||
'<div class="bg-white border border-[#e2e8f0] rounded-xl p-3"><div class="stat-value text-[#94a3b8]">' + (leaves.length - totalOS) + '</div><div class="text-xs text-[#94a3b8]">placeholders</div></div>';
|
||||
}
|
||||
|
||||
var OS_CARDS = [
|
||||
{ id: 'debian', label: 'Debian', icon: 'D', color: '#0f172a', desc: t('Distribution stable de référence', 'Stable reference distribution') },
|
||||
{ id: 'ubuntu', label: 'Ubuntu', icon: 'U', color: '#dc2626', desc: t('Basé sur Debian avec versions LTS', 'Debian-based with LTS releases'), isGroup: true },
|
||||
{ id: 'oracle', label: 'Oracle Linux', icon: 'O', color: '#b9141e', desc: t('Linux Oracle avec UEK + SELinux', 'Oracle Linux with UEK + SELinux') },
|
||||
{ id: 'windows', label: 'Windows', icon: 'W', color: '#0078d4', desc: t('CMD + PowerShell client/server', 'CMD + PowerShell client/server'), isGroup: true },
|
||||
{ id: 'solaris', label: 'Solaris', icon: 'S', color: '#b46400', desc: t('Solaris / illumos avec ZFS + DTrace', 'Solaris / illumos with ZFS + DTrace') },
|
||||
{ id: 'rhel', label: 'RHEL / CentOS', icon: 'R', color: '#94a3b8', desc: t('Enterprise Linux (à venir)', 'Enterprise Linux (pending)'), placeholder: true },
|
||||
{ id: 'fedora', label: 'Fedora', icon: 'F', color: '#94a3b8', desc: t('Basé sur RHEL (à venir)', 'RHEL-based (pending)'), placeholder: true },
|
||||
{ id: 'freebsd', label: 'FreeBSD', icon: 'B', color: '#94a3b8', desc: t('BSD Unix (à venir)', 'BSD Unix (pending)'), placeholder: true },
|
||||
{ id: 'macos', label: 'macOS', icon: 'M', color: '#94a3b8', desc: t('macOS (à venir)', 'macOS (pending)'), placeholder: true },
|
||||
{ id: 'docker', label: 'Docker', icon: 'D', color: '#94a3b8', desc: t('Containers et orchestration (à venir)', 'Containers and orchestration (pending)'), placeholder: true },
|
||||
{ id: 'cisco', label: 'Cisco IOS', icon: 'C', color: '#94a3b8', desc: t('Réseau Cisco (à venir)', 'Cisco networking (pending)'), placeholder: true },
|
||||
{ id: 'mysql', label: 'MySQL / MariaDB', icon: 'S', color: '#94a3b8', desc: t('Bases de données SQL (à venir)', 'SQL databases (pending)'), placeholder: true },
|
||||
];
|
||||
|
||||
function renderCards() {
|
||||
var el = document.getElementById('card-grid');
|
||||
el.innerHTML = '';
|
||||
OS_CARDS.forEach(function(card) {
|
||||
var div = document.createElement('div');
|
||||
var isPlaceholder = card.placeholder;
|
||||
var node = findNode(TREE, card.id);
|
||||
var cnt = node ? countNodeCmds(node) : 0;
|
||||
if (cnt > 0) isPlaceholder = false;
|
||||
|
||||
div.className = 'os-card' + (isPlaceholder ? ' placeholder-card' : '');
|
||||
|
||||
var iconBg = isPlaceholder ? 'bg-[#e2e8f0] text-[#94a3b8]' : 'text-white';
|
||||
div.innerHTML =
|
||||
'<div class="flex items-start gap-3 mb-3">' +
|
||||
'<div class="icon ' + iconBg + '" style="' + (isPlaceholder ? '' : 'background:' + card.color) + '">' +
|
||||
'<span class="font-bold ' + (isPlaceholder ? 'text-sm' : 'text-sm') + '">' + card.icon + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="flex-1 min-w-0">' +
|
||||
'<div class="font-semibold text-sm truncate">' + (card.isGroup ? card.label + ' →' : card.label) + '</div>' +
|
||||
'<div class="sublabel">' + card.desc.fr + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="flex items-center gap-2 text-xs">' +
|
||||
(cnt > 0 ? '<span class="font-semibold text-[#0f172a]">' + cnt + '</span><span class="text-[#94a3b8]">commandes</span>' : '<span class="text-[#cbd5e1]">' + (isPlaceholder ? 'À venir' : 'via sidebar') + '</span>') +
|
||||
'</div>';
|
||||
|
||||
div.onclick = function() {
|
||||
if (card.isGroup) {
|
||||
// Expand group in sidebar
|
||||
var n = findNode(TREE, card.id);
|
||||
if (n && n.children && n.children.length > 0) {
|
||||
selectNode(n.children[0]);
|
||||
}
|
||||
} else if (node) {
|
||||
selectNode(node);
|
||||
}
|
||||
};
|
||||
|
||||
el.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
// ── SHOW COMMANDS ──
|
||||
function showCmds(commands) {
|
||||
var el = document.getElementById('cmds');
|
||||
var sv = document.getElementById('search').value.trim().toLowerCase();
|
||||
|
||||
var cmds = commands;
|
||||
if (sv) {
|
||||
cmds = cmds.filter(function(x) {
|
||||
return x.d.fr.toLowerCase().includes(sv) || x.d.en.toLowerCase().includes(sv) || x.c.toLowerCase().includes(sv);
|
||||
return x.d.fr.toLowerCase().indexOf(sv) !== -1 || x.d.en.toLowerCase().indexOf(sv) !== -1 || x.c.toLowerCase().indexOf(sv) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('shown').textContent = '' + cmds.length;
|
||||
|
||||
if (cmds.length === 0) {
|
||||
el.innerHTML = '<div class="p-8 text-center text-sm text-[#94a3b8]">Aucune commande trouvee</div>';
|
||||
el.innerHTML = '<div class="p-8 text-center text-sm text-[#94a3b8]">Aucune commande trouvée</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
el.innerHTML = '';
|
||||
cmds.forEach(function(x, i) {
|
||||
const row = document.createElement('div');
|
||||
for (var i = 0; i < cmds.length; i++) {
|
||||
var x = cmds[i];
|
||||
var row = document.createElement('div');
|
||||
row.className = 'cmd-row' + (i % 2 === 0 ? ' bg-[#fafbfc]' : '');
|
||||
row.innerHTML = '<div class="cmd-desc">' + x.d.fr + '</div><div class="cmd-text"><span class="copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></span><span>' + x.c + '</span></div>';
|
||||
row.querySelector('.cmd-text').onclick = function() {
|
||||
navigator.clipboard.writeText(x.c).then(function() {
|
||||
var icon = row.querySelector('.copy-icon');
|
||||
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
setTimeout(function() { icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>'; }, 1200);
|
||||
});
|
||||
};
|
||||
row.querySelector('.cmd-text').onclick = (function(cmd) {
|
||||
return function() {
|
||||
navigator.clipboard.writeText(cmd).then(function() {
|
||||
var icon = row.querySelector('.copy-icon');
|
||||
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
setTimeout(function() { icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>'; }, 1200);
|
||||
});
|
||||
};
|
||||
})(x.c);
|
||||
el.appendChild(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── SEARCH ──
|
||||
function filterCommands(val) {
|
||||
const el = document.getElementById('filter-info');
|
||||
if (val.trim()) {
|
||||
el.textContent = 'Filtre: "' + val + '"';
|
||||
} else {
|
||||
el.textContent = 'Aucun filtre';
|
||||
}
|
||||
var el = document.getElementById('filter-info');
|
||||
el.textContent = val.trim() ? 'Filtre: "' + val + '"' : 'Aucun filtre';
|
||||
if (currentNode && currentNode._sectionId) {
|
||||
showCmds(SEC[currentNode._sectionId].commands);
|
||||
} else if (currentNode && currentNode.sectionIds && currentNode.sectionIds.length > 0) {
|
||||
@@ -349,15 +487,62 @@ function filterCommands(val) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── GLOBAL SEARCH ──
|
||||
function globalSearch(val) {
|
||||
var el = document.getElementById('global-search-results');
|
||||
var cmdsEl = document.getElementById('global-cmds');
|
||||
var sv = val.trim().toLowerCase();
|
||||
|
||||
if (!sv) {
|
||||
el.style.display = 'none';
|
||||
document.getElementById('card-grid').style.display = 'grid';
|
||||
document.getElementById('stats-row').style.display = 'grid';
|
||||
return;
|
||||
}
|
||||
|
||||
el.style.display = 'block';
|
||||
document.getElementById('card-grid').style.display = 'none';
|
||||
document.getElementById('stats-row').style.display = 'none';
|
||||
|
||||
// Search across all commands
|
||||
var results = [];
|
||||
for (var i = 0; i < ALL_COMMANDS.length; i++) {
|
||||
var x = ALL_COMMANDS[i];
|
||||
if (x.d.fr.toLowerCase().indexOf(sv) !== -1 || x.d.en.toLowerCase().indexOf(sv) !== -1 || x.c.toLowerCase().indexOf(sv) !== -1) {
|
||||
results.push(x);
|
||||
}
|
||||
}
|
||||
|
||||
cmdsEl.innerHTML = '';
|
||||
if (results.length === 0) {
|
||||
cmdsEl.innerHTML = '<div class="p-8 text-center text-sm text-[#94a3b8]">Aucune commande trouvée</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Header stat
|
||||
var header = document.createElement('div');
|
||||
header.className = 'px-4 py-2 text-xs text-[#94a3b8] border-b border-[#f1f5f9]';
|
||||
header.textContent = results.length + ' résultat(s) dans toutes les sections';
|
||||
cmdsEl.appendChild(header);
|
||||
|
||||
for (var j = 0; j < results.length; j++) {
|
||||
var x = results[j];
|
||||
var row = document.createElement('div');
|
||||
row.className = 'cmd-row' + (j % 2 === 0 ? ' bg-[#fafbfc]' : '');
|
||||
row.innerHTML = '<div class="cmd-desc">' + x.d.fr + '</div><div class="cmd-text"><span class="copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></span><span>' + x.c + '</span></div>';
|
||||
row.querySelector('.cmd-text').onclick = (function(cmd) {
|
||||
return function() {
|
||||
navigator.clipboard.writeText(cmd).then(function() {
|
||||
var icon = row.querySelector('.copy-icon');
|
||||
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
setTimeout(function() { icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>'; }, 1200);
|
||||
});
|
||||
};
|
||||
})(x.c);
|
||||
cmdsEl.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
// ── INIT ──
|
||||
renderTree();
|
||||
selectNode(findNode(TREE, 'ubuntu-2004'));
|
||||
|
||||
function findNode(tree, id) {
|
||||
if (tree.id === id) return tree;
|
||||
for (const c of (tree.children || [])) {
|
||||
const f = findNode(c, id);
|
||||
if (f) return f;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
showHome();
|
||||
|
||||
Reference in New Issue
Block a user