Phase 13: web dashboard at /_bridge/dashboard
Single-page club management UI served by the bridge. The Core URL is injected server-side so client JS calls Core directly without routing through the proxy. Vanilla HTML/CSS/JS, dark theme matching admin.html. Sections: Club (name/manager/coins/stats edit), Collection (filterable card grid with stat bars + chemistry/training pills), Packs (open with modal, history), Objectives (progress bars + one-click claim), Division (W/D/L record, season progress, rivals weekly claim), FUT Champions (current session with in-browser match simulation, claim, history), Notifications (badge count in nav). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+53
-1
@@ -9,7 +9,7 @@ use openfut_bridge::{
|
||||
proxy::ProxyState,
|
||||
routes,
|
||||
};
|
||||
use axum::routing::{any, delete, get};
|
||||
use axum::routing::{any, delete, get, post};
|
||||
use tower::ServiceExt;
|
||||
|
||||
// ── Unit tests ────────────────────────────────────────────────────────────────
|
||||
@@ -93,10 +93,12 @@ fn build_test_app() -> axum::Router {
|
||||
|
||||
axum::Router::new()
|
||||
.route("/_bridge/health", get(routes::health::get_health))
|
||||
.route("/_bridge/dashboard", get(routes::health::get_dashboard))
|
||||
.route("/_bridge/captures", get(routes::admin::get_captures))
|
||||
.route("/_bridge/captures", delete(routes::admin::delete_captures))
|
||||
.route("/_bridge/unknown", get(routes::admin::get_unknown_endpoints))
|
||||
.route("/_bridge/status", get(routes::admin::get_endpoint_status))
|
||||
.route("/_bridge/captures/:id/replay", post(routes::admin::post_replay_capture))
|
||||
.fallback(any(openfut_bridge::proxy::catch_all_handler))
|
||||
.with_state(state)
|
||||
}
|
||||
@@ -191,6 +193,56 @@ async fn test_bridge_status_endpoint() {
|
||||
assert!(json["endpoints"].is_array());
|
||||
}
|
||||
|
||||
// ── Phase 13 — Dashboard ──────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dashboard_returns_html() {
|
||||
let app = build_test_app();
|
||||
let resp = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/_bridge/dashboard")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let ct = resp.headers().get("content-type").unwrap().to_str().unwrap();
|
||||
assert!(ct.contains("text/html"), "expected text/html, got {ct}");
|
||||
let body = axum::body::to_bytes(resp.into_body(), usize::MAX).await.unwrap();
|
||||
let html = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert!(html.contains("OpenFUT Dashboard"), "title missing");
|
||||
assert!(html.contains("const CORE ="), "core URL injection missing");
|
||||
// Placeholder URL injected in test mode
|
||||
assert!(html.contains("127.0.0.1:9999"), "core URL not injected");
|
||||
assert!(!html.contains("{{CORE_URL}}"), "template placeholder was not replaced");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dashboard_contains_key_sections() {
|
||||
let app = build_test_app();
|
||||
let resp = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/_bridge/dashboard")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let body = axum::body::to_bytes(resp.into_body(), usize::MAX).await.unwrap();
|
||||
let html = String::from_utf8(body.to_vec()).unwrap();
|
||||
// Verify all major tab sections are present
|
||||
assert!(html.contains("tab-club"), "club tab missing");
|
||||
assert!(html.contains("tab-collection"), "collection tab missing");
|
||||
assert!(html.contains("tab-packs"), "packs tab missing");
|
||||
assert!(html.contains("tab-objectives"), "objectives tab missing");
|
||||
assert!(html.contains("tab-division"), "division tab missing");
|
||||
assert!(html.contains("tab-champs"), "fut champs tab missing");
|
||||
assert!(html.contains("tab-notifications"), "notifications tab missing");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tls_cert_generation() {
|
||||
let result = openfut_bridge::tls::generate_self_signed_cert();
|
||||
|
||||
Reference in New Issue
Block a user