Meniu hamburger reorganizat pe grupuri cu etichete (LUCRU / CONT) cu iconite SVG discrete; "Cont" redenumit "Setari" (URL ?tab=cont pastrat, zero rute moarte). Pagina Setari restructurata in sub-taburi interne: Date firma / Credentiale RAR / Cheie API / Cont (plan + schimbare parola), cu indicator de stare per tab. Sub-tabul activ e pastrat server-side (activ_subtab) dupa fiecare swap HTMX; comutarea intre taburi e client-side, delegata pe document (supravietuieste swap-urilor). Credentiale RAR: mediile Testare/Productie ca doua carduri paralele. Schimbare parola de acces din sub-tabul Cont (functie noua, lipsea): - users.change_password (scrypt): verifica parola actuala constant-time, valideaza noua (10..128, diferita), rescrie hash+salt nou; - ruta POST /cont/schimba-parola, scoped pe user_id din sesiune, CSRF enforce, parola niciodata re-pusa in raspuns, jurnal 'parola_schimbata'. Referinte copy "Meniu cont > Cont" -> "Setari". Teste: schimbare parola (succes / parola actuala gresita / confirmare nepotrivita / lungime) + izolare rate-limit login in fixture-ul clientului. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
236 lines
14 KiB
HTML
236 lines
14 KiB
HTML
{# Pagina Setari: sub-taburi interne — Date firma / Credentiale RAR / Cheie API /
|
|
Cont (plan + parola). `activ_subtab` (server) alege panoul activ dupa fiecare
|
|
swap HTMX, ca re-randarea sa nu resete la primul tab. Comutarea intre taburi
|
|
fara server = JS delegat in base.html (#card-cont). #}
|
|
{% set _firma_ok = account_meta.name and account_meta.email and account_meta.cui %}
|
|
{% set _rar_ok = medii_disponibile and medii_disponibile|length > 0 %}
|
|
<div class="card" id="card-cont" data-activ-subtab="{{ activ_subtab|default('firma') }}">
|
|
<div class="setari-head">
|
|
<h2 class="setari-title">
|
|
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.6 4.6l2.1 2.1M17.3 17.3l2.1 2.1M4.6 19.4l2.1-2.1M17.3 6.7l2.1-2.1"/></svg>
|
|
Setari
|
|
</h2>
|
|
<p class="setari-sub">Datele firmei, credentialele RAR, cheia API si contul tau.</p>
|
|
</div>
|
|
|
|
<div class="setari-subtabs" role="tablist" aria-label="Sectiuni setari">
|
|
<button type="button" class="setari-subtab{{ ' is-active' if activ_subtab|default('firma') == 'firma' else '' }}" role="tab" data-subtab="firma"
|
|
aria-selected="{{ 'true' if activ_subtab|default('firma') == 'firma' else 'false' }}">
|
|
<span class="st-dot {{ 'ok' if _firma_ok else 'warn' }}" aria-hidden="true"></span>Date firma</button>
|
|
<button type="button" class="setari-subtab{{ ' is-active' if activ_subtab|default('firma') == 'rar' else '' }}" role="tab" data-subtab="rar"
|
|
aria-selected="{{ 'true' if activ_subtab|default('firma') == 'rar' else 'false' }}">
|
|
<span class="st-dot {{ 'ok' if _rar_ok else 'warn' }}" aria-hidden="true"></span>Credentiale RAR</button>
|
|
<button type="button" class="setari-subtab{{ ' is-active' if activ_subtab|default('firma') == 'api' else '' }}" role="tab" data-subtab="api"
|
|
aria-selected="{{ 'true' if activ_subtab|default('firma') == 'api' else 'false' }}">
|
|
<span class="st-dot {{ 'ok' if are_cheie else 'warn' }}" aria-hidden="true"></span>Cheie API</button>
|
|
<button type="button" class="setari-subtab{{ ' is-active' if activ_subtab|default('firma') == 'cont' else '' }}" role="tab" data-subtab="cont"
|
|
aria-selected="{{ 'true' if activ_subtab|default('firma') == 'cont' else 'false' }}">
|
|
<span class="st-dot info" aria-hidden="true"></span>Cont</button>
|
|
</div>
|
|
|
|
{# ============================ Sub-tab: Date firma ============================ #}
|
|
<div class="setari-pane" data-subpane="firma" role="tabpanel"
|
|
{% if activ_subtab|default('firma') != 'firma' %}hidden{% endif %}>
|
|
<div class="setari-zone-title">
|
|
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 21V4a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v17"/><path d="M15 9h4a1 1 0 0 1 1 1v11"/><path d="M2 21h20"/><path d="M8 7h3M8 11h3M8 15h3"/></svg>
|
|
Date firma
|
|
<span class="setari-badge {{ 'sb-ok' if _firma_ok else 'sb-warn' }}">{{ 'complet' if _firma_ok else 'incomplet' }}</span>
|
|
</div>
|
|
<p class="setari-zone-desc">Apar pe declaratiile catre RAR si pe facturile de plan.</p>
|
|
|
|
{% if date_firma_mesaj %}<div class="flash" style="margin-bottom:12px;">{{ date_firma_mesaj }}</div>{% endif %}
|
|
{% if date_firma_eroare %}<div class="banner" style="margin-bottom:12px; padding:8px 12px;">{{ date_firma_eroare }}</div>{% endif %}
|
|
|
|
<form hx-post="/cont/date-firma" hx-target="#card-cont" hx-swap="outerHTML">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<p style="margin:0 0 8px;">
|
|
<label style="font-size:13px; color:var(--muted);">Companie</label><br>
|
|
<input type="text" name="companie" required value="{{ account_meta.name or '' }}"
|
|
style="width:100%; max-width:340px;" placeholder="Numele firmei (ex. Service Auto SRL)">
|
|
</p>
|
|
<p style="margin:0 0 8px;">
|
|
<label style="font-size:13px; color:var(--muted);">Email contact</label><br>
|
|
<input type="email" name="email" required value="{{ account_meta.email or '' }}"
|
|
style="width:100%; max-width:340px;" placeholder="contact@firma.ro">
|
|
</p>
|
|
<p style="margin:0 0 12px;">
|
|
<label style="font-size:13px; color:var(--muted);">CUI (cod unic de identificare)</label><br>
|
|
<input type="text" name="cui" required value="{{ account_meta.cui or '' }}"
|
|
style="width:100%; max-width:340px;" placeholder="RO12345678">
|
|
</p>
|
|
<button type="submit">Salveaza datele firmei</button>
|
|
</form>
|
|
</div>
|
|
|
|
{# ========================= Sub-tab: Credentiale RAR ========================= #}
|
|
<div class="setari-pane" data-subpane="rar" role="tabpanel"
|
|
{% if activ_subtab|default('firma') != 'rar' %}hidden{% endif %}>
|
|
<div class="setari-zone-title">
|
|
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 11l1.5-4A2 2 0 0 1 8.4 5.6h7.2A2 2 0 0 1 17.5 7L19 11"/><path d="M4 11h16v6H4z"/><path d="M6 17v2M18 17v2"/><circle cx="7.5" cy="14" r="1"/><circle cx="16.5" cy="14" r="1"/></svg>
|
|
Credentiale RAR (portal AUTOPASS)
|
|
<span class="setari-badge {{ 'sb-ok' if _rar_ok else 'sb-warn' }}">{{ 'configurat' if _rar_ok else 'neconfigurat' }}</span>
|
|
</div>
|
|
<p class="setari-zone-desc">
|
|
Configureaza mediile RAR — Testare si/sau Productie — fiecare cu credentialele lui,
|
|
validate separat la salvare pe sistemul RAR corespunzator. Un mediu devine activ cand e
|
|
bifat si are credentiale; abia atunci importul web poate trimite pe el. (Canalul API ramane
|
|
pe mediul global al instantei, {{ mediu_instanta() }}, pentru ca isi cara singur credentialele.)
|
|
</p>
|
|
|
|
{% if creds_mesaj %}<div class="flash" style="margin-bottom:12px;">{{ creds_mesaj }}</div>{% endif %}
|
|
{% if creds_eroare %}<div class="banner" style="margin-bottom:12px; padding:8px 12px;">{{ creds_eroare }}</div>{% endif %}
|
|
|
|
<form hx-post="/cont/rar-medii" hx-target="#card-cont" hx-swap="outerHTML">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
|
|
<div class="env-cards">
|
|
<!-- Testare -->
|
|
<div class="env-card{% if test_disponibil %} active-test{% endif %}">
|
|
<h4>Testare
|
|
{% if test_disponibil %}<span class="setari-badge sb-ok">configurat</span>{% else %}<span class="setari-badge sb-info">inactiv</span>{% endif %}
|
|
</h4>
|
|
<label class="env-sw">
|
|
<input type="checkbox" name="test_enabled" value="1" {% if test_enabled %}checked{% endif %}>
|
|
Activare Testare
|
|
</label>
|
|
<label class="env-lbl">Email RAR Testare</label>
|
|
<input type="email" name="test_email" placeholder="email@service.ro">
|
|
<label class="env-lbl">Parola RAR Testare</label>
|
|
<input type="password" name="test_parola" autocomplete="new-password">
|
|
{% if creds_test_mesaj %}<div class="flash" style="margin-top:6px;">{{ creds_test_mesaj }}</div>{% endif %}
|
|
{% if creds_test_eroare %}<div class="banner" style="margin-top:6px; padding:8px 12px;">{{ creds_test_eroare }}</div>{% endif %}
|
|
</div>
|
|
|
|
<!-- Productie -->
|
|
<div class="env-card{% if not prod_disponibil %} inactive{% endif %}">
|
|
<h4>Productie
|
|
{% if prod_disponibil %}<span class="setari-badge sb-ok">configurat</span>{% else %}<span class="setari-badge sb-warn">inactiv</span>{% endif %}
|
|
</h4>
|
|
<label class="env-sw">
|
|
<input type="checkbox" name="prod_enabled" value="1" {% if prod_enabled %}checked{% endif %}>
|
|
Activare Productie
|
|
</label>
|
|
<label class="env-lbl">Email RAR Productie</label>
|
|
<input type="email" name="prod_email" placeholder="email@service.ro">
|
|
<label class="env-lbl">Parola RAR Productie</label>
|
|
<input type="password" name="prod_parola" autocomplete="new-password">
|
|
<label class="env-confirm">
|
|
<input type="checkbox" name="prod_confirmare" value="1">
|
|
<span>Inteleg ca trimiterile pe Productie sunt declaratii reale (L.142), finale si fara anulare.</span>
|
|
</label>
|
|
{% if creds_prod_mesaj %}<div class="flash" style="margin-top:6px;">{{ creds_prod_mesaj }}</div>{% endif %}
|
|
{% if creds_prod_eroare %}<div class="banner" style="margin-top:6px; padding:8px 12px;">{{ creds_prod_eroare }}</div>{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="default-env">
|
|
<label style="font-size:13px; color:var(--muted);">Mediu implicit pentru trimiteri</label>
|
|
{% if medii_disponibile %}
|
|
<select name="rar_env_default">
|
|
{% for env in medii_disponibile %}
|
|
<option value="{{ env }}"{% if env == rar_env_default %} selected{% endif %}>{{ "Testare" if env == "test" else "Productie" }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% else %}
|
|
<span style="font-size:13px; color:var(--muted);">Activeaza si valideaza un mediu intai.</span>
|
|
{% endif %}
|
|
{% if creds_default_mesaj %}<div class="flash" style="margin-top:6px; flex-basis:100%;">{{ creds_default_mesaj }}</div>{% endif %}
|
|
{% if creds_default_eroare %}<div class="banner" style="margin-top:6px; padding:8px 12px; flex-basis:100%;">{{ creds_default_eroare }}</div>{% endif %}
|
|
</div>
|
|
|
|
<button type="submit">Salveaza mediile RAR</button>
|
|
<span style="font-size:12px; color:var(--muted); margin-left:8px;">Parolele stocate criptat, niciodata in clar.</span>
|
|
</form>
|
|
</div>
|
|
|
|
{# ============================ Sub-tab: Cheie API ============================ #}
|
|
<div class="setari-pane" data-subpane="api" role="tabpanel"
|
|
{% if activ_subtab|default('firma') != 'api' %}hidden{% endif %}>
|
|
<div class="setari-zone-title">
|
|
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><circle cx="8" cy="15" r="4"/><path d="M11 12l9-9"/><path d="M17 6l2.5 2.5M14 9l2.5 2.5"/></svg>
|
|
Cheie API
|
|
<span class="setari-badge {{ 'sb-ok' if are_cheie else 'sb-warn' }}">{{ 'activa' if are_cheie else 'lipsa' }}</span>
|
|
</div>
|
|
<p class="setari-zone-desc">
|
|
O foloseste softul propriu / ROAAUTO ca sa trimita prezentari prin API. Se vede integral o
|
|
singura data, la creare sau rotire.
|
|
</p>
|
|
|
|
{% if api_key %}
|
|
<div class="flash" style="margin-bottom:12px;">Cheia a fost rotita. Salveaz-o acum — nu o vei mai putea vedea.</div>
|
|
<div class="keybox">{{ api_key }}</div>
|
|
<button type="button" class="btn-secondary" style="margin-top:8px;"
|
|
data-key="{{ api_key }}"
|
|
onclick="navigator.clipboard.writeText(this.dataset.key).then(()=>this.textContent='Copiat!')">
|
|
Copiaza cheia
|
|
</button>
|
|
<p style="font-size:13px; color:var(--warn); margin:10px 0 0;">
|
|
Atentie: la urmatoarea vizita aceasta cheie dispare. Daca o pierzi, roteste din nou.
|
|
</p>
|
|
{% endif %}
|
|
|
|
{% if rot_eroare %}<div class="banner" style="margin:12px 0; padding:8px 12px;">{{ rot_eroare }}</div>{% endif %}
|
|
|
|
<form hx-post="/cont/roteste-cheie" hx-target="#card-cont" hx-swap="outerHTML"
|
|
style="margin-top:{% if api_key %}14px{% else %}0{% endif %};">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" class="btn-warn">{{ 'Roteste cheia API' if are_cheie else 'Genereaza cheia API' }}</button>
|
|
<span style="font-size:12px; color:var(--muted); margin-left:8px;">Cheia veche se revoca imediat.</span>
|
|
</form>
|
|
</div>
|
|
|
|
{# ========================= Sub-tab: Cont (plan + parola) ==================== #}
|
|
<div class="setari-pane" data-subpane="cont" role="tabpanel"
|
|
{% if activ_subtab|default('firma') != 'cont' %}hidden{% endif %}>
|
|
|
|
{# --- Plan curent --- #}
|
|
{% if plan_linie is defined %}
|
|
<div class="setari-zone-title">
|
|
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 3 8l9 5 9-5z"/><path d="M3 13l9 5 9-5"/></svg>
|
|
Plan curent
|
|
<span class="setari-badge sb-info">{{ tier_label|default('Gratuit') }}</span>
|
|
</div>
|
|
<div style="font-size:var(--fs-md); font-weight:600; margin-bottom:6px;
|
|
color:{% if plan_limita_atinsa|default(false) %}var(--err){% elif plan_warn|default(false) %}var(--warn){% else %}var(--ink){% endif %};">
|
|
{{ plan_linie }}
|
|
</div>
|
|
{% if plan_limita_atinsa|default(false) %}
|
|
<p class="setari-zone-desc" style="color:var(--err);">Limita lunara a fost atinsa — trimiterile noi sunt blocate pana la inceputul lunii urmatoare.</p>
|
|
{% elif plan_warn|default(false) %}
|
|
<p class="setari-zone-desc" style="color:var(--warn);">Te apropii de limita lunara.</p>
|
|
{% endif %}
|
|
<p class="setari-zone-desc">
|
|
Pentru mai mult volum sau import prin API, <a href="mailto:contact@romfast.ro">contacteaza-ne</a> — alocarea planurilor se face manual.
|
|
</p>
|
|
<hr class="setari-subzone-sep">
|
|
{% endif %}
|
|
|
|
{# --- Parola de acces --- #}
|
|
<div class="setari-zone-title">
|
|
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/><circle cx="12" cy="15.5" r="1.2"/></svg>
|
|
Parola de acces
|
|
</div>
|
|
<p class="setari-zone-desc">Parola cu care te autentifici in dashboard (separata de credentialele RAR). Minim 10 caractere.</p>
|
|
|
|
{% if parola_mesaj %}<div class="flash" style="margin-bottom:12px;">{{ parola_mesaj }}</div>{% endif %}
|
|
{% if parola_eroare %}<div class="banner" style="margin-bottom:12px; padding:8px 12px;">{{ parola_eroare }}</div>{% endif %}
|
|
|
|
<form hx-post="/cont/schimba-parola" hx-target="#card-cont" hx-swap="outerHTML">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<p style="margin:0 0 8px;">
|
|
<label style="font-size:13px; color:var(--muted);">Parola actuala</label><br>
|
|
<input type="password" name="parola_actuala" autocomplete="current-password" style="width:100%; max-width:340px;">
|
|
</p>
|
|
<p style="margin:0 0 8px;">
|
|
<label style="font-size:13px; color:var(--muted);">Parola noua</label><br>
|
|
<input type="password" name="parola_noua" autocomplete="new-password" style="width:100%; max-width:340px;">
|
|
</p>
|
|
<p style="margin:0 0 12px;">
|
|
<label style="font-size:13px; color:var(--muted);">Confirma parola noua</label><br>
|
|
<input type="password" name="parola_confirma" autocomplete="new-password" style="width:100%; max-width:340px;">
|
|
</p>
|
|
<button type="submit">Schimba parola</button>
|
|
</form>
|
|
</div>
|
|
</div>
|