use std::error::Error as StdError; use std::path::PathBuf; use myfsio_server::templates::TemplateEngine; use serde_json::{json, Value}; use tera::Context; fn engine() -> TemplateEngine { let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); path.push("templates"); path.push("*.html"); let glob = path.to_string_lossy().replace('\\', "/"); let engine = TemplateEngine::new(&glob).expect("template parse"); myfsio_server::handlers::ui_pages::register_ui_endpoints(&engine); engine } fn base_ctx() -> Context { let mut ctx = Context::new(); ctx.insert("csrf_token_value", &"test-csrf"); ctx.insert("is_authenticated", &true); ctx.insert("current_user", &"test"); ctx.insert("current_user_display_name", &"Test User"); ctx.insert("current_endpoint", &""); ctx.insert("request_args", &serde_json::json!({})); ctx.insert( "principal", &json!({ "access_key": "AKIATESTTEST", "user_id": "u-test", "display_name": "Test Admin", "is_admin": true }), ); ctx.insert("can_manage_iam", &true); ctx.insert("can_manage_replication", &true); ctx.insert("can_manage_sites", &true); ctx.insert("can_manage_encryption", &false); ctx.insert("website_hosting_nav", &false); ctx.insert("encryption_enabled", &false); ctx.insert("kms_enabled", &false); ctx.insert("flashed_messages", &Vec::::new()); ctx.insert("null", &Value::Null); ctx.insert("none", &Value::Null); ctx } fn format_err(e: tera::Error) -> String { let mut out = format!("{}", e); let mut src = StdError::source(&e); while let Some(s) = src { out.push_str("\n caused by: "); out.push_str(&s.to_string()); src = s.source(); } out } fn render_or_panic(tmpl: &str, ctx: &Context) { let e = engine(); match e.render(tmpl, ctx) { Ok(_) => {} Err(err) => panic!("{} failed:\n{}", tmpl, format_err(err)), } } fn render_to_string_or_panic(tmpl: &str, ctx: &Context) -> String { let e = engine(); match e.render(tmpl, ctx) { Ok(rendered) => rendered, Err(err) => panic!("{} failed:\n{}", tmpl, format_err(err)), } } #[test] fn render_buckets() { let mut ctx = base_ctx(); ctx.insert( "buckets", &json!([{ "meta": {"name": "b1", "creation_date": "2024-01-01T00:00:00Z"}, "summary": {"human_size": "0 B", "objects": 0}, "detail_url": "/ui/buckets/b1", "access_badge": "bg-secondary", "access_label": "Private" }]), ); render_or_panic("buckets.html", &ctx); } #[test] fn render_connections() { let mut ctx = base_ctx(); ctx.insert( "connections", &json!([{ "id": "c1", "name": "Prod", "endpoint_url": "https://s3.example.com", "region": "us-east-1", "access_key": "AKIAEXAMPLEKEY12345" }]), ); render_or_panic("connections.html", &ctx); } #[test] fn render_iam() { let mut ctx = base_ctx(); ctx.insert( "users", &json!([{ "user_id": "u-1", "access_key": "AKIA1", "display_name": "Alice", "enabled": true, "is_enabled": true, "expires_at": "2026-04-21T00:00:00Z", "is_admin": true, "is_expired": false, "is_expiring_soon": true, "access_keys": [{"access_key": "AKIA1", "status": "active", "created_at": "2024-01-01"}], "policy_count": 1, "policies": [{"bucket": "*", "actions": ["*"], "prefix": "*"}] }]), ); ctx.insert("iam_locked", &false); ctx.insert("locked_reason", &""); ctx.insert("iam_disabled", &false); ctx.insert("all_buckets", &Vec::::new()); ctx.insert("disclosed_secret", &Value::Null); ctx.insert("config_document", &""); ctx.insert("config_summary", &json!({"user_count": 1})); let rendered = render_to_string_or_panic("iam.html", &ctx); assert!(rendered.contains("data-user-id=\"u-1\"")); assert!(rendered.contains("data-access-key=\"AKIA1\"")); assert!(rendered.contains("Expiring soon")); } #[test] fn render_metrics() { let mut ctx = base_ctx(); ctx.insert("metrics_enabled", &false); ctx.insert("metrics_history_enabled", &false); ctx.insert("operation_metrics_enabled", &false); ctx.insert("history", &Vec::::new()); ctx.insert("operation_metrics", &Vec::::new()); ctx.insert("cpu_percent", &0); ctx.insert( "memory", &json!({ "percent": 0, "total": "0 B", "used": "0 B" }), ); ctx.insert( "disk", &json!({ "percent": 0, "free": "0 B", "total": "0 B" }), ); ctx.insert( "app", &json!({ "buckets": 0, "objects": 0, "storage_used": "0 B", "uptime_days": 0, "versions": 0 }), ); ctx.insert("has_issues", &false); ctx.insert( "summary", &json!({ "app": {"buckets": 0, "objects": 0, "storage_used": "0 B", "uptime_days": 0, "versions": 0}, "cpu_percent": 0, "disk": {"free": 0, "percent": 0, "total": 0}, "memory": {"percent": 0, "total": 0, "used": 0}, "has_issues": false }), ); render_or_panic("metrics.html", &ctx); } #[test] fn render_system() { let mut ctx = base_ctx(); ctx.insert("app_version", &"0.1.0"); ctx.insert("display_timezone", &"UTC"); ctx.insert("platform", &"linux"); ctx.insert("python_version", &"n/a"); ctx.insert("storage_root", &"/tmp/data"); ctx.insert("has_rust", &true); ctx.insert("total_issues", &0); ctx.insert("features", &Vec::::new()); ctx.insert("gc_history", &Vec::::new()); ctx.insert("integrity_history", &Vec::::new()); ctx.insert( "gc_status", &json!({ "dry_run": false, "enabled": false, "interval_hours": 6, "lock_file_max_age_hours": 1, "multipart_max_age_days": 7, "scanning": false, "temp_file_max_age_hours": 24 }), ); ctx.insert( "integrity_status", &json!({ "auto_heal": false, "batch_size": 100, "dry_run": false, "enabled": false, "interval_hours": 24, "scanning": false }), ); render_or_panic("system.html", &ctx); } #[test] fn render_sites() { let mut ctx = base_ctx(); ctx.insert("local_site", &Value::Null); ctx.insert("peers", &Vec::::new()); ctx.insert("peers_with_stats", &Vec::::new()); ctx.insert("connections", &Vec::::new()); ctx.insert("config_site_id", &""); ctx.insert("config_site_endpoint", &""); ctx.insert("config_site_region", &"us-east-1"); render_or_panic("sites.html", &ctx); } #[test] fn render_website_domains() { let mut ctx = base_ctx(); ctx.insert("mappings", &Vec::::new()); ctx.insert("buckets", &Vec::::new()); render_or_panic("website_domains.html", &ctx); } #[test] fn render_replication_wizard() { let mut ctx = base_ctx(); ctx.insert("connections", &Vec::::new()); ctx.insert("local_site", &Value::Null); ctx.insert("peers", &Vec::::new()); ctx.insert("buckets", &Vec::::new()); ctx.insert( "peer", &json!({ "site_id": "peer-1", "display_name": "Peer One", "endpoint": "https://peer.example.com", "region": "us-east-1" }), ); ctx.insert( "connection", &json!({ "id": "c1", "name": "Prod", "endpoint_url": "https://s3.example.com", "region": "us-east-1", "access_key": "AKIA" }), ); render_or_panic("replication_wizard.html", &ctx); } #[test] fn render_docs() { let mut ctx = base_ctx(); ctx.insert("api_base", &"http://127.0.0.1:9000"); ctx.insert("api_host", &"127.0.0.1:9000"); render_or_panic("docs.html", &ctx); } #[test] fn render_bucket_detail() { let mut ctx = base_ctx(); ctx.insert("bucket_name", &"my-bucket"); ctx.insert( "bucket", &json!({ "name": "my-bucket", "creation_date": "2024-01-01T00:00:00Z" }), ); ctx.insert("objects", &Vec::::new()); ctx.insert("prefixes", &Vec::::new()); ctx.insert("total_objects", &0); ctx.insert("total_bytes", &0); ctx.insert("current_objects", &0); ctx.insert("current_bytes", &0); ctx.insert("version_count", &0); ctx.insert("version_bytes", &0); ctx.insert("max_objects", &Value::Null); ctx.insert("max_bytes", &Value::Null); ctx.insert("obj_pct", &0); ctx.insert("bytes_pct", &0); ctx.insert("has_quota", &false); ctx.insert("versioning_enabled", &false); ctx.insert("versioning_status", &"Disabled"); ctx.insert("encryption_config", &json!({"Rules": []})); ctx.insert("enc_rules", &Vec::::new()); ctx.insert("enc_algorithm", &""); ctx.insert("enc_kms_key", &""); ctx.insert("replication_rules", &Vec::::new()); ctx.insert("replication_rule", &Value::Null); ctx.insert("website_config", &Value::Null); ctx.insert("bucket_policy", &""); ctx.insert("bucket_policy_text", &""); ctx.insert("connections", &Vec::::new()); ctx.insert("current_prefix", &""); ctx.insert("parent_prefix", &""); ctx.insert("has_more", &false); ctx.insert("next_token", &""); ctx.insert("active_tab", &"objects"); ctx.insert("multipart_uploads", &Vec::::new()); ctx.insert("target_conn", &Value::Null); ctx.insert("target_conn_name", &""); ctx.insert("preset_choice", &""); ctx.insert("default_policy", &""); ctx.insert("can_manage_cors", &true); ctx.insert("can_manage_lifecycle", &true); ctx.insert("can_manage_quota", &true); ctx.insert("can_manage_versioning", &true); ctx.insert("can_manage_website", &true); ctx.insert("can_edit_policy", &true); ctx.insert("is_replication_admin", &true); ctx.insert("lifecycle_enabled", &false); ctx.insert("site_sync_enabled", &false); ctx.insert("website_hosting_enabled", &false); ctx.insert("website_domains", &Vec::::new()); ctx.insert("kms_keys", &Vec::::new()); ctx.insert( "bucket_stats", &json!({ "bytes": 0, "objects": 0, "total_bytes": 0, "total_objects": 0, "version_bytes": 0, "version_count": 0 }), ); ctx.insert( "bucket_quota", &json!({ "max_bytes": null, "max_objects": null }), ); ctx.insert("buckets_for_copy_url", &""); ctx.insert("acl_url", &""); ctx.insert("cors_url", &""); ctx.insert("folders_url", &""); ctx.insert("lifecycle_url", &""); ctx.insert("objects_api_url", &""); ctx.insert("objects_stream_url", &""); render_or_panic("bucket_detail.html", &ctx); }