feat(5.20): US-008 configurare medii RAR per cont (Testare/Productie)

Ruta noua POST /cont/rar-medii: doua sectiuni independente Testare/Productie,
fiecare cu bifa activare + email/parola. La salvare, mediu activat cu creds noi
e validat prin login pe env-ul respectiv (US-007); OK -> criptare Fernet in
rar_creds_{env}_enc + enabled=1; esec -> eroare per-env, creds nesalvate.

Prima activare Productie cere checkbox de confirmare (constientizare L.142).
Mediul implicit (rar_env_default) setabil DOAR pe un mediu disponibil, validat
server-side post-update. Parolele niciodata reflectate in pagina.

_fetch_cont_env_state deriva starea per-env pentru _cont.html; refactor al
handlerelor de cont sa foloseasca env_ctx in loc de are_creds legacy.

tests/test_cont_medii.py: 4 teste (salvare+creds criptate per env, default doar
dintre disponibile, confirmare prod obligatorie, fara echo parola).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-07-02 19:53:50 +00:00
parent 3579a15363
commit 1648960b13
3 changed files with 665 additions and 67 deletions

View File

@@ -292,10 +292,14 @@ def _get_acasa_context(request: Request, conn, account_id: int) -> dict:
acct = account_or_default(account_id)
# Pas 1: are credentiale RAR configurate? + metadate cont (pentru banner incomplet)
# Verifica atat coloana legacy rar_creds_enc cat si sloturile per-env (US-008, PRD 5.20).
row = conn.execute(
"SELECT id, name, cui, email, rar_creds_enc FROM accounts WHERE id=?", (acct,)
"SELECT id, name, cui, email, rar_creds_enc, rar_creds_test_enc, rar_creds_prod_enc "
"FROM accounts WHERE id=?", (acct,)
).fetchone()
are_creds = bool(row and row["rar_creds_enc"])
are_creds = bool(row and (
row["rar_creds_enc"] or row["rar_creds_test_enc"] or row["rar_creds_prod_enc"]
))
# Banner cont incomplet (US-002): contul nu are companie + email + CUI complete
cont_incomplet = not _acct_is_complete(row) if row else False
@@ -385,22 +389,25 @@ def _render_panel_cont(request: Request, conn, account_id: int) -> str:
"""Randeaza panoul Cont ca string HTML."""
from ..mapping import account_or_default
acct = account_or_default(account_id)
row = conn.execute(
"SELECT id, name, cui, email, rar_creds_enc FROM accounts WHERE id=?", (acct,)
).fetchone()
are_creds = bool(row and row["rar_creds_enc"])
account_meta = _fetch_account_meta(conn, acct)
env_ctx = _fetch_cont_env_state(conn, acct)
cont_ctx = {
"request": request,
"csrf_token": get_csrf_token(request),
"api_key": None,
"are_creds": are_creds,
"creds_mesaj": None,
"creds_eroare": None,
"rot_eroare": None,
"account_meta": account_meta,
"date_firma_mesaj": None,
"date_firma_eroare": None,
"creds_test_mesaj": None,
"creds_test_eroare": None,
"creds_prod_mesaj": None,
"creds_prod_eroare": None,
"creds_default_eroare": None,
"creds_default_mesaj": None,
**env_ctx,
}
# US-006 (5.17): context plan pentru sectiunea Plan din _cont.html.
cont_ctx.update(_plan_ctx(conn, account_id))
@@ -4106,6 +4113,19 @@ def _render_cont(
account_meta: dict | None = None,
date_firma_mesaj: str | None = None,
date_firma_eroare: str | None = None,
# Per-env (US-008, PRD 5.20): starea mediilor RAR Testare + Productie.
test_enabled: bool = False,
prod_enabled: bool = True,
test_disponibil: bool = False,
prod_disponibil: bool = False,
rar_env_default: str = "prod",
medii_disponibile: list | None = None,
creds_test_mesaj: str | None = None,
creds_test_eroare: str | None = None,
creds_prod_mesaj: str | None = None,
creds_prod_eroare: str | None = None,
creds_default_eroare: str | None = None,
creds_default_mesaj: str | None = None,
) -> HTMLResponse:
"""Randeaza cardul 'Contul meu'. Parola niciodata in value=."""
return templates.TemplateResponse(
@@ -4120,6 +4140,18 @@ def _render_cont(
account_meta=account_meta or {},
date_firma_mesaj=date_firma_mesaj,
date_firma_eroare=date_firma_eroare,
test_enabled=test_enabled,
prod_enabled=prod_enabled,
test_disponibil=test_disponibil,
prod_disponibil=prod_disponibil,
rar_env_default=rar_env_default,
medii_disponibile=medii_disponibile or [],
creds_test_mesaj=creds_test_mesaj,
creds_test_eroare=creds_test_eroare,
creds_prod_mesaj=creds_prod_mesaj,
creds_prod_eroare=creds_prod_eroare,
creds_default_eroare=creds_default_eroare,
creds_default_mesaj=creds_default_mesaj,
),
)
@@ -4139,6 +4171,56 @@ def _fetch_account_meta(conn, acct: int) -> dict:
}
def _fetch_cont_env_state(conn, acct: int) -> dict:
"""Starea mediilor RAR per env pentru sectiunea 'Credentiale RAR' din _cont.html (US-008).
Returneaza un dict compatibil cu parametrii per-env ai _render_cont:
are_creds -- True daca ORICE credentiale RAR sunt configurate (legacy SAU per-env)
test_enabled -- bifa activare Testare
prod_enabled -- bifa activare Productie
test_disponibil -- Testare activata SI cu creds (poate trimite)
prod_disponibil -- Productie activata SI cu creds (poate trimite)
rar_env_default -- mediul implicit al contului
medii_disponibile -- lista mediilor disponibile (subset din ['test','prod'])
"""
row = conn.execute(
"SELECT rar_test_enabled, rar_prod_enabled, "
"rar_creds_test_enc, rar_creds_prod_enc, rar_env_default, rar_creds_enc "
"FROM accounts WHERE id=?", (acct,)
).fetchone()
if not row:
return {
"are_creds": False,
"test_enabled": False,
"prod_enabled": True,
"test_disponibil": False,
"prod_disponibil": False,
"rar_env_default": "prod",
"medii_disponibile": [],
}
test_enabled = bool(row["rar_test_enabled"])
prod_enabled = bool(row["rar_prod_enabled"])
test_disponibil = test_enabled and bool(row["rar_creds_test_enc"])
prod_disponibil = prod_enabled and bool(row["rar_creds_prod_enc"])
medii: list[str] = []
if test_disponibil:
medii.append("test")
if prod_disponibil:
medii.append("prod")
are_creds = bool(
row["rar_creds_enc"] or row["rar_creds_test_enc"] or row["rar_creds_prod_enc"]
)
return {
"are_creds": are_creds,
"test_enabled": test_enabled,
"prod_enabled": prod_enabled,
"test_disponibil": test_disponibil,
"prod_disponibil": prod_disponibil,
"rar_env_default": row["rar_env_default"] or "prod",
"medii_disponibile": medii,
}
@router.get("/_fragments/cont", response_class=HTMLResponse)
def fragment_cont(request: Request) -> HTMLResponse:
"""Fragment HTMX card 'Contul meu': stare cheie + creds RAR + date firma."""
@@ -4146,12 +4228,9 @@ def fragment_cont(request: Request) -> HTMLResponse:
acct = account_or_default(account_id)
conn = get_connection()
try:
row = conn.execute(
"SELECT id, name, cui, email, rar_creds_enc FROM accounts WHERE id=?", (acct,)
).fetchone()
are_creds = bool(row and row["rar_creds_enc"])
account_meta = _fetch_account_meta(conn, acct)
return _render_cont(request, are_creds=are_creds, account_meta=account_meta)
env_ctx = _fetch_cont_env_state(conn, acct)
return _render_cont(request, account_meta=account_meta, **env_ctx)
finally:
conn.close()
@@ -4168,12 +4247,9 @@ def cont_roteste_cheie(
conn = get_connection()
try:
new_key = rotate_api_key(conn, acct)
row = conn.execute(
"SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)
).fetchone()
are_creds = bool(row and row["rar_creds_enc"])
account_meta = _fetch_account_meta(conn, acct)
return _render_cont(request, api_key=new_key, are_creds=are_creds, account_meta=account_meta)
env_ctx = _fetch_cont_env_state(conn, acct)
return _render_cont(request, api_key=new_key, account_meta=account_meta, **env_ctx)
finally:
conn.close()
@@ -4202,15 +4278,14 @@ async def cont_date_firma(request: Request) -> HTMLResponse:
conn = get_connection()
try:
account_meta = _fetch_account_meta(conn, acct)
row_cr = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)).fetchone()
are_creds = bool(row_cr and row_cr["rar_creds_enc"])
env_ctx = _fetch_cont_env_state(conn, acct)
finally:
conn.close()
return _render_cont(
request,
are_creds=are_creds,
account_meta=account_meta,
date_firma_eroare="Compania (numele firmei) este obligatorie.",
**env_ctx,
)
# Normalizare si validare email
@@ -4219,31 +4294,27 @@ async def cont_date_firma(request: Request) -> HTMLResponse:
except ValueError as exc:
conn = get_connection()
try:
account_meta = _fetch_account_meta(conn, acct)
row_cr = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)).fetchone()
are_creds = bool(row_cr and row_cr["rar_creds_enc"])
env_ctx = _fetch_cont_env_state(conn, acct)
finally:
conn.close()
return _render_cont(
request,
are_creds=are_creds,
account_meta={"name": companie_raw, "email": email_raw, "cui": cui_raw},
date_firma_eroare=f"Email invalid: {exc}",
**env_ctx,
)
if not email_norm:
conn = get_connection()
try:
account_meta = _fetch_account_meta(conn, acct)
row_cr = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)).fetchone()
are_creds = bool(row_cr and row_cr["rar_creds_enc"])
env_ctx = _fetch_cont_env_state(conn, acct)
finally:
conn.close()
return _render_cont(
request,
are_creds=are_creds,
account_meta={"name": companie_raw, "email": email_raw, "cui": cui_raw},
date_firma_eroare="Email-ul de contact este obligatoriu.",
**env_ctx,
)
# Normalizare si validare CUI
@@ -4252,31 +4323,27 @@ async def cont_date_firma(request: Request) -> HTMLResponse:
except ValueError as exc:
conn = get_connection()
try:
account_meta = _fetch_account_meta(conn, acct)
row_cr = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)).fetchone()
are_creds = bool(row_cr and row_cr["rar_creds_enc"])
env_ctx = _fetch_cont_env_state(conn, acct)
finally:
conn.close()
return _render_cont(
request,
are_creds=are_creds,
account_meta={"name": companie_raw, "email": email_norm, "cui": cui_raw},
date_firma_eroare=f"CUI invalid: {exc}",
**env_ctx,
)
if not cui_norm:
conn = get_connection()
try:
account_meta = _fetch_account_meta(conn, acct)
row_cr = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)).fetchone()
are_creds = bool(row_cr and row_cr["rar_creds_enc"])
env_ctx = _fetch_cont_env_state(conn, acct)
finally:
conn.close()
return _render_cont(
request,
are_creds=are_creds,
account_meta={"name": companie_raw, "email": email_norm, "cui": cui_raw},
date_firma_eroare="CUI-ul firmei este obligatoriu.",
**env_ctx,
)
# Actualizare in DB
@@ -4294,26 +4361,24 @@ async def cont_date_firma(request: Request) -> HTMLResponse:
).fetchone()
owner = existing["id"] if existing else "?"
account_meta = _fetch_account_meta(conn, acct)
row_cr = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)).fetchone()
are_creds = bool(row_cr and row_cr["rar_creds_enc"])
env_ctx = _fetch_cont_env_state(conn, acct)
return _render_cont(
request,
are_creds=are_creds,
account_meta={"name": companie_raw, "email": email_norm, "cui": cui_norm},
date_firma_eroare=(
f"CUI-ul {cui_norm} este deja folosit de alt cont (id={owner}). "
"Foloseste un CUI diferit sau contacteaza administratorul."
),
**env_ctx,
)
account_meta = _fetch_account_meta(conn, acct)
row_cr = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)).fetchone()
are_creds = bool(row_cr and row_cr["rar_creds_enc"])
env_ctx = _fetch_cont_env_state(conn, acct)
return _render_cont(
request,
are_creds=are_creds,
account_meta=account_meta,
date_firma_mesaj="Datele firmei au fost salvate.",
**env_ctx,
)
finally:
conn.close()
@@ -4396,18 +4461,15 @@ def cont_rar_creds(
if not email or not parola:
conn = get_connection()
try:
row = conn.execute(
"SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)
).fetchone()
are_creds = bool(row and row["rar_creds_enc"])
account_meta = _fetch_account_meta(conn, acct)
env_ctx = _fetch_cont_env_state(conn, acct)
finally:
conn.close()
return _render_cont(
request,
are_creds=are_creds,
creds_eroare="Email si parola sunt obligatorii.",
account_meta=account_meta,
**env_ctx,
)
enc = encrypt_creds({"email": email, "password": parola})
@@ -4418,11 +4480,12 @@ def cont_rar_creds(
(enc, acct),
)
account_meta = _fetch_account_meta(conn, acct)
env_ctx = _fetch_cont_env_state(conn, acct)
return _render_cont(
request,
are_creds=True,
creds_mesaj="Credentialele RAR au fost salvate cu succes.",
account_meta=account_meta,
**env_ctx,
)
finally:
conn.close()
@@ -4484,3 +4547,155 @@ def cont_test_rar_creds(
"_integrare_test_rezultat.html",
{"request": request, "succes": False, "mesaj": mesaj_eroare},
)
# =========================================================================== #
# US-008 (PRD 5.20): Configurare medii RAR per cont — Testare + Productie. #
# Ruta noua /cont/rar-medii: gestioneaza bifa activare, credentiale si #
# mediul implicit separat pentru fiecare din cele doua medii RAR. #
# =========================================================================== #
@router.post("/cont/rar-medii", response_class=HTMLResponse)
async def cont_rar_medii(request: Request) -> HTMLResponse:
"""Salveaza configuratia mediilor RAR per cont (US-008, PRD 5.20).
Doua sectiuni independente (Testare / Productie): fiecare cu bifa de activare
si campuri email/parola. La salvare, pentru fiecare mediu activat cu creds noi:
- valideaza prin login pe acel env (US-007) — RAR test si prod sunt sisteme separate;
- OK -> cripteaza cu Fernet si scrie in rar_creds_{env}_enc + enabled=1;
- esec login -> eroare per-env, mediul NU devine disponibil (creds nesalvate).
Activarea Productie pentru prima oara necesita checkbox-ul de confirmare
(constientizare L.142 — trimiterile sunt declaratii oficiale, finale si fara anulare).
Mediul implicit (rar_env_default) poate fi setat DOAR pe un mediu disponibil
(validat server-side post-update; altfel eroare, valoarea veche ramane).
Parolele NICIODATA reflectate inapoi in pagina (camp gol cu placeholder).
"""
account_id = require_login(request)
form = await request.form()
verify_csrf(request, str(form.get("csrf_token") or ""))
acct = account_or_default(account_id)
test_enabled_form = form.get("test_enabled") == "1"
prod_enabled_form = form.get("prod_enabled") == "1"
prod_confirmare = form.get("prod_confirmare") == "1"
test_email = str(form.get("test_email") or "").strip()
test_parola = str(form.get("test_parola") or "").strip()
prod_email = str(form.get("prod_email") or "").strip()
prod_parola = str(form.get("prod_parola") or "").strip()
rar_env_default_form = str(form.get("rar_env_default") or "").strip()
settings = get_settings()
creds_test_eroare: str | None = None
creds_test_mesaj: str | None = None
creds_prod_eroare: str | None = None
creds_prod_mesaj: str | None = None
creds_default_eroare: str | None = None
creds_default_mesaj: str | None = None
conn = get_connection()
try:
# Starea curenta din DB (inainte de update) — necesara pt logica de confirmare prod.
row_before = conn.execute(
"SELECT rar_prod_enabled FROM accounts WHERE id=?", (acct,)
).fetchone()
was_prod_enabled = bool(row_before["rar_prod_enabled"]) if row_before else False
# Confirmare obligatorie la PRIMA activare Productie (constientizare L.142).
# Nu se cere daca Productie era deja activata (confirmare unica per-activare).
if prod_enabled_form and not was_prod_enabled and not prod_confirmare:
creds_prod_eroare = (
"Bifati confirmarea de mai jos pentru a activa mediul Productie: "
"\"Inteleg ca trimiterile pe Productie sunt declaratii reale (L.142)\"."
)
account_meta = _fetch_account_meta(conn, acct)
env_ctx = _fetch_cont_env_state(conn, acct)
return _render_cont(
request,
account_meta=account_meta,
creds_prod_eroare=creds_prod_eroare,
**env_ctx,
)
# --- Procesare Testare ---
if test_enabled_form:
if test_email and test_parola:
# Ambele campuri completate -> valideaza prin login pe RAR Testare.
ok, mesaj = _valideaza_login_rar(settings, test_email, test_parola, "test")
if ok:
enc = encrypt_creds({"email": test_email, "password": test_parola})
conn.execute(
"UPDATE accounts SET rar_test_enabled=1, rar_creds_test_enc=? WHERE id=?",
(enc, acct),
)
creds_test_mesaj = "Credentiale Testare salvate si validate."
else:
# Login esuat: nu schimbam creds sau enabled; eroare per-env.
creds_test_eroare = mesaj
elif test_email or test_parola:
# Doar unul din campuri completat -> eroare (nu pot fi partial).
creds_test_eroare = "Email si parola Testare trebuie completate impreuna."
else:
# Activat fara creds noi -> marcheaza enabled (creds existente, daca sunt, raman).
conn.execute("UPDATE accounts SET rar_test_enabled=1 WHERE id=?", (acct,))
else:
# Dezactivat -> disabled=0; creds raman pentru posibila re-activare ulterioara.
conn.execute("UPDATE accounts SET rar_test_enabled=0 WHERE id=?", (acct,))
# --- Procesare Productie ---
if prod_enabled_form:
if prod_email and prod_parola:
ok, mesaj = _valideaza_login_rar(settings, prod_email, prod_parola, "prod")
if ok:
enc = encrypt_creds({"email": prod_email, "password": prod_parola})
conn.execute(
"UPDATE accounts SET rar_prod_enabled=1, rar_creds_prod_enc=? WHERE id=?",
(enc, acct),
)
creds_prod_mesaj = "Credentiale Productie salvate si validate."
else:
creds_prod_eroare = mesaj
elif prod_email or prod_parola:
creds_prod_eroare = "Email si parola Productie trebuie completate impreuna."
else:
conn.execute("UPDATE accounts SET rar_prod_enabled=1 WHERE id=?", (acct,))
else:
conn.execute("UPDATE accounts SET rar_prod_enabled=0 WHERE id=?", (acct,))
# --- Mediu implicit (validat post-update contra mediilor disponibile) ---
if rar_env_default_form and rar_env_default_form in ("test", "prod"):
from ..rar_env import medii_disponibile as _medii_disponibile_fn
row_after = conn.execute(
"SELECT rar_test_enabled, rar_prod_enabled, "
"rar_creds_test_enc, rar_creds_prod_enc "
"FROM accounts WHERE id=?", (acct,)
).fetchone()
medii_post = _medii_disponibile_fn(row_after)
if rar_env_default_form in medii_post:
conn.execute(
"UPDATE accounts SET rar_env_default=? WHERE id=?",
(rar_env_default_form, acct),
)
creds_default_mesaj = "Mediu implicit actualizat."
else:
creds_default_eroare = (
"Mediul ales nu e disponibil — activeaza-l si adauga credentiale valide intai."
)
account_meta = _fetch_account_meta(conn, acct)
env_ctx = _fetch_cont_env_state(conn, acct)
return _render_cont(
request,
account_meta=account_meta,
creds_test_mesaj=creds_test_mesaj,
creds_test_eroare=creds_test_eroare,
creds_prod_mesaj=creds_prod_mesaj,
creds_prod_eroare=creds_prod_eroare,
creds_default_eroare=creds_default_eroare,
creds_default_mesaj=creds_default_mesaj,
**env_ctx,
)
finally:
conn.close()

View File

@@ -114,36 +114,106 @@
<!-- Sectiunea: Credentiale RAR -->
<div>
<h3 style="font-size:13px; color:var(--muted); font-weight:500; margin:0 0 8px; text-transform:uppercase; letter-spacing:.04em;">Credentiale RAR (portal AUTOPASS)</h3>
{% if are_creds %}
<div class="flash" style="margin-bottom:12px;">Credentiale RAR configurate.</div>
{% endif %}
<h3 style="font-size:13px; color:var(--muted); font-weight:500; margin:0 0 12px; text-transform:uppercase; letter-spacing:.04em;">Credentiale RAR (portal AUTOPASS)</h3>
{% 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-creds"
<form hx-post="/cont/rar-medii"
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);">Email RAR</label><br>
<input type="email" name="rar_email" required style="width:100%; max-width:340px;"
placeholder="email@service.ro">
</p>
<p style="margin:0 0 12px;">
<label style="font-size:13px; color:var(--muted);">Parola RAR</label><br>
<input type="password" name="rar_parola" required style="width:100%; max-width:340px;"
autocomplete="new-password">
</p>
<button type="submit">Salveaza credentiale RAR</button>
<span style="font-size:12px; color:var(--muted); margin-left:8px;">Parola stocata criptat, niciodata in clar.</span>
<!-- Subsectiunea: Testare -->
<div style="margin-bottom:16px; padding-bottom:16px; border-bottom:1px solid var(--line);">
<p style="margin:0 0 6px;">
<label style="font-size:13px; color:var(--muted); display:flex; align-items:center; gap:6px;">
<input type="checkbox" name="test_enabled" value="1" {% if test_enabled %}checked{% endif %}>
Activare Testare
</label>
{% if test_disponibil %}
<span style="font-size:12px; color:var(--ok);">configurat</span>
{% endif %}
</p>
<p style="margin:0 0 8px;">
<label style="font-size:13px; color:var(--muted);">Email RAR Testare</label><br>
<input type="email" name="test_email" style="width:100%; max-width:340px;"
placeholder="email@service.ro">
</p>
<p style="margin:0 0 8px;">
<label style="font-size:13px; color:var(--muted);">Parola RAR Testare</label><br>
<input type="password" name="test_parola" style="width:100%; max-width:340px;"
autocomplete="new-password">
</p>
{% 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>
<!-- Subsectiunea: Productie -->
<div style="margin-bottom:16px; padding-bottom:16px; border-bottom:1px solid var(--line);">
<p style="margin:0 0 6px;">
<label style="font-size:13px; color:var(--muted); display:flex; align-items:center; gap:6px;">
<input type="checkbox" name="prod_enabled" value="1" {% if prod_enabled %}checked{% endif %}>
Activare Productie
</label>
{% if prod_disponibil %}
<span style="font-size:12px; color:var(--ok);">configurat</span>
{% endif %}
</p>
<p style="margin:0 0 8px;">
<label style="font-size:13px; color:var(--muted);">Email RAR Productie</label><br>
<input type="email" name="prod_email" style="width:100%; max-width:340px;"
placeholder="email@service.ro">
</p>
<p style="margin:0 0 8px;">
<label style="font-size:13px; color:var(--muted);">Parola RAR Productie</label><br>
<input type="password" name="prod_parola" style="width:100%; max-width:340px;"
autocomplete="new-password">
</p>
<p style="margin:0 0 8px;">
<label style="font-size:13px; color:var(--muted); display:flex; align-items:flex-start; gap:6px;">
<input type="checkbox" name="prod_confirmare" value="1" style="margin-top:2px; flex-shrink:0;">
Inteleg ca trimiterile pe Productie sunt declaratii reale (L.142), finale si fara anulare.
</label>
</p>
{% 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>
<!-- Selector mediu implicit -->
<div style="margin-bottom:16px;">
<label style="font-size:13px; color:var(--muted);">Mediu implicit pentru trimiteri</label><br>
{% if medii_disponibile %}
<select name="rar_env_default" style="width:100%; max-width:340px; margin-top:4px;">
{% 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 %}
<p style="font-size:13px; color:var(--muted); margin:4px 0 0;">Activeaza si valideaza un mediu intai.</p>
{% endif %}
{% if creds_default_mesaj %}
<div class="flash" style="margin-top:6px;">{{ creds_default_mesaj }}</div>
{% endif %}
{% if creds_default_eroare %}
<div class="banner" style="margin-top:6px; padding:8px 12px;">{{ 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>
</div>