From 1734b3f3713d87c0aab9585d4525ed21b0f2f56a Mon Sep 17 00:00:00 2001 From: DeepZone Date: Fri, 22 May 2026 14:01:08 +0100 Subject: [PATCH] v1.0.1: UI polish and release version updates --- README.md | 4 ++-- RELEASE_NOTES.md | 8 ++++++++ ROADMAP.md | 2 +- backend/app/api/routes_system.py | 2 +- backend/app/core/system_status.py | 2 +- backend/app/main.py | 2 +- backend/pyproject.toml | 2 +- backend/tests/test_api_smoke.py | 2 +- frontend/package.json | 2 +- frontend/src/App.tsx | 8 ++++---- frontend/src/components/AuditLogView.tsx | 10 ++++++---- frontend/src/components/Layout.tsx | 15 ++++++--------- frontend/src/components/UsersView.tsx | 5 +++-- 13 files changed, 36 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index e8540fc..d67bda0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ RouteForge Logo

- Version + Version License Status Selfhosted @@ -61,7 +61,7 @@ Using read-only checks before execution helps teams validate assumptions without ## Current Release -RouteForge is a **stable** self-hosted release for production-grade read-only validation workflows. Current release: **v1.0.0**. +RouteForge is a **stable** self-hosted release for production-grade read-only validation workflows. Current release: **v1.0.1**. ## Quickstart with Docker Compose diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 16bdc0a..0df4769 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,11 @@ +## v1.0.1 (2026-05-22) + +- Polished the global layout and removed redundant runtime badges from the live header. +- Improved sidebar branding with a cleaner logo presentation. +- Improved User Management readability with formatted role labels and status badges. +- Improved Audit Log readability with cleaner filters, compact details and a more structured table. +- Kept the release read-only and without database migrations. + ## v1.0.0 (2026-05-22) ### Summary diff --git a/ROADMAP.md b/ROADMAP.md index c3fc156..e6bf9f3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,7 +1,7 @@ # RouteForge Roadmap ## Current Status -RouteForge is now **v1.0.0 stable**: a selfhosted, read-only routing preflight release for production-oriented validation workflows. +RouteForge is now **v1.0.1 stable**: a selfhosted, read-only routing preflight release for production-oriented validation workflows. ## v1.0.0 Completed Scope The stable release includes: diff --git a/backend/app/api/routes_system.py b/backend/app/api/routes_system.py index 4bb2e66..6b2928c 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': 'v1.0.0', + 'version': 'v1.0.1', 'demo_mode': settings.demo_mode, 'read_only': True, 'data_sources': ['RIPEstat', 'RIPEstat Whois/Registry'], diff --git a/backend/app/core/system_status.py b/backend/app/core/system_status.py index 0e02aa4..cf1f5f8 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": "v1.0.0", + "version": "v1.0.1", "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 1578091..8cf567a 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="1.0.0") +app = FastAPI(title="RouteForge", version="1.0.1") app.add_middleware( CORSMiddleware, diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 2e72624..4a1053b 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "routeforge-backend" -version = "1.0.0" +version = "1.0.1" 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 f5bb036..ed99135 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') == 'v1.0.0' + assert payload.get('version') == 'v1.0.1' 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 1c74cb2..c142456 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "routeforge-frontend", - "version": "1.0.0", + "version": "1.0.1", "private": true, "license": "AGPL-3.0-or-later", "type": "module", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e49900a..9ced8d1 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}` : 'RouteForge · v1.0.0' + const systemLine = system ? `${system.name} · ${system.version}` : 'RouteForge · v1.0.1' 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' @@ -88,8 +88,8 @@ export default function App() { } const readOnlyLabel = system?.read_only ? 'Enabled' : 'Disabled' - return - {active === 'dashboard' &&

{system?.name || 'RouteForge'} {system?.version || 'v1.0.0'}

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.
}
} + return + {active === 'dashboard' &&

{system?.name || 'RouteForge'} {system?.version || 'v1.0.1'}

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: {system?.version || 'v1.0.0'}

Build and maintained by Norman Sens.

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

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

Read-only: All checks are non-destructive.

Version: {system?.version || 'v1.0.1'}

Build and maintained by Norman Sens.

}
} diff --git a/frontend/src/components/AuditLogView.tsx b/frontend/src/components/AuditLogView.tsx index f74d79d..32f56c2 100644 --- a/frontend/src/components/AuditLogView.tsx +++ b/frontend/src/components/AuditLogView.tsx @@ -8,6 +8,7 @@ export function AuditLogView() { const [error, setError] = useState('') const [action, setAction] = useState('') const [targetType, setTargetType] = useState('') + const hasFilters = action.trim().length > 0 || targetType.trim().length > 0 useEffect(() => { const run = async () => { @@ -24,13 +25,14 @@ export function AuditLogView() { return

Audit Log

-
- setAction(e.target.value)} /> - setTargetType(e.target.value)} /> +
+ setAction(e.target.value)} /> + setTargetType(e.target.value)} /> +
{loading &&
Loading audit log…
} {error &&
{error}
} {!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)}
} + {!loading && !error && items.length > 0 &&
{items.map((a) => { const target = a.target_type && a.target_id ? `${a.target_type} · ${a.target_id}` : a.target_type || a.target_id || '-'; const detailsPayload = { ...(a.details_json || {}), ...(a.user_agent ? { user_agent: a.user_agent } : {}) }; return })}
TimeUserActionTargetIPDetails
{a.created_at}{a.username || a.user_id || '-'}{a.action}{target}{a.ip_address || '-'}
View
{JSON.stringify(detailsPayload, null, 2)}
}
} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index fb83c0b..1aedbcf 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -19,7 +19,7 @@ const nav: { key: NavKey; label: string; desc: string }[] = [ { key: 'about', label: 'About', desc: 'Data sources and limits' }, ] -export function Layout({ children, active, onNav, systemLine, title, demoMode, currentUser, onLogout, appVersion }: { children: ReactNode; active: NavKey; onNav: (key: NavKey) => void; systemLine: string; title: string; demoMode: boolean; currentUser?: { username: string; role: UserRole } | null; onLogout: () => void; appVersion?: string }) { +export function Layout({ children, active, onNav, systemLine, title, demoMode, currentUser, onLogout }: { children: ReactNode; active: NavKey; onNav: (key: NavKey) => void; systemLine: string; title: string; demoMode: boolean; currentUser?: { username: string; role: UserRole } | null; onLogout: () => void }) { const visibleNav = nav.filter((item) => { if (!currentUser) return ['dashboard', 'about'].includes(item.key) if (currentUser.role === 'admin') return true @@ -29,12 +29,11 @@ export function Layout({ children, active, onNav, systemLine, title, demoMode, c return