Initial commit : arborescence OS avec commandes

This commit is contained in:
2026-08-14 13:38:50 +02:00
commit c1c6b0b61e
2 changed files with 501 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SysCmd — Command Reference</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; -webkit-font-smoothing: antialiased; line-height: 1.5; }
.mono { font-family: 'JetBrains Mono', monospace; }
.sidebar-link {
display: flex; align-items: center; gap: 6px;
padding: 4px 10px; border-radius: 6px;
font-size: 0.8125rem; font-weight: 500;
cursor: pointer; user-select: none; transition: all 0.12s;
}
.sidebar-link.active { background: #0f172a; color: white; }
.sidebar-link:not(.active) { color: #475569; }
.sidebar-link:not(.active):hover { background: #f1f5f9; color: #0f172a; }
.sidebar-link-3 { padding-left: 48px; font-size: 0.75rem; }
.sidebar-link-4 { padding-left: 64px; font-size: 0.6875rem; font-weight: 400; }
.tree-toggle {
display: inline-flex; align-items: center; justify-content: center;
width: 16px; height: 16px; flex-shrink: 0;
transition: transform 0.15s; color: #94a3b8;
}
.tree-toggle.open { transform: rotate(90deg); }
.tree-children { display: none; }
.tree-children.open { display: block; }
.cmd-row {
display: grid;
grid-template-columns: 190px 1fr;
align-items: start; padding: 4px 12px;
border-radius: 5px; font-size: 0.8125rem;
transition: background 0.1s; gap: 8px;
}
.cmd-row:hover { background: #f1f5f9; }
.cmd-row .cmd-desc { color: #475569; font-weight: 500; line-height: 1.4; }
.cmd-row .cmd-text {
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem; color: #0f172a;
cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
line-height: 1.4; word-break: break-all;
}
.cmd-row .cmd-text:hover .copy-icon { opacity: 1; }
.copy-icon { opacity: 0; width: 14px; height: 14px; flex-shrink: 0; color: #94a3b8; transition: opacity 0.12s; }
.search-input {
width: 100%; padding: 7px 12px 7px 34px;
border-radius: 8px; font-size: 0.8125rem;
outline: none; border: 1.5px solid #e2e8f0;
background: white; transition: border-color 0.12s;
}
.search-input:focus { border-color: #0f172a; }
.section-tab {
padding: 4px 10px; border-radius: 5px;
font-size: 0.6875rem; font-weight: 500;
cursor: pointer; transition: all 0.12s;
border: 1px solid #e2e8f0; color: #64748b; white-space: nowrap;
}
.section-tab.active { background: #0f172a; color: white; border-color: #0f172a; }
.section-tab:hover:not(.active) { background: #f1f5f9; }
.group-label {
font-size: 0.625rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.05em; color: #94a3b8; padding: 8px 12px 4px;
}
.empty-state { padding: 40px 16px; max-width: 360px; margin: 0 auto; text-align: center; }
.empty-state h3 { font-weight: 600; font-size: 0.9375rem; margin-bottom: 4px; color: #0f172a; }
.empty-state p { font-size: 0.8125rem; color: #94a3b8; line-height: 1.5; }
.dot-green { width: 6px; height: 6px; border-radius: 50%; background: #22c55e; flex-shrink: 0; }
.dot-gray { width: 6px; height: 6px; border-radius: 50%; background: #cbd5e1; flex-shrink: 0; }
.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; }
</style>
</head>
<body class="bg-[#f8fafc] text-[#0f172a]">
<div class="flex min-h-screen">
<!-- SIDEBAR -->
<aside class="hidden lg:flex flex-col w-56 border-r py-3 bg-white border-[#e2e8f0] overflow-y-auto">
<div class="flex items-center gap-2.5 px-3 pb-3 mb-2 border-b border-[#f1f5f9]">
<div class="w-7 h-7 bg-[#0f172a] rounded-lg flex items-center justify-center">
<svg class="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
</svg>
</div>
<span class="font-bold text-sm">SysCmd</span>
</div>
<div id="tree" class="flex-1 overflow-y-auto"></div>
<div class="pt-3 border-t border-[#f1f5f9] mt-auto">
<div class="flex items-center gap-2 px-3 py-2">
<div class="w-7 h-7 bg-[#e2e8f0] rounded-full flex items-center justify-center text-xs font-semibold text-[#64748b]">LK</div>
<div class="text-xs"><div class="font-medium text-[#0f172a]">Laurent</div><div class="text-[#94a3b8]">sysadmin</div></div>
</div>
</div>
</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>
</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)">
</div>
</div>
<div class="flex gap-1 flex-wrap mb-3" id="tabs"></div>
<div class="flex gap-3 items-center mb-3 text-xs text-[#64748b]">
<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">
<div id="cmds"></div>
</div>
<div class="mt-4 text-center text-[0.6875rem] text-[#94a3b8]">
Cliquez sur une commande pour la copier
</div>
</main>
</div>
<script src="script.js"></script>
</body>
</html>
+363
View File
@@ -0,0 +1,363 @@
/* SYSCMD — arbre avec sections en sous-niveaux */
function t(fr, en) { return { fr: fr, en: en }; }
let currentNode = null;
const TREE = {
id: 'root', label: t('OS', 'OS'),
children: [
{ id: 'linux', label: t('Linux', 'Linux'), children: [
{ id: 'ubuntu', label: t('Ubuntu', 'Ubuntu'), children: [
{ id: 'ubuntu-2004', label: t('20.04', '20.04'), osKey: 'ubuntu', sectionIds: ['sys','apt','users'] },
{ id: 'ubuntu-2204', label: t('22.04', '22.04'), osKey: 'ubuntu', sectionIds: ['sys','apt','users'] },
{ id: 'ubuntu-2404', label: t('24.04', '24.04'), osKey: 'ubuntu', sectionIds: ['sys','apt','users','systemd'] },
]},
{ id: 'debian', label: t('Debian', 'Debian'), osKey: 'debian', sectionIds: ['sys','apt','users','systemd'] },
{ id: 'oracle', label: t('Oracle Linux', 'Oracle Linux'), osKey: 'oracle', sectionIds: ['oracle-dnf','oracle-spec','oracle-selinux','oracle-fw','systemd'] },
{ id: 'rhel', label: t('RHEL / CentOS', 'RHEL / CentOS') },
{ id: 'fedora', label: t('Fedora', 'Fedora') },
]},
{ id: 'windows', label: t('Windows', 'Windows'), children: [
{ id: 'win-client', label: t('Client', 'Client'), children: [
{ id: 'win-xp', label: t('XP', 'XP') },
{ id: 'win-10', label: t('10', '10'), osKey: 'windows', sectionIds: ['win-sys','win-files','win-process','win-network'] },
{ id: 'win-11', label: t('11', '11'), osKey: 'windows', sectionIds: ['win-sys','win-files','win-process','win-network'] },
]},
{ id: 'win-server', label: t('Server', 'Server'), children: [
{ id: 'win-2016', label: t('2016', '2016'), osKey: 'windows-server', sectionIds: ['win-sys','win-files','win-process','win-network'] },
{ id: 'win-2019', label: t('2019', '2019'), osKey: 'windows-server', sectionIds: ['win-sys','win-files','win-process','win-network'] },
]},
]},
{ id: 'solaris', label: t('Solaris / illumos', 'Solaris / illumos'), osKey: 'solaris', sectionIds: ['solaris-ips','solaris-smf','solaris-zfs','solaris-zones'] },
{ id: 'macos', label: t('macOS', 'macOS') },
{ id: 'docker', label: t('Docker', 'Docker') },
{ id: 'other', label: t('Other', 'Other') },
]
};
// ── SECTIONS ──
const SEC = {};
SEC.sys = { label: t('syst\u00e8me', '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('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('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('Fichiers fournis', 'Package files'), c: 'dpkg -L [package]' },
]};
SEC.users = { label: t('utilisateurs', 'users'), commands: [
{ d: t('Ajouter', 'Add user'), c: 'sudo adduser [name]' },
{ d: t('Supprimer', 'Delete user'), c: 'sudo deluser --remove-home [name]' },
{ d: t('Groupe sudo', 'Sudo group'), c: 'sudo usermod -aG sudo [name]' },
{ d: t('Permissions', 'Permissions'), c: 'chmod 755 [file]' },
{ d: t('Root', 'Root shell'), c: 'sudo -i' },
]};
SEC.systemd = { label: t('systemd', 'systemd'), commands: [
{ d: t('Start/Stop', 'Start/Stop'), c: 'sudo systemctl start|stop [service]' },
{ d: t('Restart', 'Restart'), c: 'sudo systemctl restart [service]' },
{ d: t('Enable', 'Enable'), c: 'sudo systemctl enable [service]' },
{ d: t('Statut', 'Status'), c: 'systemctl status [service]' },
{ d: t('Logs', 'Logs'), c: 'journalctl -u [service] -n 50' },
{ d: t('Purger logs', 'Purge logs'), c: 'sudo journalctl --vacuum-time=7d' },
]};
SEC['win-sys'] = { label: t('systeme', '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' },
]};
SEC['win-files'] = { label: t('fichiers', 'files'), commands: [
{ d: t('(CMD) dir', '(CMD) dir'), c: 'dir /s' },
{ d: t('(CMD) rmdir', '(CMD) rmdir'), c: 'rmdir /s [folder]' },
{ d: t('(PS) Get-ChildItem', '(PS) Get-ChildItem'), c: 'Get-ChildItem -Recurse -Filter *.txt' },
]};
SEC['win-process'] = { label: t('processus', 'processes'), commands: [
{ d: t('(CMD) tasklist', '(CMD) tasklist'), c: 'tasklist' },
{ d: t('(CMD) taskkill', '(CMD) taskkill'), c: 'taskkill /IM notepad.exe /F' },
{ d: t('(PS) Get-Process', '(PS) Get-Process'), c: 'Get-Process' },
]};
SEC['win-network'] = { label: t('reseau', '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]' },
]};
SEC['oracle-dnf'] = { label: t('DNF', 'DNF'), commands: [
{ d: t('Synchro', 'Sync'), c: 'sudo dnf check-update' },
{ d: t('Upgrade', 'Upgrade'), c: 'sudo dnf upgrade -y' },
{ d: t('Install', 'Install'), c: 'sudo dnf install [package]' },
{ d: t('Remove', 'Remove'), c: 'sudo dnf remove [package]' },
]};
SEC['oracle-spec'] = { label: t('Oracle spec.', 'Oracle spec.'), commands: [
{ d: t('Ksplice status', 'Ksplice status'), c: 'sudo uptrack-status' },
{ d: t('Ksplice upgrade', 'Ksplice upgrade'), c: 'sudo uptrack-upgrade' },
{ d: t('Changer noyau', 'Switch kernel'), c: 'sudo grubby --set-default /boot/vmlinuz-[ver]' },
]};
SEC['oracle-selinux'] = { label: t('SELinux', 'SELinux'), commands: [
{ d: t('Statut', 'Status'), c: 'getenforce' },
{ d: t('Permissif', 'Permissive'), c: 'sudo setenforce 0' },
{ d: t('Logs AVC', 'AVC logs'), c: 'sudo ausearch -m avc -ts recent' },
]};
SEC['oracle-fw'] = { label: t('firewalld', 'firewalld'), commands: [
{ d: t('Status', 'Status'), c: 'sudo firewall-cmd --state' },
{ d: t('Ouvrir port', 'Open port'), c: 'sudo firewall-cmd --permanent --add-port=22/tcp' },
]};
SEC['solaris-ips'] = { label: t('IPS', 'IPS'), commands: [
{ d: t('Refresh', 'Refresh'), c: 'sudo pkg refresh' },
{ d: t('Update', 'Update'), c: 'sudo pkg update' },
{ d: t('Install', 'Install'), c: 'sudo pkg install [package]' },
{ d: t('Remove', 'Remove'), c: 'sudo pkg uninstall [package]' },
]};
SEC['solaris-smf'] = { label: t('SMF', 'SMF'), commands: [
{ d: t('Liste', 'List'), c: 'svcs -a' },
{ d: t('Enable', 'Enable'), c: 'sudo svcadm enable [service]' },
{ d: t('Disable', 'Disable'), c: 'sudo svcadm disable [service]' },
{ d: t('Restart', 'Restart'), c: 'sudo svcadm restart [service]' },
]};
SEC['solaris-zfs'] = { label: t('ZFS', 'ZFS'), commands: [
{ d: t('Pool status', 'Pool status'), c: 'zpool status' },
{ d: t('Scrub', 'Scrub'), c: 'sudo zpool scrub [pool]' },
{ d: t('Snapshot', 'Snapshot'), c: 'sudo zfs snapshot [pool]/[ds]@[name]' },
{ d: t('Send/Receive', 'Send/Receive'), c: 'zfs send [pool]/[ds]@snap | zfs receive [dest]' },
]};
SEC['solaris-zones'] = { label: t('Zones', 'Zones'), commands: [
{ d: t('Liste', 'List'), c: 'zoneadm list -cv' },
{ d: t('Boot', 'Boot'), c: 'sudo zoneadm -z [zone] boot' },
{ d: t('Halt', 'Halt'), c: 'sudo zoneadm -z [zone] halt' },
{ d: t('Console', 'Console'), c: 'sudo zlogin -C [zone]' },
]};
// ── RENDER TREE ──
function renderTree() {
const 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');
if (depth === 0) {
const 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';
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');
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');
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';
else if (isBranch) dot.className = 'dot-orange';
else dot.className = 'dot-gray';
row.appendChild(dot);
// Label
const 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);
}
if (cnt > 0) {
const 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');
if (tg) { tg.classList.toggle('open'); row.nextElementSibling.classList.toggle('open'); e.stopPropagation(); }
return;
}
selectNode(node);
};
div.appendChild(row);
}
// Enfants
if (isBranch) {
const kids = document.createElement('div');
kids.className = 'tree-children open';
for (const ch of node.children) {
ch._parent = node;
renderNode(kids, ch, 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');
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 };
renderNode(kids, snode, depth + 1);
});
div.appendChild(kids);
}
container.appendChild(div);
}
// ── SELECT NODE ──
function selectNode(node) {
currentNode = node;
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 = '';
// Cas 1: c'est une section (a _sectionId)
if (node._sectionId) {
const sec = SEC[node._sectionId];
if (sec) {
titleEl.textContent = 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];
if (sec) {
titleEl.textContent = node.label.fr;
subEl.textContent = sec.commands.length + ' commandes';
showCmds(sec.commands);
}
return;
}
// Cas 3: pas de données
titleEl.textContent = node.label.fr;
subEl.textContent = 'Aucune donnee';
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();
let 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);
});
}
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>';
return;
}
el.innerHTML = '';
cmds.forEach(function(x, i) {
const 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);
});
};
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';
}
if (currentNode && currentNode._sectionId) {
showCmds(SEC[currentNode._sectionId].commands);
} else if (currentNode && currentNode.sectionIds && currentNode.sectionIds.length > 0) {
showCmds(SEC[currentNode.sectionIds[0]].commands);
}
}
// ── 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;
}