refacto: dashboard Flask Blueprints + templates Jinja2, engine run/lore_enricher mis a jour

This commit is contained in:
2026-06-17 14:10:01 +00:00
parent ae6356ded4
commit ea8e24e238
24 changed files with 2457 additions and 1850 deletions
+218
View File
@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PipBoy — Fallout: Venice of Wasteland</title>
<style>
:root {
--green: #4ade80;
--red: #f87171;
--amber: #fbbf24;
--blue: #60a5fa;
--bg: #0f1117;
--panel: #161b27;
--panel2: #1a1f2e;
--border: #2a3040;
--text: #e8e8e8;
--muted: #8892a4;
--dim: #4a5568;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--bg); color: var(--text); font-family: system-ui, 'Segoe UI', sans-serif; font-size: 14px; }
.topbar { display: flex; align-items: center; gap: 8px; padding: 10px 16px; background: #0a0d14; border-bottom: 1px solid var(--border); }
.topbar-title { font-size: 15px; font-weight: 600; color: var(--green); letter-spacing: 1px; margin-right: 8px; }
.session-pills { display: flex; gap: 6px; }
.session-pill { padding: 4px 12px; border-radius: 20px; border: 1px solid var(--border); color: var(--muted); text-decoration: none; font-size: 12px; font-weight: 500; display: inline-flex; align-items: center; gap: 5px; transition: all .15s; }
.session-pill:hover { border-color: var(--dim); color: var(--text); }
.session-pill.active { border-color: var(--green); color: var(--green); background: rgba(74,222,128,.08); }
.session-pill.stopped { border-color: var(--red); color: var(--red); }
.session-pill .dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
.dot-green { background: var(--green); }
.dot-red { background: var(--red); }
.dot-amber { background: var(--amber); }
.nav-sep { flex-grow: 1; }
.nav-tabs { display: flex; gap: 2px; }
.nav-tabs a { padding: 6px 14px; text-decoration: none; color: var(--muted); font-size: 13px; font-weight: 500; border-radius: 6px 6px 0 0; border: 1px solid transparent; border-bottom: none; transition: all .15s; }
.nav-tabs a:hover:not(.active) { color: var(--text); background: var(--panel2); }
.nav-tabs a.active { color: var(--amber); background: var(--panel); border-color: var(--border); border-bottom-color: var(--panel); }
.main { padding: 16px; }
.flash { padding: 8px 12px; margin: 0 16px 12px; border-radius: 6px; font-size: 13px; }
.flash-ok { background: rgba(74,222,128,.1); border: 1px solid rgba(74,222,128,.3); color: var(--green); }
.flash-err { background: rgba(248,113,113,.1); border: 1px solid rgba(248,113,113,.3); color: var(--red); }
.info-bar { display: flex; align-items: center; gap: 16px; padding: 10px 14px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 16px; font-size: 13px; flex-wrap: wrap; }
.info-bar .label { color: var(--muted); }
.info-bar .val { color: var(--text); font-weight: 500; }
.status-badge { display: inline-flex; align-items: center; gap: 5px; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
.status-active { background: rgba(74,222,128,.12); color: var(--green); }
.status-stopped { background: rgba(248,113,113,.12); color: var(--red); }
.status-paused { background: rgba(251,191,36,.12); color: var(--amber); }
.mode-tag { padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; background: rgba(96,165,250,.1); color: var(--blue); border: 1px solid rgba(96,165,250,.2); }
.metric-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 10px; margin-bottom: 16px; }
.metric-card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; }
.metric-card .metric-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 4px; }
.metric-card .metric-value { font-size: 26px; font-weight: 700; line-height: 1; }
.metric-green { color: var(--green); }
.metric-red { color: var(--red); }
.metric-amber { color: var(--amber); }
.metric-blue { color: var(--blue); }
.metric-muted { color: var(--muted); }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin-top: 12px; }
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 12px; }
.panel h2 { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .8px; color: var(--amber); border-bottom: 1px solid var(--border); padding-bottom: 8px; margin-bottom: 10px; }
table { width: 100%; border-collapse: collapse; }
th { color: var(--muted); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .5px; padding: 6px 10px; text-align: left; border-bottom: 1px solid var(--border); }
td { padding: 6px 10px; font-size: 13px; border-bottom: 1px solid rgba(42,48,64,.5); vertical-align: middle; }
tr:nth-child(even) td { background: #1a1a2e; }
tr:hover td { background: rgba(74,222,128,.04) !important; }
.dead td { opacity: .45; text-decoration: line-through; }
.tier0-text { color: #fb923c; }
.tier1-text { color: var(--green); }
.tier2-text { color: var(--blue); }
.tier3-text { color: var(--muted); }
.hp-ok { color: var(--green); font-weight: 600; }
.hp-low { color: var(--amber); font-weight: 600; }
.hp-crit { color: var(--red); font-weight: 600; }
.faction-union { color: #60a5fa; }
.faction-cda { color: #f87171; }
.faction-ecumeurs { color: #c084fc; }
.faction-syndicat { color: #facc15; }
.faction-krewe { color: #34d399; }
.faction-oak { color: #fb923c; }
.faction-regie { color: #e2e8f0; }
.faction-default { color: var(--muted); }
.ev-combat { color: var(--red); }
.ev-encounter { color: var(--amber); }
.ev-survival { color: #86efac; }
.ev-death { color: #ff0000; font-weight: 700; }
.rel-hostile { color: var(--red); }
.rel-neutral { color: var(--muted); }
.rel-allie { color: var(--green); }
.scrollable { max-height: 300px; overflow-y: auto; }
.world-zone { display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid rgba(42,48,64,.5); font-size: 13px; }
.zone-name { color: var(--amber); min-width: 150px; }
.zone-stats { display: flex; gap: 10px; font-size: 12px; color: var(--muted); }
.zone-stats span { color: var(--text); }
.cfg-block { margin-bottom: 16px; }
.cfg-block h3 { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .8px; color: var(--amber); border-bottom: 1px solid var(--border); padding-bottom: 6px; margin-bottom: 10px; }
.cfg-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.cfg-row label { min-width: 230px; font-size: 13px; color: var(--muted); }
.cfg-row input[type=number], .cfg-row input[type=text], .cfg-row select { background: #0a0d14; color: var(--text); border: 1px solid var(--border); border-radius: 5px; padding: 5px 10px; font-family: inherit; font-size: 13px; width: 140px; }
.cfg-row input[type=checkbox] { accent-color: var(--green); width: auto; }
.cfg-row select:focus, .cfg-row input:focus { outline: none; border-color: var(--amber); }
.btn { padding: 7px 18px; border: 1px solid var(--border); background: var(--panel2); color: var(--text); font-family: inherit; font-size: 13px; font-weight: 500; border-radius: 6px; cursor: pointer; transition: all .15s; }
.btn:hover { border-color: var(--dim); background: #222736; }
.btn-green { border-color: var(--green); color: var(--green); background: rgba(74,222,128,.08); }
.btn-green:hover { background: rgba(74,222,128,.15); }
.btn-red { border-color: var(--red); color: var(--red); background: rgba(248,113,113,.08); }
.btn-red:hover { background: rgba(248,113,113,.15); }
.btn-amber { border-color: var(--amber); color: var(--amber); background: rgba(251,191,36,.08); }
.btn-amber:hover { background: rgba(251,191,36,.15); }
.code-block { position: relative; margin: 8px 0; }
.code-block pre { background: #0a0d14; border: 1px solid var(--border); border-radius: 6px; padding: 10px 14px; font-family: 'Consolas', 'Courier New', monospace; font-size: 12px; color: #86efac; white-space: pre-wrap; word-break: break-all; margin: 0; padding-right: 80px; }
.copy-btn { position: absolute; top: 6px; right: 8px; padding: 3px 10px; font-size: 11px; border: 1px solid var(--border); background: var(--panel2); color: var(--muted); border-radius: 4px; cursor: pointer; font-family: inherit; }
.copy-btn:hover { color: var(--text); border-color: var(--dim); }
.lore-card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 14px; margin-bottom: 10px; }
.lore-card h3 { font-size: 13px; color: var(--text); margin-bottom: 8px; font-weight: 500; }
.lore-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
.diff-old { background: rgba(248,113,113,.06); border: 1px solid rgba(248,113,113,.2); border-radius:5px; padding: 8px; font-size: 12px; color: #fca5a5; white-space: pre-wrap; font-family: 'Consolas', monospace; }
.diff-new { background: rgba(74,222,128,.06); border: 1px solid rgba(74,222,128,.2); border-radius:5px; padding: 8px; font-size: 12px; color: #86efac; white-space: pre-wrap; font-family: 'Consolas', monospace; }
.diff-label { font-size: 11px; color: var(--muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: .5px; }
.lore-meta { font-size: 12px; color: var(--muted); margin-bottom: 8px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.lore-actions { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; }
.lore-actions textarea { background: #0a0d14; color: var(--text); border: 1px solid var(--border); border-radius: 5px; font-family: 'Consolas', monospace; font-size: 12px; padding: 6px 8px; width: 100%; min-height: 60px; resize: vertical; }
.lore-filters { display: flex; gap: 10px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; }
.lore-filters label { font-size: 13px; color: var(--muted); }
.lore-filters select { background: #0a0d14; color: var(--text); border: 1px solid var(--border); border-radius: 5px; font-family: inherit; font-size: 13px; padding: 4px 8px; }
.lore-stat-bar { display: flex; gap: 16px; margin-bottom: 12px; font-size: 13px; flex-wrap: wrap; }
.lore-stat { color: var(--muted); }
.lore-stat span { font-weight: 600; }
.badge-pending { background: rgba(251,191,36,.12); color: var(--amber); padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }
.badge-accepted { background: rgba(74,222,128,.12); color: var(--green); padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }
.badge-rejected { background: rgba(248,113,113,.12); color: var(--red); padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }
.badge-modified { background: rgba(96,165,250,.12); color: var(--blue); padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }
.change-type-badge { font-size: 11px; color: var(--dim); border: 1px solid var(--border); padding: 1px 7px; border-radius: 4px; }
.tools-section { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 14px; }
.tools-section h2 { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: .8px; color: var(--amber); border-bottom: 1px solid var(--border); padding-bottom: 8px; margin-bottom: 14px; }
.tool-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.tool-row label { font-size: 13px; color: var(--muted); min-width: 100px; }
.tool-row select, .tool-row input[type=number] { background: #0a0d14; color: var(--text); border: 1px solid var(--border); border-radius: 5px; padding: 5px 10px; font-family: inherit; font-size: 13px; }
.radio-group { display: flex; gap: 6px; flex-wrap: wrap; }
.radio-group label { display: flex; align-items: center; gap: 5px; padding: 4px 12px; border: 1px solid var(--border); border-radius: 5px; cursor: pointer; font-size: 13px; color: var(--muted); min-width: auto; transition: all .15s; }
.radio-group input[type=radio] { display: none; }
.radio-group input[type=radio]:checked + span { color: var(--amber); }
.radio-group label:has(input:checked) { border-color: var(--amber); background: rgba(251,191,36,.08); color: var(--amber); }
.enrich-card { background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:12px 14px; }
.mini-console { background:#020408;border:1px solid var(--border);padding:8px 10px;max-height:200px;overflow-y:auto;font-size:11px;color:#a8d5a2;white-space:pre-wrap;border-radius:4px;margin-top:8px; }
.results-area { margin-top: 12px; }
.results-meta { font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.diag-good { color: var(--green); font-weight: 600; }
.diag-bad { color: var(--red); font-weight: 600; }
.sim-tab-btn { background:none;border:none;border-bottom:2px solid transparent;color:var(--dim);padding:8px 18px;cursor:pointer;font-size:13px;font-family:inherit;transition:color .15s; }
.sim-tab-btn:hover { color:var(--text); }
.sim-tab-btn.active { color:var(--amber);border-bottom-color:var(--amber);font-weight:600; }
.refresh-note { font-size: 12px; color: var(--dim); margin-left: auto; }
.refresh-note a { color: var(--muted); text-decoration: none; }
.refresh-note a:hover { color: var(--text); }
.footer { text-align: center; font-size: 11px; color: var(--dim); padding: 16px; border-top: 1px solid var(--border); margin-top: 20px; }
</style>
</head>
<body>
<div class="topbar">
<span class="topbar-title">📟 PIPBOY</span>
<div class="session-pills">
{% for s in all_sessions %}
{% set is_active = s.status == 'active' %}
<a href="/?sid={{ s.id }}"
class="session-pill {{ 'active' if s.id == current_sid and tab == 'sim' else ('stopped' if not is_active else '') }}">
<span class="dot {{ 'dot-green' if is_active else 'dot-red' }}"></span>
S{{ s.id }}
</a>
{% endfor %}
</div>
<span class="nav-sep"></span>
<div class="nav-tabs">
<a href="/?sid={{ current_sid }}" class="{{ 'active' if tab=='sim' }}">📡 SIM</a>
<a href="/tools?sid={{ current_sid }}" class="{{ 'active' if tab=='tools' }}">🔧 OUTILS</a>
<a href="/lore" class="{{ 'active' if tab=='lore' }}">📜 LORE</a>
<a href="/sim-proposals" class="{{ 'active' if tab=='sim_proposals' }}">🎲 RÈGLES SIM</a>
<a href="/params?sid={{ current_sid }}" class="{{ 'active' if tab=='params' }}">⚙ PARAMS</a>
</div>
</div>
{% if flash_msg %}
<div class="flash {{ 'flash-ok' if flash_ok else 'flash-err' }}">{{ flash_msg }}</div>
{% endif %}
<div class="main">
{% block content %}{% endblock %}
</div>
<div class="footer">PipBoy v5.1 | SIM LIBRE • OUTILS • ENRICHISSEMENT • PARAMÈTRES</div>
</body>
</html>