diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/README.md b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/README.md index a38f0a1..5611859 100644 --- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/README.md +++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/README.md @@ -23,6 +23,23 @@ http://127.0.0.1:18790 <- local / prin tunel SSH Starea se reimprospateaza automat la 5 secunde. +## Pe telefon + +Panoul e gandit sa fie folosit de pe telefon (de acolo vine si comanda in Discord), +deci sub 640px se schimba trei lucruri: + +- **tabelul de fire devine carduri stivuite** — capul de tabel dispare, iar eticheta + fiecarei celule vine din `data-label`; un tabel de 5 coloane pe 390px ar fi insemnat + derulare orizontala la fiecare rand; +- **butoanele stau doua pe rand, cu tinta de atins de 44px** (sub 380px, unul pe rand); +- **verificarile si confirmarile se aseaza pe verticala**, iar comenzile lungi se rup + in loc sa impinga pagina. + +Nimic nu iese din viewport pe orizontala: jurnalul si tabelele deruleaza in propriul +container. Capcana care rupsese pagina (2621px latime pe un ecran de 390px) era +`min-width: auto` pe copiii de grid — liniile lungi din `pre.log` dilatau coloana, si +odata cu ea toata pagina; de aceea exista `min-width: 0` explicit in `static/app.css`. + ## Decizii de proiectare **O singura unitate controlata.** `/api/service` actioneaza intotdeauna pe diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html index fd8e099..a5ef28f 100644 --- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html +++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html @@ -39,7 +39,7 @@

Fire active

- +
FirModelDirectorStareUSD
@@ -161,10 +161,14 @@ async function refresh() { var st = t.inflight ? 'tur în lucru' : t.alive ? 'proces viu' : 'inactiv'; - return '' + esc(t.thread_id) + '' + esc(t.model || '—') + - '' + esc(t.cwd || '—') + '' + st + - '' + Number(t.cost_usd || 0).toFixed(2) + ''; - }).join('') : 'niciun fir înregistrat'; + // data-label: pe telefon capul de tabel dispare si eticheta vine din CSS + return '' + esc(t.thread_id) + + '' + esc(t.model || '—') + + '' + esc(t.cwd || '—') + + '' + st + + '' + Number(t.cost_usd || 0).toFixed(2) + + ''; + }).join('') : 'niciun fir înregistrat'; // aprobări document.getElementById('apprCount').textContent = s.pending_approvals || ''; @@ -182,8 +186,10 @@ async function refreshApprovals() { '
' + esc(a.reason || a.rule || '') + (a.expires_in !== null && a.expires_in !== undefined ? ' · expiră în ' + dur(a.expires_in) : '') + ' · fir ' + esc(a.thread_id) + '
' + - ' ' + - ''; + '
' + + '' + + '' + + '
'; }).join('') : '
niciuna
'; } diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/static/app.css b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/static/app.css index 199ee25..5db2bfe 100644 --- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/static/app.css +++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/static/app.css @@ -19,7 +19,8 @@ a { color: var(--accent); } z-index: 50; display: flex; align-items: center; - gap: var(--space-4); + flex-wrap: wrap; + gap: var(--space-3) var(--space-4); padding: var(--space-4) var(--space-6); background: var(--header-bg); backdrop-filter: blur(12px); @@ -56,8 +57,13 @@ a { color: var(--accent); } .grid-2 { display: grid; gap: var(--space-6); - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); } +/* Fara asta, un copil de grid se dimensioneaza dupa continutul lui (min-width:auto): + liniile lungi din `pre.log` si tabelul de fire dilatau coloana la ~2600px si toata + pagina derula orizontal pe telefon. Derularea trebuie sa ramana INAUNTRUL + containerelor cu overflow, nu pe body. */ +.wrap > *, .grid-2 > *, .card > * { min-width: 0; } .card { background: var(--bg-surface); @@ -183,6 +189,7 @@ td.num { font-variant-numeric: tabular-nums; text-align: right; } .check .name { font-weight: 500; color: var(--text-primary); min-width: 200px; } .check .detail { color: var(--text-muted); font-size: var(--text-xs); } .check .mark { font-weight: 700; } +.check-actions { display: flex; gap: var(--space-2); flex: none; } .check.pass .mark { color: var(--success); } .check.fail .mark { color: var(--error); } @@ -256,3 +263,98 @@ input[type="password"] { } input[type="password"]:focus { outline: none; border-color: var(--border-focus); } .error-msg { color: var(--error); font-size: var(--text-xs); min-height: 1em; } + + +/* ── telefon ─────────────────────────────────────────────────────── + Praguri: 640px = telefon in portret; 380px = ecranele mici (SE). + Regula de aur pe mobil: nimic nu iese din viewport pe orizontala — + tabelul si jurnalul deruleaza in propriul container. */ +@media (max-width: 640px) { + body { font-size: var(--text-base); } /* 16px: iOS nu mai da zoom pe input */ + + .header { + padding: var(--space-3) var(--space-4); + gap: var(--space-2); + } + .logo { font-size: var(--text-base); } + .header .meta { order: 3; width: 100%; } /* ora, pe randul ei */ + + .wrap { padding: var(--space-4); gap: var(--space-4); } + .card { padding: var(--space-4); border-radius: var(--radius); } + + .metrics { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); } + .metric { padding: var(--space-2) var(--space-3); } + .metric .value { font-size: var(--text-lg); } + + /* Butoanele: tinta de atins de minim 44px si doua pe rand, nu sase inghesuite. */ + .actions { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); } + .actions .spacer { display: none; } + .actions button { + min-height: 44px; + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); + } + .actions button.small { font-size: var(--text-sm); } + .header button { min-height: 36px; } + + /* Diagnostic si confirmari: eticheta peste detaliu, nu una langa alta. */ + .check { + display: grid; + grid-template-columns: auto 1fr; + column-gap: var(--space-2); + row-gap: var(--space-1); + padding: var(--space-3) 0; + } + .check .name { min-width: 0; grid-column: 2; } + .check .detail { grid-column: 2; overflow-wrap: anywhere; } + .check .mark { grid-row: 1; grid-column: 1; } + .check > div[style] { grid-column: 1 / -1; } /* corpul unei confirmari */ + .check-actions { grid-column: 1 / -1; margin-top: var(--space-2); } + .check-actions button { flex: 1; min-height: 44px; } + + th, td { padding: var(--space-2); } + .mono { font-size: 0.75rem; } + + /* Un tabel de 5 coloane pe un ecran de 390px inseamna derulare orizontala la + fiecare rand. Pe telefon randul devine card: capul de tabel dispare, iar + eticheta fiecarei celule vine din `data-label`. */ + table.stack-mobil, table.stack-mobil tbody, table.stack-mobil tr, table.stack-mobil td { + display: block; + width: 100%; + } + table.stack-mobil thead { display: none; } + table.stack-mobil tr { + border-top: 1px solid var(--border); + padding: var(--space-3) 0; + } + table.stack-mobil tr:first-child { border-top: 0; } + table.stack-mobil td { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--space-3); + border: 0; + padding: var(--space-1) 0; + text-align: left; + } + table.stack-mobil td::before { + content: attr(data-label); + flex: none; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-muted); + } + table.stack-mobil td.mono { overflow-wrap: anywhere; text-align: right; } + table.stack-mobil td.empty::before { content: none; } + pre.log { max-height: 320px; padding: var(--space-3); } + + #toast { left: var(--space-4); right: var(--space-4); bottom: var(--space-4); max-width: none; } + .login form { padding: var(--space-5); } + .login button, input[type="password"] { min-height: 44px; } +} + +@media (max-width: 380px) { + .actions { grid-template-columns: 1fr; } + .metrics { grid-template-columns: 1fr 1fr; } +}