Navigate before initializing page state
This commit is contained in:
+18
-1
@@ -6,12 +6,13 @@ import secrets
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import speedtest # type: ignore[import-untyped]
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, field_validator
|
||||
|
||||
from admin.models import (
|
||||
delete_flow_config,
|
||||
@@ -91,6 +92,17 @@ class FlowConfigIn(BaseModel):
|
||||
scenario: str = "dynamic"
|
||||
stop_on_first_click: bool = False
|
||||
|
||||
@field_validator("target_url")
|
||||
@classmethod
|
||||
def validate_target_url(cls, value: str) -> str:
|
||||
value = value.strip()
|
||||
if value and "://" not in value:
|
||||
value = f"https://{value}"
|
||||
parsed = urlsplit(value)
|
||||
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
||||
raise ValueError("Target URL must be a complete HTTP(S) URL")
|
||||
return value
|
||||
|
||||
|
||||
def _to_db_dict(body: FlowConfigIn) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -181,6 +193,11 @@ async def add_to_batch_queue(body: BatchTaskIn, _: Auth) -> dict[str, object]:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Flow config {body.config_id} not found"
|
||||
)
|
||||
if not str(cfg.get("target_url") or "").strip():
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="The selected flow config has no Target URL. Edit and save it first.",
|
||||
)
|
||||
|
||||
result = enqueue(cfg, body.workers, body.total_runs, body.stagger_ms, body.headless)
|
||||
log.info(
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
|
||||
<div class="fg">
|
||||
<label>Target URL</label>
|
||||
<input id="cfg-target-url" type="url" placeholder="https://example.com" />
|
||||
<input id="cfg-target-url" type="url" placeholder="https://example.com" required />
|
||||
</div>
|
||||
|
||||
<div class="fg" style="display:flex;align-items:center;gap:10px;margin-bottom:14px">
|
||||
@@ -1189,6 +1189,7 @@ document.getElementById('cfg-digipay-match').addEventListener('change', e => {
|
||||
document.getElementById('form-save').addEventListener('click', async () => {
|
||||
const body = collectForm();
|
||||
if (!body.name) { alert('Config name is required.'); return; }
|
||||
if (!body.target_url) { alert('Target URL is required.'); return; }
|
||||
|
||||
const cfgId = document.getElementById('cfg-id').value;
|
||||
const isEdit = !!cfgId;
|
||||
@@ -1408,6 +1409,11 @@ function startBatchPoll() {
|
||||
document.getElementById('batch-start-btn').addEventListener('click', async () => {
|
||||
const configId = parseInt(document.getElementById('batch-config-select').value);
|
||||
if (!configId) { alert('Please select a flow config.'); return; }
|
||||
const selectedConfig = _configs.find(c => c.id === configId);
|
||||
if (!selectedConfig?.target_url) {
|
||||
alert('The selected flow config has no Target URL. Edit and save it first.');
|
||||
return;
|
||||
}
|
||||
const workers = parseInt(document.getElementById('batch-workers').value) || 3;
|
||||
const runs = parseInt(document.getElementById('batch-runs').value) || 10;
|
||||
const stagger = parseInt(document.getElementById('batch-stagger').value) || 0;
|
||||
@@ -1420,8 +1426,11 @@ document.getElementById('batch-start-btn').addEventListener('click', async () =>
|
||||
});
|
||||
|
||||
if (!res) return;
|
||||
if (res.status === 404) { const d = await res.json(); alert(d.detail || 'Flow config not found.'); return; }
|
||||
if (!res.ok) { alert('Failed to add task.'); return; }
|
||||
if (!res.ok) {
|
||||
const d = await res.json().catch(() => ({}));
|
||||
alert(d.detail || 'Failed to add task.');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
await pollBatchStatus();
|
||||
|
||||
Reference in New Issue
Block a user