From 27d4f99b79839e7f0d69dfba15eba0404d50458f Mon Sep 17 00:00:00 2001 From: DeepZone Date: Fri, 22 May 2026 08:51:57 +0100 Subject: [PATCH] v0.9.2-rc: English-only output and text cleanup --- README.md | 4 +- RELEASE_NOTES.md | 6 +-- ROADMAP.md | 12 +++--- backend/app/api/routes_checks.py | 12 +++--- backend/app/api/routes_system.py | 2 +- backend/app/core/prefix_evaluation.py | 10 ++--- backend/app/core/recommendations.py | 42 +++++++++---------- backend/app/core/system_status.py | 2 +- backend/app/main.py | 2 +- backend/app/services/asn_checker.py | 34 +++++++-------- .../app/services/bgp_visibility_service.py | 22 +++++----- backend/app/services/prefix_checker.py | 6 +-- backend/app/services/registry_checker.py | 6 +-- backend/app/services/roa_planner_service.py | 6 +-- .../services/routing_visibility_checker.py | 8 ++-- backend/app/services/rpki_checker.py | 8 ++-- backend/app/templates/report.html.j2 | 20 ++++----- backend/app/templates/report.md.j2 | 20 ++++----- backend/pyproject.toml | 2 +- backend/tests/test_api_smoke.py | 2 +- frontend/package.json | 2 +- frontend/src/App.tsx | 6 +-- frontend/src/api.ts | 2 +- 23 files changed, 118 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index fc33d7a..cffc6ca 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ RouteForge Logo

- Version + Version License Status Selfhosted @@ -43,7 +43,7 @@ Routing changes often require fast but traceable checks across multiple external ## Current Alpha Status -RouteForge is a **functional beta** release with production-like workflows for read-only validation and demo usage. Current release target: **v0.9.1-rc**. +RouteForge is a **functional beta** release with production-like workflows for read-only validation and demo usage. Current release target: **v0.9.2-rc**. ## Quickstart with Docker Compose diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b6516ac..2565c57 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,8 +1,8 @@ -## v0.9.1-rc (2026-05-21) +## v0.9.2-rc (2026-05-21) - UI Cleanup & English-only polish -## v0.9.1-rc (2026-05-20) +## v0.9.2-rc (2026-05-20) ### Motivation Finalize release-candidate validation for deployment, upgrade discipline, security posture, and role-based UX quality before v1.0. @@ -11,7 +11,7 @@ Finalize release-candidate validation for deployment, upgrade discipline, securi - Added deployment health check script (`backend/scripts/check_deployment_health.py`) for API/system/database/build smoke validation. - Expanded operations docs with explicit release QA, upgrade QA, Docker QA, and security checklist guidance. - Updated release checklist with role/feature UX validation scenarios for admin/operator/viewer. -- Version bump across backend/frontend/docs to `0.9.1` / `v0.9.1-rc`. +- Version bump across backend/frontend/docs to `0.9.2` / `v0.9.2-rc`. ### Deployment QA Notes - Use `python backend/scripts/check_deployment_health.py --base-url http://localhost:8000 --check-setup`. diff --git a/ROADMAP.md b/ROADMAP.md index 0f232be..92e1676 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,22 +1,22 @@ # RouteForge Roadmap ## Current Status -v0.9.1-rc, BGP Visibility Details completed, read-only +v0.9.2-rc, BGP Visibility Details completed, read-only -## v0.9.1-rc +## v0.9.2-rc - projects/change cases - grouped preflight reports -## v0.9.1-rc +## v0.9.2-rc - bgp visibility details -## v0.9.1-rc +## v0.9.2-rc - roa planner / roa preflight -## v0.9.1-rc +## v0.9.2-rc - watch mode / scheduled rechecks -## v0.9.1-rc +## v0.9.2-rc - security review - UX review - API stability diff --git a/backend/app/api/routes_checks.py b/backend/app/api/routes_checks.py index 635b6cf..cfbce6e 100644 --- a/backend/app/api/routes_checks.py +++ b/backend/app/api/routes_checks.py @@ -26,7 +26,7 @@ def check_asn(payload: AsnCheckRequest, db: Session = Depends(get_db), user=Depe except HTTPException: raise except Exception as exc: - raise HTTPException(status_code=500, detail=f"ASN-Prüfung fehlgeschlagen: {exc}") from exc + raise HTTPException(status_code=500, detail=f"ASN check failed: {exc}") from exc @router.post('/asn-rpki', response_model=CheckResponse) @@ -37,7 +37,7 @@ def check_asn_rpki(payload: AsnRpkiBatchRequest, db: Session = Depends(get_db), except HTTPException: raise except Exception as exc: - raise HTTPException(status_code=500, detail=f"ASN-RPKI-Batchprüfung fehlgeschlagen: {exc}") from exc + raise HTTPException(status_code=500, detail=f"ASN RPKI batch check failed: {exc}") from exc @router.post('/prefix', response_model=CheckResponse) @@ -48,7 +48,7 @@ def check_prefix(payload: PrefixCheckRequest, db: Session = Depends(get_db), use except HTTPException: raise except Exception as exc: - raise HTTPException(status_code=500, detail=f"Prefix-Prüfung fehlgeschlagen: {exc}") from exc + raise HTTPException(status_code=500, detail=f"Prefix check failed: {exc}") from exc @@ -64,7 +64,7 @@ def check_bgp_visibility(payload: BgpVisibilityCheckRequest, db: Session = Depen except HTTPException: raise except Exception as exc: - raise HTTPException(status_code=500, detail=f"BGP-Visibility-Prüfung fehlgeschlagen: {exc}") from exc + raise HTTPException(status_code=500, detail=f"BGP visibility check failed: {exc}") from exc @router.post('/roa-preflight', response_model=CheckResponse) def check_roa_preflight(payload: RoaPreflightCheckRequest, db: Session = Depends(get_db), user=Depends(require_operator_or_admin)) -> CheckResponse: @@ -76,7 +76,7 @@ def check_roa_preflight(payload: RoaPreflightCheckRequest, db: Session = Depends except HTTPException: raise except Exception as exc: - raise HTTPException(status_code=500, detail=f"ROA-Preflight-Prüfung fehlgeschlagen: {exc}") from exc + raise HTTPException(status_code=500, detail=f"ROA preflight check failed: {exc}") from exc @router.post('/preflight', response_model=CheckResponse) @@ -87,7 +87,7 @@ def check_preflight(payload: PreflightCheckRequest, db: Session = Depends(get_db except HTTPException: raise except Exception as exc: - raise HTTPException(status_code=500, detail=f"Preflight-Prüfung fehlgeschlagen: {exc}") from exc + raise HTTPException(status_code=500, detail=f"Preflight check failed: {exc}") from exc def _store_and_respond(db: Session, ctype: str, resource: str, origin_as: str | None, result: dict, user_id: int | None = None, change_case_id: int | None = None) -> CheckResponse: diff --git a/backend/app/api/routes_system.py b/backend/app/api/routes_system.py index 05d7d98..9cd9344 100644 --- a/backend/app/api/routes_system.py +++ b/backend/app/api/routes_system.py @@ -12,7 +12,7 @@ def system_info(): return { 'name': 'RouteForge', - 'version': 'v0.9.1-rc', + 'version': 'v0.9.2-rc', 'demo_mode': settings.demo_mode, 'read_only': True, 'data_sources': ['RIPEstat', 'RIPEstat Whois/Registry'], diff --git a/backend/app/core/prefix_evaluation.py b/backend/app/core/prefix_evaluation.py index 27feb96..8e64ff4 100644 --- a/backend/app/core/prefix_evaluation.py +++ b/backend/app/core/prefix_evaluation.py @@ -16,7 +16,7 @@ def evaluate_prefix_overall( } if not origin_as: - return _warning("Keine vollständige Prefix-Origin-Prüfung möglich", "Ohne Origin-AS ist keine vollständige kombinierte Bewertung von RPKI, Registry/IRR und Routing Visibility möglich.") + return _warning("No complete prefix-origin check possible", "Without an origin AS, a complete combined assessment of RPKI, registry/IRR, and routing visibility is not possible.") if CheckStatus.CRITICAL in statuses.values(): if statuses["routing"] == CheckStatus.CRITICAL: @@ -26,7 +26,7 @@ def evaluate_prefix_overall( return _critical("Registry/IRR-Origin widerspricht dem angegebenen Origin-AS.", "Ein gefundenes route/route6-Origin weicht vom geprüften Origin-AS ab.") if statuses["rpki"] == statuses["registry"] == statuses["routing"] == CheckStatus.UNKNOWN: - return _unknown("Keine belastbare Gesamtbewertung möglich.", "RPKI, Registry/IRR und Routing Visibility liefern keine verlässliche Aussage.") + return _unknown("No reliable overall assessment possible.", "RPKI, Registry/IRR und Routing Visibility liefern keine verlässliche Aussage.") if statuses["rpki"] == CheckStatus.OK and statuses["registry"] == CheckStatus.OK and statuses["routing"] == CheckStatus.OK: return { @@ -38,7 +38,7 @@ def evaluate_prefix_overall( } if statuses["rpki"] == CheckStatus.OK and statuses["registry"] == CheckStatus.OK and statuses["routing"] == CheckStatus.UNKNOWN: - return _warning("Routing-Sichtbarkeit konnte nicht belastbar bestimmt werden.", "RPKI und Registry/IRR sind plausibel, aber die Routing-Sichtbarkeit bleibt unklar.") + return _warning("Routing visibility could not be determined reliably.", "RPKI und Registry/IRR sind plausibel, aber die Routing-Sichtbarkeit bleibt unklar.") if CheckStatus.WARNING in statuses.values(): return _warning("Kombinierte Prefix-Bewertung zeigt Warnhinweise.", "Mindestens eine Einzelprüfung meldet unvollständige oder unsichere Daten.") @@ -55,7 +55,7 @@ def _warning(summary: str, explanation: str) -> dict: "summary": summary, "explanation": explanation, "risk": "Die Gesamtaussage bleibt eingeschränkt.", - "recommendations": ["Einzelprüfungen und Rohdaten gezielt nacharbeiten."], + "recommendations": ["Individual checks und Rohdaten gezielt nacharbeiten."], } @@ -64,7 +64,7 @@ def _critical(summary: str, explanation: str) -> dict: "status": CheckStatus.CRITICAL.value, "summary": summary, "explanation": explanation, - "risk": "Erhöhtes Risiko für Fehlrouting, Erreichbarkeitsprobleme oder Sicherheitsvorfälle.", + "risk": "Erhöhtes Risk für Fehlrouting, Erreichbarkeitsprobleme oder Sicherheitsvorfälle.", "recommendations": ["Abweichung priorisiert prüfen und beheben."], } diff --git a/backend/app/core/recommendations.py b/backend/app/core/recommendations.py index 50817f6..2a39b5f 100644 --- a/backend/app/core/recommendations.py +++ b/backend/app/core/recommendations.py @@ -3,12 +3,12 @@ def default_recommendations(status: CheckStatus) -> list[str]: if status == CheckStatus.OK: - return ["Keine unmittelbare Aktion erforderlich. Monitoring fortsetzen."] + return ["No immediate action required. Continue monitoring."] if status == CheckStatus.WARNING: - return ["RPKI/Registry-Daten prüfen und Abdeckung verbessern."] + return ["Review RPKI/registry data and improve coverage."] if status == CheckStatus.CRITICAL: return ["Origin-AS und ROA sofort verifizieren, da Route verworfen werden kann."] - return ["Erneut prüfen; externe Datenquelle war unzuverlässig oder nicht erreichbar."] + return ["Check again; external data source was unreliable or unavailable."] def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | None) -> dict: @@ -17,11 +17,11 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | return { "status": CheckStatus.WARNING.value, "summary": "Origin-AS missing", - "explanation": "Für eine vollständige RPKI-Prüfung wird ein Origin-AS benötigt.", - "risk": "Ohne Origin-AS kann nicht geprüft werden, ob eine konkrete Route RPKI-valid wäre.", + "explanation": "A complete RPKI check requires an origin AS.", + "risk": "Without an origin AS, it cannot be verified whether a route would be RPKI valid.", "recommendations": [ - "Ergänze das Origin-AS, zum Beispiel AS3333.", - "Nutze den ASN-Check, um mögliche Origin-AS-Informationen zu finden.", + "Provide the origin AS, for example AS3333.", + "Use the ASN check to find potential origin AS information.", ], } @@ -31,8 +31,8 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | "status": CheckStatus.OK.value, "summary": "RPKI validation successful", "explanation": "Das Prefix-Origin-Paar ist durch einen passenden ROA abgedeckt.", - "risk": "Kein akutes RPKI-Risiko erkennbar.", - "recommendations": ["Keine akute Maßnahme erforderlich."], + "risk": "No immediate RPKI risk detected.", + "recommendations": ["No urgent action required."], } if normalized == "invalid": return { @@ -41,8 +41,8 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | "explanation": "Das Prefix wird mit einem Origin-AS geprüft, das nicht durch einen passenden ROA gedeckt ist.", "risk": "Validierende Netze können diese Route verwerfen. Dadurch kann Erreichbarkeit verloren gehen.", "recommendations": [ - "Prüfe, ob das Origin-AS korrekt ist.", - "Prüfe bestehende ROAs für das Prefix.", + "Check whether the origin AS is correct.", + "Review existing ROAs for the prefix.", "Erstelle oder korrigiere den ROA nur, wenn du zur Verwaltung dieser Ressourcen berechtigt bist.", ], } @@ -53,8 +53,8 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | "explanation": "Für das Prefix existiert ein ROA, aber nicht für dieses Origin-AS.", "risk": "Validierende Netze können diese Route verwerfen, weil das Origin-AS nicht autorisiert ist.", "recommendations": [ - "Origin-AS prüfen.", - "ROA prüfen.", + "Check the origin AS.", + "Review the ROA.", "Nur korrigieren, wenn man zur Verwaltung der Ressource berechtigt ist.", ], } @@ -65,9 +65,9 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | "explanation": "Für das Prefix existiert ein ROA, aber die angekündigte Prefix-Länge ist länger als die erlaubte maxLength.", "risk": "Validierende Netze können diese Route verwerfen, obwohl das AS grundsätzlich passen kann.", "recommendations": [ - "Angekündigte Prefix-Länge prüfen.", - "ROA maxLength prüfen.", - "Keine zu breite maxLength setzen, wenn sie nicht notwendig ist.", + "Check announced prefix length.", + "Check ROA maxLength.", + "Do not use an overly broad maxLength unless necessary.", ], } if normalized in {"unknown", "not_found", "unknown_roa", "notfound"}: @@ -77,7 +77,7 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | "explanation": "Für dieses Prefix-Origin-Paar wurde kein passender ROA gefunden.", "risk": "Das ist nicht automatisch ein Ausfall, schwächt aber die Routing-Sicherheit.", "recommendations": [ - "Prüfen, ob ein ROA angelegt werden sollte.", + "Check whether a ROA should be created.", "Nur anlegen, wenn man zur Verwaltung berechtigt ist.", ], } @@ -85,11 +85,11 @@ def evaluate_rpki_status(rpki_status: str | None, prefix: str, origin_as: str | return { "status": CheckStatus.UNKNOWN.value, "summary": "RPKI status could not be determined", - "explanation": "Der RPKI-Status konnte nicht zuverlässig ermittelt werden.", - "risk": "Die Bewertung ist unvollständig.", + "explanation": "The RPKI status could not be determined reliably.", + "risk": "The assessment is incomplete.", "recommendations": [ - "Prüfe die API-Rohdaten.", - "Wiederhole die Prüfung später.", + "Review the API raw data.", + "Repeat the check later.", "Vergleiche bei Bedarf mit einer zweiten Quelle oder einem lokalen RPKI-Validator.", ], } diff --git a/backend/app/core/system_status.py b/backend/app/core/system_status.py index 574a784..69253cb 100644 --- a/backend/app/core/system_status.py +++ b/backend/app/core/system_status.py @@ -153,7 +153,7 @@ def build_system_status(engine: Engine | None) -> dict: return { "status": "ok", "name": settings.app_name, - "version": "v0.9.1-rc", + "version": "v0.9.2-rc", "read_only": True, "mode": "demo" if settings.demo_mode else "live", "demo_mode": settings.demo_mode, diff --git a/backend/app/main.py b/backend/app/main.py index f778e90..1be6ba2 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -20,7 +20,7 @@ logging.basicConfig(level=getattr(logging, settings.log_level.upper(), logging.INFO)) logger = logging.getLogger("routeforge") -app = FastAPI(title="RouteForge", version="0.9.1") +app = FastAPI(title="RouteForge", version="0.9.2") app.add_middleware( CORSMiddleware, diff --git a/backend/app/services/asn_checker.py b/backend/app/services/asn_checker.py index f07dfc2..f84e6e2 100644 --- a/backend/app/services/asn_checker.py +++ b/backend/app/services/asn_checker.py @@ -53,13 +53,13 @@ def check(self, asn_input: str) -> dict: if "error" in prefixes: errors.append("announced-prefixes nicht erreichbar") status = CheckStatus.UNKNOWN.value if errors else CheckStatus.OK.value - summary = f"ASN {resource} geprüft." + summary = f"ASN {resource} checked." return { "status": status, "summary": summary, - "explanation": "RPKI kann nicht für eine ASN allein bewertet werden. Für RPKI braucht RouteForge konkrete Prefix-Origin-Paare.", - "risk": "Ohne Prefix-Origin-Paar ist keine direkte RPKI-Gültigkeitsaussage möglich.", - "recommendations": ["Fehlende Datenquellen erneut abrufen."] if errors else ["Keine unmittelbare Aktion erforderlich."], + "explanation": "RPKI cannot be assessed for an ASN alone. RouteForge requires concrete prefix-origin pairs for RPKI.", + "risk": "Without a prefix-origin pair, no direct RPKI validity statement is possible.", + "recommendations": ["Retry unavailable data sources."] if errors else ["No immediate action required."], "details": { "normalized_asn": asn, "resource": resource, @@ -144,23 +144,23 @@ def check_rpki_batch(self, asn_input: str, limit: int) -> dict: else: status = CheckStatus.OK.value - summary_text = f"RPKI-Batchprüfung für {resource}: {len(selected)} Prefixe geprüft." - explanation = "RPKI wurde für sichtbare Prefix-Origin-Paare der ASN geprüft." - recommendations = ["Kritische Ergebnisse priorisiert prüfen.", "Warnungen auf fehlende ROA-Abdeckung untersuchen."] + summary_text = f"RPKI batch check for {resource}: {len(selected)} prefixes checked." + explanation = "RPKI was checked for visible prefix-origin pairs of the ASN." + recommendations = ["Prioritize review of critical results.", "Investigate warnings about missing ROA coverage."] if not selected: - summary_text = f"RPKI-Batchprüfung für {resource} nicht möglich." - explanation = "Für diese ASN konnten keine auswertbaren Prefixe gefunden werden." + summary_text = f"RPKI batch check for {resource} not possible." + explanation = "No analyzable prefixes were found for this ASN." recommendations = [ - "Prüfe, ob die ASN aktuell Prefixe announced.", - "Wiederhole die Abfrage später.", - "Prüfe die Rohdaten der announced-prefixes Antwort.", + "Check whether the ASN is currently announcing prefixes.", + "Retry the query later.", + "Review raw data from the announced-prefixes response.", ] return { "status": status, "summary": summary_text, "explanation": explanation, - "risk": "Kritische oder warnende Einzelresultate können auf Routing-Risiken hinweisen.", + "risk": "Critical or warning-level individual results can indicate routing risks.", "recommendations": recommendations, "input": {"asn": resource, "limit": limit}, "checks": None, @@ -183,7 +183,7 @@ def _build_rpki_batch_metadata(self, prefixes_payload: dict, announced_data: dic return { "available": True, "reason_code": "prefixes_available", - "message": f"RPKI-Batchprüfung ist möglich. Es wurden {len(extracted_prefixes)} sichtbare Prefixe gefunden.", + "message": f"RPKI batch checking is possible. Found {len(extracted_prefixes)} visible prefixes.", "prefix_count": len(extracted_prefixes), "can_retry": False, } @@ -191,7 +191,7 @@ def _build_rpki_batch_metadata(self, prefixes_payload: dict, announced_data: dic return { "available": False, "reason_code": "announced_prefixes_error", - "message": "Die angekündigten Prefixe konnten über RIPEstat nicht geladen werden. Eine RPKI-Batchprüfung ist deshalb aktuell nicht möglich.", + "message": "Announced prefixes could not be loaded from RIPEstat. RPKI batch checking is currently not possible.", "prefix_count": 0, "can_retry": True, } @@ -199,14 +199,14 @@ def _build_rpki_batch_metadata(self, prefixes_payload: dict, announced_data: dic return { "available": False, "reason_code": "no_prefixes_extracted", - "message": "Für diese ASN wurden in der RIPEstat-Antwort keine auswertbaren Prefixe gefunden. Entweder announced die ASN aktuell keine Prefixe in dieser Quelle oder die Datenstruktur konnte nicht interpretiert werden.", + "message": "No analyzable prefixes were found in the RIPEstat response for this ASN. The ASN may currently announce no prefixes in this source, or the data structure could not be interpreted.", "prefix_count": 0, "can_retry": True, } return { "available": False, "reason_code": "no_announced_prefixes", - "message": "Für diese ASN wurden keine sichtbaren Prefixe gefunden. Ohne Prefixe kann RouteForge keine RPKI-Batchprüfung durchführen.", + "message": "No visible prefixes were found for this ASN. Without prefixes, RouteForge cannot perform RPKI batch checks.", "prefix_count": 0, "can_retry": True, } diff --git a/backend/app/services/bgp_visibility_service.py b/backend/app/services/bgp_visibility_service.py index ba7e1af..8d8cc74 100644 --- a/backend/app/services/bgp_visibility_service.py +++ b/backend/app/services/bgp_visibility_service.py @@ -31,30 +31,30 @@ def check(self, prefix: str, expected_origin_as: str | None) -> dict: if data_unreliable: status = CheckStatus.UNKNOWN.value - summary = "Keine belastbaren BGP-Sichtbarkeitsdaten für das Prefix verfügbar." - recommendations = ["Später erneut prüfen und ein externes Monitoring zur Gegenprüfung verwenden."] + summary = "No reliable BGP visibility data is available for the prefix." + recommendations = ["Check again later and use external monitoring for cross-validation."] elif not visible: status = CheckStatus.CRITICAL.value if normalized_expected else CheckStatus.WARNING.value - summary = "Das Prefix ist aktuell nicht sichtbar." - recommendations = ["Ankündigungspfad und Upstream-Policy prüfen.", "Route-Propagation in mehreren Looking-Glasses validieren."] + summary = "The prefix is currently not visible." + recommendations = ["Check announcement path and upstream policy.", "Validate route propagation across multiple looking glasses."] elif normalized_expected and not expected_seen: status = CheckStatus.CRITICAL.value - summary = f"Das erwartete Origin {normalized_expected} ist für {normalized_prefix} nicht sichtbar." - recommendations = ["Origin-AS Konfiguration prüfen.", "Mögliche Route-Leaks/Hijacks gegenprüfen."] + summary = f"The expected origin {normalized_expected} is not visible for {normalized_prefix} ." + recommendations = ["Check origin-AS configuration.", "Investigate potential route leaks/hijacks."] elif multiple_origins: status = CheckStatus.WARNING.value - summary = "Prefix sichtbar, aber mit mehreren Origin-ASNs (MOAS)." - recommendations = ["Mehrfach-Origin fachlich bestätigen oder unbeabsichtigte Ankündigung beheben."] + summary = "Prefix is visible but has multiple origin ASNs (MOAS)." + recommendations = ["Confirm multi-origin behavior or fix unintended announcements."] else: status = CheckStatus.OK.value - summary = "Prefix sichtbar und erwartete Origin-AS (falls angegeben) wird gesehen." - recommendations = ["Weiter beobachten; Ergebnis ist eine Momentaufnahme externer Sichtbarkeitsdaten."] + summary = "Prefix is visible and the expected origin AS (if provided) is observed." + recommendations = ["Continue monitoring; this result is a point-in-time snapshot of external visibility data."] return { "status": status, "summary": summary, "explanation": "BGP Visibility basiert auf RIPEstat-Daten und ist read-only.", - "risk": "Externe Sichtbarkeitsdaten können zeitversetzt oder unvollständig sein.", + "risk": "External visibility data may be delayed or incomplete.", "recommendations": recommendations, "input": {"prefix": normalized_prefix, "expected_origin_as": normalized_expected}, "checks": None, diff --git a/backend/app/services/prefix_checker.py b/backend/app/services/prefix_checker.py index 723341a..71e2ff1 100644 --- a/backend/app/services/prefix_checker.py +++ b/backend/app/services/prefix_checker.py @@ -41,11 +41,11 @@ def check(self, prefix: str, origin_as: str | None) -> dict: warnings: list[str] = [] if whois.get("error") or routing_status.get("error"): - warnings.append("Mindestens eine zusätzliche Datenquelle war nicht erreichbar.") + warnings.append("At least one additional data source was unavailable.") if rpki_check.get("status") == CheckStatus.UNKNOWN.value and rpki_check.get("raw", {}).get("error"): - warnings.append("RPKI-Quelle nicht erreichbar oder unvollständig.") + warnings.append("RPKI source unavailable or incomplete.") if routing_visibility_check.get("status") == CheckStatus.UNKNOWN.value: - warnings.append("Routing-Sichtbarkeit konnte nicht belastbar bestimmt werden.") + warnings.append("Routing visibility could not be determined reliably.") overall = evaluate_prefix_overall(rpki_check, registry_check, routing_visibility_check, normalized_prefix, normalized_origin) diff --git a/backend/app/services/registry_checker.py b/backend/app/services/registry_checker.py index 639f4bc..72bafd4 100644 --- a/backend/app/services/registry_checker.py +++ b/backend/app/services/registry_checker.py @@ -9,10 +9,10 @@ def check(self, prefix: str, origin_as: str | None, whois_payload: dict) -> dict "status": CheckStatus.UNKNOWN.value, "summary": "Registry-/IRR-Daten konnten nicht bestimmt werden", "explanation": "Die Whois-/Registry-Datenquelle war nicht erreichbar oder lieferte einen Fehler.", - "risk": "Die Bewertung ist unvollständig.", + "risk": "The assessment is incomplete.", "recommendations": [ "Prüfe die Rohdaten der Registry-Quelle.", - "Wiederhole die Abfrage später.", + "Retry the query later.", "Vergleiche das Ergebnis mit einer zweiten Registry-/IRR-Quelle.", ], "raw": whois_payload if isinstance(whois_payload, dict) else {}, @@ -76,7 +76,7 @@ def check(self, prefix: str, origin_as: str | None, whois_payload: dict) -> dict "status": CheckStatus.CRITICAL.value, "summary": "Route/route6-Origin widerspricht dem angegebenen Origin-AS", "explanation": f"Gefundene Origins: {', '.join(sorted(route_origins))}. Erwartet wurde {normalized_origin}.", - "risk": "Möglicher Konfigurations- oder Registry-Fehler mit Hijack-Risiko.", + "risk": "Möglicher Konfigurations- oder Registry-Fehler mit Hijack-Risk.", "recommendations": [ "Origin-AS und route/route6-Objekte in der zuständigen Registry abgleichen.", "Fehlerhafte Registry-Einträge korrigieren.", diff --git a/backend/app/services/roa_planner_service.py b/backend/app/services/roa_planner_service.py index 0eb25eb..a3b5741 100644 --- a/backend/app/services/roa_planner_service.py +++ b/backend/app/services/roa_planner_service.py @@ -67,7 +67,7 @@ def check(self, prefix: str, origin_as: str, max_length: int | None = None) -> d planned_validation_state = "not_found" status = "WARNING" - summary = "Keine passende ROA gefunden; ein read-only Vorschlag wurde erstellt." + summary = "No matching ROA found; a read-only proposal was created." max_length_risk = "none" if matching_roas: @@ -86,7 +86,7 @@ def check(self, prefix: str, origin_as: str, max_length: int | None = None) -> d max_length_risk = "broad" if status == "OK": status = "WARNING" - recommendations.append("Max Length ist relativ breit gewählt; reduzieren Sie die Breite, um Hijack-Risiko zu senken.") + recommendations.append("Max Length ist relativ breit gewählt; reduzieren Sie die Breite, um Hijack-Risk zu senken.") suggested_roa = None if matching_roas else {"prefix": normalized_prefix, "origin_as": normalized_origin, "max_length": effective_max_length} if suggested_roa: @@ -95,7 +95,7 @@ def check(self, prefix: str, origin_as: str, max_length: int | None = None) -> d return { "status": status, "summary": summary, - "recommendations": recommendations or ["Keine zusätzlichen Empfehlungen."], + "recommendations": recommendations or ["No additional recommendations."], "details": { "prefix": normalized_prefix, "origin_as": normalized_origin, diff --git a/backend/app/services/routing_visibility_checker.py b/backend/app/services/routing_visibility_checker.py index 0c6761a..10bd9d2 100644 --- a/backend/app/services/routing_visibility_checker.py +++ b/backend/app/services/routing_visibility_checker.py @@ -58,9 +58,9 @@ def check(self, prefix: str, origin_as: str | None, routing_payload: dict | None "status": CheckStatus.CRITICAL.value, "summary": "Visible Origin-AS differs from expected Origin-AS", "explanation": "Das Prefix ist sichtbar, aber nicht mit dem erwarteten Origin-AS.", - "risk": "Möglicher Routing-Fehler, falsches Announcement oder Hijack-Risiko.", + "risk": "Möglicher Routing-Fehler, falsches Announcement oder Hijack-Risk.", "recommendations": [ - "Sichtbares Origin-AS prüfen.", + "Sichtbares Check the origin AS.", "BGP Announcement und Upstream-Konfiguration prüfen.", "RPKI und Registry/IRR-Daten gegenprüfen.", ], @@ -72,10 +72,10 @@ def _result_unknown(self, payload: dict | None) -> dict: "status": CheckStatus.UNKNOWN.value, "summary": "Routing visibility could not be determined", "explanation": "RIPEstat routing-status did not respond before the configured timeout or returned no usable payload.", - "risk": "Die Bewertung ist unvollständig.", + "risk": "The assessment is incomplete.", "recommendations": [ "Prüfe die Rohdaten.", - "Wiederhole die Abfrage später.", + "Retry the query later.", "Vergleiche bei Bedarf mit einer zweiten Routing-Quelle oder einem Looking Glass.", ], "raw": {"routing_payload": payload or {}}, diff --git a/backend/app/services/rpki_checker.py b/backend/app/services/rpki_checker.py index 396f0de..55ed95a 100644 --- a/backend/app/services/rpki_checker.py +++ b/backend/app/services/rpki_checker.py @@ -22,11 +22,11 @@ def check(self, prefix: str, origin_as: str | None) -> dict: evaluation = { "status": CheckStatus.UNKNOWN.value, "summary": "RPKI status could not be determined", - "explanation": "Die RIPEstat-Quelle war nicht erreichbar oder lieferte unerwartete Daten.", - "risk": "Die Bewertung ist unvollständig.", + "explanation": "The RIPEstat source was unavailable or returned unexpected data.", + "risk": "The assessment is incomplete.", "recommendations": [ - "Prüfe die API-Rohdaten.", - "Wiederhole die Prüfung später.", + "Review the API raw data.", + "Repeat the check later.", "Vergleiche bei Bedarf mit einer zweiten Quelle oder einem lokalen RPKI-Validator.", ], } diff --git a/backend/app/templates/report.html.j2 b/backend/app/templates/report.html.j2 index 6ff5daa..c7d32ad 100644 --- a/backend/app/templates/report.html.j2 +++ b/backend/app/templates/report.html.j2 @@ -18,35 +18,35 @@

  • Status: {{ report.status }}
  • Kurzfassung: {{ report.summary }}
  • Preflight Decision: {{ report.details.preflight_decision if report.details and report.details.preflight_decision else '-' }}
  • -
  • Erklärung: {{ report.explanation or '-' }}
  • -
  • Risiko: {{ report.risk or '-' }}
  • +
  • Explanation: {{ report.explanation or '-' }}
  • +
  • Risk: {{ report.risk or '-' }}
  • -

    Empfehlungen

    +

    Recommendations

    -

    Einzelprüfungen

    +

    Individual checks

    RPKI

    Registry/IRR

    Routing Visibility

    Data Source Diagnostics

    diff --git a/backend/app/templates/report.md.j2 b/backend/app/templates/report.md.j2 index 34e01e3..4ff4cee 100644 --- a/backend/app/templates/report.md.j2 +++ b/backend/app/templates/report.md.j2 @@ -15,31 +15,31 @@ - Status: **{{ report.status }}** - Kurzfassung: {{ report.summary }} - Preflight Decision: {{ report.details.preflight_decision if report.details and report.details.preflight_decision else '-' }} -- Erklärung: {{ report.explanation or '-' }} -- Risiko: {{ report.risk or '-' }} +- Explanation: {{ report.explanation or '-' }} +- Risk: {{ report.risk or '-' }} -## Empfehlungen +## Recommendations {% for item in report.recommendations %}- {{ item }} {% endfor %} -## Einzelprüfungen +## Individual checks ### RPKI - Status: **{{ report.checks.rpki.status if report.checks and report.checks.rpki else '-' }}** - Summary: {{ report.checks.rpki.summary if report.checks and report.checks.rpki else '-' }} -- Erklärung: {{ report.checks.rpki.explanation if report.checks and report.checks.rpki else '-' }} -- Risiko: {{ report.checks.rpki.risk if report.checks and report.checks.rpki else '-' }} +- Explanation: {{ report.checks.rpki.explanation if report.checks and report.checks.rpki else '-' }} +- Risk: {{ report.checks.rpki.risk if report.checks and report.checks.rpki else '-' }} ### Registry/IRR - Status: **{{ report.checks.registry.status if report.checks and report.checks.registry else '-' }}** - Summary: {{ report.checks.registry.summary if report.checks and report.checks.registry else '-' }} -- Erklärung: {{ report.checks.registry.explanation if report.checks and report.checks.registry else '-' }} -- Risiko: {{ report.checks.registry.risk if report.checks and report.checks.registry else '-' }} +- Explanation: {{ report.checks.registry.explanation if report.checks and report.checks.registry else '-' }} +- Risk: {{ report.checks.registry.risk if report.checks and report.checks.registry else '-' }} ### Routing Visibility - Status: **{{ report.checks.routing_visibility.status if report.checks and report.checks.routing_visibility else '-' }}** - Summary: {{ report.checks.routing_visibility.summary if report.checks and report.checks.routing_visibility else '-' }} -- Erklärung: {{ report.checks.routing_visibility.explanation if report.checks and report.checks.routing_visibility else '-' }} -- Risiko: {{ report.checks.routing_visibility.risk if report.checks and report.checks.routing_visibility else '-' }} +- Explanation: {{ report.checks.routing_visibility.explanation if report.checks and report.checks.routing_visibility else '-' }} +- Risk: {{ report.checks.routing_visibility.risk if report.checks and report.checks.routing_visibility else '-' }} ## Data Source Diagnostics {% if report.details and report.details.source_diagnostics %} diff --git a/backend/pyproject.toml b/backend/pyproject.toml index eabae7a..146a8be 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "routeforge-backend" -version = "0.9.1" +version = "0.9.2" description = "RouteForge backend" license = "AGPL-3.0-or-later" requires-python = ">=3.12" diff --git a/backend/tests/test_api_smoke.py b/backend/tests/test_api_smoke.py index 8a7f78b..8f34688 100644 --- a/backend/tests/test_api_smoke.py +++ b/backend/tests/test_api_smoke.py @@ -177,7 +177,7 @@ def test_system_status_endpoint() -> None: response = client.get('/api/system/status') assert response.status_code == 200 payload = response.json() - assert payload.get('version') == 'v0.9.1-rc' + assert payload.get('version') == 'v0.9.2-rc' assert payload.get('read_only') is True assert payload.get('database', {}).get('status') assert payload.get('ripestat', {}).get('cache_ttl_seconds') is not None diff --git a/frontend/package.json b/frontend/package.json index 252d44e..6d1d785 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "routeforge-frontend", - "version": "0.9.1", + "version": "0.9.2", "private": true, "license": "AGPL-3.0-or-later", "type": "module", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e1a4d70..c746bf5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -74,7 +74,7 @@ export default function App() { if (authMode === 'login') return if (authMode === 'error') return
    {authError}
    - const systemLine = system ? `${system.name} ${system.version} · mode=${system.demo_mode ? 'DEMO' : 'LIVE'} · read_only=${String(system.read_only)}` : 'RouteForge v0.9.1-rc · read-only preflight checks' + const systemLine = system ? `${system.name} ${system.version} · mode=${system.demo_mode ? 'DEMO' : 'LIVE'} · read_only=${String(system.read_only)}` : 'RouteForge v0.9.2-rc · read-only preflight checks' const title = { dashboard: 'Dashboard', asn: 'ASN Check', prefix: 'Prefix Check', preflight: 'Preflight', 'roa-planner': 'ROA Planner', 'bgp-visibility': 'BGP Visibility', reports: 'Reports', 'watch-mode': 'Watch Mode', 'change-cases': 'Change Cases', system: 'System', users: 'Users', audit: 'Audit Log', about: 'About' }[active] const proxyStatus = systemStatusError ? 'ERROR' : 'OK' const migrationStatus = systemStatus?.database?.migration_status || 'unknown' @@ -89,7 +89,7 @@ export default function App() { const readOnlyLabel = system?.read_only ? 'Enabled' : 'Disabled' return - {active === 'dashboard' &&

    {system?.name || 'RouteForge'} {system?.version || 'v0.9.1-rc'}

    Current user: {currentUser?.username || '-'}

    Role: {currentUser?.role || '-'}

    Read-only: {readOnlyLabel}

    Mode: {system?.demo_mode ? 'DEMO' : 'LIVE'}

    {system?.demo_mode &&

    Demo mode is active.

    }

    Quick Actions

    {role === 'admin' && }
    {migrationsBlocked &&
    Database migrations are required before using RouteForge. Run: alembic current, alembic heads, alembic upgrade head.
    }
    } + {active === 'dashboard' &&

    {system?.name || 'RouteForge'} {system?.version || 'v0.9.2-rc'}

    Current user: {currentUser?.username || '-'}

    Role: {currentUser?.role || '-'}

    Read-only: {readOnlyLabel}

    Mode: {system?.demo_mode ? 'DEMO' : 'LIVE'}

    {system?.demo_mode &&

    Demo mode is active.

    }

    Quick Actions

    {role === 'admin' && }
    {migrationsBlocked &&
    Database migrations are required before using RouteForge. Run: alembic current, alembic heads, alembic upgrade head.
    }
    } {!canAccess(active) &&
    You do not have permission to access this section.
    } {active === 'asn' && canAccess('asn') && } {active === 'prefix' && canAccess('prefix') && } @@ -102,6 +102,6 @@ export default function App() { {active === 'system' && canAccess('system') &&
    {systemStatusError &&
    {systemStatusError}
    }{migrationsBlocked &&
    Database migrations are required before using RouteForge. Run: alembic current, alembic heads, alembic upgrade head.
    }{systemStatus &&
    Version: {systemStatus.version}
    Mode: {systemStatus.mode}
    API Proxy: {proxyStatus}
    Migration Status: {migrationStatus}
    DB Current Revision: {systemStatus.database?.schema_version || 'unknown'}
    DB Head Revision: {systemStatus.database?.migration_head || 'unknown'}
    }
    } {active === 'users' && canAccess('users') && } {active === 'audit' && canAccess('audit') && } - {active === 'about' &&

    RouteForge is a read-only routing operations console for validation workflows.

    Read-only: All checks are non-destructive.

    Version: v0.9.1-rc

    } + {active === 'about' &&

    RouteForge is a read-only routing operations console for validation workflows.

    Read-only: All checks are non-destructive.

    Version: v0.9.2-rc

    }
    } diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 910941f..3709a71 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -24,7 +24,7 @@ async function requestJson(url: string, options: RequestInit): Promise { if (!response.ok) { const detail = typeof parsedBody === 'object' && parsedBody !== null && 'detail' in parsedBody ? String((parsedBody as { detail: unknown }).detail) - : response.statusText || 'Unbekannter API-Fehler' + : response.statusText || 'Unknown API error' const friendly = detail.includes('Database schema is not up to date') ? 'Database migration required. Please run alembic upgrade head.' : detail throw new ApiError(`HTTP ${response.status}: ${friendly}`, response.status, parsedBody) }