423 lines
16 KiB
PHP
Executable File
423 lines
16 KiB
PHP
Executable File
<?php
|
||
$excludeDirs = ['plugins','css','js','images','uploads','chrome','tmp','storage','assets','styles','videos','media','data','config','memes','artifacts','scripts','fonts','webfonts'];
|
||
$excludeFiles = ['index.php','.htaccess','click.php','lead.php','open.php','unsub.php'];
|
||
|
||
function scanAll($dir, $base = '', $exDirs = [], $exFiles = []) {
|
||
$files = [];
|
||
if (!is_dir($dir)) return $files;
|
||
foreach (scandir($dir) as $f) {
|
||
if ($f[0] === '.') continue;
|
||
$path = $dir . '/' . $f;
|
||
$rel = $base ? $base . '/' . $f : $f;
|
||
if (is_dir($path)) {
|
||
if (!in_array($f, $exDirs)) $files = array_merge($files, scanAll($path, $rel, $exDirs, $exFiles));
|
||
} else {
|
||
$ext = pathinfo($f, PATHINFO_EXTENSION);
|
||
if (in_array($ext, ['php','html']) && !in_array($f, $exFiles)) $files[] = ['name' => $f, 'path' => '/' . $rel];
|
||
}
|
||
}
|
||
return $files;
|
||
}
|
||
|
||
$allFiles = scanAll('/opt/wevads/public', '', $excludeDirs, $excludeFiles);
|
||
usort($allFiles, fn($a,$b) => strcmp($a['name'], $b['name']));
|
||
|
||
function parseMenu($file) {
|
||
$html = file_get_contents($file);
|
||
$html = preg_replace('/<!--.*?-->/s', '', $html);
|
||
$menu = [];
|
||
$seen = [];
|
||
|
||
// Liens directs en premier (dans l'ordre du vrai menu)
|
||
$directLinks = [
|
||
['t' => 'Dashboard', 'u' => '/dashboard.html', 'd' => true],
|
||
['t' => '🎛️ Control Hub', 'u' => '/control-hub.php', 'd' => true],
|
||
['t' => '🏆 Winning Config', 'u' => '/deliverads/brain-dashboard.php', 'd' => true],
|
||
];
|
||
|
||
foreach ($directLinks as $dl) {
|
||
$menu[] = $dl;
|
||
$seen[$dl['t']] = true;
|
||
}
|
||
|
||
// Nav-toggle menus
|
||
$parts = preg_split('/(<a[^>]*nav-toggle[^>]*>)/', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||
|
||
for ($i = 1; $i < count($parts); $i += 2) {
|
||
if (!isset($parts[$i+1])) continue;
|
||
$section = $parts[$i] . $parts[$i+1];
|
||
|
||
if (preg_match('/<span class="title"[^>]*>([^<]+)/', $section, $pm)) {
|
||
$parentTitle = trim($pm[1]);
|
||
if (isset($seen[$parentTitle])) continue;
|
||
$seen[$parentTitle] = true;
|
||
|
||
$children = [];
|
||
if (preg_match('/<ul class="sub-menu">(.*?)(<\/ul>|<li class="nav-item)/s', $section, $sm)) {
|
||
preg_match_all('/href="([^"]+)"[^>]*>.*?<span class="title"[^>]*>([^<]+)/s', $sm[1], $cm, PREG_SET_ORDER);
|
||
foreach ($cm as $c) {
|
||
$url = preg_replace('/\{echo \$app\[.base_url.\]\}/', '', trim($c[1]));
|
||
$children[] = ['t' => trim($c[2]), 'u' => $url ?: '#'];
|
||
$seen[trim($c[2])] = true;
|
||
}
|
||
}
|
||
$menu[] = ['t' => $parentTitle, 'c' => $children];
|
||
}
|
||
}
|
||
|
||
// Ajouter Smart Report après Send APIs (position ~index 10)
|
||
$smartReport = ['t' => 'Smart Report', 'u' => '/dashboard/top-stats.html', 'd' => true];
|
||
$revenueReport = ['t' => 'Revenu Report', 'u' => '/statistics/full-report.html', 'd' => true];
|
||
|
||
// Trouver position après Send APIs
|
||
$insertPos = 0;
|
||
foreach ($menu as $i => $m) {
|
||
if (strpos($m['t'], 'Send APIs') !== false) {
|
||
$insertPos = $i + 1;
|
||
break;
|
||
}
|
||
}
|
||
if ($insertPos > 0) {
|
||
array_splice($menu, $insertPos, 0, [$smartReport]);
|
||
// Revenu Report après Production
|
||
foreach ($menu as $i => $m) {
|
||
if (strpos($m['t'], 'Tracking') !== false) {
|
||
array_splice($menu, $i, 0, [$revenueReport]);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $menu;
|
||
}
|
||
|
||
$menuStructure = parseMenu('/opt/wevads/app/views/includes/menu.html');
|
||
$menuJson = json_encode($menuStructure, JSON_UNESCAPED_UNICODE);
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Sidebar Admin</title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:system-ui;background:#030308;color:#fff;min-height:100vh}
|
||
.hdr{display:flex;justify-content:space-between;align-items:center;padding:10px 15px;background:linear-gradient(135deg,rgba(0,255,255,0.1),rgba(191,0,255,0.1));border-bottom:1px solid rgba(0,255,255,0.3)}
|
||
.hdr h1{font-size:1em;color:#0ff}
|
||
.stats span{margin:0 8px;color:#0f8}
|
||
.btn{padding:5px 10px;border:none;border-radius:4px;cursor:pointer;font-size:0.75em;background:#0f8;color:#000;font-weight:bold}
|
||
.btn-s{background:transparent;border:1px solid rgba(0,255,255,0.3);color:#fff}
|
||
.btn-r{background:#f36;color:#fff}
|
||
.main{display:grid;grid-template-columns:1fr 40px 1fr;height:calc(100vh - 45px)}
|
||
.pan{background:#0a0a15;display:flex;flex-direction:column}
|
||
.pan-h{padding:8px;border-bottom:1px solid rgba(0,255,255,0.2);font-size:0.8em;color:#0ff}
|
||
.pan-b{flex:1;overflow-y:auto;padding:5px}
|
||
.srch{width:100%;padding:6px;background:#000;border:1px solid rgba(0,255,255,0.2);border-radius:4px;color:#fff;margin-bottom:5px;font-size:0.75em}
|
||
.ctr{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;background:#050508;border-left:1px solid rgba(0,255,255,0.2);border-right:1px solid rgba(0,255,255,0.2)}
|
||
.tb{width:28px;height:28px;border:2px solid #0ff;border-radius:50%;background:rgba(0,255,255,0.1);color:#0ff;cursor:pointer}
|
||
.tb:hover{background:#0ff;color:#000}
|
||
.it{display:flex;align-items:center;padding:4px 6px;margin:2px 0;background:rgba(0,0,0,0.3);border:1px solid transparent;border-radius:3px;cursor:pointer;font-size:0.7em}
|
||
.it:hover{border-color:rgba(0,255,255,0.3)}
|
||
.it.sel{background:rgba(0,255,255,0.15);border-color:#0ff}
|
||
.it.del{background:rgba(255,100,100,0.1);border-color:rgba(255,100,100,0.3)}
|
||
.nm{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.pt{font-size:0.65em;color:#667;margin-left:5px;max-width:120px;overflow:hidden;text-overflow:ellipsis}
|
||
.par{background:rgba(0,255,255,0.08);border:1px solid rgba(0,255,255,0.2);font-weight:bold;margin-top:5px}
|
||
.dir{background:rgba(0,255,136,0.08);border:1px solid rgba(0,255,136,0.2)}
|
||
.ch{margin-left:10px;padding-left:5px;border-left:2px solid rgba(0,255,255,0.2)}
|
||
.chi{font-size:0.65em;padding:3px 5px}
|
||
.mb{width:16px;height:16px;border:none;border-radius:2px;background:rgba(0,255,255,0.1);color:#0ff;cursor:pointer;margin-left:2px;font-size:0.55em}
|
||
.mb:hover{background:#0ff;color:#000}
|
||
.mb.x{background:rgba(255,50,100,0.1);color:#f36}
|
||
.mb.x:hover{background:#f36;color:#fff}
|
||
.cb{background:none;border:none;color:#0ff;cursor:pointer;width:12px;font-size:0.6em}
|
||
.deld{margin-top:8px;padding-top:8px;border-top:1px dashed rgba(255,100,100,0.3)}
|
||
.deld-h{font-size:0.65em;color:#f66;margin-bottom:4px}
|
||
::-webkit-scrollbar{width:4px}
|
||
::-webkit-scrollbar-thumb{background:rgba(0,255,255,0.3);border-radius:2px}
|
||
</style>
|
||
|
||
</head>
|
||
<body>
|
||
<div class="hdr">
|
||
<h1>🎛️ Sidebar Admin</h1>
|
||
<div class="stats"><span><?=count($allFiles)?> fichiers</span><span id="mc2"><?=count($menuStructure)?> menus</span></div>
|
||
<div>
|
||
<button class="btn btn-s" onclick="location.reload()">↻</button>
|
||
<button class="btn btn-s" onclick="eAll()">▼</button>
|
||
<button class="btn btn-s" onclick="cAll()">▲</button>
|
||
<button class="btn" onclick="saveToFile()">💾 Appliquer</button>
|
||
</div>
|
||
</div>
|
||
<div class="main">
|
||
<div class="pan">
|
||
<div class="pan-h">📁 Fichiers (<?=count($allFiles)?>)</div>
|
||
<div class="pan-b">
|
||
<input class="srch" id="sF" placeholder="🔍 Rechercher..." oninput="fF()">
|
||
<div id="fL"><?php foreach($allFiles as $i=>$f):?><div class="it" data-i="<?=$i?>" data-n="<?=strtolower($f['name'])?>" data-p="<?=$f['path']?>" onclick="sF(this)"><span class="nm"><?=$f['name']?></span><span class="pt"><?=$f['path']?></span></div><?php endforeach;?></div>
|
||
<div id="dS" class="deld" style="display:none"><div class="deld-h">🗑️ Supprimés (clic=restaurer)</div><div id="dL"></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="ctr"><button class="tb" onclick="add()">→</button><button class="tb" onclick="rem()">←</button></div>
|
||
<div class="pan">
|
||
<div class="pan-h">📋 Menu (<span id="mc"><?=count($menuStructure)?></span>) <button class="mb" onclick="addP()" style="float:right">+</button></div>
|
||
<div class="pan-b">
|
||
<input class="srch" id="sM" placeholder="🔍 Rechercher..." oninput="fM()">
|
||
<div id="mL"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
let M=<?=$menuJson?>;
|
||
let D=[];
|
||
let selIdx=null; // {type:'file'|'parent'|'child', i:number, j?:number}
|
||
let col={};
|
||
|
||
function r(){
|
||
let h='';
|
||
M.forEach((p,i)=>{
|
||
const isDirect=p.d;
|
||
const isCollapsed=col[i];
|
||
const childCount=p.c?p.c.length:0;
|
||
|
||
h+=`<div class="it ${isDirect?'dir':'par'}" data-type="parent" data-i="${i}" onclick="selP(${i})">`;
|
||
|
||
if(!isDirect && childCount>0){
|
||
h+=`<button class="cb" onclick="event.stopPropagation();tg(${i})">${isCollapsed?'+':'-'}</button>`;
|
||
}else{
|
||
h+=`<span style="width:12px;display:inline-block"></span>`;
|
||
}
|
||
|
||
h+=`<span class="nm">${isDirect?'🔗':'📁'} ${p.t}</span>`;
|
||
h+=`<span class="pt">${isDirect?p.u:childCount+' items'}</span>`;
|
||
|
||
if(!isDirect) h+=`<button class="mb" onclick="event.stopPropagation();addToParent(${i})">+</button>`;
|
||
h+=`<button class="mb" onclick="event.stopPropagation();editP(${i})">✏</button>`;
|
||
h+=`<button class="mb x" onclick="event.stopPropagation();delP(${i})">×</button>`;
|
||
h+=`</div>`;
|
||
|
||
if(!isDirect && childCount>0 && !isCollapsed){
|
||
h+=`<div class="ch">`;
|
||
p.c.forEach((c,j)=>{
|
||
h+=`<div class="it chi" data-type="child" data-i="${i}" data-j="${j}" onclick="selC(${i},${j})">`;
|
||
h+=`<span class="nm">${c.t}</span>`;
|
||
h+=`<span class="pt">${c.u}</span>`;
|
||
h+=`<button class="mb" onclick="event.stopPropagation();editC(${i},${j})">✏</button>`;
|
||
h+=`<button class="mb x" onclick="event.stopPropagation();delC(${i},${j})">×</button>`;
|
||
h+=`</div>`;
|
||
});
|
||
h+=`</div>`;
|
||
}
|
||
});
|
||
document.getElementById('mL').innerHTML=h;
|
||
document.getElementById('mc').textContent=M.length;
|
||
document.getElementById('mc2').textContent=M.length+' menus';
|
||
rD();
|
||
updateSel();
|
||
}
|
||
|
||
function rD(){
|
||
const ds=document.getElementById('dS');
|
||
const dl=document.getElementById('dL');
|
||
if(D.length===0){ds.style.display='none';return;}
|
||
ds.style.display='block';
|
||
let h='';
|
||
D.forEach((d,i)=>{
|
||
h+=`<div class="it del" onclick="restore(${i})"><span class="nm">🔄 ${d.t}</span><span class="pt">${d.u||''}</span></div>`;
|
||
});
|
||
dl.innerHTML=h;
|
||
}
|
||
|
||
function restore(i){
|
||
const item=D[i];
|
||
D.splice(i,1);
|
||
M.push(item);
|
||
r();
|
||
}
|
||
|
||
function updateSel(){
|
||
document.querySelectorAll('.it').forEach(e=>e.classList.remove('sel'));
|
||
if(selIdx){
|
||
if(selIdx.type==='file'){
|
||
document.querySelector(`#fL .it[data-i="${selIdx.i}"]`)?.classList.add('sel');
|
||
}else if(selIdx.type==='parent'){
|
||
document.querySelector(`#mL .it[data-type="parent"][data-i="${selIdx.i}"]`)?.classList.add('sel');
|
||
}else if(selIdx.type==='child'){
|
||
document.querySelector(`#mL .it[data-type="child"][data-i="${selIdx.i}"][data-j="${selIdx.j}"]`)?.classList.add('sel');
|
||
}
|
||
}
|
||
}
|
||
|
||
function tg(i){col[i]=!col[i];r();}
|
||
function eAll(){col={};r();}
|
||
function cAll(){M.forEach((p,i)=>{if(p.c&&p.c.length)col[i]=true;});r();}
|
||
|
||
function fF(){
|
||
const q=document.getElementById('sF').value.toLowerCase();
|
||
document.querySelectorAll('#fL .it').forEach(e=>{
|
||
const n=e.dataset.n||'';
|
||
const p=e.dataset.p||'';
|
||
e.style.display=(n.includes(q)||p.toLowerCase().includes(q))?'flex':'none';
|
||
});
|
||
}
|
||
|
||
function fM(){
|
||
const q=document.getElementById('sM').value.toLowerCase();
|
||
document.querySelectorAll('#mL .it').forEach(e=>{
|
||
const nm=e.querySelector('.nm');
|
||
if(nm){
|
||
const t=nm.textContent.toLowerCase();
|
||
e.style.display=t.includes(q)?'flex':'none';
|
||
}
|
||
});
|
||
document.querySelectorAll('#mL .ch').forEach(e=>{
|
||
const visible=Array.from(e.querySelectorAll('.it')).some(c=>c.style.display!=='none');
|
||
e.style.display=visible?'block':'none';
|
||
});
|
||
}
|
||
|
||
function sF(el){
|
||
selIdx={type:'file',i:parseInt(el.dataset.i),name:el.querySelector('.nm').textContent,path:el.dataset.p};
|
||
updateSel();
|
||
}
|
||
|
||
function selP(i){
|
||
selIdx={type:'parent',i:i};
|
||
updateSel();
|
||
}
|
||
|
||
function selC(i,j){
|
||
selIdx={type:'child',i:i,j:j};
|
||
updateSel();
|
||
}
|
||
|
||
function add(){
|
||
if(!selIdx||selIdx.type!=='file'){alert('Sélectionnez un fichier à gauche!');return;}
|
||
// Trouver le parent sélectionné
|
||
const selParent=document.querySelector('#mL .it.sel[data-type="parent"]');
|
||
if(!selParent){alert('Sélectionnez un menu 📁 à droite!');return;}
|
||
const pi=parseInt(selParent.dataset.i);
|
||
if(M[pi].d){alert('Impossible d\'ajouter à un lien direct!');return;}
|
||
if(!M[pi].c)M[pi].c=[];
|
||
M[pi].c.push({t:selIdx.name,u:selIdx.path});
|
||
selIdx=null;
|
||
r();
|
||
}
|
||
|
||
function addToParent(i){
|
||
if(!selIdx||selIdx.type!=='file'){alert('Sélectionnez un fichier à gauche!');return;}
|
||
if(!M[i].c)M[i].c=[];
|
||
M[i].c.push({t:selIdx.name,u:selIdx.path});
|
||
selIdx=null;
|
||
r();
|
||
}
|
||
|
||
function rem(){
|
||
if(!selIdx){alert('Sélectionnez un élément!');return;}
|
||
if(selIdx.type==='parent'){
|
||
D.push(M[selIdx.i]);
|
||
M.splice(selIdx.i,1);
|
||
}else if(selIdx.type==='child'){
|
||
D.push(M[selIdx.i].c[selIdx.j]);
|
||
M[selIdx.i].c.splice(selIdx.j,1);
|
||
}
|
||
selIdx=null;
|
||
r();
|
||
}
|
||
|
||
function addP(){
|
||
const t=prompt('Nom du nouveau menu:');
|
||
if(t&&t.trim()){
|
||
M.push({t:t.trim(),c:[]});
|
||
r();
|
||
}
|
||
}
|
||
|
||
function editP(i){
|
||
const p=M[i];
|
||
const t=prompt('Titre:',p.t);
|
||
if(t&&t.trim()){
|
||
p.t=t.trim();
|
||
if(p.d){
|
||
const u=prompt('URL:',p.u);
|
||
if(u)p.u=u;
|
||
}
|
||
r();
|
||
}
|
||
}
|
||
|
||
function delP(i){
|
||
D.push(M[i]);
|
||
M.splice(i,1);
|
||
if(selIdx&&selIdx.type==='parent'&&selIdx.i===i)selIdx=null;
|
||
r();
|
||
}
|
||
|
||
function editC(i,j){
|
||
const c=M[i].c[j];
|
||
const t=prompt('Titre:',c.t);
|
||
if(t&&t.trim()){
|
||
c.t=t.trim();
|
||
const u=prompt('URL:',c.u);
|
||
if(u)c.u=u;
|
||
r();
|
||
}
|
||
}
|
||
|
||
function delC(i,j){
|
||
D.push(M[i].c[j]);
|
||
M[i].c.splice(j,1);
|
||
if(selIdx&&selIdx.type==='child'&&selIdx.i===i&&selIdx.j===j)selIdx=null;
|
||
r();
|
||
}
|
||
|
||
function saveToFile(){
|
||
let html=`<div class="page-sidebar navbar-collapse collapse">
|
||
<ul class="page-sidebar-menu page-header-fixed page-sidebar-menu-closed" data-keep-expanded="false" data-auto-scroll="false" data-slide-speed="200" style="padding-top: 20px">
|
||
<li class="sidebar-search-box"><input type="text" id="menu-search" class="form-control" placeholder="🔍 Rechercher..." onkeyup="filterMenu(this.value)"></li>\n`;
|
||
|
||
M.forEach(p=>{
|
||
if(p.d){
|
||
html+=`<li class="nav-item"><a href="${p.u}" class="nav-link"><i class="fa fa-link"></i><span class="title">${p.t}</span></a></li>\n`;
|
||
}else{
|
||
const icon=p.t.includes('IA')?'fa-brain':p.t.includes('Cloud')?'fa-cloud':p.t.includes('Domain')?'fa-globe':p.t.includes('Send')?'fa-envelope':p.t.includes('N8N')?'fa-project-diagram':'fa-folder';
|
||
html+=`<li class="nav-item"><a href="javascript:;" class="nav-link nav-toggle"><i class="fa ${icon}"></i><span class="title">${p.t}</span><span class="arrow"></span></a>\n<ul class="sub-menu">\n`;
|
||
if(p.c){
|
||
p.c.forEach(c=>{
|
||
html+=`<li><a href="${c.u}" class="nav-link"><span class="title">${c.t}</span></a></li>\n`;
|
||
});
|
||
}
|
||
html+=`</ul></li>\n`;
|
||
}
|
||
});
|
||
|
||
html+=`</ul></div>`;
|
||
|
||
// Sauvegarder via API
|
||
fetch('/api/save-menu.php',{
|
||
method:'POST',
|
||
headers:{'Content-Type':'application/json'},
|
||
body:JSON.stringify({html:html})
|
||
}).then(r=>r.json()).then(d=>{
|
||
if(d.success){
|
||
alert('✅ Menu sauvegardé!\nRechargez la page principale pour voir les changements.');
|
||
}else{
|
||
// Fallback: copier dans clipboard
|
||
navigator.clipboard.writeText(html).then(()=>{
|
||
alert('⚠️ API indisponible.\n\nHTML copié dans le presse-papier.\nCollez dans:\n/opt/wevads/app/views/includes/menu.html');
|
||
});
|
||
}
|
||
}).catch(()=>{
|
||
navigator.clipboard.writeText(html).then(()=>{
|
||
alert('HTML copié!\nCollez dans menu.html puis:\nsystemctl restart apache2');
|
||
});
|
||
});
|
||
}
|
||
|
||
r();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|