refacto: dashboard Flask Blueprints + templates Jinja2, engine run/lore_enricher mis a jour
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
{% if session %}
|
||||
<div class="info-bar">
|
||||
<span><span class="label">Session</span> <strong class="val">#{{ session.id }} — {{ session.name }}</strong></span>
|
||||
<span><span class="label">Jour</span> <strong class="val">{{ session.current_day }}</strong> <span class="label">/ Tick</span> <strong class="val">{{ session.current_tick }}h</strong></span>
|
||||
<span class="mode-tag">{{ session.mode | upper }}</span>
|
||||
{% set st = session.status %}
|
||||
<span class="status-badge {{ 'status-active' if st=='active' else 'status-stopped' if st=='stopped' else 'status-paused' }}">
|
||||
<span class="dot {{ 'dot-green' if st=='active' else 'dot-red' if st=='stopped' else 'dot-amber' }}"></span>
|
||||
{{ st | upper }}
|
||||
</span>
|
||||
<span class="label">Seed: <span class="val">{{ session.seed_global }}</span></span>
|
||||
<span class="refresh-note">
|
||||
<a href="?sid={{ session.id }}">↻ Actualiser</a>
|
||||
<span id="countdown"></span>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="metric-row">
|
||||
<div class="metric-card"><div class="metric-label">PNJ Vivants</div><div class="metric-value metric-green">{{ alive_count }}</div></div>
|
||||
<div class="metric-card"><div class="metric-label">PNJ Morts</div><div class="metric-value metric-red">{{ stats.get('PNJ morts', 0) }}</div></div>
|
||||
<div class="metric-card"><div class="metric-label">Total PNJ</div><div class="metric-value metric-muted">{{ total_count }}</div></div>
|
||||
<div class="metric-card"><div class="metric-label">Combats</div><div class="metric-value metric-amber">{{ stats.get('Combats', 0) }}</div></div>
|
||||
<div class="metric-card"><div class="metric-label">Rencontres</div><div class="metric-value metric-blue">{{ stats.get('Rencontres', 0) }}</div></div>
|
||||
<div class="metric-card"><div class="metric-label">Événements</div><div class="metric-value metric-muted">{{ stats.get('Événements total', 0) }}</div></div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<h2>👥 PNJ ({{ alive_count }}/{{ total_count }})</h2>
|
||||
<div class="scrollable">
|
||||
<table>
|
||||
<tr><th>Tier</th><th>Nom</th><th>PV</th><th>Faction</th><th>Zone</th><th>Caps</th></tr>
|
||||
{% for c in characters %}
|
||||
{% set pct = (c.hp / c.max_hp * 100) | int if c.max_hp else 0 %}
|
||||
<tr class="{{ 'dead' if not c.is_alive else '' }}">
|
||||
<td>
|
||||
{% if c.tier == 0 %}<span class="tier0-text">★ BOSS</span>
|
||||
{% elif c.tier == 2 %}<span class="tier2-text">T2</span>
|
||||
{% elif c.tier == 3 %}<span class="tier3-text">T3</span>
|
||||
{% else %}<span class="tier1-text">T1</span>{% endif %}
|
||||
</td>
|
||||
<td>{{ c.name }}</td>
|
||||
<td>
|
||||
{% if c.is_alive %}
|
||||
<span class="{{ 'hp-crit' if pct < 25 else 'hp-low' if pct < 50 else 'hp-ok' }}">{{ c.hp }}/{{ c.max_hp }}</span>
|
||||
{% else %}<span class="tier3-text">†</span>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% set f = c.faction_slug or 'sans' %}
|
||||
<span class="faction-{{ 'union' if 'union' in f else 'cda' if 'cda' in f else 'ecumeurs' if 'ecumeur' in f else 'krewe' if 'krewe' in f else 'oak' if 'oak' in f else 'regie' if 'regie' in f else 'syndicat' if 'syndicat' in f else 'default' }}">{{ f[:14] }}</span>
|
||||
</td>
|
||||
<td style="font-size:12px;color:var(--muted)">{{ (c.location_slug or '—')[:16] }}</td>
|
||||
<td style="color:var(--amber)">{{ c.caps }}¢</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>📡 Événements récents</h2>
|
||||
<div class="scrollable">
|
||||
<table>
|
||||
<tr><th>J/T</th><th>Type</th><th>Description</th></tr>
|
||||
{% for ev in events %}
|
||||
<tr>
|
||||
<td style="color:var(--muted);white-space:nowrap">{{ ev.day }}/{{ ev.tick }}</td>
|
||||
<td class="ev-{{ ev.event_type }}" style="white-space:nowrap"><small>{{ ev.event_type }}</small></td>
|
||||
<td style="font-size:12px">{{ ev.description[:90] }}{% if ev.description|length > 90 %}…{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not events %}<tr><td colspan="3" style="color:var(--dim);text-align:center;padding:16px">Aucun événement</td></tr>{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid3">
|
||||
<div class="panel">
|
||||
<h2>🗺 État du monde</h2>
|
||||
<div class="scrollable">
|
||||
{% for ws in world_state %}
|
||||
<div class="world-zone">
|
||||
<span class="zone-name">{{ ws.location_slug }}</span>
|
||||
<span class="zone-stats">
|
||||
<span>🍖 <span>{{ ws.food_level or 0 }}%</span></span>
|
||||
<span>💧 <span>{{ ws.water_level or 0 }}%</span></span>
|
||||
<span>🛡 <span>{{ ws.security_level or 0 }}%</span></span>
|
||||
<span>👤 <span>{{ ws.pop_count or 0 }}</span></span>
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not world_state %}<p style="color:var(--dim);padding:12px 0">Pas de données monde</p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>⚔ Relations factions</h2>
|
||||
<div class="scrollable">
|
||||
<table>
|
||||
<tr><th>Faction A</th><th>Faction B</th><th>Score</th><th>État</th></tr>
|
||||
{% for r in faction_relations %}
|
||||
{% set cls = 'rel-hostile' if r.relation_score < -20 else 'rel-allie' if r.relation_score > 20 else 'rel-neutral' %}
|
||||
<tr>
|
||||
<td style="font-size:12px">{{ r.faction_a[:14] }}</td>
|
||||
<td style="font-size:12px">{{ r.faction_b[:14] }}</td>
|
||||
<td class="{{ cls }}">{{ r.relation_score }}</td>
|
||||
<td class="{{ cls }}" style="font-size:12px">{{ r.relation_label or ('HOSTILE' if r.relation_score < -20 else 'ALLIÉ' if r.relation_score > 20 else 'NEUTRE') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not faction_relations %}<tr><td colspan="4" style="color:var(--dim)">Pas de données</td></tr>{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>🎭 PNJ par tier</h2>
|
||||
<table style="margin-bottom:12px">
|
||||
<tr><th>Tier</th><th>Label</th><th>Vivants</th><th>Morts</th></tr>
|
||||
{% for row in tier_stats %}
|
||||
<tr>
|
||||
<td class="tier{{ row.tier }}-text">{{ row.tier }}</td>
|
||||
<td>{{ row.label }}</td>
|
||||
<td class="metric-green">{{ row.alive }}</td>
|
||||
<td style="color:var(--muted)">{{ row.dead }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<h2 style="margin-top:4px">🏙 PNJ par zone (top 5)</h2>
|
||||
<table>
|
||||
<tr><th>Zone</th><th>PNJ</th></tr>
|
||||
{% for row in zone_stats %}
|
||||
<tr>
|
||||
<td style="font-size:12px">{{ row.location_slug }}</td>
|
||||
<td class="metric-green">{{ row.cnt }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="margin-top:12px">
|
||||
<h2>⚡ Rencontres récentes</h2>
|
||||
<div class="scrollable" style="max-height:180px">
|
||||
<table>
|
||||
<tr><th>J/T</th><th>Type</th><th>Acteur</th><th>Zone</th><th>Description</th></tr>
|
||||
{% for ev in encounters %}
|
||||
<tr>
|
||||
<td style="color:var(--muted);white-space:nowrap;font-size:12px">{{ ev.day }}/{{ ev.tick }}</td>
|
||||
<td class="ev-encounter" style="font-size:12px;white-space:nowrap">{{ ev.event_type }}</td>
|
||||
<td style="font-size:12px">{{ ev.actor_name or '—' }}</td>
|
||||
<td style="font-size:12px;color:var(--muted)">{{ (ev.location_slug or '—')[:16] }}</td>
|
||||
<td style="font-size:12px">{{ ev.description[:90] }}{% if ev.description|length > 90 %}…{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not encounters %}<tr><td colspan="5" style="color:var(--dim);text-align:center;padding:12px">Aucune rencontre</td></tr>{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if boss_inventory %}
|
||||
<div class="panel" style="margin-top:12px">
|
||||
<h2>★ Équipement chefs de faction</h2>
|
||||
<table>
|
||||
<tr><th>Boss</th><th>Item</th><th>Type</th><th>Qté</th><th>Équipé</th></tr>
|
||||
{% for row in boss_inventory %}
|
||||
<tr>
|
||||
<td class="tier0-text">{{ row.char_name }}</td>
|
||||
<td>{{ row.item_name }}</td>
|
||||
<td style="color:var(--muted);font-size:12px">{{ row.item_type }}</td>
|
||||
<td>{{ row.quantity }}</td>
|
||||
<td style="color:var(--green)">{{ '✓' if row.is_equipped else '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Contrôles simulation -->
|
||||
<div style="margin-top:16px">
|
||||
<div style="display:flex;align-items:center;gap:0;border-bottom:1px solid var(--border);flex-wrap:wrap">
|
||||
<button class="sim-tab-btn active" id="stab-truth" onclick="showSimTab('truth')">📚 Vérité</button>
|
||||
<button class="sim-tab-btn" id="stab-enriched" onclick="showSimTab('enriched')">✨ Enrichie</button>
|
||||
<button class="sim-tab-btn" id="stab-mix" onclick="showSimTab('mix')">⚡ MIX</button>
|
||||
<div style="margin-left:auto;display:flex;align-items:center;gap:8px;padding:4px 6px;flex-wrap:wrap">
|
||||
<span style="font-size:11px;color:var(--dim)">Tick global :</span>
|
||||
<div class="radio-group" style="margin:0">
|
||||
<label><input type="radio" name="sim_tick" value="0"><span>INSTANT</span></label>
|
||||
<label><input type="radio" name="sim_tick" value="5" checked><span>5s</span></label>
|
||||
<label><input type="radio" name="sim_tick" value="30"><span>30s</span></label>
|
||||
<label><input type="radio" name="sim_tick" value="60"><span>60s</span></label>
|
||||
<label><input type="radio" name="sim_tick" value="3600"><span>1h</span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="simtab-truth" class="sim-tab-content" style="display:block">
|
||||
<div class="panel" style="margin-top:12px;padding:16px">
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
|
||||
<span class="dot dot-red" id="dot-sim-truth"></span>
|
||||
<span style="font-weight:600">📚 Sim Vérité</span>
|
||||
<span style="font-size:10px;color:var(--dim)">sources : fallout_lore + fallout_sim_rules uniquement</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:12px">
|
||||
<span style="font-size:12px;color:var(--dim)">Session : <strong style="color:var(--text)">#{{ current_sid }}</strong></span>
|
||||
<label style="font-size:12px;color:var(--dim)">Mode :
|
||||
<select id="truth-mode" onchange="setSessionMode(this.value)" style="background:var(--bg);border:1px solid var(--border);color:var(--text);font-size:12px;padding:2px 8px;margin-left:4px">
|
||||
{% for m in ['pacifiste','politique','guerre_commerciale','guerre','survie_extreme'] %}
|
||||
<option value="{{ m }}" {{ 'selected' if session and session.mode==m else '' }}>{{ m }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="sim-truth-status" style="font-size:12px;color:var(--dim);margin-bottom:10px">Arrêtée</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-green" id="btn-truth-start" onclick="simStart('truth')">▶ Démarrer</button>
|
||||
<button class="btn btn-red" id="btn-truth-stop" onclick="simStop('truth')">■ Arrêter</button>
|
||||
<button class="btn" onclick="simDownloadLog('truth')" style="font-size:11px">⬇ Log JSON</button>
|
||||
</div>
|
||||
<div id="console-sim-truth" class="mini-console" style="display:none;margin-top:12px;max-height:320px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="simtab-enriched" class="sim-tab-content" style="display:none">
|
||||
<div class="panel" style="margin-top:12px;padding:16px">
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
|
||||
<span class="dot dot-red" id="dot-sim-enriched"></span>
|
||||
<span style="font-weight:600;color:var(--amber)">✨ Sim Enrichie</span>
|
||||
<span style="font-size:10px;color:var(--dim)">+ fallout_lore_enriched + fallout_sim_enriched</span>
|
||||
</div>
|
||||
<div id="sim-enriched-status" style="font-size:12px;color:var(--dim);margin-bottom:10px">Arrêtée</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-green" id="btn-enriched-start" onclick="simStart('enriched')">▶ Démarrer</button>
|
||||
<button class="btn btn-red" id="btn-enriched-stop" onclick="simStop('enriched')">■ Arrêter</button>
|
||||
<button class="btn" onclick="simDownloadLog('enriched')" style="font-size:11px">⬇ Log JSON</button>
|
||||
</div>
|
||||
<div id="console-sim-enriched" class="mini-console" style="display:none;margin-top:12px;max-height:320px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="simtab-mix" class="sim-tab-content" style="display:none">
|
||||
<div class="panel" style="margin-top:12px;padding:16px">
|
||||
<div style="margin-bottom:12px">
|
||||
<span style="font-weight:600;color:var(--green)">⚡ Mode MIX — Comparaison simultanée</span>
|
||||
<p style="font-size:12px;color:var(--dim);margin:6px 0 0">Lance les deux simulations en même temps.</p>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px">
|
||||
<button class="btn btn-green" id="btn-mix-start" onclick="simMixStart()">▶ Lancer les 2</button>
|
||||
<button class="btn btn-red" id="btn-mix-stop" onclick="simMixStop()">■ Arrêter les 2</button>
|
||||
<button class="btn" onclick="simDownloadLog('truth')" style="font-size:11px">⬇ Log Vérité</button>
|
||||
<button class="btn" onclick="simDownloadLog('enriched')" style="font-size:11px">⬇ Log Enrichie</button>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
|
||||
<div>
|
||||
<div style="font-size:11px;color:var(--dim);margin-bottom:6px;display:flex;align-items:center;gap:6px">
|
||||
<span class="dot dot-red" id="dot-mix-truth"></span>📚 Vérité
|
||||
</div>
|
||||
<div id="console-mix-truth" class="mini-console" style="max-height:260px"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:var(--amber);margin-bottom:6px;display:flex;align-items:center;gap:6px">
|
||||
<span class="dot dot-red" id="dot-mix-enriched"></span>✨ Enrichie
|
||||
</div>
|
||||
<div id="console-mix-enriched" class="mini-console" style="max-height:260px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var _simPolls = {truth: null, enriched: null};
|
||||
var _simLastLines = {truth: 0, enriched: 0};
|
||||
var _mixLastLines = {truth: 0, enriched: 0};
|
||||
|
||||
function showSimTab(tab) {
|
||||
['truth','enriched','mix'].forEach(function(t) {
|
||||
document.getElementById('simtab-' + t).style.display = (t === tab) ? 'block' : 'none';
|
||||
document.getElementById('stab-' + t).classList.toggle('active', t === tab);
|
||||
});
|
||||
}
|
||||
|
||||
function simStart(mode) {
|
||||
var tickEl = document.querySelector('input[name="sim_tick"]:checked');
|
||||
var tickSpeed = tickEl ? parseInt(tickEl.value) : 5;
|
||||
fetch('/api/sim/' + mode + '/run', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({sid: {{ current_sid }}, tick_speed_s: tickSpeed})
|
||||
}).then(function(r){ return r.json(); }).then(function(d){
|
||||
if (d.error) { alert(d.error); return; }
|
||||
_simLastLines[mode] = 0;
|
||||
var cons = document.getElementById('console-sim-' + mode);
|
||||
cons.textContent = '';
|
||||
cons.style.display = 'block';
|
||||
document.getElementById('dot-sim-' + mode).className = 'dot dot-green';
|
||||
document.getElementById('sim-' + mode + '-status').textContent = 'En cours — ' + (tickSpeed === 0 ? 'INSTANT' : tickSpeed + 's') + '/tick';
|
||||
document.getElementById('btn-' + mode + '-start').disabled = true;
|
||||
if (_simPolls[mode]) clearInterval(_simPolls[mode]);
|
||||
_simPolls[mode] = setInterval(function(){ simPoll(mode); }, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function simPoll(mode) {
|
||||
fetch('/api/sim/' + mode + '/status').then(function(r){ return r.json(); }).then(function(d){
|
||||
var cons = document.getElementById('console-sim-' + mode);
|
||||
var lines = d.output || [];
|
||||
if (lines.length > _simLastLines[mode]) {
|
||||
cons.textContent += lines.slice(_simLastLines[mode]).join('\n') + '\n';
|
||||
cons.scrollTop = cons.scrollHeight;
|
||||
_simLastLines[mode] = lines.length;
|
||||
}
|
||||
if (!d.running) {
|
||||
clearInterval(_simPolls[mode]);
|
||||
document.getElementById('dot-sim-' + mode).className = 'dot ' + (d.returncode === 0 ? 'dot-amber' : 'dot-red');
|
||||
document.getElementById('sim-' + mode + '-status').textContent = d.error || ('Arrêtée (code ' + d.returncode + ')');
|
||||
document.getElementById('btn-' + mode + '-start').disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function simStop(mode) {
|
||||
fetch('/api/sim/' + mode + '/stop', {method:'POST'}).then(function(){ simPoll(mode); });
|
||||
}
|
||||
|
||||
function simDownloadLog(mode) { window.location.href = '/api/sim/' + mode + '/log'; }
|
||||
|
||||
function simMixStart() {
|
||||
var tickEl = document.querySelector('input[name="sim_tick"]:checked');
|
||||
var tickSpeed = tickEl ? parseInt(tickEl.value) : 5;
|
||||
['truth','enriched'].forEach(function(mode) {
|
||||
fetch('/api/sim/' + mode + '/run', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({sid: {{ current_sid }}, tick_speed_s: tickSpeed})
|
||||
}).then(function(r){ return r.json(); }).then(function(d){
|
||||
if (d.error) { console.warn('[MIX] ' + mode + ':', d.error); return; }
|
||||
_mixLastLines[mode] = 0;
|
||||
document.getElementById('console-mix-' + mode).textContent = '';
|
||||
document.getElementById('dot-mix-' + mode).className = 'dot dot-green';
|
||||
document.getElementById('btn-mix-start').disabled = true;
|
||||
if (_simPolls[mode]) clearInterval(_simPolls[mode]);
|
||||
_simPolls[mode] = setInterval(function(){ simMixPoll(mode); }, 2000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function simMixPoll(mode) {
|
||||
fetch('/api/sim/' + mode + '/status').then(function(r){ return r.json(); }).then(function(d){
|
||||
var cons = document.getElementById('console-mix-' + mode);
|
||||
var lines = d.output || [];
|
||||
if (lines.length > _mixLastLines[mode]) {
|
||||
cons.textContent += lines.slice(_mixLastLines[mode]).join('\n') + '\n';
|
||||
cons.scrollTop = cons.scrollHeight;
|
||||
_mixLastLines[mode] = lines.length;
|
||||
}
|
||||
if (!d.running) {
|
||||
clearInterval(_simPolls[mode]);
|
||||
document.getElementById('dot-mix-' + mode).className = 'dot ' + (d.returncode === 0 ? 'dot-amber' : 'dot-red');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function simMixStop() { ['truth','enriched'].forEach(function(mode){ simStop(mode); }); }
|
||||
|
||||
function setSessionMode(mode) {
|
||||
fetch('/api/session/set-mode', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({sid: {{ current_sid }}, mode: mode})
|
||||
}).then(function(r){ return r.json(); }).then(function(d){
|
||||
if (d.error) alert('Erreur : ' + d.error);
|
||||
});
|
||||
}
|
||||
|
||||
(function(){
|
||||
['truth','enriched'].forEach(function(mode){
|
||||
fetch('/api/sim/' + mode + '/status').then(function(r){ return r.json(); }).then(function(d){
|
||||
if (d.running) {
|
||||
document.getElementById('dot-sim-' + mode).className = 'dot dot-green';
|
||||
document.getElementById('sim-' + mode + '-status').textContent = 'En cours';
|
||||
document.getElementById('btn-' + mode + '-start').disabled = true;
|
||||
document.getElementById('console-sim-' + mode).style.display = 'block';
|
||||
_simLastLines[mode] = (d.output||[]).length;
|
||||
_simPolls[mode] = setInterval(function(){ simPoll(mode); }, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
var t = 30;
|
||||
var el = document.getElementById('countdown');
|
||||
if (el) {
|
||||
setInterval(function() {
|
||||
t--;
|
||||
el.textContent = ' (' + t + 's)';
|
||||
if (t <= 0) { location.reload(); }
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user