refonte: sidebar transform fixe, sticky headers, pipeline filters, margin-left mainWrapper
This commit is contained in:
parent
ba77ef8240
commit
a14d030a5a
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(ls /Volumes/web/rla/*.env /Volumes/web/rla/.env* 2>/dev/null; ls /Volumes/web/rla/node_modules | grep -E \"puppeteer|pptxgen|xlsx|exceljs|docx|pdf\" 2>/dev/null)",
|
||||||
|
"Bash(npm install:*)",
|
||||||
|
"Bash(node -e \"require\\('./services/calc'\\); console.log\\('calc OK'\\)\" 2>&1)",
|
||||||
|
"Bash(node -e \"require\\('./services/export-pdf'\\); console.log\\('export-pdf OK'\\)\" 2>&1)",
|
||||||
|
"Bash(node -e \"\nrequire\\('dotenv'\\).config\\(\\);\n// Mock pour tester sans baserow\nconsole.log\\('Routes test...'\\);\nrequire\\('./routes/synthese'\\);\nrequire\\('./routes/alertes'\\);\nrequire\\('./routes/en-service'\\);\nrequire\\('./routes/en-cours'\\);\nrequire\\('./routes/par-region'\\);\nrequire\\('./routes/clotures'\\);\nrequire\\('./routes/pilotage'\\);\nrequire\\('./routes/matrice-risque'\\);\nrequire\\('./routes/export'\\);\nconsole.log\\('Toutes les routes chargées OK'\\);\n\")",
|
||||||
|
"Bash(node -e \"\n// Test complet de chargement\nrequire\\('dotenv'\\).config\\(\\);\nconst exp = require\\('./services/export-pdf'\\);\nconst xl = require\\('./services/export-xlsx'\\);\nconsole.log\\('PDF service:', typeof exp.generateSynthese\\);\nconsole.log\\('XLSX service:', typeof xl.generateXlsx\\);\n\n// Test mock PDF\nconst buf = exp.generateSynthese\\({\n total:3, actifs:2, clotures:1,\n taux_avancement_moyen: 65,\n budget: { total:'1 200 000 DT', consomme:'800 000 DT', restant:'400 000 DT' },\n par_statut: { 'En cours': 2, 'Clôturé': 1 },\n alertes_delais: { count:1, critique:0, items:[{ ref:'M-001', projet:'Projet A', region:'Gabes', entrepreneur:'Ent X', delai_restant:60, niveau:'attention' }] }\n}\\);\nconsole.log\\('PDF synthese OK, size:', buf.length, 'bytes'\\);\n\")",
|
||||||
|
"Bash(node -e \"\nconst pdf = require\\('./services/export-pdf'\\);\npdf.generateSynthese\\({\n total:5, actifs:4, clotures:1,\n taux_avancement_moyen: 68,\n budget: { total:'1 500 000 DT', consomme:'900 000 DT', restant:'600 000 DT' },\n par_statut: { 'En cours': 3, 'En service': 1, 'Clôturé': 1 },\n alertes_delais: { count:2, critique:1, items:[\n { ref:'M-001', projet:'Projet A', region:'Gabes', entrepreneur:'Ent X', delai_restant:30, niveau:'critique' },\n { ref:'M-002', projet:'Projet B', region:'Sfax', entrepreneur:'Ent Y', delai_restant:75, niveau:'attention' },\n ]}\n}\\).then\\(buf => {\n console.log\\('PDF généré OK:', buf.length, 'bytes'\\);\n require\\('fs'\\).writeFileSync\\('/tmp/test-rla.pdf', buf\\);\n console.log\\('Fichier écrit : /tmp/test-rla.pdf'\\);\n}\\).catch\\(e => console.error\\('ERREUR:', e.message\\)\\);\n\")",
|
||||||
|
"Bash(curl -s -X POST http://localhost:3005/api/auth/login \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"username\":\"nabil\",\"password\":\"Rla2025Sfax\"}' | python3 -c \"\nimport sys,json; d=json.load\\(sys.stdin\\)\nif 'token' in d:\n import base64\n payload = d['token'].split\\('.'\\)[1]\n payload += '=' * \\(4 - len\\(payload\\) % 4\\)\n info = json.loads\\(base64.b64decode\\(payload\\)\\)\n print\\('Login OK — role:', info['role'], '| region:', info['region']\\)\nelse:\n print\\('ERREUR:', d\\)\n\")",
|
||||||
|
"Bash(which sshpass:*)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {sudo docker ps | grep rla}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1\n\nexpect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {ls /usr/local/bin/docker /usr/bin/docker /var/packages/ContainerManager/usr/bin/docker 2>&1; which docker; echo PATH=\\\\$PATH}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"WebFetch(domain:baserow.bolbol.tn)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {curl -s -H 'Authorization: Token zJaDdkttN1gr6oPvd3cxfCXNwzvvwMMF' 'https://baserow.bolbol.tn/api/database/rows/table/856/?page=1&size=3&user_field_names=true'}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {curl -s -H 'Authorization: Token zJaDdkttN1gr6oPvd3cxfCXNwzvvwMMF' 'https://baserow.bolbol.tn/api/database/rows/table/872/?page=1&size=3&user_field_names=true'}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuYWJpbCIsImlkIjoxLCJ1c2VybmFtZSI6Im5hYmlsIiwicm9sZSI6InN1cGVyYWRtaW4iLCJyZWdpb24iOiJhbGwiLCJpYXQiOjE3NzMzNTg3MTksImV4cCI6MTc3MzM4NzUxOX0.A1XEUDQAiOOyl8R-rPO6qYl8W_aa2Os-oB23aNIu2vs\" __NEW_LINE_096044aa0eaba021__ echo \"=== /api/en-service ===\" curl -sk https://rla.bolbol.tn/api/en-service -H \"Authorization: Bearer $TOKEN\" 2)",
|
||||||
|
"Bash(1 __NEW_LINE_096044aa0eaba021__ echo \"\" echo \"=== /api/marches \\(first 2000 chars\\) ===\" curl -sk https://rla.bolbol.tn/api/marches -H \"Authorization: Bearer $TOKEN\")",
|
||||||
|
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuYWJpbCIsImlkIjoxLCJ1c2VybmFtZSI6Im5hYmlsIiwicm9sZSI6InN1cGVyYWRtaW4iLCJyZWdpb24iOiJhbGwiLCJpYXQiOjE3NzMzNTg3MTksImV4cCI6MTc3MzM4NzUxOX0.A1XEUDQAiOOyl8R-rPO6qYl8W_aa2Os-oB23aNIu2vs\"\necho \"=== /api/marches ===\"\ncurl -sk https://rla.bolbol.tn/api/marches -H \"Authorization: Bearer $TOKEN\" 2>&1 | python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); items=d.get\\('items',[d]\\) if isinstance\\(d,dict\\) else d; print\\('Keys in response:', list\\(d.keys\\(\\)\\) if isinstance\\(d,dict\\) else 'array'\\); print\\('First item keys:', list\\(items[0].keys\\(\\)\\) if items else 'empty'\\)\" 2>&1\n\necho \"\"\necho \"=== /api/synthese ===\"\ncurl -sk https://rla.bolbol.tn/api/synthese -H \"Authorization: Bearer $TOKEN\" 2>&1 | python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d, indent=2, ensure_ascii=False\\)[:3000]\\)\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {ls /app 2>/dev/null || /usr/local/bin/docker exec rla-api ls /app}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/server.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/routes/marches.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/routes/en-service.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api ls /app/routes/ && /usr/local/bin/docker exec rla-api cat /app/services/baserow.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/services/calc.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/routes/synthese.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/routes/pipeline.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
|
||||||
|
"Bash(sshpass -p '2L2u519w@ommi' ssh -p 22222 -o StrictHostKeyChecking=no Best0f@192.168.100.33 \\\\\n \"echo 'SSH OK'; docker inspect rla-api --format '{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}'; find /volume1 -name 'server.js' -path '*rla*' 2>/dev/null | head -3\")",
|
||||||
|
"Bash(expect -c \"\nspawn ssh -p 22222 -o StrictHostKeyChecking=no Best0f@192.168.100.33 {sudo docker inspect rla-api --format '{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}' 2>/dev/null; find /volume1 -name server.js 2>/dev/null | grep -i rla | head -5; ls /volume1/web/ 2>/dev/null}\nexpect {\n \\\\\"password\\\\\" { send \\\\\"2L2u519w\\\\@ommi\\\\r\\\\\"; exp_continue }\n eof { }\n timeout { exit 1 }\n}\n\" 2>/dev/null)",
|
||||||
|
"Bash(curl -s -X POST http://192.168.100.33:9000/api/auth \\\\\n -H 'Content-Type: application/json' \\\\\n -d '{\"username\":\"bestof\",\"password\":\"2L2u519wportainer\"}' | python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('jwt','ERROR'\\)[:40]\\)\")",
|
||||||
|
"Bash(curl -s http://192.168.100.33:3005/api/health | python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d\\)\")",
|
||||||
|
"Bash(curl -s https://rla.bolbol.tn/api/health 2>/dev/null | python3 -c \"import sys,json; print\\(json.load\\(sys.stdin\\)\\)\" || echo \"Public URL check failed \\(may need VPN\\)\")",
|
||||||
|
"Bash(ls -la \"/Volumes/web/rla/Fichiers-cibles/\" && file \"/Volumes/web/rla/Fichiers-cibles/\"*)",
|
||||||
|
"Bash(ls /Volumes/web/rla/Fichiers-cibles/ 2>/dev/null && ls /Volumes/web/rla/services/export-*.js 2>/dev/null)",
|
||||||
|
"Bash(cd /Volumes/web/rla/Fichiers-cibles && unzip -p \"Marches_RLA_2025_Zone_Sud_02_2026.xlsx\" xl/worksheets/sheet1.xml 2>/dev/null | python3 -c \"import sys; from xml.etree import ElementTree as ET; root=ET.parse\\(sys.stdin\\); [print\\(c.attrib\\) for c in root.findall\\('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'\\)[:30]]\" 2>/dev/null || echo \"fail\")",
|
||||||
|
"Bash(cd /Volumes/web/rla/Fichiers-cibles && unzip -l \"Marches_RLA_2025_Zone_Sud_02_2026.xlsx\" 2>/dev/null | head -20)",
|
||||||
|
"Bash(python3 << 'EOF'\nimport zipfile, xml.etree.ElementTree as ET\n\nns = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'\n\nwith zipfile.ZipFile\\('/Volumes/web/rla/Fichiers-cibles/Marches_RLA_2025_Zone_Sud_02_2026.xlsx'\\) as z:\n strings = []\n with z.open\\('xl/sharedStrings.xml'\\) as f:\n tree = ET.parse\\(f\\)\n for si in tree.findall\\(f'{ns}si'\\):\n t = ''.join\\(e.text or '' for e in si.iter\\(f'{ns}t'\\)\\)\n strings.append\\(t\\)\n \n def get_row_values\\(row\\):\n vals = []\n for c in row:\n v = c.find\\(f'{ns}v'\\)\n if v is not None:\n if c.get\\('t'\\) == 's':\n vals.append\\(strings[int\\(v.text\\)]\\)\n else:\n vals.append\\(v.text\\)\n else:\n vals.append\\(''\\)\n return vals\n \n # Sheet 1 - first 5 rows\n print\\('=== Sheet 1: Situation des Marchés ==='\\)\n with z.open\\('xl/worksheets/sheet1.xml'\\) as f:\n tree = ET.parse\\(f\\)\n rows = tree.findall\\(f'.//{ns}row'\\)\n for row in rows[:6]:\n print\\(get_row_values\\(row\\)\\)\n \n # Sheet 2 - first 5 rows\n print\\('\\\\n=== Sheet 2: Estimation Évolution ==='\\)\n with z.open\\('xl/worksheets/sheet2.xml'\\) as f:\n tree = ET.parse\\(f\\)\n rows = tree.findall\\(f'.//{ns}row'\\)\n for row in rows[:6]:\n print\\(get_row_values\\(row\\)\\)\nEOF)",
|
||||||
|
"Bash(python3 << 'EOF'\nimport zipfile, xml.etree.ElementTree as ET\n\nns = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'\n\nwith zipfile.ZipFile\\('/Volumes/web/rla/Fichiers-cibles/Marches_RLA_2025_Zone_Sud_02_2026.xlsx'\\) as z:\n strings = []\n with z.open\\('xl/sharedStrings.xml'\\) as f:\n tree = ET.parse\\(f\\)\n for si in tree.findall\\(f'{ns}si'\\):\n t = ''.join\\(e.text or '' for e in si.iter\\(f'{ns}t'\\)\\)\n strings.append\\(t\\)\n \n def get_row_values\\(row\\):\n vals = []\n for c in row:\n v = c.find\\(f'{ns}v'\\)\n if v is not None:\n if c.get\\('t'\\) == 's':\n vals.append\\(strings[int\\(v.text\\)]\\)\n else:\n vals.append\\(v.text\\)\n else:\n vals.append\\(''\\)\n return vals\n \n # Sheet 1 - all non-empty rows\n print\\('=== Sheet 1: Situation des Marchés ==='\\)\n with z.open\\('xl/worksheets/sheet1.xml'\\) as f:\n tree = ET.parse\\(f\\)\n rows = tree.findall\\(f'.//{ns}row'\\)\n for i, row in enumerate\\(rows[:60]\\):\n vals = get_row_values\\(row\\)\n non_empty = [v for v in vals if v]\n if non_empty:\n print\\(f\"Row {i+1}: {non_empty}\"\\)\nEOF)",
|
||||||
|
"Bash(python3 << 'EOF'\nimport zipfile, xml.etree.ElementTree as ET\n\nns = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'\n\nwith zipfile.ZipFile\\('/Volumes/web/rla/Fichiers-cibles/Marches_RLA_2025_Zone_Sud_02_2026.xlsx'\\) as z:\n strings = []\n with z.open\\('xl/sharedStrings.xml'\\) as f:\n tree = ET.parse\\(f\\)\n for si in tree.findall\\(f'{ns}si'\\):\n t = ''.join\\(e.text or '' for e in si.iter\\(f'{ns}t'\\)\\)\n strings.append\\(t\\)\n \n def get_row_values\\(row\\):\n vals = []\n for c in row:\n v = c.find\\(f'{ns}v'\\)\n if v is not None:\n if c.get\\('t'\\) == 's':\n vals.append\\(strings[int\\(v.text\\)]\\)\n else:\n vals.append\\(v.text\\)\n else:\n vals.append\\(''\\)\n return vals\n \n # Sheet 2\n print\\('=== Sheet 2: Estimation Évolution ==='\\)\n with z.open\\('xl/worksheets/sheet2.xml'\\) as f:\n tree = ET.parse\\(f\\)\n rows = tree.findall\\(f'.//{ns}row'\\)\n for i, row in enumerate\\(rows[:40]\\):\n vals = get_row_values\\(row\\)\n non_empty = [v for v in vals if v]\n if non_empty:\n print\\(f\"Row {i+1}: {non_empty}\"\\)\nEOF)",
|
||||||
|
"Bash(python3 << 'EOF'\nimport zipfile, xml.etree.ElementTree as ET\n\nwith zipfile.ZipFile\\('/Volumes/web/rla/Fichiers-cibles/Rapport_RLA_2025_Zone_Sud_02_2026.docx'\\) as z:\n print\\(\"Files:\", z.namelist\\(\\)[:15]\\)\n \n with z.open\\('word/document.xml'\\) as f:\n content = f.read\\(\\).decode\\('utf-8'\\)\n # Extract text paragraphs\n tree = ET.fromstring\\(content\\)\n ns = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'\n texts = []\n for p in tree.findall\\(f'.//{ns}p'\\)[:50]:\n text = ''.join\\(r.text or '' for r in p.findall\\(f'.//{ns}t'\\)\\)\n if text.strip\\(\\):\n texts.append\\(text.strip\\(\\)\\)\n for t in texts[:40]:\n print\\(t\\)\nEOF)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git tag:*)",
|
||||||
|
"Bash(git push:*)",
|
||||||
|
"Bash(curl -s -X POST \"http://192.168.100.33:9000/api/auth\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"username\":\"bestof\",\"password\":\"2L2u519wportainer\"}' | python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('jwt','ERROR:',d\\)\\)\")",
|
||||||
|
"Bash(python3 -m json.tool)",
|
||||||
|
"Bash(git remote:*)",
|
||||||
|
"Bash(git add *)",
|
||||||
|
"Bash(git fetch *)",
|
||||||
|
"Bash(python3 -c \"import sys,json; commits=json.load\\(sys.stdin\\); [print\\(c['sha'][:10], c['commit']['message'][:70]\\) for c in commits]\")",
|
||||||
|
"Bash(python3 -c \"import sys,json; data=json.load\\(sys.stdin\\); [print\\(b['name'], b['commit']['id'][:10]\\) for b in data]\")",
|
||||||
|
"Bash(python3 -c \"import sys,json; commits=json.load\\(sys.stdin\\); [print\\(c['sha'][:10], c['commit']['message'][:80]\\) for c in commits]\")",
|
||||||
|
"Bash(curl -s -X PATCH -H 'Authorization: token 0aad77091496c9fcfab53dc2d8be79a52c781d84' -H 'Content-Type: application/json' -d '{\"sha\":\"4168f74ded2c1b79a52f2a8b0e0c9e0c7e0a2b3c\",\"force\":true}' https://gitea.bolbol.tn/api/v1/repos/bolbol/reglement-definitif/branches/main)",
|
||||||
|
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('name','?'\\), d.get\\('commit',{}\\).get\\('id','?'\\)[:10] if 'commit' in d else d\\)\")",
|
||||||
|
"Bash(curl *)",
|
||||||
|
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d['commit']['id'][:10], d['commit']['message'][:70]\\)\")",
|
||||||
|
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('jwt','ERREUR: ' + str\\(d\\)\\)\\)\")",
|
||||||
|
"Bash(env)",
|
||||||
|
"Bash(ssh *)",
|
||||||
|
"Bash(sshpass *)",
|
||||||
|
"Bash(brew install *)",
|
||||||
|
"Bash(expect -v)",
|
||||||
|
"Bash(expect *)",
|
||||||
|
"Bash(mount)",
|
||||||
|
"Bash(node -e \"const b=require\\('bcryptjs'||'bcrypt'\\);b.compare\\('rla2025','\\\\$2a\\\\$10\\\\$4nqlMxOSsDV99mJHvVQJzubVLCArz6fnkQ0RSTD9p1CwdGxwibHdq'\\).then\\(r=>console.log\\('rla2025 :',r\\)\\)\")",
|
||||||
|
"Bash(node -e \"require\\('bcryptjs'\\).compare\\('rla2025','\\\\$2a\\\\$10\\\\$4nqlMxOSsDV99mJHvVQJzubVLCArz6fnkQ0RSTD9p1CwdGxwibHdq'\\).then\\(r=>console.log\\('rla2025 :',r\\)\\)\")",
|
||||||
|
"Bash(python3 -c ' *)",
|
||||||
|
"Bash(python3 -)",
|
||||||
|
"Bash(pip3 install *)",
|
||||||
|
"Bash(python3 -c \" from docx import Document from docx.shared import Pt, RGBColor, Cm import json doc = Document\\('Fichiers-cibles/Rapport_RLA_2025_Zone_Sud_03_2026.docx'\\) print\\('=== SECTIONS ==='\\) for i, section in enumerate\\(doc.sections\\): print\\(f'Section {i}: page_w={section.page_width.cm:.1f}cm page_h={section.page_height.cm:.1f}cm margin_top={section.top_margin.cm:.1f}cm'\\) print\\(\\) print\\('=== PARAGRAPHS & TABLES ==='\\) from docx.oxml.ns import qn body = doc.element.body def rgb_from_hex\\(hex_str\\): if not hex_str: return None h = hex_str.lstrip\\('#'\\) if len\\(h\\) == 6: return tuple\\(int\\(h[i:i+2],16\\) for i in \\(0,2,4\\)\\) return None def get_cell_bg\\(cell\\): shd = cell._tc.find\\(qn\\('w:tcPr'\\)\\) if shd is None: return None s = cell._tc.find\\('.//' + qn\\('w:shd'\\)\\) if s is not None: return s.get\\(qn\\('w:fill'\\)\\) return None def get_para_bg\\(para\\): pPr = para._p.find\\(qn\\('w:pPr'\\)\\) if pPr is None: return None shd = pPr.find\\(qn\\('w:shd'\\)\\) if shd is not None: return shd.get\\(qn\\('w:fill'\\)\\) return None items = [] for child in body: tag = child.tag.split\\('}'\\)[-1] if '}' in child.tag else child.tag if tag == 'p': from docx.text.paragraph import Paragraph para = Paragraph\\(child, doc\\) text = para.text.strip\\(\\) bg = get_para_bg\\(para\\) style = para.style.name if para.style else '' if text or bg: font_sizes = [] font_bolds = [] font_colors = [] for run in para.runs: if run.font.size: font_sizes.append\\(run.font.size.pt\\) font_bolds.append\\(run.font.bold\\) if run.font.color and run.font.color.type is not None: try: font_colors.append\\(str\\(run.font.color.rgb\\)\\) except: pass items.append\\({ 'type': 'para', 'style': style, 'text': text[:100], 'bg': bg, 'sizes': font_sizes[:3], 'bolds': font_bolds[:3], 'colors': font_colors[:3], }\\) elif tag == 'tbl': from docx.table import Table tbl = Table\\(child, doc\\) rows_count = len\\(tbl.rows\\) cols_count = len\\(tbl.columns\\) first_row = [] first_row_bg = [] for cell in tbl.rows[0].cells: first_row.append\\(cell.text.strip\\(\\)[:40]\\) first_row_bg.append\\(get_cell_bg\\(cell\\)\\) items.append\\({ 'type': 'table', 'rows': rows_count, 'cols': cols_count, 'header': first_row, 'header_bg': first_row_bg, }\\) for item in items: print\\(json.dumps\\(item, ensure_ascii=False\\)\\) \")",
|
||||||
|
"Bash(python3 -c \" import fitz doc = fitz.open\\('Fichiers-cibles/Marches_RLA_Marchés_En_Service___Raccordement___Medenine__Sfax_2026-03-12.pdf'\\) print\\(f'Pages: {doc.page_count}'\\) for i, page in enumerate\\(doc\\): print\\(f'--- Page {i+1} ---'\\) blocks = page.get_text\\('dict'\\) colors_seen = set\\(\\) for block in blocks['blocks']: if block.get\\('type'\\) == 0: for line in block['lines']: for span in line['spans']: c = span.get\\('color', 0\\) r = \\(c >> 16\\) & 0xFF g = \\(c >> 8\\) & 0xFF b = c & 0xFF hex_c = f'#{r:02X}{g:02X}{b:02X}' if hex_c != '#000000' and hex_c != '#FFFFFF': colors_seen.add\\(hex_c\\) txt = span['text'].strip\\(\\) if txt: print\\(f' size={span[\\\\\"size\\\\\"]:.1f} color={hex_c} font={span[\\\\\"font\\\\\"][:30]} text={txt[:60]}'\\) print\\(f' Colors used: {colors_seen}'\\) if i >= 1: break \")",
|
||||||
|
"Bash(python3 *)",
|
||||||
|
"Bash(node *)",
|
||||||
|
"Read(//Users/nabil.derouiche/.ssh/**)",
|
||||||
|
"Bash(ssh-keyscan -p 22222 192.168.100.33)",
|
||||||
|
"Bash(/usr/local/bin/brew install *)",
|
||||||
|
"Bash(/opt/homebrew/bin/brew install *)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
49
index.html
49
index.html
|
|
@ -109,8 +109,8 @@
|
||||||
.app-layout{display:flex;min-height:calc(100vh - var(--header-h));}
|
.app-layout{display:flex;min-height:calc(100vh - var(--header-h));}
|
||||||
|
|
||||||
/* ── SIDEBAR ── */
|
/* ── SIDEBAR ── */
|
||||||
.sidebar{width:var(--sidebar-w);flex-shrink:0;background:var(--sidebar-bg);border-right:1px solid var(--sidebar-border);position:sticky;top:var(--topbar-h);height:calc(100vh - var(--topbar-h));align-self:flex-start;overflow-y:auto;transition:margin-left var(--transition),width var(--transition);display:flex;flex-direction:column;}
|
.sidebar{width:var(--sidebar-w);flex-shrink:0;background:var(--sidebar-bg);border-right:1px solid var(--sidebar-border);position:fixed;left:0;top:var(--topbar-h);height:calc(100vh - var(--topbar-h));overflow-y:auto;transform:translateX(0);transition:transform var(--transition);display:flex;flex-direction:column;z-index:90;}
|
||||||
.sidebar.hidden{margin-left:calc(-1 * var(--sidebar-w));width:0;overflow:hidden;}
|
.sidebar.hidden{transform:translateX(-100%);}
|
||||||
.sidebar-nav{padding:12px 8px;flex:1;display:flex;flex-direction:column;gap:2px;}
|
.sidebar-nav{padding:12px 8px;flex:1;display:flex;flex-direction:column;gap:2px;}
|
||||||
.sidebar-section-label{font-size:0.68em;font-weight:700;text-transform:uppercase;letter-spacing:0.8px;color:var(--text-muted);padding:8px 10px 4px;margin-top:8px;}
|
.sidebar-section-label{font-size:0.68em;font-weight:700;text-transform:uppercase;letter-spacing:0.8px;color:var(--text-muted);padding:8px 10px 4px;margin-top:8px;}
|
||||||
.sidebar-section-label:first-child{margin-top:0;}
|
.sidebar-section-label:first-child{margin-top:0;}
|
||||||
|
|
@ -135,7 +135,7 @@
|
||||||
.nav-hidden{display:none !important;}
|
.nav-hidden{display:none !important;}
|
||||||
|
|
||||||
/* ── MAIN WRAPPER ── */
|
/* ── MAIN WRAPPER ── */
|
||||||
.main-wrapper{flex:1;min-width:0;transition:all var(--transition);}
|
.main-wrapper{flex:1;min-width:0;margin-left:var(--sidebar-w);transition:margin-left var(--transition);}
|
||||||
|
|
||||||
/* ── SLIDES ── */
|
/* ── SLIDES ── */
|
||||||
.slides-container{max-width:1400px;margin:0 auto;padding:24px;min-height:75vh;}
|
.slides-container{max-width:1400px;margin:0 auto;padding:24px;min-height:75vh;}
|
||||||
|
|
@ -208,10 +208,10 @@
|
||||||
.table-header{background:var(--primary);padding:12px 18px;display:flex;justify-content:space-between;align-items:center;color:white;}
|
.table-header{background:var(--primary);padding:12px 18px;display:flex;justify-content:space-between;align-items:center;color:white;}
|
||||||
.table-header h3{font-size:0.9em;font-weight:600;display:flex;align-items:center;gap:8px;}
|
.table-header h3{font-size:0.9em;font-weight:600;display:flex;align-items:center;gap:8px;}
|
||||||
.table-header .badge{background:rgba(255,255,255,0.2);padding:2px 9px;border-radius:16px;font-size:0.78em;font-weight:600;}
|
.table-header .badge{background:rgba(255,255,255,0.2);padding:2px 9px;border-radius:16px;font-size:0.78em;font-weight:600;}
|
||||||
.table-wrapper{overflow-x:auto;}
|
.table-wrapper{overflow-x:auto;overflow-y:auto;max-height:560px;}
|
||||||
table{width:100%;border-collapse:collapse;min-width:600px;}
|
table{width:100%;border-collapse:collapse;min-width:600px;}
|
||||||
th,td{padding:12px 16px;text-align:left;border-bottom:1px solid var(--border-color);vertical-align:middle;}
|
th,td{padding:12px 16px;text-align:left;border-bottom:1px solid var(--border-color);vertical-align:middle;}
|
||||||
th{background:var(--table-header);font-weight:600;font-size:0.72em;text-transform:uppercase;letter-spacing:0.6px;color:var(--text-muted);cursor:pointer;user-select:none;white-space:nowrap;}
|
th{background:var(--table-header);font-weight:600;font-size:0.72em;text-transform:uppercase;letter-spacing:0.6px;color:var(--text-muted);cursor:pointer;user-select:none;white-space:nowrap;position:sticky;top:0;z-index:2;}
|
||||||
th:hover{color:var(--accent);}
|
th:hover{color:var(--accent);}
|
||||||
th .sort-icon{margin-left:4px;opacity:0.35;font-size:0.85em;}
|
th .sort-icon{margin-left:4px;opacity:0.35;font-size:0.85em;}
|
||||||
th.sort-asc .sort-icon,th.sort-desc .sort-icon{opacity:1;color:var(--accent);}
|
th.sort-asc .sort-icon,th.sort-desc .sort-icon{opacity:1;color:var(--accent);}
|
||||||
|
|
@ -393,7 +393,7 @@
|
||||||
.logo-section h1{font-size:0.95em;}
|
.logo-section h1{font-size:0.95em;}
|
||||||
.slides-container{padding:12px;}
|
.slides-container{padding:12px;}
|
||||||
.kpi-grid{grid-template-columns:1fr 1fr;}
|
.kpi-grid{grid-template-columns:1fr 1fr;}
|
||||||
.sidebar{position:fixed;z-index:80;height:calc(100vh - 56px);}
|
.sidebar{top:56px;height:calc(100vh - 56px);}
|
||||||
.main-wrapper{margin-left:0 !important;}
|
.main-wrapper{margin-left:0 !important;}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -746,6 +746,10 @@
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
<input class="search-input-full" type="text" id="searchPipeline" placeholder="Rechercher par N° AO, description, région..." oninput="filterPipeline()">
|
<input class="search-input-full" type="text" id="searchPipeline" placeholder="Rechercher par N° AO, description, région..." oninput="filterPipeline()">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="filters-bar">
|
||||||
|
<select class="filter-select" id="pipelineFilterRegion" onchange="filterPipeline()"><option value="">Toutes régions</option></select>
|
||||||
|
<select class="filter-select" id="pipelineFilterPhase" onchange="filterPipeline()"><option value="">Toutes phases</option></select>
|
||||||
|
</div>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<div class="table-header" style="background:linear-gradient(90deg,#4F46E5,#6366F1);">
|
<div class="table-header" style="background:linear-gradient(90deg,#4F46E5,#6366F1);">
|
||||||
<h3><i class="fas fa-rocket"></i> AO en cours de lancement</h3>
|
<h3><i class="fas fa-rocket"></i> AO en cours de lancement</h3>
|
||||||
|
|
@ -1388,6 +1392,18 @@ function renderPipeline() {
|
||||||
const total = pipelineData._total_estimation ?? 0;
|
const total = pipelineData._total_estimation ?? 0;
|
||||||
document.getElementById('pipelineTotalEstimation').textContent =
|
document.getElementById('pipelineTotalEstimation').textContent =
|
||||||
total > 0 ? parseNum(total).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g,' ') + ' DT' : '—';
|
total > 0 ? parseNum(total).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g,' ') + ' DT' : '—';
|
||||||
|
// Peupler filtre regions
|
||||||
|
const regSel = document.getElementById('pipelineFilterRegion');
|
||||||
|
if (regSel) {
|
||||||
|
const regs = [...new Set(pipelineData.flatMap(r => (r._regions||[]).map(rg=>rg.name)))].sort();
|
||||||
|
regSel.innerHTML = '<option value="">Toutes r\u00e9gions</option>' + regs.map(r=>'<option>'+escapeHtml(r)+'</option>').join('');
|
||||||
|
}
|
||||||
|
// Peupler filtre phases
|
||||||
|
const phSel = document.getElementById('pipelineFilterPhase');
|
||||||
|
if (phSel) {
|
||||||
|
const phases = [...new Set(pipelineData.map(r => r._phase?.label).filter(Boolean))].sort();
|
||||||
|
phSel.innerHTML = '<option value="">Toutes phases</option>' + phases.map(p=>'<option>'+escapeHtml(p)+'</option>').join('');
|
||||||
|
}
|
||||||
_pipelineFiltered = [...pipelineData];
|
_pipelineFiltered = [...pipelineData];
|
||||||
const sq = document.getElementById('searchPipeline');
|
const sq = document.getElementById('searchPipeline');
|
||||||
if (sq) sq.value = '';
|
if (sq) sq.value = '';
|
||||||
|
|
@ -1642,7 +1658,9 @@ function toggleSidebar() {
|
||||||
}
|
}
|
||||||
function applySidebarState() {
|
function applySidebarState() {
|
||||||
const sb = document.getElementById('appSidebar');
|
const sb = document.getElementById('appSidebar');
|
||||||
|
const mw = document.getElementById('mainWrapper');
|
||||||
if (sb) sb.classList.toggle('hidden', !_sidebarVisible);
|
if (sb) sb.classList.toggle('hidden', !_sidebarVisible);
|
||||||
|
if (mw) mw.style.marginLeft = _sidebarVisible ? 'var(--sidebar-w)' : '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── ALERT TICKER ── */
|
/* ── ALERT TICKER ── */
|
||||||
|
|
@ -1673,13 +1691,18 @@ function updateTicker() {
|
||||||
/* ── PIPELINE SEARCH ── */
|
/* ── PIPELINE SEARCH ── */
|
||||||
let _pipelineFiltered = [];
|
let _pipelineFiltered = [];
|
||||||
function filterPipeline() {
|
function filterPipeline() {
|
||||||
const q = (document.getElementById('searchPipeline')?.value || '').toLowerCase();
|
const q = (document.getElementById('searchPipeline')?.value || '').toLowerCase();
|
||||||
_pipelineFiltered = q
|
const region = document.getElementById('pipelineFilterRegion')?.value || '';
|
||||||
? pipelineData.filter(r => {
|
const phase = document.getElementById('pipelineFilterPhase')?.value || '';
|
||||||
const text = `${r['num-ao']||''} ${r['Description du projet']||''} ${(r._regions||[]).map(rg=>rg.name).join(' ')}`.toLowerCase();
|
_pipelineFiltered = pipelineData.filter(r => {
|
||||||
return text.includes(q);
|
if (q) {
|
||||||
})
|
const text = `${r['num-ao']||''} ${r['Description du projet']||''} ${(r._regions||[]).map(rg=>rg.name).join(' ')}`.toLowerCase();
|
||||||
: [...pipelineData];
|
if (!text.includes(q)) return false;
|
||||||
|
}
|
||||||
|
if (region && !(r._regions||[]).some(rg => rg.name === region)) return false;
|
||||||
|
if (phase && (r._phase?.label || '') !== phase) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
renderPipelineTable(_pipelineFiltered);
|
renderPipelineTable(_pipelineFiltered);
|
||||||
}
|
}
|
||||||
function renderPipelineTable(data) {
|
function renderPipelineTable(data) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue