{# P3-T03..T08 — per-step Validation Explorer. Replaces the P2 placeholder. Renders the per-endpoint matrix (library × proxy attempts), header reduce summary, cookie scenarios, and rate-limit probe data persisted in each ValidationRun row. Inputs (assembled by ``services.validation_view.build_validation_step_view``): * ``view.scan_id`` / ``view.scan_id_short`` / ``view.back_to_trace_url`` * ``view.step_index`` / ``view.step_index_padded`` / ``view.total_step_count`` * ``view.step_name`` / ``view.status_label`` / ``view.status_pill_class`` * ``view.duration`` / ``view.summary_sub_line`` * ``view.error_class`` / ``view.error_message`` * ``view.endpoints`` — list of ValidationEndpointView (per-endpoint table) * ``view.is_in_progress`` / ``view.is_cancelled_scan`` #} {% extends "admin_layout.html" %} {% from 'partials/help_tooltip.html' import tip, tip_styles %} {% from 'partials/notifications.html' import notify_styles %} {% block page_title %}admin :: step :: {{ view.scan_id_short }} / {{ view.step_index_padded }}{% endblock %} {% block breadcrumb %} admin/ scans/ {{ view.scan_id_short }}/ step {{ view.step_index_padded }} {% endblock %} {% block head_extra %} {{ tip_styles() }} {{ notify_styles() }} {% if view.is_in_progress %}{% endif %} {% endblock %} {% block content %} {# ============== Sticky breadcrumb strip ============== #}
{{ view.empty_state_body }}
| # | method · url {{ tip("HTTP method and URL template the endpoint was validated against.", position="bottom") }} | bucket {{ tip("Validation bucket: A = primary data path, B = bootstrap / session, C = ignored.", position="bottom") }} | best lib {{ tip("First library whose proxy ladder passed; '—' when every library was blocked.", position="bottom") }} | headers req. {{ tip("Empirically required headers / total tested. Headers dropped without effect are 'optional'.", position="bottom") }} | cookies {{ tip("Cheapest cookie scenario that worked. 'cold' = none needed; 'warmup' = homepage hit required; 'full' = full session cookies needed.", position="bottom") }} | rate {{ tip("Estimated safe rate from the rate_limit_probe (1 / safe-delay). '—' when not measured.", position="bottom") }} | status {{ tip("Endpoint validation status. complete = winning library found; blocked all = no library passed any tier.", position="bottom") }} | |||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ ep.endpoint_id }} |
{{ ep.method }} {{ ep.url_template or ep.url }}
|
{% if ep.bucket %} {{ ep.bucket }} {% else %} — {% endif %} | {{ ep.best_library or '—' }} | {{ ep.headers_required_label }} | {{ ep.cookies_label }} | {{ ep.rate_limit_summary }} | {{ ep.validation_status.replace('_', ' ') }} | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
{# ── library_compare (P3-T05) ────────── #}
library_compare · phase A · {{ ep.library_comparison.attempts | length }} fire{{ '' if ep.library_comparison.attempts | length == 1 else 's' }}
{{ tip("One row per (library × proxy tier) attempt. Status pill follows the request outcome; the replay action lands in P3-T13.") }}
{% if not ep.library_comparison.attempts %}
no library_comparison entries persisted
{% else %}
header_reduce · {{ ep.header_reduce.tested_count if ep.header_reduce else 0 }} headers tested · {{ ep.header_reduce.required_count if ep.header_reduce else 0 }} required
{{ tip("Drop-then-test cascade. Headers that broke the response when removed are 'required'; headers safely removable are 'optional'; baseline headers (always-optional / always-required) are skipped.") }}
{% if not ep.header_reduce or not ep.header_reduce.rows %}
no header_reduce data persisted
{% else %}
{% for hr in ep.header_reduce.rows %}
{{ hr.header_name }}
{% if hr.verdict == 'required' %}
required · {{ hr.reason }}
{% elif hr.verdict == 'optional' %}
optional · {{ hr.reason }}
{% else %}
{{ hr.verdict.replace('_', ' ') }} · {{ hr.reason }}
{% endif %}
{% endfor %}
{% endif %}
cookie scenarios · cookies_required = {{ 'true' if ep.cookie_dependency and ep.cookie_dependency.cookies_required else 'false' }}
{{ tip("Each scenario fires the endpoint with a different cookie state. 'cold' = no cookies; 'warmup' = cookies harvested from a homepage hit; 'full' = full session cookies. Cheapest passing scenario wins.") }}
{% if not ep.cookie_dependency or not ep.cookie_dependency.scenarios %}
no cookie scenario data persisted
{% else %}
rate_limit_probe · {{ ep.rate_limit.trigger_signal if ep.rate_limit and ep.rate_limit.trigger_signal else 'no trigger' }}
{{ tip("Increasing-delay probe to find the slowest cadence the endpoint accepts. Each round fires N requests at delay X; a trigger fires when a 429 (or equivalent) lands.") }}
{% if not ep.rate_limit %}
no rate_limit_probe data persisted
{% else %}
rate_limit_detected
{% if ep.rate_limit.rate_limit_detected %}
true
{% else %}
false
{% endif %}
{% if ep.rate_limit.estimated_safe_delay_s is not none %}
estimated_safe_delay_s
{{ '%.2f' % ep.rate_limit.estimated_safe_delay_s }}s
({{ '%.1f' % (1.0 / ep.rate_limit.estimated_safe_delay_s) if ep.rate_limit.estimated_safe_delay_s > 0 else '0' }} r/s sustained)
{% endif %}
{% if ep.rate_limit.proxy_rotation_mode %}
proxy_rotation_mode
{{ ep.rate_limit.proxy_rotation_mode }}
{% endif %}
{% if ep.rate_limit.rounds %}
{{ ep.rate_limit.measurement_caveat }}
{{ tip("Rotating proxies hide per-IP rate limits — the measurement underestimates production risk. Re-validate under a sticky IP for a true single-IP cadence.") }}
{% endif %}
{% endif %}
replay a request · admin · cost-capped · audit-logged
{{ tip("Fires this exact endpoint again as a one-shot HTTP request through the chosen library + proxy tier. Real network call against the live site; every fire is logged and counts against your daily cap.") }}
// fire this exact request again, now
replay history · {{ rh_active | length }} prior fire{{ '' if rh_active | length == 1 else 's' }}{% if ep.replay.deleted_count %} · {{ ep.replay.deleted_count }} deleted{% endif %}
{{ tip("Every replay fire — successful or failed — lands here. Soft-deleted rows are hidden by default; restorable within 30 days of deletion.") }}
{% if not rh %}
no replay attempts yet
{% else %}
{% if ep.replay.deleted_count %}
{{ tip("Toggle hidden soft-deleted rows. Restorable within 30 days; struck-through styling distinguishes them.") }}
{% endif %}
warnings · {{ ep.warnings | length }}
{% for w in ep.warnings %}
warn
{{ w }}
{% endfor %}
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||