From 5141d7bfca8739135a8005032b838ad44d5b75c9 Mon Sep 17 00:00:00 2001 From: DeepZone Date: Thu, 21 May 2026 12:51:55 +0100 Subject: [PATCH] v0.9.1-rc: UI Cleanup & English-Only Product Polish --- RELEASE_NOTES.md | 4 ++++ frontend/src/App.tsx | 10 ++++----- frontend/src/components/AsnCheckForm.tsx | 2 +- frontend/src/components/AuditLogView.tsx | 2 +- frontend/src/components/BgpVisibilityForm.tsx | 2 +- frontend/src/components/ChangeCasesView.tsx | 6 ++--- frontend/src/components/Layout.tsx | 2 +- frontend/src/components/PrefixCheckForm.tsx | 2 +- .../src/components/PreflightCheckForm.tsx | 2 +- frontend/src/components/ReportView.tsx | 22 +++++++++---------- frontend/src/components/RoaPlannerForm.tsx | 8 +++---- frontend/src/components/UsersView.tsx | 2 +- frontend/src/components/WatchModeView.tsx | 2 +- 13 files changed, 35 insertions(+), 31 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f434aac..b6516ac 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +## v0.9.1-rc (2026-05-21) + +- UI Cleanup & English-only polish + ## v0.9.1-rc (2026-05-20) ### Motivation diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cf7e8a4..e1a4d70 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -75,7 +75,7 @@ export default function App() { 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 title = { dashboard: 'Dashboard', asn: 'ASN Check', prefix: 'Prefix Check', preflight: 'Preflight Check', 'roa-planner': 'ROA Planner', 'bgp-visibility': 'BGP Visibility', reports: 'Reports', 'watch-mode': 'Watch Mode', 'change-cases': 'Change Cases', system: 'System Status', users: 'User Management', audit: 'Audit Log', about: 'About RouteForge' }[active] + 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' const migrationsBlocked = migrationStatus === 'behind' || migrationStatus === 'error' @@ -86,22 +86,22 @@ export default function App() { if (role === 'operator') return !['users', 'audit'].includes(view) return ['dashboard', 'reports', 'watch-mode', 'change-cases', 'bgp-visibility', 'roa-planner', 'about'].includes(view) } - const allowedActions = role === 'admin' ? 'You can run checks, manage users, view reports and system status.' : role === 'operator' ? 'You can run checks and view reports.' : 'You can view reports and change cases.' + const readOnlyLabel = system?.read_only ? 'Enabled' : 'Disabled' return - {active === 'dashboard' &&

RouteForge v0.9.1-rc

Modernes read-only Operator-Tool für Preflight Checks von ASN, Prefix, RPKI und Registry/IRR.

Logged in as: {currentUser?.username}
Role: {currentUser?.role}
Allowed actions: {allowedActions}
{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.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.
}
} {!canAccess(active) &&
You do not have permission to access this section.
} {active === 'asn' && canAccess('asn') && } {active === 'prefix' && canAccess('prefix') && } {active === 'preflight' && canAccess('preflight') && } {active === 'bgp-visibility' && canAccess('bgp-visibility') && } {active === 'roa-planner' && canAccess('roa-planner') && } - {active === 'reports' &&

Reports

{reports.length===0 ?
Noch keine Reports vorhanden.
:
{reports.map(r=>)}
{r.summary}
}
} + {active === 'reports' &&

Reports

{reports.length===0 ?
No reports yet.
:
{reports.map(r=>)}
{r.summary}
}
} {active === 'watch-mode' && canAccess('watch-mode') && } {active === 'change-cases' && canAccess('change-cases') && } {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' &&

Version: v0.9.1

} + {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

}
} diff --git a/frontend/src/components/AsnCheckForm.tsx b/frontend/src/components/AsnCheckForm.tsx index d9546a9..ba091c7 100644 --- a/frontend/src/components/AsnCheckForm.tsx +++ b/frontend/src/components/AsnCheckForm.tsx @@ -16,7 +16,7 @@ export function AsnCheckForm() { const onSubmit = async () => { setError(null); setLoading(true); try { setResult(await checkAsn(asn)) } catch (e) { setError(e as ApiError) } finally { setLoading(false) } } const onBatch = async () => { setError(null); setBatchLoading(true); try { setBatchResult(await checkAsnRpki(asn, 25)) } catch (e) { setError(e as ApiError) } finally { setBatchLoading(false) } } return
-

ASN Check

RPKI bewertet Prefix-Origin-Paare, nicht die ASN isoliert.

setAsn(e.target.value)} />
{result && }
{result &&

Extrahierte Prefixe: {extracted.length}

}{result && !batchAvailable &&

RPKI-Batch nicht möglich

{rpkiBatch?.message || 'Für diese ASN konnten keine auswertbaren Prefixe gefunden werden.'}

Reason: {rpkiBatch?.reason_code || 'unknown'} · Prefix count: {rpkiBatch?.prefix_count ?? 0}

}{(loading || batchLoading) &&

Prüfung läuft…

}{error &&

{error.message}

}
+

ASN Check

RPKI evaluates prefix-origin pairs, not an ASN in isolation.

setAsn(e.target.value)} />
{result && }
{result &&

Extracted prefixes: {extracted.length}

}{result && !batchAvailable &&

RPKI batch unavailable

{rpkiBatch?.message || 'No evaluable prefixes were found for this ASN.'}

Reason: {rpkiBatch?.reason_code || 'unknown'} · Prefix count: {rpkiBatch?.prefix_count ?? 0}

}{(loading || batchLoading) &&

Running check…

}{error &&

{error.message}

}
{result && } {batchResult && }
diff --git a/frontend/src/components/AuditLogView.tsx b/frontend/src/components/AuditLogView.tsx index 08b58fa..f74d79d 100644 --- a/frontend/src/components/AuditLogView.tsx +++ b/frontend/src/components/AuditLogView.tsx @@ -30,7 +30,7 @@ export function AuditLogView() { {loading &&
Loading audit log…
} {error &&
{error}
} - {!loading && !error && items.length === 0 &&
No audit events yet.
} + {!loading && !error && items.length === 0 &&
No audit entries found.
} {!loading && !error && items.length > 0 &&
{items.map((a)=>)}
TimeUserActionTargetIPUser AgentDetails
{a.created_at}{a.username || a.user_id || '-'}{a.action}{a.target_type || '-'}:{a.target_id || '-'}{a.ip_address || '-'}{a.user_agent || '-'}
{JSON.stringify(a.details_json || {}, null, 2)}
} } diff --git a/frontend/src/components/BgpVisibilityForm.tsx b/frontend/src/components/BgpVisibilityForm.tsx index 96c3c4f..603ccaa 100644 --- a/frontend/src/components/BgpVisibilityForm.tsx +++ b/frontend/src/components/BgpVisibilityForm.tsx @@ -20,7 +20,7 @@ export function BgpVisibilityForm({ role }: { role: UserRole }) { return

BGP Visibility

- {!canRun &&

Viewer role: Seite ist sichtbar, aber Checks dürfen nicht ausgeführt werden.

} + {!canRun &&

Viewer role: this page is visible, but checks cannot be run.

} setPrefix(e.target.value)} /> setExpectedOriginAs(e.target.value)} /> diff --git a/frontend/src/components/ChangeCasesView.tsx b/frontend/src/components/ChangeCasesView.tsx index d25d284..b955a43 100644 --- a/frontend/src/components/ChangeCasesView.tsx +++ b/frontend/src/components/ChangeCasesView.tsx @@ -63,7 +63,7 @@ export function ChangeCasesView({ role }: { role: UserRole }) { } - {loading ?
Loading change cases…
: items.length===0 ?
No change cases yet. Create one to attach checks and reports.
: {items.map(i=>setSelected(i)}>)}
TitleStatusOwnerCreatedUpdated
{i.title}{i.created_by_user_id ?? '—'}{new Date(i.created_at).toLocaleString()}{new Date(i.updated_at).toLocaleString()}
} + {loading ?
Loading change cases…
: items.length===0 ?
No change cases yet.
: {items.map(i=>setSelected(i)}>)}
TitleStatusOwnerCreatedUpdated
{i.title}{i.created_by_user_id ?? '—'}{new Date(i.created_at).toLocaleString()}{new Date(i.updated_at).toLocaleString()}
} {selected &&
@@ -97,11 +97,11 @@ export function ChangeCasesView({ role }: { role: UserRole }) {

Reports

- {reports.length === 0 ?

No reports for this Change Case yet.

: {reports.map(r=>)}
TypeStatusSummaryCreatedOpen
{r.check_type}{r.status}{r.summary}{new Date(r.created_at).toLocaleString()}
} + {reports.length === 0 ?

No reports yet.

: {reports.map(r=>)}
TypeStatusSummaryCreatedOpen
{r.check_type}{r.status}{r.summary}{new Date(r.created_at).toLocaleString()}
}
{canEdit &&
- +
}
}
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 748a9ea..2dcb36a 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -45,7 +45,7 @@ export function Layout({ children, active, onNav, systemLine, title, demoMode, c

{title}

- {currentUser && Angemeldet als {currentUser.username} · {currentUser.role}} + {currentUser && Signed in as {currentUser.username} · {currentUser.role}} {demoMode ? 'DEMO' : 'LIVE'} READ-ONLY v0.9.1 diff --git a/frontend/src/components/PrefixCheckForm.tsx b/frontend/src/components/PrefixCheckForm.tsx index d362696..e1212ba 100644 --- a/frontend/src/components/PrefixCheckForm.tsx +++ b/frontend/src/components/PrefixCheckForm.tsx @@ -11,7 +11,7 @@ export function PrefixCheckForm() { const [result, setResult] = useState(null) const onSubmit = async () => { setLoading(true); setError(null); try { setResult(await checkPrefix(prefix, originAs || undefined)) } catch (e) { setError(e as ApiError) } finally { setLoading(false) } } return
-

Prefix Check

Origin-AS empfohlen für vollständige Bewertung.

setPrefix(e.target.value)} />
setOriginAs(e.target.value)} />
{loading &&

Prüfung läuft…

}{error &&

{error.message}

}
+

Prefix Check

Provide Origin AS for a complete evaluation.

setPrefix(e.target.value)} />
setOriginAs(e.target.value)} />
{loading &&

Running check…

}{error &&

{error.message}

}
{result && }
} diff --git a/frontend/src/components/PreflightCheckForm.tsx b/frontend/src/components/PreflightCheckForm.tsx index fbfebb4..fc3fd7f 100644 --- a/frontend/src/components/PreflightCheckForm.tsx +++ b/frontend/src/components/PreflightCheckForm.tsx @@ -22,7 +22,7 @@ export function PreflightCheckForm() {
setPrefix(e.target.value)} />
setPlannedOriginAs(e.target.value)} />
- {loading &&

Prüfung läuft…

} + {loading &&

Running check…

} {error &&

{error.message}

} {result && } diff --git a/frontend/src/components/ReportView.tsx b/frontend/src/components/ReportView.tsx index abe01e3..b1c84b5 100644 --- a/frontend/src/components/ReportView.tsx +++ b/frontend/src/components/ReportView.tsx @@ -30,7 +30,7 @@ export function ReportView({ report }: { report: CheckResponse }) { const registry = report.checks?.registry const routingVisibility = report.checks?.routing_visibility const sortedResults = ([...(Array.isArray(details.results) ? details.results : [])] as RpkiBatchResult[]).sort((a, b) => (order[a.status as keyof typeof order] ?? 99) - (order[b.status as keyof typeof order] ?? 99)) - const recommendationsTitle = report.status === 'CRITICAL' ? 'Sofort prüfen' : report.status === 'WARNING' ? 'Empfohlen' : report.status === 'OK' ? 'Hinweis' : 'Datenlage prüfen' + const recommendationsTitle = report.status === 'CRITICAL' ? 'Immediate actions' : report.status === 'WARNING' ? 'Recommended actions' : report.status === 'OK' ? 'Notes' : 'Review data quality' const reportId = Number((report as { report_id?: number; id?: number; details?: { report_id?: number } }).report_id ?? (report as { id?: number }).id ?? (report.details as { report_id?: number } | undefined)?.report_id) const hasReportId = Number.isFinite(reportId) && reportId > 0 const rpkiBatch = details.rpki_batch as { message?: string } | undefined @@ -69,9 +69,9 @@ export function ReportView({ report }: { report: CheckResponse }) { return
-

Ergebnis

+

Result

-

Check-Typ: {checkType}

Resource: {report.input?.prefix || report.input?.asn || '-'}

+

Check type: {checkType}

Resource: {report.input?.prefix || report.input?.asn || '-'}

Origin-AS: {report.input?.origin_as || report.input?.planned_origin_as || '-'}

Holder: {holder}

{report.input?.planned_origin_as &&

Preflight Decision: {preflightDecision}

} @@ -79,8 +79,8 @@ export function ReportView({ report }: { report: CheckResponse }) {
-

Gesamtbewertung

-

Erklärung: {report.explanation || '-'}

Risiko: {report.risk || '-'}

+

Overall assessment

+

Explanation: {report.explanation || '-'}

Risk: {report.risk || '-'}

{recommendationsTitle}

    {(report.recommendations || []).map((r, i) =>
  • {r}
  • )}
@@ -91,16 +91,16 @@ export function ReportView({ report }: { report: CheckResponse }) { {sourceDiagnostics.length > 0 &&

Data Source Diagnostics

{sourceDiagnostics.map((d, idx) => )}
SourceEndpointStatusFreshnessAttemptsRetriesFallbackStale CacheDurationCacheAgeTTLMessage
{d.name || '-'}{d.endpoint || '-'}{d.freshness || 'UNKNOWN'}{typeof d.attempts === 'number' ? d.attempts : '-'}{typeof d.retry_count === 'number' ? d.retry_count : '-'}{d.fallback_used === true ? 'yes' : d.fallback_used === false ? 'no' : '-'}{d.stale_cache_used === true ? 'yes' : d.stale_cache_used === false ? 'no' : '-'}{typeof d.duration_ms === 'number' ? `${d.duration_ms} ms` : '-'}{d.cached === true ? 'HIT' : d.cached === false ? 'MISS' : '-'}{typeof d.cache_age_seconds === 'number' ? formatDurationSeconds(d.cache_age_seconds) : 'Unknown'}{typeof d.cache_ttl_seconds === 'number' ? formatDurationSeconds(d.cache_ttl_seconds) : 'Unknown'}
{d.message || '-'}
{d.fallback_used === true &&
Live request failed. RouteForge used cached data. The result may be outdated.
}{d.status === 'RATE_LIMITED' &&
Rate limited by data source.
}
} -
Technische Details
{JSON.stringify({ input: report.input, holder: details.resource_holder, warnings: details.warnings, source_errors: details.source_errors }, null, 2)}
+
Technical details
{JSON.stringify({ input: report.input, holder: details.resource_holder, warnings: details.warnings, source_errors: details.source_errors }, null, 2)}
{sortedResults.length > 0 &&

Batch Results

{sortedResults.map((item, idx) => )}
StatusPrefixSummary
{item.prefix}{item.summary || '-'}
} - {sortedResults.length === 0 && ((details.checked_prefixes as number | undefined) === 0 || rpkiBatch?.message) &&

Keine Prefixe geprüft

{rpkiBatch?.message || report.explanation || 'Keine Daten verfügbar.'}

} - {rpkiBatch?.message &&

RPKI-Batch Hinweis

{rpkiBatch.message}

} + {sortedResults.length === 0 && ((details.checked_prefixes as number | undefined) === 0 || rpkiBatch?.message) &&

No prefixes checked

{rpkiBatch?.message || report.explanation || 'No data available.'}

} + {rpkiBatch?.message &&

RPKI batch note

{rpkiBatch.message}

}

Export

- {!hasReportId &&

Export ist erst verfügbar, nachdem der Report gespeichert wurde.

} + {!hasReportId &&

Export is available after the report is saved.

}
- - + +
diff --git a/frontend/src/components/RoaPlannerForm.tsx b/frontend/src/components/RoaPlannerForm.tsx index 2edb46b..ebe31f5 100644 --- a/frontend/src/components/RoaPlannerForm.tsx +++ b/frontend/src/components/RoaPlannerForm.tsx @@ -18,14 +18,14 @@ export function RoaPlannerForm({ role }: { role: UserRole }) { } return
-

ROA Planner / ROA Preflight

-

Read-only: RouteForge erstellt, ändert oder löscht keine ROAs.

+

ROA Planner

+

Read-only: RouteForge does not create, update, or delete ROAs.

setPrefix(e.target.value)} placeholder='203.0.113.0/24' />
setOriginAs(e.target.value)} placeholder='AS64500' />
setMaxLength(e.target.value)} placeholder='24' />
- {!canRun &&

Viewer können Checks nicht ausführen.

} - {loading &&

Prüfung läuft…

} + {!canRun &&

Viewer role cannot run checks.

} + {loading &&

Running check…

} {error &&

{error.message}

}
{result && }
} diff --git a/frontend/src/components/UsersView.tsx b/frontend/src/components/UsersView.tsx index e9dfaa3..db1a506 100644 --- a/frontend/src/components/UsersView.tsx +++ b/frontend/src/components/UsersView.tsx @@ -52,6 +52,6 @@ export function UsersView() {
- {loading ?
Loading users…
:
{users.map(u => )}
UserRoleStatusActions
{u.username}
{u.email || '—'}
{u.role}{u.is_active ? 'active' : 'inactive'}
Password resets via API/admin procedure only.
} + {loading ?
Loading users…
: users.length === 0 ?
No users found.
:
{users.map(u => )}
UserRoleStatusActions
{u.username}
{u.email || '—'}
{u.role}{u.is_active ? 'active' : 'inactive'}
Password resets via API/admin procedure only.
} } diff --git a/frontend/src/components/WatchModeView.tsx b/frontend/src/components/WatchModeView.tsx index c1f7e99..dad1584 100644 --- a/frontend/src/components/WatchModeView.tsx +++ b/frontend/src/components/WatchModeView.tsx @@ -154,7 +154,7 @@ export function WatchModeView({ role }: { role: UserRole }) { }

Runs History

- {runs.length === 0 ?
No runs yet for this watch target.
: {runs.map(r=>)}
created_atprevious_statusstatuschangedsummaryreport_id
{r.created_at}{r.previous_status ?? 'n/a'}{r.status}{String(r.changed)}{r.summary}{r.report_id ? {r.report_id} : 'n/a'}
} + {runs.length === 0 ?
No runs yet.
: {runs.map(r=>)}
created_atprevious_statusstatuschangedsummaryreport_id
{r.created_at}{r.previous_status ?? 'n/a'}{r.status}{String(r.changed)}{r.summary}{r.report_id ? {r.report_id} : 'n/a'}
} }