v0.25.7
This commit is contained in:
1 parent
e1a59f6e56
commit
d168ee2afa
49 files changed
+5608
-4680
No files matched your search
+2
-450
@@ -11,456 +11,8 @@ use std::sync::OnceLock;
|
||||
// Keep Reader input independent from build-script output. `include_str!` makes every
|
||||
// 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.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")),
|
||||
),
|
||||
];
|
||||
mod documents;
|
||||
use documents::READER_DOCUMENTS;
|
||||
|
||||
pub(super) async fn index() -> Html<&'static str> {
|
||||
Html(include_str!("web/docs.html"))
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
//! Embedded Markdown documents for the authenticated Reader.
|
||||
|
||||
pub(super) 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.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")),
|
||||
),
|
||||
];
|
||||
+70
-2
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "RustProxy Administration API",
|
||||
"version": "0.25.6",
|
||||
"version": "0.25.7",
|
||||
"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": [
|
||||
@@ -2917,6 +2917,74 @@
|
||||
],
|
||||
"default": "dark",
|
||||
"description": "Account-scoped administration interface theme. Existing accounts and omitted values default to the current dark theme."
|
||||
},
|
||||
"backend_table_columns": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 14,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"vhost",
|
||||
"backend",
|
||||
"status",
|
||||
"active",
|
||||
"requests",
|
||||
"errors",
|
||||
"latency",
|
||||
"traffic",
|
||||
"generation",
|
||||
"generationRequests",
|
||||
"generationAge",
|
||||
"rotations",
|
||||
"lastFailure",
|
||||
"lastRecovery"
|
||||
]
|
||||
},
|
||||
"default": [
|
||||
"vhost",
|
||||
"backend",
|
||||
"status",
|
||||
"active",
|
||||
"requests",
|
||||
"errors",
|
||||
"latency",
|
||||
"traffic",
|
||||
"generation",
|
||||
"generationRequests",
|
||||
"generationAge",
|
||||
"rotations",
|
||||
"lastFailure",
|
||||
"lastRecovery"
|
||||
],
|
||||
"description": "Account-scoped Backends diagnostics columns shown in the dashboard."
|
||||
},
|
||||
"backend_table_sort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"vhost",
|
||||
"backend",
|
||||
"status",
|
||||
"active",
|
||||
"requests",
|
||||
"errors",
|
||||
"latency",
|
||||
"traffic",
|
||||
"generation",
|
||||
"generationRequests",
|
||||
"generationAge",
|
||||
"rotations",
|
||||
"lastFailure",
|
||||
"lastRecovery"
|
||||
],
|
||||
"default": "vhost",
|
||||
"description": "Account-scoped sort key for the Backends diagnostics table."
|
||||
},
|
||||
"backend_table_descending": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Account-scoped sort direction for the Backends diagnostics table."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -5093,7 +5161,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Sets an HttpOnly SameSite=Strict session cookie. Browser sessions use the configured 7\u201330 day absolute lifetime. Use the panel sign-in page. No bearer token or URL token is accepted.",
|
||||
"description": "Sets an HttpOnly SameSite=Strict session cookie. Browser sessions use the configured 7–30 day absolute lifetime. Use the panel sign-in page. No bearer token or URL token is accepted.",
|
||||
"security": [],
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -267,10 +267,35 @@ const LIVE_LOG_COLUMNS: &[&str] = &[
|
||||
"websocket",
|
||||
"upstream_error",
|
||||
];
|
||||
const BACKEND_TABLE_COLUMNS: &[&str] = &[
|
||||
"vhost",
|
||||
"backend",
|
||||
"status",
|
||||
"active",
|
||||
"requests",
|
||||
"errors",
|
||||
"latency",
|
||||
"traffic",
|
||||
"generation",
|
||||
"generationRequests",
|
||||
"generationAge",
|
||||
"rotations",
|
||||
"lastFailure",
|
||||
"lastRecovery",
|
||||
];
|
||||
|
||||
fn default_admin_theme() -> String {
|
||||
"dark".to_owned()
|
||||
}
|
||||
fn default_backend_table_columns() -> Vec<String> {
|
||||
BACKEND_TABLE_COLUMNS
|
||||
.iter()
|
||||
.map(|value| (*value).to_owned())
|
||||
.collect()
|
||||
}
|
||||
fn default_backend_table_sort() -> String {
|
||||
"vhost".to_owned()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -278,6 +303,12 @@ struct AdminPreferences {
|
||||
live_log_columns: Vec<String>,
|
||||
#[serde(default = "default_admin_theme")]
|
||||
theme: String,
|
||||
#[serde(default = "default_backend_table_columns")]
|
||||
backend_table_columns: Vec<String>,
|
||||
#[serde(default = "default_backend_table_sort")]
|
||||
backend_table_sort: String,
|
||||
#[serde(default)]
|
||||
backend_table_descending: bool,
|
||||
}
|
||||
|
||||
impl Default for AdminPreferences {
|
||||
@@ -288,6 +319,9 @@ impl Default for AdminPreferences {
|
||||
.map(|value| (*value).to_owned())
|
||||
.collect(),
|
||||
theme: default_admin_theme(),
|
||||
backend_table_columns: default_backend_table_columns(),
|
||||
backend_table_sort: default_backend_table_sort(),
|
||||
backend_table_descending: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,6 +350,29 @@ fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiE
|
||||
"invalid administration theme preference".into(),
|
||||
));
|
||||
}
|
||||
if preferences.backend_table_columns.is_empty()
|
||||
|| preferences.backend_table_columns.len() > BACKEND_TABLE_COLUMNS.len()
|
||||
{
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"at least one backend table column must be visible".into(),
|
||||
));
|
||||
}
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for column in &preferences.backend_table_columns {
|
||||
if !BACKEND_TABLE_COLUMNS.contains(&column.as_str()) || !seen.insert(column.as_str()) {
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"invalid backend table column preference".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
if !BACKEND_TABLE_COLUMNS.contains(&preferences.backend_table_sort.as_str()) {
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"invalid backend table sort preference".into(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
LIVE_LOG_COLUMNS.len()
|
||||
);
|
||||
assert_eq!(initial["theme"], "dark");
|
||||
assert_eq!(
|
||||
initial["backend_table_columns"].as_array().unwrap().len(),
|
||||
BACKEND_TABLE_COLUMNS.len()
|
||||
);
|
||||
assert_eq!(initial["backend_table_sort"], "vhost");
|
||||
assert_eq!(initial["backend_table_descending"], false);
|
||||
|
||||
assert!(store
|
||||
.save_admin_preferences("owner", r#"{"live_log_columns":["time","status"]}"#)
|
||||
@@ -39,10 +45,19 @@
|
||||
let legacy = json_body(response).await;
|
||||
assert_eq!(legacy["live_log_columns"], json!(["time", "status"]));
|
||||
assert_eq!(legacy["theme"], "dark");
|
||||
assert_eq!(
|
||||
legacy["backend_table_columns"].as_array().unwrap().len(),
|
||||
BACKEND_TABLE_COLUMNS.len()
|
||||
);
|
||||
assert_eq!(legacy["backend_table_sort"], "vhost");
|
||||
assert_eq!(legacy["backend_table_descending"], false);
|
||||
|
||||
let selected = json!({
|
||||
"live_log_columns":["time","request","status","upstream_error"],
|
||||
"theme":"light"
|
||||
"theme":"light",
|
||||
"backend_table_columns":["vhost","backend","status","requests","latency"],
|
||||
"backend_table_sort":"requests",
|
||||
"backend_table_descending":true
|
||||
});
|
||||
let response = router
|
||||
.clone()
|
||||
@@ -101,6 +116,7 @@
|
||||
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
|
||||
let response = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request("/api/preferences")
|
||||
.method("PUT")
|
||||
@@ -117,6 +133,24 @@
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
|
||||
let response = router
|
||||
.oneshot(
|
||||
request("/api/preferences")
|
||||
.method("PUT")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
r#"{"live_log_columns":["time"],"backend_table_columns":["unknown"]}"#,
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
store.close().await;
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ function lock(notify = true) {
|
||||
if (navigating) { document.documentElement.classList.add("app-loading"); location.replace(destination); }
|
||||
}
|
||||
|
||||
async function activate(info) { pendingAuthDestination = null; authMessage(""); $("api-docs").hidden = false; $("logs-open").hidden = false; authEpoch++; sessionInfo = info; csrf = info.csrf_token; authenticated = true; const preferences = await loadAdminPreferences(); window.rustProxyLogs?.applyPreferences(preferences); await loadConfig(); $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("login").hidden = true; $("workspace").hidden = false; $("logout").hidden = false; renderSession(); await refresh(true); startLive(); document.documentElement.classList.remove("app-loading"); }
|
||||
async function activate(info) { pendingAuthDestination = null; authMessage(""); $("api-docs").hidden = false; $("logs-open").hidden = false; authEpoch++; sessionInfo = info; csrf = info.csrf_token; authenticated = true; const preferences = await loadAdminPreferences(); window.rustProxyLogs?.applyPreferences(preferences); window.rustProxyVhosts?.applyPreferences(preferences); await loadConfig(); $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("login").hidden = true; $("workspace").hidden = false; $("logout").hidden = false; renderSession(); await refresh(true); startLive(); document.documentElement.classList.remove("app-loading"); }
|
||||
async function loadAudit() { if (!authenticated) return; const epoch = authEpoch; try { const data = await api("/api/audit"); if (!authenticated || epoch !== authEpoch) return; const rows = data.entries.map(item => { const tr = el("tr"); for (const value of [new Date(item.time * 1000).toLocaleString(locale()), item.actor, item.action, item.detail]) { const td = el("td", value); td.title = value; tr.append(td); } return tr; }); $("audit-rows").replaceChildren(...rows); $("empty-audit").hidden = rows.length > 0; } catch (e) { toast(e.message, true); } }
|
||||
let tunnelRefreshTimer = null;
|
||||
function tunnelMessage(value = "", error = false) { const node = $("tunnels-status"); if (!node) return; node.textContent = value; node.classList.toggle("error", !!error); }
|
||||
|
||||
@@ -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.6";
|
||||
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.7";
|
||||
for (const name of ["gzip", "br", "zstd", "deflate"]) $(`compress-${name}`).checked = !!compression[name];
|
||||
$("compress-min").value = compression.min_bytes ?? 1024; syncServerMode();
|
||||
}
|
||||
|
||||
+89
-20
@@ -523,9 +523,75 @@
|
||||
</article>
|
||||
</div>
|
||||
<article class="card pool-card" id="backend-diagnostics-card">
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="vhHealthTitle">Backends</h2><button class="quiet" data-i18n="vhManage"
|
||||
id="pool-open" type="button">Manage vhosts</button>
|
||||
<div class="card-heading backend-card-heading">
|
||||
<h2 data-i18n="vhHealthTitle">Backends</h2>
|
||||
<div class="backend-table-tools">
|
||||
<label class="sr-only" data-i18n="backendSearch" for="pool-search">Search
|
||||
backends</label>
|
||||
<input autocomplete="off" data-i18n-aria="backendSearch"
|
||||
data-i18n-placeholder="backendSearchPlaceholder" id="pool-search" type="search" />
|
||||
<label class="backend-sort-control"><span data-i18n="backendSortBy">Sort
|
||||
by</span><select data-i18n-aria="backendSortBy" id="pool-sort">
|
||||
<option data-i18n="vhVhost" value="vhost">Vhost</option>
|
||||
<option data-i18n="backend" value="backend">Backend</option>
|
||||
<option data-i18n="status" value="status">Status</option>
|
||||
<option data-i18n="vhActive" value="active">Active</option>
|
||||
<option data-i18n="vhRequests" value="requests">Requests</option>
|
||||
<option data-i18n="vhErrors" value="errors">Errors</option>
|
||||
<option data-i18n="vhLatency" value="latency">Latency</option>
|
||||
<option data-i18n="vhTraffic" value="traffic">Traffic/s</option>
|
||||
<option data-i18n="vhClientGeneration" value="generation">Client gen.</option>
|
||||
<option data-i18n="vhGenerationRequests" value="generationRequests">Gen.
|
||||
requests</option>
|
||||
<option data-i18n="vhGenerationAge" value="generationAge">Gen. age</option>
|
||||
<option data-i18n="vhClientRotations" value="rotations">Rotations</option>
|
||||
<option data-i18n="vhLastFailure" value="lastFailure">Last failure</option>
|
||||
<option data-i18n="vhLastRecovery" value="lastRecovery">Last recovery</option>
|
||||
</select></label>
|
||||
<button class="quiet backend-sort-direction" data-i18n-aria="backendSortAscending"
|
||||
id="pool-sort-direction" type="button">A-Z</button>
|
||||
<details class="backend-column-picker">
|
||||
<summary class="quiet" data-i18n="backendColumns">Columns</summary>
|
||||
<div class="backend-column-options">
|
||||
<label class="check"><input checked data-pool-column-toggle="vhost"
|
||||
type="checkbox" /><span data-i18n="vhVhost">Vhost</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="backend"
|
||||
type="checkbox" /><span data-i18n="backend">Backend</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="status"
|
||||
type="checkbox" /><span data-i18n="status">Status</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="active"
|
||||
type="checkbox" /><span data-i18n="vhActive">Active</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="requests"
|
||||
type="checkbox" /><span data-i18n="vhRequests">Requests</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="errors"
|
||||
type="checkbox" /><span data-i18n="vhErrors">Errors</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="latency"
|
||||
type="checkbox" /><span data-i18n="vhLatency">Latency</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="traffic"
|
||||
type="checkbox" /><span data-i18n="vhTraffic">Traffic/s</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="generation"
|
||||
type="checkbox" /><span data-i18n="vhClientGeneration">Client
|
||||
gen.</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="generationRequests"
|
||||
type="checkbox" /><span data-i18n="vhGenerationRequests">Gen.
|
||||
requests</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="generationAge"
|
||||
type="checkbox" /><span data-i18n="vhGenerationAge">Gen.
|
||||
age</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="rotations"
|
||||
type="checkbox" /><span
|
||||
data-i18n="vhClientRotations">Rotations</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="lastFailure"
|
||||
type="checkbox" /><span data-i18n="vhLastFailure">Last
|
||||
failure</span></label>
|
||||
<label class="check"><input checked data-pool-column-toggle="lastRecovery"
|
||||
type="checkbox" /><span data-i18n="vhLastRecovery">Last
|
||||
recovery</span></label>
|
||||
</div>
|
||||
</details>
|
||||
<button class="quiet" data-i18n="vhManage" id="pool-open" type="button">Manage
|
||||
vhosts</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="fine" data-i18n="vhHealthHint">Live state: leases cover the entire exchange; traffic
|
||||
is an EWMA of payload bytes.</p>
|
||||
@@ -533,20 +599,23 @@
|
||||
<table class="pool-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-i18n="vhVhost">Vhost</th>
|
||||
<th data-i18n="backend">Backend</th>
|
||||
<th data-i18n="status">Status</th>
|
||||
<th data-i18n="vhActive">Active</th>
|
||||
<th data-i18n="vhRequests">Requests</th>
|
||||
<th data-i18n="vhErrors">Errors</th>
|
||||
<th data-i18n="vhLatency">Latency</th>
|
||||
<th data-i18n="vhTraffic">Traffic/s</th>
|
||||
<th data-i18n="vhClientGeneration">Client gen.</th>
|
||||
<th data-i18n="vhGenerationRequests">Gen. requests</th>
|
||||
<th data-i18n="vhGenerationAge">Gen. age</th>
|
||||
<th data-i18n="vhClientRotations">Rotations</th>
|
||||
<th data-i18n="vhLastFailure">Last failure</th>
|
||||
<th data-i18n="vhLastRecovery">Last recovery</th>
|
||||
<th data-i18n="vhVhost" data-pool-column="vhost">Vhost</th>
|
||||
<th data-i18n="backend" data-pool-column="backend">Backend</th>
|
||||
<th data-i18n="status" data-pool-column="status">Status</th>
|
||||
<th data-i18n="vhActive" data-pool-column="active">Active</th>
|
||||
<th data-i18n="vhRequests" data-pool-column="requests">Requests</th>
|
||||
<th data-i18n="vhErrors" data-pool-column="errors">Errors</th>
|
||||
<th data-i18n="vhLatency" data-pool-column="latency">Latency</th>
|
||||
<th data-i18n="vhTraffic" data-pool-column="traffic">Traffic/s</th>
|
||||
<th data-i18n="vhClientGeneration" data-pool-column="generation">Client gen.
|
||||
</th>
|
||||
<th data-i18n="vhGenerationRequests" data-pool-column="generationRequests">Gen.
|
||||
requests</th>
|
||||
<th data-i18n="vhGenerationAge" data-pool-column="generationAge">Gen. age</th>
|
||||
<th data-i18n="vhClientRotations" data-pool-column="rotations">Rotations</th>
|
||||
<th data-i18n="vhLastFailure" data-pool-column="lastFailure">Last failure</th>
|
||||
<th data-i18n="vhLastRecovery" data-pool-column="lastRecovery">Last recovery
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pool-rows"></tbody>
|
||||
@@ -1598,7 +1667,7 @@
|
||||
<option data-i18n="serverCustom" value="custom">Replace / add custom value</option>
|
||||
</select>
|
||||
<label data-i18n="serverValue" for="server-value">Custom Server value</label><input
|
||||
id="server-value" maxlength="256" placeholder="RustProxy/v0.25.6"
|
||||
id="server-value" maxlength="256" placeholder="RustProxy/v0.25.7"
|
||||
spellcheck="false" />
|
||||
<h3 class="subheading" data-i18n="diagnosticHeaders">Diagnostic headers</h3>
|
||||
<div class="switch-grid">
|
||||
@@ -2386,7 +2455,7 @@
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="page-footer"><span>RustProxy <b>0.25.6</b></span><span id="footer-status">API - /
|
||||
<footer class="page-footer"><span>RustProxy <b>0.25.7</b></span><span id="footer-status">API - /
|
||||
dropped: 0</span></footer>
|
||||
</div>
|
||||
</main>
|
||||
@@ -3097,7 +3166,7 @@ max_execution_time = 30</code></pre>
|
||||
<article class="card about-card">
|
||||
<div class="about-product">
|
||||
<span aria-hidden="true" class="brand-mark">r<span>/</span>p</span>
|
||||
<div><span class="small-label">RustProxy</span><strong>0.25.6</strong></div>
|
||||
<div><span class="small-label">RustProxy</span><strong>0.25.7</strong></div>
|
||||
</div>
|
||||
<dl class="about-meta">
|
||||
<div>
|
||||
|
||||
+196
-2
@@ -50,6 +50,9 @@
|
||||
--shadow-mid: #0006;
|
||||
--shadow-strong: #0009;
|
||||
--neutral-soft: rgba(127, 127, 127, .05);
|
||||
--scrollbar-track: #151a16;
|
||||
--scrollbar-thumb: #4f5d50;
|
||||
--scrollbar-thumb-hover: #6b7b69;
|
||||
--mono: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||
--sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif
|
||||
}
|
||||
@@ -97,11 +100,39 @@ html[data-theme="light"] {
|
||||
--transform-chart: #7d4fa3;
|
||||
--shadow-mid: rgb(31 39 33 / .16);
|
||||
--shadow-strong: rgb(31 39 33 / .26);
|
||||
--neutral-soft: rgba(70, 78, 72, .05)
|
||||
--neutral-soft: rgba(70, 78, 72, .05);
|
||||
--scrollbar-track: #e7ece4;
|
||||
--scrollbar-thumb: #a9b5a6;
|
||||
--scrollbar-thumb-hover: #879684
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box
|
||||
box-sizing: border-box;
|
||||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||
scrollbar-width: thin
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-track)
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb);
|
||||
border: 2px solid var(--scrollbar-track);
|
||||
border-radius: 999px
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-thumb-hover)
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-corner {
|
||||
background: var(--scrollbar-track)
|
||||
}
|
||||
|
||||
html.app-loading {
|
||||
@@ -2463,6 +2494,63 @@ td {
|
||||
flex: 0 0 auto
|
||||
}
|
||||
|
||||
.vh-enable-control {
|
||||
display: grid !important;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
grid-template-rows: auto auto;
|
||||
align-items: center;
|
||||
gap: 2px 10px;
|
||||
margin-top: 5px !important;
|
||||
padding: 11px 12px;
|
||||
border: 1px solid var(--pill-live-line);
|
||||
background: var(--panel2);
|
||||
box-shadow: inset 3px 0 0 var(--green)
|
||||
}
|
||||
|
||||
.vh-enable-control>input {
|
||||
grid-column: 1;
|
||||
grid-row: 1/3;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: var(--green)
|
||||
}
|
||||
|
||||
.vh-enable-control>span {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
.vh-enable-hint {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
color: var(--muted);
|
||||
font-size: 10px
|
||||
}
|
||||
|
||||
.vh-enable-state {
|
||||
grid-column: 3;
|
||||
grid-row: 1/3;
|
||||
padding: 3px 7px;
|
||||
border: 1px solid var(--pill-live-line);
|
||||
color: var(--green);
|
||||
font: 600 10px var(--mono);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em
|
||||
}
|
||||
|
||||
.vh-enable-control.is-disabled {
|
||||
border-color: var(--danger-line-strong);
|
||||
box-shadow: inset 3px 0 0 var(--red)
|
||||
}
|
||||
|
||||
.vh-enable-control.is-disabled .vh-enable-state {
|
||||
border-color: var(--danger-line);
|
||||
color: var(--red)
|
||||
}
|
||||
|
||||
.vh-wide {
|
||||
grid-column: 1/-1;
|
||||
min-width: 0
|
||||
@@ -2524,6 +2612,78 @@ td {
|
||||
margin-bottom: 12px
|
||||
}
|
||||
|
||||
.backend-card-heading {
|
||||
align-items: flex-start;
|
||||
gap: 10px
|
||||
}
|
||||
|
||||
.backend-table-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-left: auto
|
||||
}
|
||||
|
||||
#pool-search {
|
||||
width: min(250px, 30vw);
|
||||
min-width: 170px
|
||||
}
|
||||
|
||||
.backend-sort-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--muted);
|
||||
font-size: 11px
|
||||
}
|
||||
|
||||
.backend-sort-control select {
|
||||
width: auto;
|
||||
min-width: 120px
|
||||
}
|
||||
|
||||
.backend-sort-direction {
|
||||
min-width: 48px
|
||||
}
|
||||
|
||||
.backend-column-picker {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.backend-column-picker>summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
user-select: none
|
||||
}
|
||||
|
||||
.backend-column-picker>summary::-webkit-details-marker {
|
||||
display: none
|
||||
}
|
||||
|
||||
.backend-column-options {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(145px, 1fr));
|
||||
gap: 1px 12px;
|
||||
min-width: 330px;
|
||||
max-height: 330px;
|
||||
overflow: auto;
|
||||
padding: 10px 12px 12px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
box-shadow: 0 12px 28px var(--shadow-strong)
|
||||
}
|
||||
|
||||
.backend-column-options .check {
|
||||
margin-top: 4px !important;
|
||||
font-size: 11px
|
||||
}
|
||||
|
||||
.pool-card .fine {
|
||||
margin: 0 0 8px
|
||||
}
|
||||
@@ -4861,6 +5021,16 @@ dialog .window-heading {
|
||||
margin-top: 8px
|
||||
}
|
||||
|
||||
.vhost-quick-toggle.is-enable {
|
||||
border-color: var(--pill-live-line);
|
||||
color: var(--green)
|
||||
}
|
||||
|
||||
.vhost-quick-toggle.is-disable {
|
||||
border-color: var(--danger-line);
|
||||
color: var(--red)
|
||||
}
|
||||
|
||||
@media(max-width:980px) {
|
||||
.logging-grid {
|
||||
grid-template-columns: 1fr
|
||||
@@ -4896,6 +5066,30 @@ dialog .window-heading {
|
||||
min-width: 0
|
||||
}
|
||||
|
||||
.backend-card-heading {
|
||||
align-items: stretch;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.backend-table-tools {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
margin-left: 0
|
||||
}
|
||||
|
||||
#pool-search {
|
||||
flex: 1 1 100%;
|
||||
width: 100%;
|
||||
min-width: 0
|
||||
}
|
||||
|
||||
.backend-column-options {
|
||||
right: auto;
|
||||
left: 0;
|
||||
grid-template-columns: 1fr;
|
||||
min-width: min(290px, 82vw)
|
||||
}
|
||||
|
||||
.vhost-card-head .entity-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-start
|
||||
|
||||
@@ -38,7 +38,11 @@
|
||||
editing = value.id || null; revision = envelope?.revision ?? null; dirty = false; initialized = true;
|
||||
container.replaceChildren(); $("vhost-editing").textContent = t(editing ? "vhEditLabel" : "vhNewLabel");
|
||||
let g = group("vhEditor"); field(g, "id", "vhId", value.id, "text", { required: true, maxLength: 64, pattern: "[A-Za-z0-9_-]{1,64}" }); if (editing) note(g, "vhIdRenameHelp");
|
||||
readHosts = hostTagEditor(g, value.frontend?.hosts || []); field(g, "enabled", "vhEnabled", value.enabled !== false, "checkbox");
|
||||
readHosts = hostTagEditor(g, value.frontend?.hosts || []);
|
||||
const enabledControl = field(g, "enabled", "vhEnabled", value.enabled !== false, "checkbox"), enabledLabel = enabledControl.closest("label"), enabledState = el("strong", "", "vh-enable-state");
|
||||
if (enabledLabel) { enabledLabel.classList.add("vh-enable-control", "vh-wide"); enabledLabel.append(text("small", "vhEnableHint", "vh-enable-hint"), enabledState); }
|
||||
const syncEnabledState = () => { const key = enabledControl.checked ? "vhEnabled" : "vhDisabled"; enabledLabel?.classList.toggle("is-disabled", !enabledControl.checked); enabledLabel?.classList.toggle("is-enabled", enabledControl.checked); enabledState.dataset.i18n = key; enabledState.textContent = t(key); };
|
||||
enabledControl.addEventListener("change", syncEnabledState); syncEnabledState();
|
||||
const mainSource = field(g, "source-type", "sourceType", value.source?.type || "reverse_proxy", "select", { values: [["reverse_proxy", "sourceReverseProxy"], ["web_server", "sourceWebServer"]] });
|
||||
const mainWebService = field(g, "source-web-service", "webService", value.source?.web_service_id || "", "select"); fillWebServices(mainWebService, value.source?.web_service_id || "");
|
||||
const syncMainSource = () => { const web = mainSource.value === "web_server"; mainWebService.disabled = !web; mainWebService.required = web; }; mainSource.addEventListener("change", syncMainSource); syncMainSource();
|
||||
|
||||
@@ -1,3 +1,87 @@
|
||||
const poolColumns = ["vhost", "backend", "status", "active", "requests", "errors", "latency", "traffic", "generation", "generationRequests", "generationAge", "rotations", "lastFailure", "lastRecovery"];
|
||||
const defaultPoolTablePrefs = () => ({ sort: "vhost", descending: false, columns: [...poolColumns] });
|
||||
let poolTablePrefs = defaultPoolTablePrefs(), poolPreferenceSaveRunning = false, poolPreferenceSaveQueued = false;
|
||||
function normalizePoolTablePrefs(preferences) {
|
||||
const selected = Array.isArray(preferences?.backend_table_columns) ? preferences.backend_table_columns.filter(key => poolColumns.includes(key)) : [];
|
||||
return {
|
||||
sort: poolColumns.includes(preferences?.backend_table_sort) ? preferences.backend_table_sort : "vhost",
|
||||
descending: preferences?.backend_table_descending === true,
|
||||
columns: selected.length ? selected : [...poolColumns],
|
||||
};
|
||||
}
|
||||
function applyPoolPreferences(preferences) {
|
||||
poolTablePrefs = normalizePoolTablePrefs(preferences);
|
||||
const sort = $("pool-sort"); if (sort) sort.value = poolTablePrefs.sort;
|
||||
syncPoolColumns(); syncPoolSortDirection(); applyPoolTableView();
|
||||
}
|
||||
async function savePoolTablePrefs() {
|
||||
if (!authenticated) return;
|
||||
poolPreferenceSaveQueued = true;
|
||||
if (poolPreferenceSaveRunning) return;
|
||||
poolPreferenceSaveRunning = true;
|
||||
try {
|
||||
while (poolPreferenceSaveQueued && authenticated) {
|
||||
poolPreferenceSaveQueued = false;
|
||||
const saved = await window.rustProxyPreferences.update({
|
||||
backend_table_columns: poolColumns.filter(key => poolTablePrefs.columns.includes(key)),
|
||||
backend_table_sort: poolTablePrefs.sort,
|
||||
backend_table_descending: poolTablePrefs.descending,
|
||||
});
|
||||
if (!authenticated) return;
|
||||
if (!poolPreferenceSaveQueued) poolTablePrefs = normalizePoolTablePrefs(saved);
|
||||
}
|
||||
syncPoolColumns(); syncPoolSortDirection();
|
||||
} catch (error) { if (error.status !== 401) toast(error.message, true); }
|
||||
finally { poolPreferenceSaveRunning = false; }
|
||||
}
|
||||
function syncPoolColumns() {
|
||||
const visible = new Set(poolTablePrefs.columns);
|
||||
document.querySelectorAll("[data-pool-column]").forEach(node => { node.hidden = !visible.has(node.dataset.poolColumn); });
|
||||
document.querySelectorAll("[data-pool-column-toggle]").forEach(input => { input.checked = visible.has(input.dataset.poolColumnToggle); });
|
||||
}
|
||||
function syncPoolSortDirection() {
|
||||
const control = $("pool-sort-direction"); if (!control) return;
|
||||
control.textContent = poolTablePrefs.descending ? "Z-A" : "A-Z";
|
||||
control.setAttribute("aria-label", t(poolTablePrefs.descending ? "backendSortDescending" : "backendSortAscending"));
|
||||
}
|
||||
function applyPoolTableView() {
|
||||
const body = $("pool-rows"); if (!body) return;
|
||||
const query = ($("pool-search")?.value || "").trim().toLowerCase();
|
||||
const key = $("pool-sort")?.value || poolTablePrefs.sort;
|
||||
const rows = [...body.querySelectorAll("tr")];
|
||||
rows.sort((left, right) => {
|
||||
const a = left.poolSort?.[key] ?? "", b = right.poolSort?.[key] ?? "";
|
||||
const result = typeof a === "number" && typeof b === "number" ? a - b : String(a).localeCompare(String(b), locale(), { numeric: true, sensitivity: "base" });
|
||||
return poolTablePrefs.descending ? -result : result;
|
||||
});
|
||||
body.replaceChildren(...rows);
|
||||
let visible = 0;
|
||||
for (const row of rows) { const match = !query || (row.poolSearchValue || "").includes(query); row.hidden = !match; if (match) visible++; }
|
||||
syncPoolColumns(); syncPoolSortDirection();
|
||||
const empty = $("pool-empty");
|
||||
if (empty) { empty.textContent = query && rows.length && !visible ? t("backendSearchEmpty") : t("vhHealthEmpty"); empty.hidden = visible > 0 || (!rows.length && !document.querySelector("#backend-diagnostics-card:not([hidden])")); }
|
||||
}
|
||||
function initPoolTableTools() {
|
||||
const sort = $("pool-sort"), direction = $("pool-sort-direction"), search = $("pool-search");
|
||||
if (sort) { sort.value = poolTablePrefs.sort; sort.addEventListener("change", () => { poolTablePrefs.sort = sort.value; void savePoolTablePrefs(); applyPoolTableView(); }); }
|
||||
direction?.addEventListener("click", () => { poolTablePrefs.descending = !poolTablePrefs.descending; void savePoolTablePrefs(); applyPoolTableView(); });
|
||||
search?.addEventListener("input", applyPoolTableView);
|
||||
document.querySelectorAll("[data-pool-column-toggle]").forEach(input => input.addEventListener("change", () => {
|
||||
const visible = new Set(poolTablePrefs.columns);
|
||||
if (input.checked) visible.add(input.dataset.poolColumnToggle); else visible.delete(input.dataset.poolColumnToggle);
|
||||
if (!visible.size) { input.checked = true; toast(t("backendColumnsRequired"), true); return; }
|
||||
poolTablePrefs.columns = poolColumns.filter(key => visible.has(key)); void savePoolTablePrefs(); syncPoolColumns();
|
||||
}));
|
||||
syncPoolColumns(); syncPoolSortDirection();
|
||||
}
|
||||
async function toggleVhostEnabled(vhost) {
|
||||
if (!envelope || busy) return;
|
||||
const next = structuredClone(envelope.config), target = (next.vhosts || []).find(item => item.id === vhost.id);
|
||||
if (!target) return;
|
||||
target.enabled = vhost.enabled === false;
|
||||
if (await saveSection("vhosts", next.vhosts || [])) render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!envelope) return;
|
||||
if (!initialized) load(defaults(), [], false);
|
||||
@@ -33,7 +117,9 @@
|
||||
const browseTls = configuredEndpoints.some(endpoint => endpoint.binding?.tls === true);
|
||||
const openButton = button("vhOpenBrowser", () => { if (browseHost) window.open(`${browseTls ? "https" : "http"}://${browseHost}/`, "_blank", "noopener,noreferrer"); });
|
||||
openButton.disabled = !browseHost || v.enabled === false;
|
||||
actions.append(openButton, button("edit", async () => { if (await allowDiscard()) load(structuredClone(v)); }), button("remove", async () => {
|
||||
const quickToggle = button(v.enabled === false ? "vhQuickEnable" : "vhQuickDisable", () => toggleVhostEnabled(v), `quiet vhost-quick-toggle ${v.enabled === false ? "is-enable" : "is-disable"}`);
|
||||
quickToggle.setAttribute("aria-pressed", String(v.enabled !== false));
|
||||
actions.append(quickToggle, openButton, button("edit", async () => { if (await allowDiscard()) load(structuredClone(v)); }), button("remove", async () => {
|
||||
if (busy || !(await appConfirm(t("vhDelete"), { danger: true }))) return;
|
||||
const next = structuredClone(envelope.config); next.vhosts = (next.vhosts || []).filter(item => item.id !== v.id);
|
||||
if (await saveSection("vhosts", next.vhosts || [])) { if (editing === v.id) load(); render(); }
|
||||
@@ -147,7 +233,9 @@
|
||||
const generationAgeText = generationAgeRaw == null || !Number.isFinite(generationAge) ? "-" : generationAge >= 3600 ? `${Math.floor(generationAge / 3600)}h ${Math.floor((generationAge % 3600) / 60)}m` : generationAge >= 60 ? `${Math.floor(generationAge / 60)}m ${Math.floor(generationAge % 60)}s` : `${Math.floor(generationAge)}s`;
|
||||
const row = el("tr"); if (vhostDisabled) row.classList.add("pool-vhost-disabled"); const poolDomain = status.backend_pool_domains?.[pool.id]; const poolLabel = poolDomain ? `${pool.id} (${poolDomain})` : pool.id; const cells = [poolLabel, `${member.id}${member.backup ? " / " + t("vhBackup") : ""}`, label, activeLimit, member.requests, member.errors,
|
||||
member.ewma_latency_ms > 0 ? `${member.ewma_latency_ms.toFixed(1)} ms` : "-", `${rateBytes(memberTraffic)}/s`, member.upstream_client_generation ?? "-", member.upstream_client_generation_requests ?? "-", generationAgeText, member.upstream_client_rotations_total ?? "-", lastFailure, lastRecovery];
|
||||
cells.forEach((value, index) => { const cell = el("td", String(value ?? "-")); cell.title = index === 2 ? (vhostDisabled ? t("vhDisabled") : `${label} · ${healthDetail}`) : index === 12 ? (member.last_failure_detail || lastFailure) : String(value ?? "-"); if (index === 2 && !vhostDisabled) cell.className = member.status === "down" ? "status-error" : member.status === "up" ? "status-good" : ""; if (index === 12 && member.last_failure_at_ms) cell.classList.add("backend-last-failure"); row.append(cell); }); rows.push(row);
|
||||
const sortValues = { vhost: poolLabel.toLowerCase(), backend: String(member.id || "").toLowerCase(), status: label.toLowerCase(), active: Number(member.active) || 0, requests: Number(member.requests) || 0, errors: Number(member.errors) || 0, latency: Number(member.ewma_latency_ms) || 0, traffic: memberTraffic, generation: Number(member.upstream_client_generation) || 0, generationRequests: Number(member.upstream_client_generation_requests) || 0, generationAge: Number.isFinite(generationAge) ? generationAge : -1, rotations: Number(member.upstream_client_rotations_total) || 0, lastFailure: Number(member.last_failure_at_ms) || 0, lastRecovery: Number(member.last_recovered_ms) || 0 };
|
||||
row.poolSort = sortValues; row.poolSearchValue = [poolLabel, member.id, label, member.status, member.last_failure_class, member.last_failure_detail].filter(Boolean).join(" ").toLowerCase();
|
||||
cells.forEach((value, index) => { const cell = el("td", String(value ?? "-")); cell.dataset.poolColumn = poolColumns[index]; cell.title = index === 2 ? (vhostDisabled ? t("vhDisabled") : `${label} · ${healthDetail}`) : index === 12 ? (member.last_failure_detail || lastFailure) : String(value ?? "-"); if (index === 2 && !vhostDisabled) cell.className = member.status === "down" ? "status-error" : member.status === "up" ? "status-good" : ""; if (index === 12 && member.last_failure_at_ms) cell.classList.add("backend-last-failure"); row.append(cell); }); rows.push(row);
|
||||
if (!vhostDisabled && member.status === "up") up++; active += Number(member.active) || 0; requests += Number(member.requests) || 0; errors += Number(member.errors) || 0; traffic += memberTraffic;
|
||||
const latency = Number(member.ewma_latency_ms || member.latency_ms) || 0; if (latency > 0) { const weight = Math.max(1, Number(member.requests) || 1); weightedLatency += latency * weight; latencyWeight += weight; }
|
||||
}
|
||||
@@ -190,9 +278,9 @@
|
||||
vhost.source?.type !== "web_server" || (vhost.routes || []).some(route => !route.response && (route.source?.type || "reverse_proxy") === "reverse_proxy")
|
||||
);
|
||||
$("pool-rows").replaceChildren(...rows);
|
||||
$("pool-empty").hidden = rows.length > 0 || !hasConfiguredProxySource;
|
||||
const backendDiagnosticsCard = $("backend-diagnostics-card");
|
||||
if (backendDiagnosticsCard) backendDiagnosticsCard.hidden = !hasConfiguredProxySource;
|
||||
applyPoolTableView();
|
||||
document.querySelectorAll("[data-health-for]").forEach(node => node.textContent = summaries.get(node.dataset.healthFor) || t("vhHealthEmpty"));
|
||||
const fill = (attr, format) => document.querySelectorAll(`[data-${attr.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)}]`).forEach(node => { const data = aggregate.get(node.dataset[attr]); node.textContent = data ? format(data) : "-"; });
|
||||
fill("vhostHealth", d => d.disabled ? t("vhDisabled") : d.staticOnly ? t("vhUp") : d.healthMode === "off" ? t("vhHealthDisabled") : `${d.up}/${d.total} ${t("vhUp")}`);
|
||||
@@ -242,6 +330,7 @@
|
||||
load(saved ? structuredClone(saved) : defaults());
|
||||
});
|
||||
$("pool-open").addEventListener("click", () => setPanel("vhosts"));
|
||||
initPoolTableTools();
|
||||
$("location-help-close")?.addEventListener("click", () => $("location-help-dialog")?.close());
|
||||
window.rustProxyVhosts = { render, renderStatus, reset() { dialog.close(); draftEpoch++; stagedCertificates = []; uploading = false; editing = null; revision = null; dirty = false; initialized = false; chartHistory.clear(); backendTrafficHistory.clear(); vhostTrafficHistory.clear(); container.replaceChildren(); $("vhost-search").value = ""; $("vhost-list").replaceChildren(); $("pool-rows").replaceChildren(); } };
|
||||
window.rustProxyVhosts = { render, renderStatus, applyPreferences: applyPoolPreferences, reset() { dialog.close(); draftEpoch++; stagedCertificates = []; uploading = false; editing = null; revision = null; dirty = false; initialized = false; poolTablePrefs = defaultPoolTablePrefs(); poolPreferenceSaveRunning = false; poolPreferenceSaveQueued = false; chartHistory.clear(); backendTrafficHistory.clear(); vhostTrafficHistory.clear(); container.replaceChildren(); $("vhost-search").value = ""; if ($("pool-search")) $("pool-search").value = ""; $("vhost-list").replaceChildren(); $("pool-rows").replaceChildren(); const sort = $("pool-sort"); if (sort) sort.value = poolTablePrefs.sort; applyPoolTableView(); } };
|
||||
if (envelope) render(); if (latestStatus) renderStatus(latestStatus);
|
||||
@@ -29,7 +29,6 @@ impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for RedactingStderr {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn redact_regex(pattern: &'static str) -> &'static regex::Regex {
|
||||
static AUTH: OnceLock<regex::Regex> = OnceLock::new();
|
||||
static QUERY: OnceLock<regex::Regex> = OnceLock::new();
|
||||
|
||||
@@ -41,7 +41,7 @@ use zeroize::Zeroizing;
|
||||
#[command(
|
||||
name = "rust-proxy",
|
||||
version,
|
||||
about = "RustProxy - independent Rust reverse proxy and static Web Server 0.25.6"
|
||||
about = "RustProxy - independent Rust reverse proxy and static Web Server 0.25.7"
|
||||
)]
|
||||
struct Cli {
|
||||
#[arg(
|
||||
|
||||
+1
-1
@@ -239,7 +239,7 @@ async fn live_logs_command(dir: PathBuf, addr: SocketAddr) -> Result<()> {
|
||||
}
|
||||
});
|
||||
let interactive = io::stdin().is_terminal() && io::stdout().is_terminal();
|
||||
println!("RustProxy live logs 0.25.6 - all vhosts. Type 'help' for interactive filters; Ctrl-C or 'quit' exits.");
|
||||
println!("RustProxy live logs 0.25.7 - all vhosts. Type 'help' for interactive filters; Ctrl-C or 'quit' exits.");
|
||||
if interactive { print!("> "); io::stdout().flush()?; }
|
||||
let mut filters = LiveLogFilters::default();
|
||||
let mut history = std::collections::BTreeMap::<u64, serde_json::Value>::new();
|
||||
|
||||
@@ -102,6 +102,8 @@ fn render_backends(
|
||||
("io_errors_total", "passive_io_errors_total", "counter"),
|
||||
("slow_responses_total", "slow_responses_total", "counter"),
|
||||
("slow_responses_total", "passive_slow_total", "counter"),
|
||||
("retry_attempts_total", "retry_attempts_total", "counter"),
|
||||
("retry_successes_total", "retry_successes_total", "counter"),
|
||||
(
|
||||
"capacity_rejections_total",
|
||||
"capacity_rejections_total",
|
||||
@@ -117,6 +119,17 @@ fn render_backends(
|
||||
),
|
||||
("read_timeouts_total", "read_timeouts_total", "counter"),
|
||||
("send_timeouts_total", "send_timeouts_total", "counter"),
|
||||
("upstream_client_generation", "upstream_client_generation", "gauge"),
|
||||
(
|
||||
"upstream_client_generation_requests",
|
||||
"upstream_client_generation_requests",
|
||||
"gauge",
|
||||
),
|
||||
(
|
||||
"upstream_client_rotations_total",
|
||||
"upstream_client_rotations_total",
|
||||
"counter",
|
||||
),
|
||||
(
|
||||
"max_concurrent_requests",
|
||||
"max_concurrent_requests",
|
||||
@@ -151,6 +164,11 @@ fn render_backends(
|
||||
1000.0,
|
||||
),
|
||||
("traffic_bytes_per_second", "traffic_bytes_per_second", 1.0),
|
||||
(
|
||||
"upstream_client_generation_age_seconds",
|
||||
"upstream_client_generation_age_seconds",
|
||||
1.0,
|
||||
),
|
||||
] {
|
||||
if let Some(n) = member[field].as_f64() {
|
||||
f.add(
|
||||
@@ -379,6 +397,8 @@ fn render_docker(
|
||||
("io_errors_total", "passive_io_errors_total", "counter"),
|
||||
("slow_responses_total", "slow_responses_total", "counter"),
|
||||
("slow_responses_total", "passive_slow_total", "counter"),
|
||||
("retry_attempts_total", "retry_attempts_total", "counter"),
|
||||
("retry_successes_total", "retry_successes_total", "counter"),
|
||||
(
|
||||
"capacity_rejections_total",
|
||||
"capacity_rejections_total",
|
||||
@@ -394,6 +414,17 @@ fn render_docker(
|
||||
),
|
||||
("read_timeouts_total", "read_timeouts_total", "counter"),
|
||||
("send_timeouts_total", "send_timeouts_total", "counter"),
|
||||
("upstream_client_generation", "upstream_client_generation", "gauge"),
|
||||
(
|
||||
"upstream_client_generation_requests",
|
||||
"upstream_client_generation_requests",
|
||||
"gauge",
|
||||
),
|
||||
(
|
||||
"upstream_client_rotations_total",
|
||||
"upstream_client_rotations_total",
|
||||
"counter",
|
||||
),
|
||||
(
|
||||
"max_concurrent_requests",
|
||||
"max_concurrent_requests",
|
||||
@@ -428,6 +459,11 @@ fn render_docker(
|
||||
1000.0,
|
||||
),
|
||||
("traffic_bytes_per_second", "traffic_bytes_per_second", 1.0),
|
||||
(
|
||||
"upstream_client_generation_age_seconds",
|
||||
"upstream_client_generation_age_seconds",
|
||||
1.0,
|
||||
),
|
||||
] {
|
||||
if let Some(n) = member[field].as_f64() {
|
||||
f.add(
|
||||
|
||||
@@ -156,7 +156,7 @@ pub(super) fn fastcgi_params(
|
||||
}
|
||||
params.insert("HTTPS".into(), if context.tls { "on" } else { "off" }.into());
|
||||
params.insert("GATEWAY_INTERFACE".into(), "CGI/1.1".into());
|
||||
params.insert("SERVER_SOFTWARE".into(), "RustProxy/0.25.6".into());
|
||||
params.insert("SERVER_SOFTWARE".into(), "RustProxy/0.25.7".into());
|
||||
params.insert("REDIRECT_STATUS".into(), "200".into());
|
||||
if let Some(value) = headers.get(CONTENT_TYPE).and_then(|value| value.to_str().ok()) {
|
||||
params.insert("CONTENT_TYPE".into(), value.into());
|
||||
|
||||
@@ -180,7 +180,7 @@ pub(super) fn directory_listing(path: &str, entries: &[DirectoryEntry]) -> Vec<u
|
||||
"<a class=\"{class}\" href=\"{href}\"><span class=\"type\">{type_label}</span><span class=\"name\">{label}{slash}</span><span class=\"kind\">{kind}</span></a>"
|
||||
));
|
||||
}
|
||||
html.push_str(r#"</div></section><footer class="page-footer"><span>RustProxy <b>0.25.6</b></span><span>Web Server · Directory listing</span></footer></main></body></html>"#);
|
||||
html.push_str(r#"</div></section><footer class="page-footer"><span>RustProxy <b>0.25.7</b></span><span>Web Server · Directory listing</span></footer></main></body></html>"#);
|
||||
html.into_bytes()
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ async fn empty_index_can_go_directly_to_directory_listing() {
|
||||
let body = std::str::from_utf8(&body).unwrap();
|
||||
assert!(body.contains("file.txt"));
|
||||
assert!(body.contains("DIRECTORY LISTING"));
|
||||
assert!(body.contains("RustProxy <b>0.25.6</b>"));
|
||||
assert!(body.contains("RustProxy <b>0.25.7</b>"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in new issue
Block a user