diff options
| author | Nikolay Govorov <me@govorov.online> | 2026-02-01 05:07:00 +0000 |
|---|---|---|
| committer | Nikolay Govorov <me@govorov.online> | 2026-02-01 09:36:48 +0000 |
| commit | 9e52f7965db5fdb9e32bf46dedcbac136c8f71a4 (patch) | |
| tree | 7a56d26ef8c5560b33ecdc673562cad8ffc14688 | |
| parent | ea030e0dcdc9f282b3e3c96623e5e323b2a8b106 (diff) | |
| download | tar tar.gz tar.bz2 tar.lz tar.xz tar.zst zip | |
Refactor frontend layer
Diffstat
| -rw-r--r-- | Cargo.lock | 310 | +70 −240 |
| -rw-r--r-- | Cargo.toml | 61 | +19 −42 |
| -rw-r--r-- | LICENSES/OFL-1.1.txt | 93 | +93 −0 |
| -rw-r--r-- | REUSE.toml | 8 | +8 −0 |
| -rw-r--r-- | src/assets/base.css | 77 | +77 −0 |
| -rw-r--r-- | src/assets/jetbrainsmono/JetBrainsMono-Italic[wght].woff2 | bin | 0 -> 122452 bytes |
| -rw-r--r-- | src/assets/jetbrainsmono/JetBrainsMono[wght].woff2 | bin | 0 -> 113700 bytes |
| -rw-r--r-- | src/controller_web.rs | 289 | +129 −160 |
8 files changed, 396 insertions, 442 deletions
diff --git a/Cargo.lock b/Cargo.lock index c3f41c1..7017aee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,33 +218,17 @@ checksum = "fef252edff26ddba56bbcdf2ee3307b8129acb86f5749b68990c168a6fcc9c76" dependencies = [ "axum", "axum-core", - "axum-macros", "bytes", - "fastrand", - "form_urlencoded", "futures-core", "futures-util", - "headers", "http", "http-body", "http-body-util", "mime", - "multer", - "percent-encoding", "pin-project-lite", - "prost", - "rustversion", - "serde_core", - "serde_html_form", - "serde_json", - "serde_path_to_error", - "tokio", - "tokio-stream", - "tokio-util", "tower-layer", "tower-service", "tracing", - "typed-json", ] [[package]] @@ -400,18 +384,6 @@ dependencies = [ ] [[package]] -name = "cargo-config2" -version = "0.1.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3795d3a48839a46854805f56c8fe9c558f10804bcf57df53925ca843d87c788f" -dependencies = [ - "serde", - "serde_derive", - "toml", - "windows-sys 0.61.2", -] - -[[package]] name = "cargo-deny" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -456,33 +428,6 @@ dependencies = [ ] [[package]] -name = "cargo-llvm-cov" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8132c5e1b6489a345e12bbc3deb7154cb8d8b51bd9d9b168505eff5e5ce88ee" -dependencies = [ - "anyhow", - "camino", - "cargo-config2", - "duct", - "fs-err", - "glob", - "lcov2cobertura", - "lexopt", - "opener", - "regex", - "rustc-demangle", - "ruzstd", - "serde", - "serde_derive", - "serde_json", - "shell-escape", - "tar", - "termcolor", - "walkdir", -] - -[[package]] name = "cargo-lock" version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -864,18 +809,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "duct" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e66e9c0c03d094e1a0ba1be130b849034aa80c3a2ab8ee94316bc809f3fa684" -dependencies = [ - "libc", - "os_pipe", - "shared_child", - "shared_thread", -] - -[[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1961,12 +1894,6 @@ dependencies = [ ] [[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] name = "globset" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2089,30 +2016,6 @@ dependencies = [ ] [[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - -[[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2606,24 +2509,6 @@ dependencies = [ ] [[package]] -name = "lcov2cobertura" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaa0cf456e88a45378a5737f228c0800175d94be6856908dc4718b3a91c7c9f8" -dependencies = [ - "anyhow", - "quick-xml", - "regex", - "rustc-demangle", -] - -[[package]] -name = "lexopt" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7" - -[[package]] name = "libc" version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2700,6 +2585,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] +name = "maud" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e" +dependencies = [ + "axum-core", + "http", + "itoa", + "maud_macros", +] + +[[package]] +name = "maud_macros" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] name = "maybe-async" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2752,15 +2661,6 @@ dependencies = [ ] [[package]] -name = "minijinja" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ea9ac0a51fb5112607099560fdf0f90366ab088a2a9e6e8ae176794e9806aa" -dependencies = [ - "serde", -] - -[[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2828,15 +2728,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" [[package]] -name = "normpath" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2910,17 +2801,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "opener" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9024962ab91e00c89d2a14352a8d0fc1a64346bf96f1839b45c09149564e47" -dependencies = [ - "bstr", - "normpath", - "windows-sys 0.60.2", -] - -[[package]] name = "openssl" version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3065,16 +2945,6 @@ dependencies = [ ] [[package]] -name = "os_pipe" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3252,6 +3122,18 @@ dependencies = [ ] [[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] name = "prodash" version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3299,15 +3181,6 @@ dependencies = [ ] [[package]] -name = "quick-xml" -version = "0.37.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", -] - -[[package]] name = "quote" version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3571,10 +3444,38 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.27" +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "sha2", + "walkdir", +] [[package]] name = "rustc-stable-hash" @@ -3693,12 +3594,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "ruzstd" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" - -[[package]] name = "ryu" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3834,19 +3729,6 @@ dependencies = [ ] [[package]] -name = "serde_html_form" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" -dependencies = [ - "form_urlencoded", - "indexmap", - "itoa", - "ryu", - "serde_core", -] - -[[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3939,28 +3821,6 @@ dependencies = [ ] [[package]] -name = "shared_child" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "shared_thread" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" - -[[package]] -name = "shell-escape" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" - -[[package]] name = "shell-words" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4428,17 +4288,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] -name = "tar" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] name = "target-lexicon" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4918,16 +4767,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] -name = "typed-json" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6024a8d0025400b3f6b189366e9aa92012cf9c4fe1cd2620848dd61425c49eed" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5597,16 +5436,6 @@ dependencies = [ ] [[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix", -] - -[[package]] name = "yoke" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5731,7 +5560,6 @@ dependencies = [ "bytes", "bytesize", "cargo-deny", - "cargo-llvm-cov", "chrono", "crc32fast", "hex", @@ -5739,12 +5567,14 @@ dependencies = [ "hyper", "hyper-tls", "hyper-util", - "minijinja", + "maud", + "mime_guess", "opentelemetry", "opentelemetry-appender-tracing", "opentelemetry-otlp", "opentelemetry-semantic-conventions", "opentelemetry_sdk", + "rust-embed", "rustls", "sd-notify", "semver", diff --git a/Cargo.toml b/Cargo.toml index 3f2528a..52168f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,37 +10,10 @@ repository = "https://github.com/mrdimidium/Zorian" [dependencies] axum = { version = "0.8", features = ["http2", "macros", "multipart"] } -axum-server = { version = "0.8", default-features = false, features = [ - "tls-rustls-no-provider", -] } -rustls = { version = "0.23", default-features = false, features = [ - "ring", - "std", - "tls12", -] } -axum-extra = { version = "0.12", features = [ - "async-read-body", - "attachment", - "cached", - "erased-json", - "error-response", - "file-stream", - "form", - "handler", - "json-deserializer", - "json-lines", - "middleware", - "multipart", - "optional-path", - "protobuf", - "query", - "routing", - "tracing", - "typed-header", - "typed-routing", - "with-rejection", -] } +axum-extra = "0.12" +axum-server = { version = "0.8", features = ["tls-rustls-no-provider"] } bytes = "1.11" +bytesize = { version = "2.3", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] } crc32fast = "1.5" hex = "0.4" @@ -48,7 +21,14 @@ http-body-util = "0.1" hyper = "1.8" hyper-tls = "0.6" hyper-util = { version = "0.1", features = ["full"] } -minijinja = "2.14" +maud = { version = "0.27", features = ["axum"] } +mime_guess = "2.0" +rust-embed = "8.11" +rustls = { version = "0.23", default-features = false, features = [ + "ring", + "std", + "tls12", +] } semver = "1.0" serde = { version = "1.0", features = ["derive"] } sqlx = { version = "0.8", features = [ @@ -59,22 +39,17 @@ sqlx = { version = "0.8", features = [ "chrono", "json", ] } -bytesize = { version = "2.3", features = ["serde"] } thiserror = "2.0" tokio = { version = "1.49", features = ["full"] } toml = "0.9" +tonic = "0.14" tower = { version = "0.5", features = ["full"] } -tower_governor = { version = "0.8", features = ["axum", "tracing"] } tower-http = { version = "0.6", features = ["full"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +tower_governor = { version = "0.8", features = ["axum", "tracing"] } url = "2.5" uuid = { version = "1.19", features = ["v4", "v5"] } -[target.'cfg(target_os = "linux")'.dependencies] -sd-notify = { version = "0.4" } - -# OpenTelemetry +# telemetry opentelemetry = "0.31" opentelemetry-appender-tracing = "0.31" opentelemetry-otlp = { version = "0.31", features = [ @@ -86,7 +61,6 @@ opentelemetry-otlp = { version = "0.31", features = [ "tls", "tls-roots", ] } -tonic = "0.14" opentelemetry-semantic-conventions = "0.31" opentelemetry_sdk = { version = "0.31", features = [ "rt-tokio", @@ -94,10 +68,13 @@ opentelemetry_sdk = { version = "0.31", features = [ "metrics", "trace", ] } +tracing = "0.1" tracing-opentelemetry = "0.32" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } + +[target.'cfg(target_os = "linux")'.dependencies] +sd-notify = { version = "0.4" } [dev-dependencies] cargo-deny = "0.19" -cargo-llvm-cov = "0.8.2" tempfile = "3.24" -url = "2.5" diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt new file mode 100644 --- /dev/null +++ b/LICENSES/OFL-1.1.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/REUSE.toml b/REUSE.toml index dc89013..ec848e2 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -9,3 +9,11 @@ path = [ ] SPDX-FileCopyrightText = "2026 Nikolay Govorov <me@govorov.online>" SPDX-License-Identifier = "AGPL-3.0-or-later" + +[[annotations]] +path = [ + "src/assets/jetbrainsmono/OFL.txt", + "src/assets/jetbrainsmono/*.woff2", +] +SPDX-FileCopyrightText = "Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)" +SPDX-License-Identifier = "OFL-1.1" diff --git a/src/assets/base.css b/src/assets/base.css new file mode 100644 --- /dev/null +++ b/src/assets/base.css @@ -0,0 +1,77 @@ +/* SPDX-FileCopyrightText: 2026 Nikolay Govorov <me@govorov.online> */ +/* SPDX-License-Identifier: AGPL-3.0-or-later */ + +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 100 700; + font-stretch: 100%; + font-display: swap; + src: url(./jetbrainsmono/JetBrainsMono[wght].woff2) format('woff2'); +} + +@font-face { + font-family: 'JetBrains Mono'; + font-style: italic; + font-weight: 100 700; + font-stretch: 100%; + font-display: swap; + src: url(./jetbrainsmono/JetBrainsMono-Italic[wght].woff2) format('woff2'); +} + +*, *::before, *::after { + box-sizing: border-box; +} + +:root { + line-height: 1.4; + font-family: 'JetBrains Mono', monospace; +} + +html { + margin: 0; + padding: 0; +} + +body { + margin: 0 auto; + max-width: 680px; + padding: 1.5em 2em; +} + +pre, code { + font-size: .75rem; + font-weight: 600; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: 700; + line-height: 1.2; + text-wrap: balance; + margin: 0; +} + +h1 { + font-size: 2.75rem; +} + +h1:first-child { + margin-top: 0; +} + +p { + text-wrap: pretty; +} + +th { + text-align: start; +} + +h3 a { + color: inherit; + text-decoration: none; +} + +h3 a:hover { + text-decoration: underline; +} diff --git a/src/assets/jetbrainsmono/JetBrainsMono-Italic[wght].woff2 b/src/assets/jetbrainsmono/JetBrainsMono-Italic[wght].woff2 new file mode 100644 --- /dev/null +++ b/src/assets/jetbrainsmono/JetBrainsMono-Italic[wght].woff2 Binary files differ diff --git a/src/assets/jetbrainsmono/JetBrainsMono[wght].woff2 b/src/assets/jetbrainsmono/JetBrainsMono[wght].woff2 new file mode 100644 --- /dev/null +++ b/src/assets/jetbrainsmono/JetBrainsMono[wght].woff2 Binary files differ diff --git a/src/controller_web.rs b/src/controller_web.rs index 4866c15..072a6d2 100644 --- a/src/controller_web.rs +++ b/src/controller_web.rs @@ -7,40 +7,40 @@ use std::sync::Arc; use std::task::{Context, Poll}; use axum::body::Body; -use axum::extract; use axum::http::{HeaderValue, Method, Request, Response, StatusCode, header}; -use axum::response; +use axum::{extract, routing}; use chrono::{DateTime, Utc}; +use maud::html; +use rust_embed::Embed; use sqlx::types::chrono; use tower::{Layer, Service}; use tracing::error; +#[derive(Embed)] +#[folder = "src/assets/"] +struct Assets; + const CSP: &str = "default-src 'self'; base-uri 'none'; img-src 'self'; font-src 'self'; style-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'"; /// Handles html pages rendering and static files -pub struct WebController { - jinja: minijinja::Environment<'static>, -} - -impl Default for WebController { - fn default() -> Self { - let mut jinja = minijinja::Environment::new(); - jinja.add_template("index", HTML).unwrap(); - - Self { jinja } - } -} +#[derive(Default)] +pub struct WebController {} impl WebController { pub fn router(self: Arc<Self>) -> axum::Router { - axum::Router::new() - .route("/index.css", axum::routing::get(Self::styles)) + let pages = axum::Router::new() .route("/", axum::routing::get(Self::index)) - .layer(LastModifiedLayer {}) .layer(tower_http::set_header::SetResponseHeaderLayer::overriding( header::CACHE_CONTROL, HeaderValue::from_static("no-cache"), - )) + )); + + let assets = axum::Router::new().route("/assets/{*path}", routing::get(Self::assets)); + + axum::Router::new() + .merge(pages) + .merge(assets) + .layer(LastModifiedLayer {}) .layer(tower_http::set_header::SetResponseHeaderLayer::overriding( header::CONTENT_SECURITY_POLICY, HeaderValue::from_static(CSP), @@ -48,21 +48,119 @@ impl WebController { .with_state(self) } - async fn index( - extract::State(controller): extract::State<Arc<Self>>, - ) -> Result<axum::response::Html<String>, StatusCode> { - let tmpl = controller.jinja.get_template("index"); - - tmpl.and_then(|template| template.render(minijinja::context! {})) - .map(response::Html) - .map_err(|err| { - error!("html rendering failed: {err}"); - StatusCode::INTERNAL_SERVER_ERROR - }) + async fn assets(extract::Path(path): extract::Path<String>) -> Response<Body> { + match Assets::get(&path) { + Some(file) => { + let mime = mime_guess::from_path(&path).first_or_octet_stream(); + let is_font = matches!( + path.rsplit('.').next(), + Some("ttf" | "otf" | "woff" | "woff2" | "eot") + ); + + let mut builder = Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, mime.as_ref()); + + if is_font { + builder = builder + .header(header::CACHE_CONTROL, "public, max-age=31536000, immutable"); + } + + builder.body(Body::from(file.data.into_owned())).unwrap() + } + None => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap(), + } } - async fn styles() -> axum_extra::response::Css<&'static str> { - axum_extra::response::Css(CSS) + async fn index() -> maud::Markup { + html! { + (maud::DOCTYPE) + + html lang="en" { + meta charset="UTF-8"; + meta name="viewport" content="width=device-width,initial-scale=1"; + meta http-equiv="X-UA-Compatible" content="ie=edge"; + + link rel="stylesheet" href="assets/base.css"; + title { "Earth PKG — tiny & opinionated packages mirror" } + } + + body { + h1 { "Zorian — tiny & opinionated packages mirror." } + + main { + p { + r#"This site provides a caching proxy for downloading Zig and Go installation files. + It reduces load on upstream servers and makes your infrastructure more reliable by adding redundancy."# + } + + p { + "Zorian is open source software licensed under " a href="https://www.gnu.org/licenses/agpl-3.0.html" { "AGPL-3.0" } ". " + "Source code is available on " a href="https://github.com/mrdimidium/zorian" { "GitHub" } + } + + h2 { "Usage" } + + p { + "Replace official download URLs with " code { "https://pkg.earth/{tool}/{filename}" } ". " + "Files are cached automatically after the first download." + } + + h3 id="zig" { + a href="#zig" { "Zig" } + } + + p { + "Read more about community mirrors in the " a href="https://ziglang.org/download/community-mirrors/" { "blog post" } ". " + "Information on how to deploy your own mirror is available " a href="https://github.com/ziglang/www.ziglang.org/blob/main/MIRRORS.md" { "in the documentation" } "." + } + + p { + "For simplicity, you can use tools like " a href="https://github.com/prantlf/zigup" { "prantlf/zigup" } + " and " a href="https://github.com/mlugg/setup-zig" { "mlugg/setup-zig" } "." + } + + p { + "To install manually:" + ol { + li { "download zig dist file:" br; code { "wget https://pkg.earth/zig/zig-x86_64-linux-0.15.1.tar.xz" } ";" } + li { "download zig minisig file:" br; code { "wget https://pkg.earth/zig/zig-x86_64-linux-0.15.1.tar.xz.minisig" } ";" } + li { "check archive integrity:" br; code { "minisign -Vm zig-x86_64-linux-0.15.1.tar.xz -P RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U" } ";" } + li { "unpack archive:" br; code { "tar -xf 'zig-x86_64-linux-0.15.1.tar.xz'" } ";" } + li { "check installed zig:" br; code { "./zig-x86_64-linux-0.15.1/zig --version" } ";" } + } + "You can take actual minisig public key at " a href="https://ziglang.org/download/" { "ziglang.org/download" } "." + } + + h3 id="go" { a href="#go" { "Go" } } + + p { + "To install manually:" + ol { + li { "download go dist file:" br; code { "wget https://pkg.earth/go/go1.23.0.linux-amd64.tar.gz" } ";" } + li { "download go sha256 file:" br; code { "wget https://pkg.earth/go/go1.23.0.linux-amd64.tar.gz.sha256" } ";" } + li { "check archive integrity:" br; code { "sha256sum -c go1.23.0.linux-amd64.tar.gz.sha256" } ";" } + li { "unpack archive:" br; code { "tar -xzf go1.23.0.linux-amd64.tar.gz" } ";" } + li { "check installed go:" br; code { "./go/bin/go version" } ";" } + } + "You can find available versions at " a href="https://go.dev/dl/" { "go.dev/dl" } "." + } + + h2 { "Privacy policy" } + + p { "This mirror is a non-profit project available on a voluntary basis. The author has no plans to fund it." } + + p { r#"Since the mirror is hosted on hardware, we collect access logs to combat bots and brute-force attacks. + The logs are used for security purposes and load planning, are not shared with third parties, + and are deleted after 30 days."# } + + p { "Third-party analytics systems are not used, same as client-side trackers." } + } + } + } } } @@ -147,132 +245,3 @@ fn parse_http_date(value: &str) -> Option<DateTime<Utc>> { .map(|d| d.with_timezone(&Utc)) .ok() } - -const HTML: &str = r##" -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width,initial-scale=1"> - <meta http-equiv="X-UA-Compatible" content="ie=edge"> - - <title>Earth PKG — tiny & opinionated packages mirror</title> - <link rel="stylesheet" href="index.css"> - </head> - - <body> - <h1>Zorian — tiny & opinionated packages mirror.</h1> - - <main> - <p>This site provides a caching proxy for downloading Zig and Go installation files. - It reduces load on upstream servers and makes your infrastructure more reliable by adding redundancy.</p> - - <p>Zorian is open source software licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0</a>. - Source code is available on <a href="https://github.com/mrdimidium/zorian">GitHub</a>.</p> - - <h2>Usage</h2> - - <p>Replace official download URLs with <code>https://pkg.earth/{tool}/{filename}</code>. - Files are cached automatically after the first download.</p> - - <h3 id="zig"><a href="#zig">Zig</a></h3> - - <p>Read more about community mirrors in the <a href="https://ziglang.org/download/community-mirrors/">blog post</a>. - Information on how to deploy your own mirror is available - <a href="https://github.com/ziglang/www.ziglang.org/blob/main/MIRRORS.md">in the documentation</a>.</p> - - <p>For simplicity, you can use tools like <a href="https://github.com/prantlf/zigup">prantlf/zigup</a> and - <a href="https://github.com/mlugg/setup-zig">mlugg/setup-zig</a>.</p> - - <p> - To install manually: - <ol> - <li>download zig dist file:<br><code>wget https://pkg.earth/zig/zig-x86_64-linux-0.15.1.tar.xz</code>;</li> - <li>download zig minisig file:<br><code>wget https://pkg.earth/zig/zig-x86_64-linux-0.15.1.tar.xz.minisig</code>;</li> - <li>check archive integrity:<br><code>minisign -Vm zig-x86_64-linux-0.15.1.tar.xz -P RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U</code>;</li> - <li>unpack archive:<br><code>tar -xf "zig-x86_64-linux-0.15.1.tar.xz"</code>;</li> - <li>check installed zig:<br><code>./zig-x86_64-linux-0.15.1/zig --version</code>.</li> - </ol> - You can take actual minisig public key at <a href="https://ziglang.org/download/">ziglang.org/download</a>. - </p> - - <h3 id="go"><a href="#go">Go</a></h3> - - <p> - To install manually: - <ol> - <li>download go dist file:<br><code>wget https://pkg.earth/go/go1.23.0.linux-amd64.tar.gz</code>;</li> - <li>download go sha256 file:<br><code>wget https://pkg.earth/go/go1.23.0.linux-amd64.tar.gz.sha256</code>;</li> - <li>check archive integrity:<br><code>sha256sum -c go1.23.0.linux-amd64.tar.gz.sha256</code>;</li> - <li>unpack archive:<br><code>tar -xzf go1.23.0.linux-amd64.tar.gz</code>;</li> - <li>check installed go:<br><code>./go/bin/go version</code>.</li> - </ol> - You can find available versions at <a href="https://go.dev/dl/">go.dev/dl</a>. - </p> - - <h2>Privacy policy</h2> - - <p>This mirror is a non-profit project available on a voluntary basis. The author has no plans to fund it.</p> - - <p>Since the mirror is hosted on hardware, we collect access logs to combat bots and brute-force attacks. - The logs are used for security purposes and load planning, are not shared with third parties, - and are deleted after 30 days.</p> - - <p>Third-party analytics systems are not used, same as client-side trackers.</p> - </main> - </body> -</html> -"##; - -const CSS: &str = r##" -:root { - font-size: 1.125rem; - line-height: 1.4; - font-family: - 'Alegreya Sans', -apple-system, BlinkMacSystemFont, - 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, - 'Open Sans', 'Helvetica Neue', sans-serif; -} - -html { - margin: 0; - padding: 0; -} - -body { - margin: 0 auto; - padding: 1.5em 2em; - max-width: 680px; -} - -code { - font-size: .75rem; -} - -h1, h2, h3, h4, h5, h6 { - font-weight: 700; - line-height: 1.2; - margin: 0; -} - -h1 { - font-size: 2.75rem; -} - -h1:first-child { - margin-top: 0; -} - -th { - text-align: start; -} - -h3 a { - color: inherit; - text-decoration: none; -} - -h3 a:hover { - text-decoration: underline; -} -"##; |
