From 03f4f11373778a582be0e042ba9ccbed58e66daa Mon Sep 17 00:00:00 2001 From: kawa Date: Sun, 12 Apr 2026 20:12:46 +0200 Subject: [PATCH] Initial commit - Phone Refurb system --- README.md | 170 ++++++++++++++++ phone-cli | 241 +++++++++++++++++++++++ phone-refurb.service | 14 ++ phones.db | Bin 0 -> 49152 bytes web-app.log | 10 + web-app.py | 457 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 892 insertions(+) create mode 100644 README.md create mode 100755 phone-cli create mode 100644 phone-refurb.service create mode 100644 phones.db create mode 100644 web-app.log create mode 100755 web-app.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6b0b0d --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +# Phone Refurb - Gestion de stock de téléphones + +Outil de gestion pour le reconditionnement de téléphones contre l'obsolescence programmée. + +## Installation + +```bash +cd ~/phone-refurb +python3 -m venv venv +./venv/bin/pip install flask +``` + +## Lancer la Web App + +```bash +# Manuel +./venv/bin/python web-app.py + +# Via systemd (recommandé) +sudo cp phone-refurb.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable phone-refurb +sudo systemctl start phone-refurb +``` + +## Accès + +- **LAN:** http://192.168.1.149:5050 +- **Mesh KAWA:** http://100.64.0.4:5050 + +## CLI - phone-cli + +### Commandes principales + +```bash +# Lister les téléphones +./phone-cli list [status] + +# Ajouter un téléphone +./phone-cli add [imei] [serial] [status] [notes] + +# Détails d'un téléphone +./phone-cli info + +# Ajouter une action +./phone-cli action [status] [notes] + +# Changer le statut +./phone-cli status + +# Lister les ROMs +./phone-cli roms [device_code] + +# Rechercher +./phone-cli search +``` + +### Exemples + +```bash +# Ajouter un Galaxy S7 +./phone-cli add Samsung "Galaxy S7" SM-G930F + +# Ajouter avec IMEI et notes +./phone-cli add Samsung "Galaxy J5" SM-J510FN 352154091234567 51c1e4fc stock "Écran fêlé" + +# Lister les téléphones en stock +./phone-cli list stock + +# Voir les détails du téléphone #1 +./phone-cli info 1 + +# Enregistrer une action +./phone-cli action 1 flash_rom success "LineageOS 18.1 installée" + +# Enregistrer un échec +./phone-cli action 1 unlock_bootloader failed "Bootloader bloqué par opérateur" + +# Marquer comme prêt +./phone-cli status 1 ready + +# Marquer comme vendu +./phone-cli status 1 sold + +# Voir les ROMs pour j5xnlte +./phone-cli roms j5xnlte + +# Rechercher +./phone-cli search samsung +./phone-cli search j5 +``` + +## Statuts + +| Statut | Description | +|--------|-------------| +| stock | En stock, pas encore traité | +| wip | En cours de traitement | +| paused | Traitement suspendu | +| ready | Prêt à être utilisé/vendu | +| sold | Vendu | + +## Types d'actions + +| Action | Description | +|--------|-------------| +| backup | Sauvegarde des données | +| unlock_bootloader | Déverrouillage du bootloader | +| flash_recovery | Flash du recovery (TWRP, etc.) | +| flash_rom | Installation d'une ROM | +| root | Root du téléphone | +| test | Tests fonctionnels | +| repair | Réparation matérielle | +| sell | Vente | +| other | Autre action | + +## Base de données + +SQLite: `phones.db` + +### Tables + +- **phones** — Téléphones (marque, modèle, IMEI, statut) +- **phone_specs** — Caractéristiques (CPU, RAM, stockage) +- **software_state** — État logiciel (OS, kernel, root) +- **roms** — ROMs disponibles +- **actions** — Historique des actions + +### Requêtes utiles + +```sql +-- Voir tous les téléphones avec leur OS +SELECT * FROM phone_summary; + +-- Téléphones rootés +SELECT * FROM phone_summary WHERE root = 1; + +-- Téléphones par marque +SELECT brand, COUNT(*) FROM phones GROUP BY brand; + +-- Actions récentes +SELECT p.brand, p.model, a.action_type, a.action_date, a.status +FROM actions a JOIN phones p ON a.phone_id = p.id +ORDER BY a.action_date DESC LIMIT 20; + +-- Taux de succès +SELECT status, COUNT(*) as count FROM actions GROUP BY status; +``` + +## Structure des fichiers + +``` +~/phone-refurb/ +├── phones.db # Base SQLite +├── web-app.py # Web app Flask +├── phone-cli # CLI +├── venv/ # Environnement Python +├── web-app.log # Logs +└── README.md # Cette doc +``` + +## Intégration KAWA + +La web app est accessible depuis n'importe quel nœud du mesh via: +- `http://kawaAgent1:5050` (si DNS mesh configuré) +- `http://100.64.0.4:5050` (IP mesh) + +--- + +*Dernière mise à jour: 2026-04-12* \ No newline at end of file diff --git a/phone-cli b/phone-cli new file mode 100755 index 0000000..f82cc6c --- /dev/null +++ b/phone-cli @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +""" +Phone Refurb CLI - Gestion du stock de téléphones +Usage: ./phone-cli [args] +""" + +import sqlite3 +import sys +import os +from datetime import datetime +from pathlib import Path + +DB_PATH = Path(__file__).parent / "phones.db" + +def get_conn(): + return sqlite3.connect(DB_PATH) + +def cmd_add(brand, model, model_code, imei=None, serial=None, status="stock", notes=None): + """Ajouter un téléphone""" + conn = get_conn() + c = conn.cursor() + c.execute(""" + INSERT INTO phones (brand, model, model_code, imei, serial, status, notes) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, (brand, model, model_code, imei, serial, status, notes)) + conn.commit() + print(f"✅ Téléphone ajouté (ID: {c.lastrowid})") + conn.close() + +def cmd_list(status=None): + """Lister les téléphones""" + conn = get_conn() + c = conn.cursor() + if status: + c.execute("SELECT * FROM phone_summary WHERE status = ?", (status,)) + else: + c.execute("SELECT * FROM phone_summary") + rows = c.fetchall() + conn.close() + + if not rows: + print("Aucun téléphone trouvé") + return + + print(f"{'ID':<4} {'Marque':<10} {'Modèle':<20} {'Code':<15} {'Statut':<10} {'OS':<10} {'Root':<5}") + print("-" * 80) + for r in rows: + print(f"{r[0]:<4} {r[1]:<10} {r[2]:<20} {r[3] or '':<15} {r[4]:<10} {r[5] or '':<10} {'✓' if r[7] else '✗':<5}") + +def cmd_info(phone_id): + """Détails d'un téléphone""" + conn = get_conn() + c = conn.cursor() + + # Infos de base + c.execute("SELECT * FROM phones WHERE id = ?", (phone_id,)) + phone = c.fetchone() + if not phone: + print(f"❌ Téléphone {phone_id} non trouvé") + conn.close() + return + + print(f"\n📱 {phone[1]} {phone[2]} ({phone[3]})") + print(f" IMEI: {phone[4] or 'N/A'}") + print(f" Serial: {phone[5] or 'N/A'}") + print(f" Statut: {phone[6]}") + print(f" Notes: {phone[7] or 'Aucune'}") + + # Specs + c.execute("SELECT * FROM phone_specs WHERE phone_id = ?", (phone_id,)) + specs = c.fetchone() + if specs: + print(f"\n🔧 Specs:") + print(f" CPU: {specs[2] or 'N/A'}") + print(f" RAM: {specs[4]} Mo" if specs[4] else "") + print(f" Stockage: {specs[5]} Go" if specs[5] else "") + print(f" Écran: {specs[6] or 'N/A'}") + print(f" Batterie: {specs[8]} mAh" if specs[8] else "") + + # Software + c.execute("SELECT * FROM software_state WHERE phone_id = ? ORDER BY date_recorded DESC LIMIT 1", (phone_id,)) + sw = c.fetchone() + if sw: + print(f"\n💾 Logiciel:") + print(f" OS: {sw[2]} {sw[3] or ''}") + print(f" SDK: {sw[4] or 'N/A'}") + print(f" Kernel: {sw[5] or 'N/A'}") + print(f" Bootloader: {'🔒 Verrouillé' if sw[6] else '🔓 Déverrouillé'}") + print(f" Root: {'✓' if sw[7] else '✗'}") + print(f" Recovery: {sw[8] or 'Stock'}") + + # Actions + c.execute("SELECT action_type, action_date, status, notes FROM actions WHERE phone_id = ? ORDER BY action_date DESC LIMIT 10", (phone_id,)) + actions = c.fetchall() + if actions: + print(f"\n📋 Actions récentes:") + for a in actions: + status_icon = "✅" if a[2] == "success" else "⚠️" if a[2] == "partial" else "❌" + print(f" {status_icon} {a[0]} ({a[1][:10]})") + if a[3]: + print(f" {a[3][:50]}") + + conn.close() + +def cmd_action(phone_id, action_type, status="success", notes=None, rom_id=None): + """Ajouter une action""" + conn = get_conn() + c = conn.cursor() + c.execute(""" + INSERT INTO actions (phone_id, action_type, status, notes, rom_id) + VALUES (?, ?, ?, ?, ?) + """, (phone_id, action_type, status, notes, rom_id)) + conn.commit() + + # Update phone timestamp + c.execute("UPDATE phones SET updated_at = CURRENT_TIMESTAMP WHERE id = ?", (phone_id,)) + conn.commit() + + print(f"✅ Action '{action_type}' ajoutée") + conn.close() + +def cmd_update_status(phone_id, status): + """Changer le statut d'un téléphone""" + conn = get_conn() + c = conn.cursor() + c.execute("UPDATE phones SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", (status, phone_id)) + conn.commit() + print(f"✅ Statut mis à jour: {status}") + conn.close() + +def cmd_roms(device_code=None): + """Lister les ROMs disponibles""" + conn = get_conn() + c = conn.cursor() + if device_code: + c.execute("SELECT id, name, version, android_version, file_size_mb FROM roms WHERE device_code = ?", (device_code,)) + else: + c.execute("SELECT id, name, version, android_version, device_code, file_size_mb FROM roms") + rows = c.fetchall() + conn.close() + + print(f"{'ID':<4} {'ROM':<20} {'Version':<25} {'Android':<10} {'Device':<15} {'Size':<10}") + print("-" * 90) + for r in rows: + if device_code: + print(f"{r[0]:<4} {r[1]:<20} {r[2] or '':<25} {r[3] or '':<10} {r[4]}M") + else: + print(f"{r[0]:<4} {r[1]:<20} {r[2] or '':<25} {r[3] or '':<10} {r[4]:<15} {r[5]}M") + +def cmd_search(query): + """Rechercher un téléphone""" + conn = get_conn() + c = conn.cursor() + c.execute(""" + SELECT * FROM phone_summary + WHERE brand LIKE ? OR model LIKE ? OR model_code LIKE ? OR status LIKE ? + """, (f"%{query}%", f"%{query}%", f"%{query}%", f"%{query}%")) + rows = c.fetchall() + conn.close() + + if not rows: + print("Aucun résultat") + return + + print(f"{'ID':<4} {'Marque':<10} {'Modèle':<20} {'Code':<15} {'Statut':<10}") + print("-" * 60) + for r in rows: + print(f"{r[0]:<4} {r[1]:<10} {r[2]:<20} {r[3] or '':<15} {r[4]:<10}") + +def print_help(): + print(""" +Phone Refurb CLI - Gestion du stock de téléphones + +Usage: ./phone-cli [args] + +Commands: + add [imei] [serial] [status] [notes] + Ajouter un nouveau téléphone + + list [status] + Lister les téléphones (filtrer par statut: stock, paused, sold, etc.) + + info + Détails complets d'un téléphone + + action [status] [notes] + Ajouter une action (status: success, partial, failed) + + status + Changer le statut d'un téléphone + + roms [device_code] + Lister les ROMs disponibles + + search + Rechercher un téléphone + +Examples: + ./phone-cli add Samsung "Galaxy S7" SM-G930F + ./phone-cli list stock + ./phone-cli info 1 + ./phone-cli action 1 flash_rom success "LineageOS 18.1 installée" + ./phone-cli status 1 ready + ./phone-cli roms j5xnlte + ./phone-cli search samsung +""") + +def main(): + if len(sys.argv) < 2: + print_help() + return + + cmd = sys.argv[1] + + try: + if cmd == "add": + args = sys.argv[2:] + cmd_add(*args[:7] if len(args) >= 3 else (print("Usage: add "), sys.exit(1))) + elif cmd == "list": + cmd_list(sys.argv[2] if len(sys.argv) > 2 else None) + elif cmd == "info": + cmd_info(int(sys.argv[2])) + elif cmd == "action": + args = sys.argv[2:] + cmd_action(int(args[0]), args[1], args[2] if len(args) > 2 else "success", args[3] if len(args) > 3 else None) + elif cmd == "status": + cmd_update_status(int(sys.argv[2]), sys.argv[3]) + elif cmd == "roms": + cmd_roms(sys.argv[2] if len(sys.argv) > 2 else None) + elif cmd == "search": + cmd_search(sys.argv[2]) + elif cmd in ["help", "-h", "--help"]: + print_help() + else: + print(f"Commande inconnue: {cmd}") + print_help() + except Exception as e: + print(f"❌ Erreur: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/phone-refurb.service b/phone-refurb.service new file mode 100644 index 0000000..0f11aaf --- /dev/null +++ b/phone-refurb.service @@ -0,0 +1,14 @@ +[Unit] +Description=Phone Refurb Web App +After=network.target + +[Service] +Type=simple +User=kawa +WorkingDirectory=/home/kawa/phone-refurb +ExecStart=/home/kawa/phone-refurb/venv/bin/python web-app.py +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/phones.db b/phones.db new file mode 100644 index 0000000000000000000000000000000000000000..ac99bbbad3c00e701778e3fc13fa4b1b043b1125 GIT binary patch literal 49152 zcmeI5O>f)C8OKS-JpviaQ=iOa^C0p zJu?qEq&QQ5R55Hy_D!p!+N3DV3!*6eln_DShk`K6zx^%24<`B!z86EsZ#tY8awpHO zNdFdQCoN&_6X~yW-=F=RF^oOxO$IRG=Q-9(C*gyaTK;XR~@N#oDwy?N3{n?}E z20iMv%r0#*r_)ib7XyygSRFw4wNf5sEa)y2@INGz+?zy95 z>a^Vt@f=N!d-Z*ot=bOrT`Xs^=5uN>!|e8b4yo0p?P1?N)3n>Bs#B}kHnl??uwAYaX71ZZs>Q|UHKBx&?W!jX`G~Nb zFS4O0wrls58u|Ds4{~}skEl2Knd-h$Uw<+)mCUBaBcrR+V?#e~s+!H~&%9ED*lCxKPKj4Q6(xIfs;J3}@;!20v~5O?i58LRot5%%|Ur#ggf?`07`- zy4R+Gz`<@h$g5HMxT5s&ktMGM8agRg8_Fiv*iNmyRjNHD50$5+wAy6UZ zj-RDvcA6LE1x?lK1AYnp%UGBc#6Y$&`=>qsalO=f2UV&7Nwv zhW(gvLYtkvLBv{%YKPoHHd#wuUcDVk!)5jQYshO@ADTtaO~!w1CyC6bYm>2LCL{hf z;~^fjP=l_i0qlceZ>@lSOX*8K)ZDpob}gwV=V;KVmY9BcuA!hm`i*VSriZwVnDz6T z^t*-Z=ir+8k+4D#7B>5#%-wFp3C$4+dP8+8Jr%%r-6cfHn_?+CJm$mvYm?`$Z_fz}KS8Y&))P z3We>dw(XvIJ8G-1rZIM=-`)l+jprHInw{uW^5#wPsoxErwfP{C8^C7{?Q+vN=$sgU zIuy9%nwDEUW#;8C#8@(w5?>`R6cn8CA_F=2)p%=-_-C>)=L+x2$0Ibdr0x6u;{|0qIh*{cyRkd^=lM$;!(_Uc0xta!+0x zaqq7K{%bICF`Dw-%>V!YnIN6LJA$DmAOHd&00JNY0w4eaAOHd&00JNY0wV-| zEGC5b-Rlu?;cBGbReL(0NrF!au_719R(#=dNmXmXG%A1;S%=`cD_&+lDJ3+e6f3Sf72!H?xfB*=900@8p2!H?xfWZHS z!0CMCim-4^NUtlRa8)Tb9@ln?xwTv&dpBG7Wc9e)wrR_@d+c65ueWpdk=4s}Xg&zZ z=UXOs9jZqv_sV*+~o!8o;e1#oGS>9F;@+E(Lm8>jhYrg+Dq1t{OawkSl{D|HQ2=+2k%)tck=b0yY|R5{%!Z~{EKcmr_nuC zJ9K*P+I_ak#*`JTRJ$y%@HGLnmcbWrzD0_;)lHKy)p<@2R7*d5&4{jc8TobI&o0aF zjktLgR^;WpKmY$%eEz@pUcmqW0w4eaAOHd&00JNY0w4eaAOHfFi$Efpjt9#Q`s4qv z1?lU{B_gB&0T2KI5C8!X009sH0T2KI5C8!Xh`aOus}7^OJN}PI{}QBc_zyM^009sH z0T2KI5C8!X009sH0T2Lz%S2!z8W$tcXguzZ|GyNZe@b6oCIKM}2!H?xfB*=900@8p e2!H?xfB*=*PXy+o*TswJ3L^8-#rVLa0{;PI + * { box-sizing: border-box; } + body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 0; background: #1a1a2e; color: #eee; } + .container { max-width: 1200px; margin: 0 auto; padding: 20px; } + h1, h2, h3 { color: #00d4aa; margin-top: 0; } + a { color: #00d4aa; text-decoration: none; } + a:hover { text-decoration: underline; } + table { width: 100%; border-collapse: collapse; background: #16213e; border-radius: 8px; overflow: hidden; } + th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #0f3460; } + th { background: #0f3460; color: #00d4aa; } + tr:hover { background: #1a1a4e; } + .badge { display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: bold; } + .badge-stock { background: #3498db; } + .badge-paused { background: #f39c12; color: #000; } + .badge-wip { background: #e67e22; } + .badge-ready { background: #27ae60; } + .badge-sold { background: #9b59b6; } + .badge-success { background: #27ae60; } + .badge-partial { background: #f39c12; color: #000; } + .badge-failed { background: #e74c3c; } + .badge-custom { background: #9b59b6; } + .badge-recovery { background: #e67e22; } + .badge-kernel { background: #1abc9c; } + .btn { display: inline-block; padding: 8px 16px; background: #00d4aa; color: #000; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; font-weight: bold; margin: 2px; } + .btn:hover { background: #00b894; } + .btn-sm { padding: 4px 10px; font-size: 12px; } + .card { background: #16213e; border-radius: 8px; padding: 20px; margin-bottom: 20px; } + .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; } + .stat { font-size: 2em; font-weight: bold; color: #00d4aa; margin: 10px 0; } + form { margin: 20px 0; } + input, select, textarea { padding: 10px; border: 1px solid #0f3460; border-radius: 4px; background: #1a1a2e; color: #eee; width: 100%; max-width: 400px; } + textarea { min-height: 80px; } + label { display: block; margin: 10px 0 5px; color: #aaa; } + nav { background: #0f3460; padding: 15px 20px; display: flex; gap: 20px; align-items: center; flex-wrap: wrap; } + nav a { color: #fff; font-weight: bold; } + nav a:hover { color: #00d4aa; } + .search-form input { width: 180px; padding: 8px; } + .mono { font-family: monospace; } + .back-link { margin-bottom: 20px; display: inline-block; } + +""" + +NAV = """ + +""" + +def page(content, query=""): + return f""" +Phone Refurb - KAWA + +{CSS} + +{NAV.format(query=query)} +
+{content} +
""" + +@app.route('/') +def index(): + db = get_db() + phones = db.execute(""" + SELECT p.id, p.brand, p.model, p.model_code, p.status, p.updated_at, + s.os_version, s.root + FROM phones p + LEFT JOIN software_state s ON p.id = s.phone_id + ORDER BY p.updated_at DESC + """).fetchall() + + stats = { + 'stock': len([p for p in phones if p['status'] == 'stock']), + 'wip': len([p for p in phones if p['status'] in ('wip', 'paused')]), + 'ready': len([p for p in phones if p['status'] == 'ready']), + 'sold': len([p for p in phones if p['status'] == 'sold']), + } + + rows = "" + for p in phones: + rows += f""" + {p['id']} + {p['brand']} + {p['model']} + {p['model_code'] or '-'} + {p['status']} + {p['os_version'] or '-'} + {'✓' if p['root'] else '✗'} + + Voir + Action + + """ + + content = f""" +
+

📱 Stock

{stats['stock']}
+

🔧 En cours

{stats['wip']}
+

✅ Prêts

{stats['ready']}
+

💰 Vendus

{stats['sold']}
+
+

Téléphones

+ + + {rows} +
IDMarqueModèleCodeStatutOSRootActions
+ """ + return page(content) + +@app.route('/phone/') +def phone(phone_id): + db = get_db() + phone = db.execute('SELECT * FROM phones WHERE id = ?', (phone_id,)).fetchone() + if not phone: + return page("

❌ Téléphone non trouvé

") + + specs = db.execute('SELECT * FROM phone_specs WHERE phone_id = ?', (phone_id,)).fetchone() + sw = db.execute('SELECT * FROM software_state WHERE phone_id = ? ORDER BY date_recorded DESC LIMIT 1', (phone_id,)).fetchone() + actions = db.execute('SELECT * FROM actions WHERE phone_id = ? ORDER BY action_date DESC LIMIT 20', (phone_id,)).fetchall() + + specs_html = "" + if specs: + specs_html = f"""
+

🔧 Caractéristiques

+
+
CPU: {specs['cpu'] or 'N/A'}
+
GPU: {specs['gpu'] or 'N/A'}
+
RAM: {specs['ram_mb']} Mo
+
Stockage: {specs['storage_gb']} Go
+
Écran: {specs['screen_size'] or 'N/A'}
+
Batterie: {specs['battery_mah']} mAh
+
+
""" + + sw_html = "" + if sw: + bootloader = '🔒 Verrouillé' if sw['bootloader_locked'] else '🔓 Déverrouillé' + root = '✓ Oui' if sw['root'] else '✗ Non' + sw_html = f"""
+

💾 Logiciel

+
+
OS: {sw['os_type']} {sw['os_version'] or ''}
+
SDK: {sw['sdk_version'] or 'N/A'}
+
Kernel: {sw['kernel_version'] or 'N/A'}
+
Bootloader: {bootloader}
+
Root: {root}
+
Recovery: {sw['recovery_type'] or 'Stock'}
+
+
""" + + actions_rows = "" + for a in actions: + actions_rows += f""" + {a['action_date'][:16] if a['action_date'] else '-'} + {a['action_type']} + {a['status']} + {a['notes'] or '-'} + """ + + actions_html = f"""
+

📋 Historique

+ {'' + actions_rows + '
DateActionStatutNotes
' if actions else '

Aucune action

'} + ➕ Ajouter une action +
""" + + content = f""" + ← Retour +
+

📱 {phone['brand']} {phone['model']}

+

Code: {phone['model_code'] or 'N/A'}

+

IMEI: {phone['imei'] or 'N/A'}

+

Serial: {phone['serial'] or 'N/A'}

+

Statut: {phone['status']}

+

Notes: {phone['notes'] or 'Aucune'}

+
+ {specs_html} + {sw_html} + {actions_html} + """ + return page(content) + +@app.route('/add', methods=['GET', 'POST']) +def add(): + if request.method == 'POST': + db = get_db() + db.execute(""" + INSERT INTO phones (brand, model, model_code, imei, serial, status, notes) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, ( + request.form['brand'], + request.form['model'], + request.form['model_code'], + request.form.get('imei') or None, + request.form.get('serial') or None, + request.form.get('status', 'stock'), + request.form.get('notes') or None + )) + db.commit() + return redirect('/') + + content = """ + ← Retour +
+

➕ Ajouter un téléphone

+
+ + + + + + + + + + + + + + + +
+
+ """ + return page(content) + +@app.route('/action/', methods=['GET', 'POST']) +def add_action(phone_id): + db = get_db() + phone = db.execute('SELECT brand, model FROM phones WHERE id = ?', (phone_id,)).fetchone() + + if request.method == 'POST': + db.execute(""" + INSERT INTO actions (phone_id, action_type, status, notes) + VALUES (?, ?, ?, ?) + """, ( + phone_id, + request.form['action_type'], + request.form['status'], + request.form.get('notes') or None + )) + db.execute('UPDATE phones SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', (phone_id,)) + db.commit() + return redirect(f'/phone/{phone_id}') + + content = f""" + ← Retour +
+

📝 Action sur {phone['brand']} {phone['model']}

+
+ + + + + + + +
+
+ """ + return page(content) + +@app.route('/roms', methods=['GET', 'POST']) +def roms(): + db = get_db() + + if request.method == 'POST': + db.execute(""" + INSERT INTO roms (name, version, android_version, device_code, source_url, file_path, file_size_mb, rom_type, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + request.form['name'], + request.form.get('version') or None, + request.form.get('android_version') or None, + request.form['device_code'], + request.form.get('source_url') or None, + request.form.get('file_path') or None, + request.form.get('file_size_mb') or None, + request.form.get('rom_type', 'custom'), + request.form.get('notes') or None + )) + db.commit() + return redirect('/roms') + + roms = db.execute('SELECT * FROM roms ORDER BY device_code, name').fetchall() + rows = "" + for r in roms: + rows += f""" + {r['id']} + {r['name']} + {r['version'] or '-'} + {r['android_version'] or '-'} + {r['device_code']} + {r['file_size_mb']}M + {r['rom_type']} + """ + + content = f""" +

💾 ROMs disponibles

+ + + {rows} +
IDNomVersionAndroidDeviceTailleType
+
+

➕ Ajouter une ROM

+
+ + + + + + + + + + + + + + + + + + + +
+
+ """ + return page(content) + +@app.route('/stats') +def stats(): + db = get_db() + + total_phones = db.execute('SELECT COUNT(*) FROM phones').fetchone()[0] + total_actions = db.execute('SELECT COUNT(*) FROM actions').fetchone()[0] + total_roms = db.execute('SELECT COUNT(*) FROM roms').fetchone()[0] + + action_stats = db.execute('SELECT status, COUNT(*) as cnt FROM actions GROUP BY status').fetchall() + type_stats = db.execute('SELECT action_type, COUNT(*) as cnt FROM actions GROUP BY action_type ORDER BY cnt DESC').fetchall() + + action_rows = "" + for s in action_stats: + action_rows += f""" + {s['status']} + {s['cnt']} + """ + + type_rows = "" + for t in type_stats: + type_rows += f""" + {t['action_type']} + {t['cnt']} + """ + + content = f""" +

📊 Statistiques

+
+

📱 Téléphones

{total_phones}
+

🔧 Actions

{total_actions}
+

💾 ROMs

{total_roms}
+
+
+

Actions par statut

+ {action_rows}
StatutNombre
+
+
+

Actions par type

+ {type_rows}
TypeNombre
+
+ """ + return page(content) + +@app.route('/search') +def search(): + query = request.args.get('q', '') + db = get_db() + phones = db.execute(""" + SELECT * FROM phones + WHERE brand LIKE ? OR model LIKE ? OR model_code LIKE ? OR notes LIKE ? + ORDER BY updated_at DESC + """, (f'%{query}%', f'%{query}%', f'%{query}%', f'%{query}%')).fetchall() + + rows = "" + for p in phones: + rows += f""" + {p['id']} + {p['brand']} + {p['model']} + {p['model_code'] or '-'} + {p['status']} + """ + + content = f""" +

🔍 Résultats pour "{query}"

+ {'' + rows + '
IDMarqueModèleCodeStatut
' if phones else '

Aucun résultat

'} + """ + return page(content, query=query) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5050, debug=False) \ No newline at end of file