This commit is contained in:
Mateusz Gruszczyński committed 2026-10-04 09:29:10 +02:00
1 parent 2e4aa4df4d
commit 87e571c170
106 files changed
+17952 -6185

No files matched your search

+102
View File
@@ -45,6 +45,7 @@ pub fn router(state: Arc<App>) -> Router {
.route("/", get(public::index))
.route("/overview", get(public::index))
.route("/vhosts", get(public::index))
.route("/web-server", get(public::index))
.route("/docker-vhosts", get(public::index))
.route("/redirects", get(public::index))
.route("/404-hosts", get(public::index))
@@ -52,6 +53,7 @@ pub fn router(state: Arc<App>) -> Router {
.route("/acl-profiles", get(public::index))
.route("/configuration", get(public::index))
.route("/tls", get(public::index))
.route("/traffic", get(public::index))
.route("/cache", get(public::index))
.route("/transform", get(public::index))
.route("/logging", get(public::index))
@@ -77,6 +79,7 @@ pub fn router(state: Arc<App>) -> Router {
.route("/docs", get(documentation::index))
.route("/docs/", get(documentation::index))
.route("/docs/openapi.json", get(documentation::schema))
.route("/docs/content.json", get(documentation::content))
.route("/docs/init.js", get(documentation::script))
.route("/docs/style.css", get(documentation::style))
.route(
@@ -127,6 +130,15 @@ pub fn router(state: Arc<App>) -> Router {
)
}),
)
.route(
"/assets/traffic-panel.js",
get(|| async {
(
[("content-type", "text/javascript; charset=utf-8")],
include_str!("web/traffic-panel.js"),
)
}),
)
.route(
"/assets/cache-panel.js",
get(|| async {
@@ -172,6 +184,15 @@ pub fn router(state: Arc<App>) -> Router {
)
}),
)
.route(
"/assets/web-server.js",
get(|| async {
(
[("content-type", "text/javascript; charset=utf-8")],
include_str!("web/web-server.js"),
)
}),
)
.route(
"/assets/docker.js",
get(|| async {
@@ -272,6 +293,7 @@ pub fn router(state: Arc<App>) -> Router {
"/api/certificate-api/{key_id}",
delete(certificate_api_revoke).layer(DefaultBodyLimit::max(4096)),
)
.route("/api/traffic", get(traffic_status))
.route("/api/cache", get(cache_status))
.route("/api/transform", get(transform_status))
.route("/api/cache/purge", post(cache_purge))
@@ -360,6 +382,7 @@ fn panel_document_path(path: &str) -> bool {
path,
"/" | "/overview"
| "/vhosts"
| "/web-server"
| "/docker-vhosts"
| "/redirects"
| "/404-hosts"
@@ -367,6 +390,7 @@ fn panel_document_path(path: &str) -> bool {
| "/acl-profiles"
| "/configuration"
| "/tls"
| "/traffic"
| "/cache"
| "/transform"
| "/logging"
@@ -1280,6 +1304,11 @@ fn status_payload(state: &App) -> Value {
});
payload["system"] = json!(system);
payload["backend_pools"] = json!(snapshot.pools.iter().map(|p| p.view()).collect::<Vec<_>>());
payload["vhost_metrics"] = json!(snapshot
.vhost_metrics
.iter()
.map(|(id, metrics)| (id.clone(), metrics.view()))
.collect::<std::collections::BTreeMap<_, _>>());
let mut backend_pool_domains = std::collections::BTreeMap::new();
for agent in &snapshot.envelope.config.docker_agents.agents {
for assignment in agent
@@ -1544,6 +1573,10 @@ async fn update_section(
config.vhosts =
serde_json::from_value(value).map_err(|e| invalid_section(&section, e))?
}
"web-services" => {
config.web_services =
serde_json::from_value(value).map_err(|e| invalid_section(&section, e))?
}
"redirects" => {
config.redirects =
serde_json::from_value(value).map_err(|e| invalid_section(&section, e))?
@@ -2174,6 +2207,11 @@ mod tests {
router.clone().oneshot(anonymous).await.unwrap().status(),
StatusCode::UNAUTHORIZED
);
let anonymous = request("/api/traffic").body(Body::empty()).unwrap();
assert_eq!(
router.clone().oneshot(anonymous).await.unwrap().status(),
StatusCode::UNAUTHORIZED
);
let anonymous = request("/api/transform").body(Body::empty()).unwrap();
assert_eq!(
router.clone().oneshot(anonymous).await.unwrap().status(),
@@ -2526,6 +2564,7 @@ mod tests {
for path in [
"/overview",
"/vhosts",
"/web-server",
"/docker-vhosts",
"/redirects",
"/404-hosts",
@@ -2533,6 +2572,7 @@ mod tests {
"/acl-profiles",
"/configuration",
"/tls",
"/traffic",
"/cache",
"/transform",
"/logging",
@@ -2651,6 +2691,7 @@ mod tests {
for (path, expected) in [
("/api/logs?vhost=demo&limit=8", 200),
("/api/logs?cache=BAD", 400),
("/api/traffic", 200),
("/api/cache", 200),
("/api/transform", 200),
] {
@@ -3199,6 +3240,7 @@ mod tests {
"/api/config",
"/api/backends",
"/api/docker",
"/api/traffic",
"/api/cache?limit=invalid",
"/api/logs?limit=invalid",
"/api/live-logs",
@@ -3212,6 +3254,7 @@ mod tests {
"/api/metrics",
"/metrics",
"/docs/openapi.json",
"/docs/content.json",
"/docs/init.js",
"/docs/style.css",
"/docs/swagger-ui-bundle.js",
@@ -3221,6 +3264,7 @@ mod tests {
"/assets/app.js",
"/assets/management.js",
"/assets/integrations.js",
"/assets/traffic-panel.js",
"/assets/cache-panel.js",
"/assets/transform-panel.js",
"/assets/vhosts.js",
@@ -3281,6 +3325,7 @@ mod tests {
("/api/auth/setup", "GET"),
("/healthz", "POST"),
("/docs/openapi.json", "HEAD"),
("/docs/content.json", "HEAD"),
] {
let response = router
.clone()
@@ -3381,6 +3426,7 @@ mod tests {
"/docs/swagger-ui-bundle.js",
"/docs/swagger-ui.css",
"/docs/licenses.txt",
"/docs/content.json",
"/assets/app.js",
] {
let response = router
@@ -3415,6 +3461,23 @@ mod tests {
"cookie"
);
assert!(spec["paths"].get("/api/config").is_some());
let response = router
.clone()
.oneshot(
request("/docs/content.json")
.header("cookie", &cookie)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let docs = json_body(response).await;
let documents = docs["documents"].as_array().expect("documentation bundle");
assert!(documents.iter().any(|document| document["path"] == "docs/README.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/WEB_SERVER.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/PHP_FPM.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/V0.25.0_AUDIT.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/V0.25.0_SECURITY_PREPROD_AUDIT.md"));
store.close().await;
}
#[tokio::test]
@@ -3515,6 +3578,45 @@ mod tests {
}
}
#[derive(Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct TrafficQuery {
vhost: Option<String>,
}
async fn traffic_status(
State(state): State<Arc<App>>,
Query(query): Query<TrafficQuery>,
) -> Result<Json<Value>, ApiError> {
let snapshot = state.config.load_full();
if query.vhost.as_ref().is_some_and(|id| {
id != "*" && !snapshot.envelope.config.vhosts.iter().any(|vhost| &vhost.id == id)
}) {
return Err(ApiError(
StatusCode::NOT_FOUND,
"unknown vhost traffic scope".into(),
));
}
let scope = query.vhost.as_deref().unwrap_or("*");
let vhosts = snapshot
.envelope
.config
.vhosts
.iter()
.map(|vhost| json!({"id": vhost.id, "hosts": vhost.frontend.hosts}))
.collect::<Vec<_>>();
Ok(Json(json!({
"scope": scope,
"since": "process_start",
"settings": {
"capture_user_agent": snapshot.envelope.config.logging.capture_user_agent,
"capture_country": snapshot.envelope.config.logging.capture_country
},
"geoip": state.geoip.view(),
"analytics": state.traffic.view(Some(scope)),
"vhosts": vhosts
})))
}
#[derive(Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct CacheQuery {