Files
rar-autopass/app/web/templates/_mapcoloane.html
Claude Agent 504b490d3b feat(web): self-onboarding multi-tenant + auth sesiune (PRD 3.3a)
Canalul web trece de la 100% deschis (hardcodat cont 1) la autentificat si
multi-tenant. Un service nou se inregistreaza din browser, primeste o cheie API
(o singura data) si o sesiune; contul se creeaza "in asteptare" (active=0) si nu
trimite la RAR pana la activarea de catre admin (tools/account.py activate).

- users + app/users.py: parole scrypt (salt per-user, eticheta parametri onorata
  la verify pentru migrare cost), email unic case-insensitive
- sesiune: SessionMiddleware (same_site=strict, https_only config) + app/web/session.py
  (current_account/web_account/require_login->LoginRequired, set_session clear-inainte)
- CSRF (app/web/csrf.py) enforce in prod inclusiv pe login/signup + rate-limit
  in-proces (app/web/ratelimit.py) pe signup si login
- signup/login/logout (app/web/auth_routes.py): signup tranzactie atomica,
  cheie-o-data, log SIGNUP pentru descoperire admin
- dashboard + import scoped pe contul sesiunii (regula NULL->cont 1); toate rutele
  web care ating date sensibile sub require_login; nomenclator ramane global
- banner "cont in asteptare" pentru conturi active=0
- gate worker: claim_one LEFT JOIN accounts COALESCE(active,1)=1 (account_id NULL=activ)

VERIFY context curat (2 runde): leak cross-account /_fragments/mapari prins+reparat.
/code-review high: csrf_token lipsa pe re-randari de eroare, scrypt_params ignorat,
login fara rate-limit -- toate reparate. 361 teste pass (de la 313).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:43:21 +00:00

98 lines
3.7 KiB
HTML

<div id="import-section">
<div class="card">
<h2 style="font-size:15px; margin:0 0 12px;">
Mapare coloane —
<span class="muted" style="font-weight:400;">{{ filename or ("import #" ~ import_id) }}</span>
</h2>
{% if message %}
<div class="flash" style="{% if error %}border-color:var(--err); background:#241a1a;{% endif %} margin-bottom:12px;"
{% if error %}role="alert"{% endif %}>
{{ message }}
</div>
{% endif %}
<p class="muted" style="margin:0 0 12px; font-size:13px;">
Asociaza fiecare coloana din fisier cu campul canonic corespunzator.
Maparea se retine automat pentru fisiere cu acelasi antet.
</p>
<form hx-post="/_import/{{ import_id }}/mapare-coloane"
hx-target="#import-section"
hx-swap="outerHTML">
<input type="hidden" name="csrf_token" value="{{ csrf_token or '' }}">
<div style="margin-bottom:8px; display:flex; align-items:center; gap:10px; flex-wrap:wrap;">
<label for="format-data" style="font-size:13px; color:var(--muted);">
Format data
</label>
<input type="text" id="format-data" name="format_data"
value="{{ format_data or 'DD.MM.YYYY' }}"
placeholder="ex: DD.MM.YYYY"
style="max-width:160px;"
aria-describedby="format-data-hint">
<span id="format-data-hint" class="muted" style="font-size:12px;">
sau YYYY-MM-DD, MM/DD/YYYY etc.
</span>
</div>
{% for col in columns %}
{%- set sugg = fuzzy_suggestions.get(col, []) -%}
{%- set best = sugg[0].camp_canonic if sugg else '' -%}
<input type="hidden" name="colname" value="{{ col }}">
<div class="maprow">
<div class="mapcol grow">
<div><strong>{{ col }}</strong></div>
{% if sugg %}
<div class="muted" style="font-size:12px; margin-top:2px;">
sugestie: <span class="sugg">{{ sugg[0].camp_canonic }}
({{ sugg[0].score | round | int }}%)</span>
</div>
{% endif %}
{%- set ns = namespace(samples=[]) -%}
{%- for row in sample_rows -%}
{%- if row.get(col) is not none and row.get(col) != '' -%}
{%- set ns.samples = ns.samples + [row[col] | string] -%}
{%- endif -%}
{%- endfor -%}
{% if ns.samples %}
<div class="muted" style="font-size:11px; margin-top:2px;">
ex: {{ ns.samples[:2] | join(", ") }}
</div>
{% endif %}
</div>
<div class="mapcol" style="min-width:200px;">
<label for="canon-{{ loop.index }}"
style="display:block; font-size:12px; color:var(--muted); margin-bottom:2px;">
Camp canonic
</label>
<select id="canon-{{ loop.index }}" name="canon">
<option value="">— ignorat —</option>
{% for field_key, field_label in canonical_fields %}
<option value="{{ field_key }}"
{% if field_key == best %}selected{% endif %}>
{{ field_key }} — {{ field_label }}
</option>
{% endfor %}
</select>
</div>
</div>
{% endfor %}
<div style="margin-top:16px; display:flex; align-items:center; gap:12px; flex-wrap:wrap;">
<button type="submit"
style="min-height:44px; padding:10px 24px; font-size:14px;">
Salveaza si continua la preview
</button>
<span class="muted" style="font-size:12px;">
maparea se retine pentru fisiere cu acelasi antet
</span>
</div>
</form>
<div style="margin-top:12px;">
<a href="/" class="muted" style="font-size:13px;">Incarca alt fisier</a>
</div>
</div>
</div>