This commit is contained in:
Mateusz Gruszczyński committed 2026-10-05 09:30:21 +02:00
1 parent 1dea7f55c8
commit c819f0a335
151 files changed
+7610 -4264

No files matched your search

+3 -3
View File
@@ -1,8 +1,8 @@
fn valid_certificate_api_token(value: &str) -> bool {
(32..=128).contains(&value.len())
&& value.bytes().all(|byte| {
byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'~')
})
&& value
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'~'))
}
fn valid_certificate_api_key_id(value: &str) -> bool {
+31 -20
View File
@@ -169,8 +169,7 @@ async fn certificate_deploy(
}
let administration_restart_required = config.administration.tls
&& config.administration.certificate_id.as_deref()
== Some(certificate_id.as_str());
&& config.administration.certificate_id.as_deref() == Some(certificate_id.as_str());
let result = state
.update_as(
Update {
@@ -196,8 +195,12 @@ async fn certificate_deploy(
})
.await
{
Err(join_error) => tracing::warn!(%join_error, "external certificate rollback task failed"),
Ok(Err(rollback_error)) => tracing::warn!(%rollback_error, "external certificate rollback failed"),
Err(join_error) => {
tracing::warn!(%join_error, "external certificate rollback task failed")
}
Ok(Err(rollback_error)) => {
tracing::warn!(%rollback_error, "external certificate rollback failed")
}
Ok(Ok(())) => {}
}
}
@@ -227,8 +230,12 @@ async fn certificate_deploy(
})
.await
{
Err(join_error) => tracing::warn!(%join_error, "previous external certificate cleanup task failed"),
Ok(Err(cleanup_error)) => tracing::warn!(%cleanup_error, "previous external certificate cleanup failed"),
Err(join_error) => {
tracing::warn!(%join_error, "previous external certificate cleanup task failed")
}
Ok(Err(cleanup_error)) => {
tracing::warn!(%cleanup_error, "previous external certificate cleanup failed")
}
Ok(Ok(())) => {}
}
}
@@ -271,12 +278,13 @@ async fn certificate_reload(
) -> Result<Json<Value>, ApiError> {
let revision = state.reload_certificates().await?;
let snapshot = state.config.load_full();
let admin_certificate_restart_required = snapshot
.envelope
.config
.administration
.tls
&& snapshot.envelope.config.administration.certificate_id.is_some();
let admin_certificate_restart_required = snapshot.envelope.config.administration.tls
&& snapshot
.envelope
.config
.administration
.certificate_id
.is_some();
state
.store
.audit(
@@ -295,23 +303,26 @@ async fn certificate_reload(
})))
}
async fn certificate_reload_one(
State(state): State<Arc<App>>,
Extension(session): Extension<Session>,
Path(certificate_id): Path<String>,
) -> Result<Json<Value>, ApiError> {
if !crate::config::valid_id(&certificate_id) {
return Err(ApiError(StatusCode::NOT_FOUND, "certificate not found".into()));
return Err(ApiError(
StatusCode::NOT_FOUND,
"certificate not found".into(),
));
}
let revision = state.reload_certificate(&certificate_id).await?;
let snapshot = state.config.load_full();
let admin_certificate_restart_required = snapshot
.envelope
.config
.administration
.tls
&& snapshot.envelope.config.administration.certificate_id.as_deref()
let admin_certificate_restart_required = snapshot.envelope.config.administration.tls
&& snapshot
.envelope
.config
.administration
.certificate_id
.as_deref()
== Some(certificate_id.as_str());
state
.store
+448 -67
View File
@@ -12,73 +12,454 @@ use std::sync::OnceLock;
// document a direct compiler input, so a clean or partially removed target directory
// cannot leave the crate pointing at a missing OUT_DIR artifact.
static READER_DOCUMENTS: &[(&str, &str, &str)] = &[
("README.md", "project", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))),
("ROADMAP.md", "project", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/ROADMAP.md"))),
("CHANGELOG.md", "project", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/CHANGELOG.md"))),
("docs/ACME_PROVIDERS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ACME_PROVIDERS.md"))),
("docs/ACME_RFC2136.md", "historical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ACME_RFC2136.md"))),
("docs/API.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API.md"))),
("docs/API_ACCESS.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API_ACCESS.md"))),
("docs/ARCHITECTURE.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ARCHITECTURE.md"))),
("docs/CACHE.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CACHE.md"))),
("docs/CACHE_DIAGNOSTICS.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CACHE_DIAGNOSTICS.md"))),
("docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "historical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CHANGELOG_TECHNICAL_ARCHIVE.md"))),
("docs/CONTAINER.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CONTAINER.md"))),
("docs/CUSTOM_LOCATIONS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CUSTOM_LOCATIONS.md"))),
("docs/DOCKER_DISCOVERY.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/DOCKER_DISCOVERY.md"))),
("docs/ERROR_LOGGING.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ERROR_LOGGING.md"))),
("docs/HTTP3.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/HTTP3.md"))),
("docs/INSTALL.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/INSTALL.md"))),
("docs/INTEGRATIONS_040.md", "historical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/INTEGRATIONS_040.md"))),
("docs/LIVE_LOGS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/LIVE_LOGS.md"))),
("docs/MANAGEMENT.md", "historical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MANAGEMENT.md"))),
("docs/MIGRATION.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MIGRATION.md"))),
("docs/MOCK_AUTH.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_AUTH.md"))),
("docs/MOCK_SERVER.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_SERVER.md"))),
("docs/NOT_FOUND_HOSTS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/NOT_FOUND_HOSTS.md"))),
("docs/NPM_MIGRATION.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/NPM_MIGRATION.md"))),
("docs/PHP_FPM.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PHP_FPM.md"))),
("docs/PROMETHEUS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PROMETHEUS.md"))),
("docs/README.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/README.md"))),
("docs/REDIRECTS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REDIRECTS.md"))),
("docs/REFERENCES.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REFERENCES.md"))),
("docs/RENEWAL.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/RENEWAL.md"))),
("docs/ROADMAP_HISTORY.md", "historical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ROADMAP_HISTORY.md"))),
("docs/SECRET_STORE_BACKUP.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SECRET_STORE_BACKUP.md"))),
("docs/SECURITY.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SECURITY.md"))),
("docs/SECURITY_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SECURITY_AUDIT.md"))),
("docs/SETTINGS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SETTINGS.md"))),
("docs/STREAMS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/STREAMS.md"))),
("docs/TEST_REPORT.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TEST_REPORT.md"))),
("docs/TODO.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TODO.md"))),
("docs/TRANSFORM.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSFORM.md"))),
("docs/TRANSPORT.md", "technical", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSPORT.md"))),
("docs/V0.21.0_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.21.0_AUDIT.md"))),
("docs/V0.22.0_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.22.0_AUDIT.md"))),
("docs/V0.22.1_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.22.1_AUDIT.md"))),
("docs/V0.22.2_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.22.2_AUDIT.md"))),
("docs/V0.22.3_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.22.3_AUDIT.md"))),
("docs/V0.23.0_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.23.0_AUDIT.md"))),
("docs/V0.23.1_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.23.1_AUDIT.md"))),
("docs/V0.23.2_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.23.2_AUDIT.md"))),
("docs/V0.23.3_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.23.3_AUDIT.md"))),
("docs/V0.24.0_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.0_AUDIT.md"))),
("docs/V0.24.1_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.1_AUDIT.md"))),
("docs/V0.24.3_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.3_AUDIT.md"))),
("docs/V0.24.4_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.4_AUDIT.md"))),
("docs/V0.24.5_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.5_AUDIT.md"))),
("docs/V0.24.6_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.6_AUDIT.md"))),
("docs/V0.24.7_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.7_AUDIT.md"))),
("docs/V0.24.8_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.8_AUDIT.md"))),
("docs/V0.25.0_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.0_AUDIT.md"))),
("docs/V0.25.1_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.1_AUDIT.md"))),
("docs/V0.25.4_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.4_AUDIT.md"))),
("docs/V0.25.3_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.3_AUDIT.md"))),
("docs/V0.25.2_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.2_AUDIT.md"))),
("docs/V0.25.0_SECURITY_PREPROD_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md"))),
("docs/VHOSTS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOSTS.md"))),
("docs/VHOST_ACCESS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOST_ACCESS.md"))),
("docs/WEB_SERVER.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/WEB_SERVER.md"))),
(
"README.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")),
),
(
"ROADMAP.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/ROADMAP.md")),
),
(
"CHANGELOG.md",
"project",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/CHANGELOG.md")),
),
(
"docs/ACME_PROVIDERS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ACME_PROVIDERS.md"
)),
),
(
"docs/ACME_RFC2136.md",
"historical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ACME_RFC2136.md")),
),
(
"docs/API.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API.md")),
),
(
"docs/API_ACCESS.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API_ACCESS.md")),
),
(
"docs/ARCHITECTURE.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ARCHITECTURE.md")),
),
(
"docs/CACHE.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CACHE.md")),
),
(
"docs/CACHE_DIAGNOSTICS.md",
"technical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CACHE_DIAGNOSTICS.md"
)),
),
(
"docs/CHANGELOG_TECHNICAL_ARCHIVE.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CHANGELOG_TECHNICAL_ARCHIVE.md"
)),
),
(
"docs/CONTAINER.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CONTAINER.md")),
),
(
"docs/CUSTOM_LOCATIONS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/CUSTOM_LOCATIONS.md"
)),
),
(
"docs/DOCKER_DISCOVERY.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/DOCKER_DISCOVERY.md"
)),
),
(
"docs/ERROR_LOGGING.md",
"technical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ERROR_LOGGING.md"
)),
),
(
"docs/HTTP3.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/HTTP3.md")),
),
(
"docs/INSTALL.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/INSTALL.md")),
),
(
"docs/INTEGRATIONS_040.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/INTEGRATIONS_040.md"
)),
),
(
"docs/LIVE_LOGS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/LIVE_LOGS.md")),
),
(
"docs/MANAGEMENT.md",
"historical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MANAGEMENT.md")),
),
(
"docs/MIGRATION.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MIGRATION.md")),
),
(
"docs/MOCK_AUTH.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_AUTH.md")),
),
(
"docs/MOCK_SERVER.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_SERVER.md")),
),
(
"docs/NOT_FOUND_HOSTS.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/NOT_FOUND_HOSTS.md"
)),
),
(
"docs/NPM_MIGRATION.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/NPM_MIGRATION.md"
)),
),
(
"docs/PHP_FPM.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PHP_FPM.md")),
),
(
"docs/PROMETHEUS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PROMETHEUS.md")),
),
(
"docs/README.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/README.md")),
),
(
"docs/REDIRECTS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REDIRECTS.md")),
),
(
"docs/REFERENCES.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REFERENCES.md")),
),
(
"docs/RENEWAL.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/RENEWAL.md")),
),
(
"docs/ROADMAP_HISTORY.md",
"historical",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/ROADMAP_HISTORY.md"
)),
),
(
"docs/SECRET_STORE_BACKUP.md",
"guides",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/SECRET_STORE_BACKUP.md"
)),
),
(
"docs/SECURITY.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SECURITY.md")),
),
(
"docs/SECURITY_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/SECURITY_AUDIT.md"
)),
),
(
"docs/SETTINGS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SETTINGS.md")),
),
(
"docs/STREAMS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/STREAMS.md")),
),
(
"docs/TEST_REPORT.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TEST_REPORT.md")),
),
(
"docs/TODO.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TODO.md")),
),
(
"docs/TRANSFORM.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSFORM.md")),
),
(
"docs/TRANSPORT.md",
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSPORT.md")),
),
(
"docs/V0.21.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.21.0_AUDIT.md"
)),
),
(
"docs/V0.22.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.0_AUDIT.md"
)),
),
(
"docs/V0.22.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.1_AUDIT.md"
)),
),
(
"docs/V0.22.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.2_AUDIT.md"
)),
),
(
"docs/V0.22.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.3_AUDIT.md"
)),
),
(
"docs/V0.23.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.0_AUDIT.md"
)),
),
(
"docs/V0.23.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.1_AUDIT.md"
)),
),
(
"docs/V0.23.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.2_AUDIT.md"
)),
),
(
"docs/V0.23.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.3_AUDIT.md"
)),
),
(
"docs/V0.24.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.0_AUDIT.md"
)),
),
(
"docs/V0.24.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.1_AUDIT.md"
)),
),
(
"docs/V0.24.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.3_AUDIT.md"
)),
),
(
"docs/V0.24.4_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.4_AUDIT.md"
)),
),
(
"docs/V0.24.5_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.5_AUDIT.md"
)),
),
(
"docs/V0.24.6_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.6_AUDIT.md"
)),
),
(
"docs/V0.24.7_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.7_AUDIT.md"
)),
),
(
"docs/V0.24.8_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.8_AUDIT.md"
)),
),
(
"docs/V0.25.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.0_AUDIT.md"
)),
),
(
"docs/V0.25.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.1_AUDIT.md"
)),
),
(
"docs/V0.25.5_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.5_AUDIT.md"
)),
),
(
"docs/V0.25.4_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.4_AUDIT.md"
)),
),
(
"docs/V0.25.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.3_AUDIT.md"
)),
),
(
"docs/V0.25.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.2_AUDIT.md"
)),
),
(
"docs/V0.25.0_SECURITY_PREPROD_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md"
)),
),
(
"docs/VHOSTS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOSTS.md")),
),
(
"docs/VHOST_ACCESS.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOST_ACCESS.md")),
),
(
"docs/WEB_SERVER.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/WEB_SERVER.md")),
),
];
pub(super) async fn index() -> Html<&'static str> {
+19 -10
View File
@@ -188,14 +188,12 @@ async fn logs(
})
.take(limit)
.collect();
Ok(Json(
json!({
"entries": entries,
"capacity": crate::logging::HISTORY_CAPACITY,
"window": crate::logging::LIVE_WINDOW,
"dropped": state.logs.dropped.load(Ordering::Relaxed)
}),
))
Ok(Json(json!({
"entries": entries,
"capacity": crate::logging::HISTORY_CAPACITY,
"window": crate::logging::LIVE_WINDOW,
"dropped": state.logs.dropped.load(Ordering::Relaxed)
})))
}
async fn live_logs_stream(
@@ -208,7 +206,12 @@ async fn live_logs_stream(
.viewer_slots
.clone()
.try_acquire_owned()
.map_err(|_| ApiError(StatusCode::TOO_MANY_REQUESTS, "events.too_many_viewers".into()))?;
.map_err(|_| {
ApiError(
StatusCode::TOO_MANY_REQUESTS,
"events.too_many_viewers".into(),
)
})?;
let receiver = state.logs.subscribe();
let next_auth_check = tokio::time::Instant::now() + Duration::from_secs(10);
let stream = futures_util::stream::unfold(
@@ -344,7 +347,13 @@ async fn traffic_status(
) -> 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)
id != "*"
&& !snapshot
.envelope
.config
.vhosts
.iter()
.any(|vhost| &vhost.id == id)
}) {
return Err(ApiError(
StatusCode::NOT_FOUND,
+29 -3
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
"version": "0.25.4",
"version": "0.25.5",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust."
},
"servers": [
@@ -489,7 +489,7 @@
"cloudflare_client_ip": {
"type": "boolean",
"default": false,
"description": "Use a verified Cloudflare connecting-IP header only when the immediate peer is in the configured trusted Cloudflare ranges."
"description": "Use verified Cloudflare client-IP and country headers only when the immediate peer is in the built-in Cloudflare edge ranges; country falls back to local GeoIP when unavailable or invalid."
},
"timing_headers": {
"type": "boolean",
@@ -2305,6 +2305,11 @@
"type": "boolean",
"default": true
},
"include_traffic": {
"type": "boolean",
"default": true,
"description": "Export bounded Traffic analytics aggregates. Raw client IPs, request paths and full User-Agent strings are intentionally excluded."
},
"include_backends": {
"type": "boolean",
"default": true
@@ -4584,6 +4589,13 @@
"$ref": "#/components/schemas/TrafficTopEntry"
}
},
"user_agent_families": {
"type": "array",
"description": "Fixed-cardinality User-Agent families used by Prometheus; raw User-Agent top values remain available only in the authenticated Traffic API/UI.",
"items": {
"$ref": "#/components/schemas/TrafficTopEntry"
}
},
"client_ips": {
"type": "array",
"items": {
@@ -4596,6 +4608,20 @@
"$ref": "#/components/schemas/TrafficTopEntry"
}
},
"method_families": {
"type": "array",
"description": "Fixed-cardinality common HTTP method families with all extension methods folded into OTHER.",
"items": {
"$ref": "#/components/schemas/TrafficTopEntry"
}
},
"sources": {
"type": "array",
"description": "Fixed serving-source families: reverse_proxy, web_server, local_response or unresolved.",
"items": {
"$ref": "#/components/schemas/TrafficTopEntry"
}
},
"statuses": {
"type": "array",
"items": {
@@ -9350,4 +9376,4 @@
}
}
}
}
}
+28 -17
View File
@@ -66,7 +66,6 @@ async fn cache_purge(
))
}
async fn cache_invalidate(
State(state): State<Arc<App>>,
Extension(session): Extension<Session>,
@@ -123,11 +122,18 @@ async fn cache_ban_delete(
Path(id): Path<u64>,
) -> Result<Json<Value>, ApiError> {
if !state.cache.remove_ban(id) {
return Err(ApiError(StatusCode::NOT_FOUND, "unknown cache BAN rule".into()));
return Err(ApiError(
StatusCode::NOT_FOUND,
"unknown cache BAN rule".into(),
));
}
state
.store
.audit(&session.username, "cache.ban.remove", &format!("ban_id={id}"))
.audit(
&session.username,
"cache.ban.remove",
&format!("ban_id={id}"),
)
.await
.map_err(auth::internal)?;
Ok(Json(json!({"removed": id, "bans": state.cache.bans()})))
@@ -164,7 +170,10 @@ async fn kill_tunnel(
));
}
let Some(tunnel) = state.tunnel_registry.kill(id) else {
return Err(ApiError(StatusCode::NOT_FOUND, "unknown active tunnel".into()));
return Err(ApiError(
StatusCode::NOT_FOUND,
"unknown active tunnel".into(),
));
};
state
.store
@@ -207,10 +216,12 @@ async fn cache_cold_cleanup_job(
State(state): State<Arc<App>>,
Path(id): Path<u64>,
) -> Result<Json<Value>, ApiError> {
let job = state
.cache
.cold_cleanup_job(id)
.ok_or_else(|| ApiError(StatusCode::NOT_FOUND, "unknown cold-cache cleanup job".into()))?;
let job = state.cache.cold_cleanup_job(id).ok_or_else(|| {
ApiError(
StatusCode::NOT_FOUND,
"unknown cold-cache cleanup job".into(),
)
})?;
Ok(Json(json!({"job": job})))
}
#[derive(Deserialize)]
@@ -268,7 +279,6 @@ async fn certificate_upload(
Ok(Json(json!({"certificate":certificate, "staged":true})))
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct CertificateApiRotate {
@@ -339,12 +349,7 @@ async fn certificate_api_rotate(
let token_hash = auth::secret_hash(&token);
let credential = state
.store
.upsert_certificate_api_credential(
&key_id,
&token_hash,
&comment,
&session.username,
)
.upsert_certificate_api_credential(&key_id, &token_hash, &comment, &session.username)
.await
.map_err(|error| {
if error.to_string().contains("credential limit reached") {
@@ -371,7 +376,10 @@ async fn certificate_api_revoke(
Path(key_id): Path<String>,
) -> Result<Json<Value>, ApiError> {
if !valid_certificate_api_key_id(&key_id) {
return Err(ApiError(StatusCode::NOT_FOUND, "certificate_api.not_found".into()));
return Err(ApiError(
StatusCode::NOT_FOUND,
"certificate_api.not_found".into(),
));
}
let revoked = state
.store
@@ -379,7 +387,10 @@ async fn certificate_api_revoke(
.await
.map_err(auth::internal)?;
if !revoked {
return Err(ApiError(StatusCode::NOT_FOUND, "certificate_api.not_found".into()));
return Err(ApiError(
StatusCode::NOT_FOUND,
"certificate_api.not_found".into(),
));
}
Ok(Json(json!({"revoked": true, "key_id": key_id})))
}
+11 -7
View File
@@ -36,7 +36,8 @@ fn admin_app_javascript() -> &'static str {
include_str!("web/app/config-history.js"),
include_str!("web/app/runtime.js"),
include_str!("web/app/settings.js"),
].join("\n")
]
.join("\n")
});
ASSET.as_str()
}
@@ -49,7 +50,8 @@ fn admin_vhosts_javascript() -> &'static str {
include_str!("web/vhosts/editor.js"),
include_str!("web/vhosts/render.js"),
"})();",
].join("\n")
]
.join("\n")
});
ASSET.as_str()
}
@@ -449,9 +451,11 @@ fn certificate_api_operation(method: &Method, path: &str) -> bool {
if method != Method::POST {
return false;
}
matches!(path, "/api/certificates/deploy" | "/api/certificates/reload")
|| path
.strip_prefix("/api/certificates/")
.and_then(|tail| tail.strip_suffix("/reload"))
.is_some_and(crate::config::valid_id)
matches!(
path,
"/api/certificates/deploy" | "/api/certificates/reload"
) || path
.strip_prefix("/api/certificates/")
.and_then(|tail| tail.strip_suffix("/reload"))
.is_some_and(crate::config::valid_id)
}
+4 -1
View File
@@ -45,7 +45,10 @@ async fn guard(State(state): State<Arc<App>>, mut request: Request, next: Next)
Ok(None) => None,
Ok(Some((key_id, token))) => {
if peer.is_some_and(|peer| !peer.0.ip().is_loopback()) && scheme != "https" {
return secure(reject(StatusCode::FORBIDDEN, "certificate_api.https_required"));
return secure(reject(
StatusCode::FORBIDDEN,
"certificate_api.https_required",
));
}
let token_hash = auth::secret_hash(&token);
match state
+14 -4
View File
@@ -158,8 +158,8 @@ fn status_payload(state: &App) -> Value {
payload["transform_resources"] = json!(&transform_resources);
payload["geoip"] = json!(state.geoip.view());
let system = state.diagnostics.view();
let cache_ram_bytes = (cache_view.hot_allocated_bytes as u64)
.saturating_add(cache_view.transient_bytes);
let cache_ram_bytes =
(cache_view.hot_allocated_bytes as u64).saturating_add(cache_view.transient_bytes);
let non_cache_rss_bytes = system
.process_rss_bytes
.map(|rss| rss.saturating_sub(cache_ram_bytes));
@@ -359,7 +359,12 @@ async fn config_history(
.configuration_revision(revision)
.await
.map_err(auth::internal)?
.ok_or_else(|| ApiError(StatusCode::NOT_FOUND, "configuration revision not found".into()))?;
.ok_or_else(|| {
ApiError(
StatusCode::NOT_FOUND,
"configuration revision not found".into(),
)
})?;
return Ok(Json(json!({"revision": detail})));
}
let limit = query.limit.unwrap_or(512).clamp(1, 512);
@@ -393,7 +398,12 @@ async fn restore_config_history(
.configuration_revision(input.revision)
.await
.map_err(auth::internal)?
.ok_or_else(|| ApiError(StatusCode::NOT_FOUND, "configuration revision not found".into()))?;
.ok_or_else(|| {
ApiError(
StatusCode::NOT_FOUND,
"configuration revision not found".into(),
)
})?;
state
.restore_as(
Update {
+13 -2
View File
@@ -390,6 +390,8 @@ function stopTunnelRefresh() { if (tunnelRefreshTimer) clearInterval(tunnelRefre
function startTunnelRefresh() { stopTunnelRefresh(); tunnelRefreshTimer = setInterval(() => { if ($("tunnels-dialog")?.open) void loadTunnels(); }, 2000); }
$("login-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; authMessage(""); try { await withFormSubmitState(form, event.submitter, async () => { const username = $("username").value.trim(); const password = $("password").value; if (setupRequired) { if (password !== $("password-confirm").value) throw fieldError("password-confirm", t("passwordMismatch")); await api("/api/auth/setup", { method: "POST", body: JSON.stringify({ token: $("setup-token").value.trim(), username, password }) }); setupRequired = false; syncAuthForm(); } const info = await api("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password }) }); await activate(info); clearTimeout(toastTimer); hideToast(); }); } catch (e) { if (e.code === "auth.already_initialized") { setupRequired = false; syncAuthForm(); } $("password").value = ""; $("password-confirm").value = ""; authMessage(e.message); } });
$("logout").addEventListener("click", async () => { try { await api("/api/auth/logout", { method: "POST" }); lock(); toast(t("locked")); } catch (e) { if (e.status === 401) lock(); toast(e.message, true); } });
$("about-open").addEventListener("click", () => { const dialog = $("about-dialog"); if (!dialog.open) dialog.showModal(); });
$("about-close").addEventListener("click", () => $("about-dialog").close());
$("language").addEventListener("change", () => { language = $("language").value; try { localStorage.setItem("rp-language", language); } catch { } translate(); });
$("ui-theme").addEventListener("change", async () => {
const select = $("ui-theme"), previousTheme = adminPreferences?.theme || document.documentElement.dataset.theme || "dark", nextTheme = normalizeAdminTheme(select.value);
@@ -398,8 +400,17 @@ $("ui-theme").addEventListener("change", async () => {
catch (error) { applyAdminTheme(previousTheme); if (error.status !== 401) toast(error.message, true); }
finally { select.disabled = false; }
});
document.querySelectorAll(".nav").forEach(button => button.addEventListener("click", () => setPanel(button.dataset.panel)));
window.addEventListener("popstate", () => setPanel(panelFromLocation(), false));
const mobileMenuToggle = $("mobile-menu-toggle"), dashboardSidebar = document.querySelector(".sidebar");
function setMobileMenu(open) {
if (!mobileMenuToggle || !dashboardSidebar) return;
dashboardSidebar.classList.toggle("mobile-nav-open", open);
mobileMenuToggle.setAttribute("aria-expanded", String(open));
}
mobileMenuToggle?.addEventListener("click", event => { event.stopPropagation(); setMobileMenu(!dashboardSidebar.classList.contains("mobile-nav-open")); });
document.querySelectorAll(".nav").forEach(button => button.addEventListener("click", () => { setPanel(button.dataset.panel); setMobileMenu(false); }));
document.addEventListener("click", event => { if (dashboardSidebar?.classList.contains("mobile-nav-open") && !dashboardSidebar.contains(event.target)) setMobileMenu(false); });
document.addEventListener("keydown", event => { if (event.key === "Escape" && dashboardSidebar?.classList.contains("mobile-nav-open")) { setMobileMenu(false); mobileMenuToggle?.focus(); } });
window.addEventListener("popstate", () => { setPanel(panelFromLocation(), false); setMobileMenu(false); });
$("host-policy-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; if (!envelope || busy) return; try { await withFormSubmitState(form, event.submitter, async () => { const hosts = [...new Set($("policy-hosts").value.split(",").map(h => h.trim().toLowerCase()).filter(Boolean))]; const acl = { default: $("policy-default").value, rules: parseRules($("policy-rules").value, $("policy-rules")) }; const next = structuredClone(envelope.config); next.host_policies = (next.host_policies || []).map(p => ({ ...p, hosts: p.hosts.filter(h => !hosts.includes(h)) })).filter(p => p.hosts.length); next.host_policies.push({ hosts, acl }); if (await saveSection("host-policies", next.host_policies)) { form.reset(); if ($("security-policy-editor")) $("security-policy-editor").open = false; } }); } catch (e) { toast(e.message, true); } });
$("password-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; try { await withFormSubmitState(form, event.submitter, async () => { if ($("new-password").value !== $("new-password-confirm").value) throw fieldError("new-password-confirm", t("passwordMismatch")); await api("/api/auth/password", { method: "POST", body: JSON.stringify({ current_password: $("current-password").value, new_password: $("new-password").value }) }); lock(); toast(t("passwordChanged")); }); } catch (e) { toast(e.message, true); } });
$("revoke-sessions").addEventListener("click", async () => { if (!(await appConfirm(t("confirmRevoke"), { danger: true }))) return; try { await api("/api/auth/logout-all", { method: "POST" }); lock(); toast(t("locked")); } catch (e) { toast(e.message, true); } });
+1 -1
View File
@@ -6,7 +6,7 @@ function renderSettings() {
if (!settingsDirty.has("response-form")) {
const server = config.server_header || {}, compression = config.compression || {}, diagnostics = config.diagnostic_headers || {};
$("expose-request-id").checked = !!diagnostics.request_id;
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.4";
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.5";
for (const name of ["gzip", "br", "zstd", "deflate"]) $(`compress-${name}`).checked = !!compression[name];
$("compress-min").value = compression.min_bytes ?? 1024; syncServerMode();
}
+3 -2
View File
@@ -176,6 +176,7 @@ main {
background: #101923;
color: #fff
}
.docs-tabs {
display: flex;
gap: 8px;
@@ -312,7 +313,7 @@ main {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
}
.markdown-reader :not(pre) > code {
.markdown-reader :not(pre)>code {
padding: 2px 4px;
background: #edf1f4
}
@@ -342,4 +343,4 @@ main {
.markdown-reader {
padding: 18px
}
}
}
+14 -6
View File
@@ -1,5 +1,6 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -10,10 +11,12 @@
<script defer src="/docs/swagger-ui-bundle.js"></script>
<script defer src="/docs/init.js"></script>
</head>
<body>
<header class="docs-bar"><a class="docs-name" href="/">RustProxy</a>
<h1 data-i18n="docsTitle">Documentation</h1>
<a href="/" data-i18n="docsPanel">Back to panel</a><button type="button" id="docs-logout" data-i18n="docsLogout">Sign out</button>
<a href="/" data-i18n="docsPanel">Back to panel</a><button type="button" id="docs-logout"
data-i18n="docsLogout">Sign out</button>
</header>
<main>
<section class="docs-session">
@@ -22,14 +25,16 @@
</section>
<p id="docs-error" role="alert"></p>
<nav class="docs-tabs" aria-label="Documentation sections">
<button type="button" id="docs-tab-reader" class="active" data-docs-tab="reader" data-i18n="docsReader">Reader</button>
<button type="button" id="docs-tab-reader" class="active" data-docs-tab="reader"
data-i18n="docsReader">Reader</button>
<button type="button" id="docs-tab-api" data-docs-tab="api" data-i18n="docsApi">API</button>
</nav>
<section id="docs-reader-pane" class="docs-pane">
<div class="reader-layout">
<aside class="reader-sidebar">
<label for="docs-reader-search" data-i18n="docsReaderSearch">Search documentation</label>
<input id="docs-reader-search" type="search" autocomplete="off" data-i18n-placeholder="docsReaderSearchPlaceholder" placeholder="Search title or path">
<input id="docs-reader-search" type="search" autocomplete="off"
data-i18n-placeholder="docsReaderSearchPlaceholder" placeholder="Search title or path">
<div id="docs-reader-list" class="reader-list"></div>
</aside>
<article id="docs-reader-content" class="markdown-reader" aria-live="polite"></article>
@@ -37,12 +42,15 @@
</section>
<section id="docs-api-pane" class="docs-pane" hidden>
<section class="docs-api-controls">
<label><input type="checkbox" id="docs-writes"><span data-i18n="docsWrites">Enable write operations</span></label>
<p data-i18n="docsReadOnly">Read-only execution by default. SSE streams and login/setup are not executed here.</p>
<label><input type="checkbox" id="docs-writes"><span data-i18n="docsWrites">Enable write
operations</span></label>
<p data-i18n="docsReadOnly">Read-only execution by default. SSE streams and login/setup are not executed
here.</p>
</section>
<div id="swagger-ui"></div>
<p><a href="/docs/licenses.txt" data-i18n="docsLicenses">Swagger UI licenses</a></p>
</section>
</main>
</body>
</html>
</html>
+577 -248
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -126,7 +126,7 @@ curl --fail --show-error --request POST \
catch (error) { $("certificate-api-status").textContent = error.message; }
finally { certificateApiLoading = false; }
}
const defaults = { enabled: false, listen: "127.0.0.1:9191", path: "/metrics", allowed_cidrs: ["127.0.0.0/8", "::1/128"], bearer_token_env: null, allow_trusted_network_without_token: false, include_system: true, include_vhosts: true, include_backends: true, include_docker_vhosts: true, include_cache_domains: true, include_streams: true, include_logging: true };
const defaults = { enabled: false, listen: "127.0.0.1:9191", path: "/metrics", allowed_cidrs: ["127.0.0.0/8", "::1/128"], bearer_token_env: null, allow_trusted_network_without_token: false, include_system: true, include_vhosts: true, include_traffic: true, include_backends: true, include_docker_vhosts: true, include_cache_domains: true, include_streams: true, include_logging: true };
function example() {
const addr = $("prom-listen").value.trim(), path = $("prom-path").value.trim(), env = $("prom-token-env").value.trim();
$("prom-scrape-example").textContent = `scrape_configs:\n - job_name: rustproxy\n metrics_path: ${JSON.stringify(path)}\n static_configs:\n - targets: [${JSON.stringify(addr)}]${env ? "\n authorization:\n credentials_file: /etc/prometheus/rustproxy.token" : ""}`;
@@ -136,7 +136,7 @@ curl --fail --show-error --request POST \
if (!settingsDirty.has("prometheus-form")) {
$("prom-enabled").checked = p.enabled; $("prom-listen").value = p.listen; $("prom-path").value = p.path;
$("prom-cidrs").value = p.allowed_cidrs.join("\n"); $("prom-token-env").value = p.bearer_token_env || ""; $("prom-trusted-no-token").checked = !!p.allow_trusted_network_without_token;
for (const [id, key] of [["system", "include_system"], ["vhosts", "include_vhosts"], ["backends", "include_backends"], ["docker-vhosts", "include_docker_vhosts"], ["domains", "include_cache_domains"], ["streams", "include_streams"], ["logging", "include_logging"]]) $(`prom-${id}`).checked = p[key];
for (const [id, key] of [["system", "include_system"], ["vhosts", "include_vhosts"], ["traffic", "include_traffic"], ["backends", "include_backends"], ["docker-vhosts", "include_docker_vhosts"], ["domains", "include_cache_domains"], ["streams", "include_streams"], ["logging", "include_logging"]]) $(`prom-${id}`).checked = p[key];
}
const status = latestStatus?.prometheus;
$("prom-status").textContent = status?.restart_required ? t("restartPending") : status?.active?.enabled ? `${t("prometheusActive")}: ${status.active.listen}${p.path}` : t("prometheusDisabled"); example();
@@ -170,7 +170,7 @@ curl --fail --show-error --request POST \
bindSettingsForm("prometheus-form", next => {
next.prometheus = {
enabled: $("prom-enabled").checked, listen: $("prom-listen").value.trim(), path: $("prom-path").value.trim(), allowed_cidrs: $("prom-cidrs").value.split(/[\n,]/).map(v => v.trim()).filter(Boolean), bearer_token_env: $("prom-token-env").value.trim() || null, allow_trusted_network_without_token: $("prom-trusted-no-token").checked,
include_system: $("prom-system").checked, include_vhosts: $("prom-vhosts").checked, include_backends: $("prom-backends").checked, include_docker_vhosts: $("prom-docker-vhosts").checked, include_cache_domains: $("prom-domains").checked, include_streams: $("prom-streams").checked, include_logging: $("prom-logging").checked
include_system: $("prom-system").checked, include_vhosts: $("prom-vhosts").checked, include_traffic: $("prom-traffic").checked, include_backends: $("prom-backends").checked, include_docker_vhosts: $("prom-docker-vhosts").checked, include_cache_domains: $("prom-domains").checked, include_streams: $("prom-streams").checked, include_logging: $("prom-logging").checked
};
}, "prometheus");
bindSettingsForm("administration-form", next => {
+230 -28
View File
@@ -3781,7 +3781,7 @@ body[data-density="dense"] .stat strong {
max-width: none
}
.settings-page>.edit-form + .edit-form {
.settings-page>.edit-form+.edit-form {
margin-top: var(--ui-gap)
}
@@ -3920,7 +3920,8 @@ body[data-density="dense"] .stat strong {
dialog.vhost-dialog,
dialog.logs-dialog,
dialog.audit-dialog,
dialog.certificate-dialog {
dialog.certificate-dialog,
dialog.config-history-dialog {
border-radius: 0;
background: var(--bg);
color: var(--text);
@@ -4692,11 +4693,16 @@ dialog .window-heading {
}
@media(max-width:1100px) {
.traffic-wide-chart { grid-column: span 1 }
.traffic-wide-chart {
grid-column: span 1
}
}
@media(max-width:650px) {
.traffic-bar-row { grid-template-columns: minmax(88px, 1.2fr) minmax(90px, 2fr) minmax(60px, auto); gap: 7px }
.traffic-bar-row {
grid-template-columns: minmax(88px, 1.2fr) minmax(90px, 2fr) minmax(60px, auto);
gap: 7px
}
}
.logging-grid label {
@@ -4720,6 +4726,7 @@ dialog .window-heading {
}
@media(max-width:720px) {
.traffic-chart-grid,
.logging-grid {
grid-template-columns: 1fr
@@ -6778,7 +6785,6 @@ body[data-density="compact"] .page-footer {
}
}
/* Certificates workspace (0.20.0): use the same tab language as Settings. */
#panel-tls .certificate-workspace-heading {
align-items: flex-start;
margin-bottom: 12px
@@ -7031,7 +7037,6 @@ body[data-density="compact"] .page-footer {
}
}
/* Inline operation feedback mirrors the login feedback pattern and remains local to the action. */
.operation-message:empty {
display: none;
}
@@ -7330,7 +7335,6 @@ body[data-density="compact"] .page-footer {
white-space: normal;
}
/* 0.24.3 dashboard refinements */
.payload-split {
display: flex !important;
align-items: center;
@@ -7515,7 +7519,7 @@ body[data-density="compact"] .page-footer {
color: var(--notice-text);
font-weight: 600;
}
/* 0.24.8 cache occupancy charts and compact memory-accounting help */
.mini-heading-actions {
display: inline-flex;
align-items: center;
@@ -7607,7 +7611,6 @@ body[data-density="compact"] .page-footer {
color: var(--transform-chart);
}
/* Web Service editor mirrors the vhost tabbed configuration layout. */
.web-service-pages {
min-width: 0;
}
@@ -7628,21 +7631,10 @@ body[data-density="compact"] .page-footer {
}
.config-history-dialog {
color: var(--text);
background: var(--bg);
border: 1px solid var(--line);
padding: 0;
width: min(1180px, calc(100vw - 24px));
max-width: calc(100vw - 24px);
max-height: calc(100dvh - 28px);
overflow: hidden;
border-radius: 7px;
box-shadow: 0 18px 70px var(--shadow-strong);
}
.config-history-dialog::backdrop {
background: var(--shadow-strong);
backdrop-filter: blur(2px);
}
.config-history-layout {
@@ -7667,7 +7659,6 @@ body[data-density="compact"] .page-footer {
.config-history-month {
border: 1px solid var(--line);
border-radius: 6px;
padding: 9px;
background: var(--panel);
}
@@ -7695,7 +7686,6 @@ body[data-density="compact"] .page-footer {
min-height: 34px;
padding: 4px;
border: 1px solid transparent;
border-radius: 4px;
background: transparent;
color: var(--muted);
font: 10px var(--mono);
@@ -7769,7 +7759,6 @@ button.config-history-day.is-selected {
.config-history-change {
border: 1px solid var(--line);
border-radius: 5px;
padding: 8px;
background: var(--panel);
}
@@ -7797,9 +7786,222 @@ button.config-history-day.is-selected {
}
@media (max-width: 860px) {
.config-history-layout { grid-template-columns: 1fr; overflow: auto; }
.config-history-browser, .config-history-detail { overflow: visible; }
.config-history-change-values { grid-template-columns: 1fr; }
.config-history-detail-head { flex-direction: column; }
.config-history-detail-actions { align-items: flex-start; text-align: left; }
.config-history-layout {
grid-template-columns: 1fr;
overflow: auto;
}
.config-history-browser,
.config-history-detail {
overflow: visible;
}
.config-history-change-values {
grid-template-columns: 1fr;
}
.config-history-detail-head {
flex-direction: column;
}
.config-history-detail-actions {
align-items: flex-start;
text-align: left;
}
}
.mobile-menu-toggle {
display: none;
}
@media(max-width:850px) {
.sidebar {
position: sticky;
top: 0;
z-index: 60;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 10px 12px;
padding: 10px 12px;
}
.sidebar .brand {
min-width: 0;
}
.mobile-menu-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 9px;
min-height: 36px;
padding: 7px 10px;
border: 1px solid var(--line);
background: transparent;
color: var(--muted);
font: 600 11px var(--mono);
}
.mobile-menu-toggle:hover,
.mobile-menu-toggle[aria-expanded="true"] {
color: var(--text);
border-color: var(--border-hover);
background: var(--nav-active-bg);
}
.mobile-menu-icon {
display: block;
flex: 0 0 auto;
width: 15px;
height: 1px;
background: currentColor;
box-shadow: 0 -5px currentColor, 0 5px currentColor;
}
.sidebar .main-navigation {
grid-column: 1 / -1;
display: none;
width: 100%;
margin: 0;
padding-top: 10px;
border-top: 1px solid var(--line);
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 4px;
}
.sidebar.mobile-nav-open .main-navigation {
display: grid;
}
.sidebar .main-navigation .nav {
width: 100%;
min-width: 0;
justify-content: flex-start;
gap: 8px;
padding: 9px 10px;
border: 1px solid transparent;
border-left: 2px solid transparent;
text-align: left;
}
.sidebar .main-navigation .nav.active {
border-color: var(--line);
border-left-color: var(--green);
background: var(--nav-active-bg);
}
}
@media(max-width:600px) {
.sidebar .main-navigation .nav span {
display: none;
}
}
.about-dialog {
width: min(560px, calc(100vw - 24px));
overflow: hidden;
}
.about-dialog .window-heading button {
margin-inline-start: auto;
}
.about-card {
display: grid;
gap: 16px;
margin: 0;
padding: 18px;
border: 0;
box-shadow: none;
}
.about-product {
display: flex;
align-items: center;
gap: 12px;
}
.about-product>div {
display: grid;
gap: 4px;
}
.about-product strong {
font: 600 16px var(--mono);
}
.about-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px;
margin: 0;
border: 1px solid var(--line);
background: var(--line);
}
.about-meta>div {
min-width: 0;
padding: 10px 12px;
background: var(--panel);
}
.about-meta dt {
margin-bottom: 5px;
color: var(--muted);
font: 9px var(--mono);
letter-spacing: .8px;
text-transform: uppercase;
}
.about-meta dd {
margin: 0;
overflow-wrap: anywhere;
}
.about-card>p {
margin: 0;
color: var(--muted);
line-height: 1.65;
}
.about-repository {
justify-content: space-between;
gap: 12px;
width: 100%;
white-space: normal;
text-align: left;
}
.about-repository span:last-child {
color: var(--muted);
font: 10px var(--mono);
overflow-wrap: anywhere;
}
@media(max-width:620px) {
.topbar {
height: auto;
min-height: 46px;
padding: 7px 0;
}
.topbar .breadcrumb {
display: none;
}
.top-actions {
width: 100%;
flex-wrap: wrap;
justify-content: flex-end;
}
.about-meta {
grid-template-columns: 1fr;
}
.about-repository {
align-items: flex-start;
flex-direction: column;
}
}
+2 -2
View File
@@ -38,12 +38,12 @@
liveChartLimit = LIVE_CHART_LIMITS.has(parsed) ? parsed : 60;
for (const key of Object.keys(liveChartSamples)) liveChartSamples[key] = liveChartSamples[key].slice(-liveChartLimit);
if ($("transform-live-samples")) $("transform-live-samples").value = String(liveChartLimit);
if (persist) { try { localStorage.setItem(LIVE_CHART_STORAGE_KEY, String(liveChartLimit)); } catch {} }
if (persist) { try { localStorage.setItem(LIVE_CHART_STORAGE_KEY, String(liveChartLimit)); } catch { } }
redrawLiveChart();
}
function initializeLiveChartLimit() {
let saved = 60;
try { saved = Number(localStorage.getItem(LIVE_CHART_STORAGE_KEY) || 60); } catch {}
try { saved = Number(localStorage.getItem(LIVE_CHART_STORAGE_KEY) || 60); } catch { }
setLiveChartLimit(saved, false);
}
function renderLiveChart(counters) {