Author:
Hash:
Timestamp:
+362 -37 +/-9 browse
Kevin Schoon [me@kevinschoon.com]
50721fa0683a63b18a093b27915a4a5cfe2509c7
Thu, 24 Apr 2025 13:31:39 +0000 (1.1 years ago)
| 1 | diff --git a/Cargo.lock b/Cargo.lock |
| 2 | index ef5e606..8a14aee 100644 |
| 3 | --- a/Cargo.lock |
| 4 | +++ b/Cargo.lock |
| 5 | @@ -310,6 +310,17 @@ dependencies = [ |
| 6 | ] |
| 7 | |
| 8 | [[package]] |
| 9 | + name = "displaydoc" |
| 10 | + version = "0.2.5" |
| 11 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 12 | + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" |
| 13 | + dependencies = [ |
| 14 | + "proc-macro2", |
| 15 | + "quote", |
| 16 | + "syn", |
| 17 | + ] |
| 18 | + |
| 19 | + [[package]] |
| 20 | name = "fnv" |
| 21 | version = "1.0.7" |
| 22 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 23 | @@ -547,12 +558,151 @@ dependencies = [ |
| 24 | ] |
| 25 | |
| 26 | [[package]] |
| 27 | + name = "icu_collections" |
| 28 | + version = "1.5.0" |
| 29 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 30 | + checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" |
| 31 | + dependencies = [ |
| 32 | + "displaydoc", |
| 33 | + "yoke", |
| 34 | + "zerofrom", |
| 35 | + "zerovec", |
| 36 | + ] |
| 37 | + |
| 38 | + [[package]] |
| 39 | + name = "icu_locid" |
| 40 | + version = "1.5.0" |
| 41 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 42 | + checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" |
| 43 | + dependencies = [ |
| 44 | + "displaydoc", |
| 45 | + "litemap", |
| 46 | + "tinystr", |
| 47 | + "writeable", |
| 48 | + "zerovec", |
| 49 | + ] |
| 50 | + |
| 51 | + [[package]] |
| 52 | + name = "icu_locid_transform" |
| 53 | + version = "1.5.0" |
| 54 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 55 | + checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" |
| 56 | + dependencies = [ |
| 57 | + "displaydoc", |
| 58 | + "icu_locid", |
| 59 | + "icu_locid_transform_data", |
| 60 | + "icu_provider", |
| 61 | + "tinystr", |
| 62 | + "zerovec", |
| 63 | + ] |
| 64 | + |
| 65 | + [[package]] |
| 66 | + name = "icu_locid_transform_data" |
| 67 | + version = "1.5.1" |
| 68 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 69 | + checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" |
| 70 | + |
| 71 | + [[package]] |
| 72 | + name = "icu_normalizer" |
| 73 | + version = "1.5.0" |
| 74 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 75 | + checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" |
| 76 | + dependencies = [ |
| 77 | + "displaydoc", |
| 78 | + "icu_collections", |
| 79 | + "icu_normalizer_data", |
| 80 | + "icu_properties", |
| 81 | + "icu_provider", |
| 82 | + "smallvec", |
| 83 | + "utf16_iter", |
| 84 | + "utf8_iter", |
| 85 | + "write16", |
| 86 | + "zerovec", |
| 87 | + ] |
| 88 | + |
| 89 | + [[package]] |
| 90 | + name = "icu_normalizer_data" |
| 91 | + version = "1.5.1" |
| 92 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 93 | + checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" |
| 94 | + |
| 95 | + [[package]] |
| 96 | + name = "icu_properties" |
| 97 | + version = "1.5.1" |
| 98 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 99 | + checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" |
| 100 | + dependencies = [ |
| 101 | + "displaydoc", |
| 102 | + "icu_collections", |
| 103 | + "icu_locid_transform", |
| 104 | + "icu_properties_data", |
| 105 | + "icu_provider", |
| 106 | + "tinystr", |
| 107 | + "zerovec", |
| 108 | + ] |
| 109 | + |
| 110 | + [[package]] |
| 111 | + name = "icu_properties_data" |
| 112 | + version = "1.5.1" |
| 113 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 114 | + checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" |
| 115 | + |
| 116 | + [[package]] |
| 117 | + name = "icu_provider" |
| 118 | + version = "1.5.0" |
| 119 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 120 | + checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" |
| 121 | + dependencies = [ |
| 122 | + "displaydoc", |
| 123 | + "icu_locid", |
| 124 | + "icu_provider_macros", |
| 125 | + "stable_deref_trait", |
| 126 | + "tinystr", |
| 127 | + "writeable", |
| 128 | + "yoke", |
| 129 | + "zerofrom", |
| 130 | + "zerovec", |
| 131 | + ] |
| 132 | + |
| 133 | + [[package]] |
| 134 | + name = "icu_provider_macros" |
| 135 | + version = "1.5.0" |
| 136 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 137 | + checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" |
| 138 | + dependencies = [ |
| 139 | + "proc-macro2", |
| 140 | + "quote", |
| 141 | + "syn", |
| 142 | + ] |
| 143 | + |
| 144 | + [[package]] |
| 145 | name = "ident_case" |
| 146 | version = "1.0.1" |
| 147 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 148 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" |
| 149 | |
| 150 | [[package]] |
| 151 | + name = "idna" |
| 152 | + version = "1.0.3" |
| 153 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 154 | + checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" |
| 155 | + dependencies = [ |
| 156 | + "idna_adapter", |
| 157 | + "smallvec", |
| 158 | + "utf8_iter", |
| 159 | + ] |
| 160 | + |
| 161 | + [[package]] |
| 162 | + name = "idna_adapter" |
| 163 | + version = "1.2.0" |
| 164 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 165 | + checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" |
| 166 | + dependencies = [ |
| 167 | + "icu_normalizer", |
| 168 | + "icu_properties", |
| 169 | + ] |
| 170 | + |
| 171 | + [[package]] |
| 172 | name = "itoa" |
| 173 | version = "1.0.15" |
| 174 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 175 | @@ -571,6 +721,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
| 176 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" |
| 177 | |
| 178 | [[package]] |
| 179 | + name = "litemap" |
| 180 | + version = "0.7.5" |
| 181 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 182 | + checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" |
| 183 | + |
| 184 | + [[package]] |
| 185 | name = "lock_api" |
| 186 | version = "0.4.12" |
| 187 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 188 | @@ -697,6 +853,7 @@ dependencies = [ |
| 189 | "tower-http", |
| 190 | "tracing", |
| 191 | "tracing-subscriber", |
| 192 | + "url", |
| 193 | "uuid", |
| 194 | "walkdir", |
| 195 | ] |
| 196 | @@ -974,6 +1131,12 @@ dependencies = [ |
| 197 | ] |
| 198 | |
| 199 | [[package]] |
| 200 | + name = "stable_deref_trait" |
| 201 | + version = "1.2.0" |
| 202 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 203 | + checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" |
| 204 | + |
| 205 | + [[package]] |
| 206 | name = "strsim" |
| 207 | version = "0.11.1" |
| 208 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 209 | @@ -1016,6 +1179,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
| 210 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" |
| 211 | |
| 212 | [[package]] |
| 213 | + name = "synstructure" |
| 214 | + version = "0.13.1" |
| 215 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 216 | + checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" |
| 217 | + dependencies = [ |
| 218 | + "proc-macro2", |
| 219 | + "quote", |
| 220 | + "syn", |
| 221 | + ] |
| 222 | + |
| 223 | + [[package]] |
| 224 | name = "thiserror" |
| 225 | version = "2.0.12" |
| 226 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 227 | @@ -1046,6 +1220,16 @@ dependencies = [ |
| 228 | ] |
| 229 | |
| 230 | [[package]] |
| 231 | + name = "tinystr" |
| 232 | + version = "0.7.6" |
| 233 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 234 | + checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" |
| 235 | + dependencies = [ |
| 236 | + "displaydoc", |
| 237 | + "zerovec", |
| 238 | + ] |
| 239 | + |
| 240 | + [[package]] |
| 241 | name = "tokio" |
| 242 | version = "1.44.1" |
| 243 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 244 | @@ -1202,6 +1386,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
| 245 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" |
| 246 | |
| 247 | [[package]] |
| 248 | + name = "url" |
| 249 | + version = "2.5.4" |
| 250 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 251 | + checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" |
| 252 | + dependencies = [ |
| 253 | + "form_urlencoded", |
| 254 | + "idna", |
| 255 | + "percent-encoding", |
| 256 | + ] |
| 257 | + |
| 258 | + [[package]] |
| 259 | + name = "utf16_iter" |
| 260 | + version = "1.0.5" |
| 261 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 262 | + checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" |
| 263 | + |
| 264 | + [[package]] |
| 265 | + name = "utf8_iter" |
| 266 | + version = "1.0.4" |
| 267 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 268 | + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" |
| 269 | + |
| 270 | + [[package]] |
| 271 | name = "uuid" |
| 272 | version = "1.16.0" |
| 273 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 274 | @@ -1368,3 +1575,82 @@ checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" |
| 275 | dependencies = [ |
| 276 | "bitflags", |
| 277 | ] |
| 278 | + |
| 279 | + [[package]] |
| 280 | + name = "write16" |
| 281 | + version = "1.0.0" |
| 282 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 283 | + checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" |
| 284 | + |
| 285 | + [[package]] |
| 286 | + name = "writeable" |
| 287 | + version = "0.5.5" |
| 288 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 289 | + checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" |
| 290 | + |
| 291 | + [[package]] |
| 292 | + name = "yoke" |
| 293 | + version = "0.7.5" |
| 294 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 295 | + checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" |
| 296 | + dependencies = [ |
| 297 | + "serde", |
| 298 | + "stable_deref_trait", |
| 299 | + "yoke-derive", |
| 300 | + "zerofrom", |
| 301 | + ] |
| 302 | + |
| 303 | + [[package]] |
| 304 | + name = "yoke-derive" |
| 305 | + version = "0.7.5" |
| 306 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 307 | + checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" |
| 308 | + dependencies = [ |
| 309 | + "proc-macro2", |
| 310 | + "quote", |
| 311 | + "syn", |
| 312 | + "synstructure", |
| 313 | + ] |
| 314 | + |
| 315 | + [[package]] |
| 316 | + name = "zerofrom" |
| 317 | + version = "0.1.6" |
| 318 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 319 | + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" |
| 320 | + dependencies = [ |
| 321 | + "zerofrom-derive", |
| 322 | + ] |
| 323 | + |
| 324 | + [[package]] |
| 325 | + name = "zerofrom-derive" |
| 326 | + version = "0.1.6" |
| 327 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 328 | + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" |
| 329 | + dependencies = [ |
| 330 | + "proc-macro2", |
| 331 | + "quote", |
| 332 | + "syn", |
| 333 | + "synstructure", |
| 334 | + ] |
| 335 | + |
| 336 | + [[package]] |
| 337 | + name = "zerovec" |
| 338 | + version = "0.10.4" |
| 339 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 340 | + checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" |
| 341 | + dependencies = [ |
| 342 | + "yoke", |
| 343 | + "zerofrom", |
| 344 | + "zerovec-derive", |
| 345 | + ] |
| 346 | + |
| 347 | + [[package]] |
| 348 | + name = "zerovec-derive" |
| 349 | + version = "0.10.3" |
| 350 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
| 351 | + checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" |
| 352 | + dependencies = [ |
| 353 | + "proc-macro2", |
| 354 | + "quote", |
| 355 | + "syn", |
| 356 | + ] |
| 357 | diff --git a/Cargo.toml b/Cargo.toml |
| 358 | index 1348aac..360944a 100644 |
| 359 | --- a/Cargo.toml |
| 360 | +++ b/Cargo.toml |
| 361 | @@ -28,6 +28,7 @@ base16ct = { version = "0.2.0", features = ["alloc"] } |
| 362 | base64 = "0.22.1" |
| 363 | askama = { version = "0.13.1", features = ["serde_json"], optional = true} |
| 364 | walkdir = "2.5.0" |
| 365 | + url = "2.5.4" |
| 366 | |
| 367 | [dev-dependencies] |
| 368 | tokio = { version = "1.44.1", features = ["full"] } |
| 369 | diff --git a/examples/server.rs b/examples/server.rs |
| 370 | index 3696881..3a866ae 100644 |
| 371 | --- a/examples/server.rs |
| 372 | +++ b/examples/server.rs |
| 373 | @@ -5,6 +5,7 @@ use papyri::storage::Storage; |
| 374 | use tower::Layer; |
| 375 | use tower_http::{normalize_path::NormalizePathLayer, trace::TraceLayer}; |
| 376 | use tracing::{Level, event, info_span}; |
| 377 | + use url::Url; |
| 378 | |
| 379 | const ADDRESS: &str = "127.0.0.1:8700"; |
| 380 | |
| 381 | @@ -27,8 +28,9 @@ async fn main() -> Result<(), Box<dyn Error>> { |
| 382 | fs.init()?; |
| 383 | |
| 384 | // Registry middleware must be wrapped with namespace extraction/rewrite. |
| 385 | - let web_interface = papyri::axum::web::router::router(&fs); |
| 386 | - let registry = papyri::axum::router(&fs); |
| 387 | + let base_url = Url::parse("http://127.0.0.1:8700").unwrap(); |
| 388 | + let web_interface = papyri::axum::web::router::router(&fs, &base_url); |
| 389 | + let registry = papyri::axum::router(&fs, &base_url); |
| 390 | let middleware = tower::util::MapRequestLayer::new(papyri::axum::extract_namespace); |
| 391 | |
| 392 | let router = Router::new() |
| 393 | diff --git a/src/axum/mod.rs b/src/axum/mod.rs |
| 394 | index 687136b..27de97b 100644 |
| 395 | --- a/src/axum/mod.rs |
| 396 | +++ b/src/axum/mod.rs |
| 397 | @@ -9,6 +9,7 @@ use axum::routing::{delete, head, patch, post, put}; |
| 398 | use axum::{Router, routing::get}; |
| 399 | use http::Uri; |
| 400 | use serde_json::json; |
| 401 | + use url::Url; |
| 402 | |
| 403 | use crate::Namespace; |
| 404 | use crate::oci_interface::OciInterface; |
| 405 | @@ -27,13 +28,19 @@ pub mod web; |
| 406 | #[derive(Clone)] |
| 407 | pub struct AppState { |
| 408 | pub oci: OciInterface, |
| 409 | + pub base_url: Url, |
| 410 | } |
| 411 | |
| 412 | pub fn read_ns(uri: &str) -> Option<(Namespace, String)> { |
| 413 | let mut components: Vec<String> = uri.split("/").map(|cmp| cmp.to_string()).collect(); |
| 414 | components.reverse(); |
| 415 | let stop = components.iter().enumerate().find_map(|(i, entry)| { |
| 416 | - if *entry == "blobs" || *entry == "manifests" || *entry == "tags" || *entry == "tag" || *entry == "index" { |
| 417 | + if *entry == "blobs" |
| 418 | + || *entry == "manifests" |
| 419 | + || *entry == "tags" |
| 420 | + || *entry == "tag" |
| 421 | + || *entry == "index" |
| 422 | + { |
| 423 | Some(i + 1) |
| 424 | } else { |
| 425 | None |
| 426 | @@ -98,7 +105,7 @@ pub async fn index() -> Result<Json<serde_json::Value>, error::Error> { |
| 427 | |
| 428 | const MAXIMUM_MANIFEST_SIZE: usize = 5_000_000; |
| 429 | |
| 430 | - pub fn router(storage: &Storage) -> Router { |
| 431 | + pub fn router(storage: &Storage, base_url: &Url) -> Router { |
| 432 | let store = Arc::new(storage.inner()); |
| 433 | Router::new() |
| 434 | .route("/v2", get(index)) |
| 435 | @@ -149,5 +156,7 @@ pub fn router(storage: &Storage) -> Router { |
| 436 | .layer(from_fn(global_headers)) |
| 437 | .with_state(Arc::new(AppState { |
| 438 | oci: OciInterface { storage: store }, |
| 439 | + |
| 440 | + base_url: base_url.clone(), |
| 441 | })) |
| 442 | } |
| 443 | diff --git a/src/axum/web/router.rs b/src/axum/web/router.rs |
| 444 | index 9ec6bb8..a904f20 100644 |
| 445 | --- a/src/axum/web/router.rs |
| 446 | +++ b/src/axum/web/router.rs |
| 447 | @@ -8,12 +8,13 @@ use axum::{ |
| 448 | }; |
| 449 | use bytes::Bytes; |
| 450 | use http::header::CONTENT_TYPE; |
| 451 | + use url::Url; |
| 452 | |
| 453 | use crate::{Namespace, axum::AppState, oci_interface::OciInterface, storage::Storage}; |
| 454 | |
| 455 | use super::{ |
| 456 | link_tree, |
| 457 | - template::{self, LOGO, RepositoryIndex, STYLESHEET}, |
| 458 | + template::{self, LOGO, BrowserTemplate, STYLESHEET}, |
| 459 | }; |
| 460 | |
| 461 | pub async fn logo() -> Response { |
| 462 | @@ -44,16 +45,19 @@ pub async fn browser( |
| 463 | &link_builder, |
| 464 | namespace.as_ref().map(|ns| ns.to_string()).as_ref(), |
| 465 | ); |
| 466 | - let mut template = RepositoryIndex { |
| 467 | + let mut template = BrowserTemplate { |
| 468 | title: "Repositories", |
| 469 | tree: &tree, |
| 470 | - namespace: None, |
| 471 | + base_url: &state.base_url, |
| 472 | manifest: None, |
| 473 | config: None, |
| 474 | tags: Vec::new(), |
| 475 | + namespace: None, |
| 476 | + tag: current_tag.as_ref().map(|tag| tag.as_str()), |
| 477 | }; |
| 478 | |
| 479 | - if let Some(ns) = namespace { |
| 480 | + if let Some(ns) = namespace.as_ref() { |
| 481 | + template.namespace = Some(ns); |
| 482 | let tags = state |
| 483 | .oci |
| 484 | .list_tags(&ns, None, None) |
| 485 | @@ -71,7 +75,7 @@ pub async fn browser( |
| 486 | .collect() |
| 487 | }); |
| 488 | template.tags = tags; |
| 489 | - if let Some(current_tag) = current_tag { |
| 490 | + if let Some(current_tag) = current_tag.as_ref() { |
| 491 | let manifest = state |
| 492 | .oci |
| 493 | .read_manifest(&ns, &crate::TagOrDigest::Tag(current_tag.to_string())) |
| 494 | @@ -100,15 +104,12 @@ pub async fn browser( |
| 495 | .collect(), |
| 496 | }); |
| 497 | } |
| 498 | - template.namespace = Some(template::Namespace { |
| 499 | - name: ns.to_string(), |
| 500 | - }); |
| 501 | }; |
| 502 | |
| 503 | Ok(Html::from(template.to_string())) |
| 504 | } |
| 505 | |
| 506 | - pub fn router(storage: &Storage) -> Router { |
| 507 | + pub fn router(storage: &Storage, base_url: &Url) -> Router { |
| 508 | let store = Arc::new(storage.inner()); |
| 509 | Router::new() |
| 510 | .route("/", get(browser)) |
| 511 | @@ -118,5 +119,6 @@ pub fn router(storage: &Storage) -> Router { |
| 512 | .route("/logo.png", get(logo)) |
| 513 | .with_state(Arc::new(AppState { |
| 514 | oci: OciInterface { storage: store }, |
| 515 | + base_url: base_url.clone(), |
| 516 | })) |
| 517 | } |
| 518 | diff --git a/src/axum/web/template.rs b/src/axum/web/template.rs |
| 519 | index 9a258c0..bb08ca4 100644 |
| 520 | --- a/src/axum/web/template.rs |
| 521 | +++ b/src/axum/web/template.rs |
| 522 | @@ -3,10 +3,46 @@ use std::fmt::Display; |
| 523 | use askama::Template; |
| 524 | use oci_spec::image::ImageConfiguration; |
| 525 | use serde::Serialize; |
| 526 | + use url::Url; |
| 527 | + |
| 528 | + use crate::Namespace; |
| 529 | |
| 530 | pub const STYLESHEET: &[u8] = include_bytes!("../../../templates/style.css"); |
| 531 | pub const LOGO: &[u8] = include_bytes!("../../../assets/logo.png"); |
| 532 | |
| 533 | + mod filters { |
| 534 | + use url::Url; |
| 535 | + |
| 536 | + pub fn pull_url( |
| 537 | + base_url: &Url, |
| 538 | + ns: &Option<&crate::namespace::Namespace>, |
| 539 | + tag: &Option<&str>, |
| 540 | + ) -> askama::Result<String> { |
| 541 | + let mut copied = base_url.clone(); |
| 542 | + if let Some(ns) = ns { |
| 543 | + let mut path = ns.to_string(); |
| 544 | + if let Some(tag) = tag { |
| 545 | + path.push(':'); |
| 546 | + path.push_str(tag); |
| 547 | + } |
| 548 | + copied.set_path(&path); |
| 549 | + } |
| 550 | + Ok(copied.to_string().trim_start_matches("http://").trim_start_matches("https://").to_string()) |
| 551 | + } |
| 552 | + |
| 553 | + pub fn digest_url(base_url: &Url, ns: &Option<&crate::namespace::Namespace>, digest: &String) -> askama::Result<String> { |
| 554 | + let mut copied = base_url.clone(); |
| 555 | + copied.set_path(&format!("/v2/{}/blobs/{}", ns.unwrap(), digest)); |
| 556 | + Ok(copied.to_string()) |
| 557 | + } |
| 558 | + |
| 559 | + pub fn manifest_url(base_url: &Url, ns: &Option<&crate::namespace::Namespace>, tag: &Option<&str>) -> askama::Result<String> { |
| 560 | + let mut copied = base_url.clone(); |
| 561 | + copied.set_path(&format!("/v2/{}/manifests/{}", ns.unwrap(), tag.unwrap())); |
| 562 | + Ok(copied.to_string()) |
| 563 | + } |
| 564 | + } |
| 565 | + |
| 566 | #[derive(Debug, Serialize)] |
| 567 | pub struct ImageConfig { |
| 568 | pub pretty: String, |
| 569 | @@ -33,17 +69,6 @@ impl Display for Tag { |
| 570 | } |
| 571 | |
| 572 | #[derive(Debug, Serialize)] |
| 573 | - pub struct Namespace { |
| 574 | - pub name: String, |
| 575 | - } |
| 576 | - |
| 577 | - impl Display for Namespace { |
| 578 | - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 579 | - write!(f, "{}", self.name) |
| 580 | - } |
| 581 | - } |
| 582 | - |
| 583 | - #[derive(Debug, Serialize)] |
| 584 | pub struct Layer { |
| 585 | pub digest: String, |
| 586 | pub size: u64, |
| 587 | @@ -64,7 +89,7 @@ pub struct Manifest { |
| 588 | pub upstream_url: Option<String>, |
| 589 | } |
| 590 | |
| 591 | - impl Display for Manifest { |
| 592 | + impl<'a> Display for Manifest { |
| 593 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 594 | todo!() |
| 595 | } |
| 596 | @@ -72,11 +97,13 @@ impl Display for Manifest { |
| 597 | |
| 598 | #[derive(Template)] |
| 599 | #[template(path = "browser.html")] |
| 600 | - pub struct RepositoryIndex<'a> { |
| 601 | + pub struct BrowserTemplate<'a> { |
| 602 | pub title: &'a str, |
| 603 | pub tree: &'a str, |
| 604 | - pub namespace: Option<Namespace>, |
| 605 | + pub base_url: &'a Url, |
| 606 | pub tags: Vec<Tag>, |
| 607 | + pub tag: Option<&'a str>, |
| 608 | + pub namespace: Option<&'a Namespace>, |
| 609 | pub manifest: Option<Manifest>, |
| 610 | pub config: Option<ImageConfig>, |
| 611 | } |
| 612 | diff --git a/src/namespace.rs b/src/namespace.rs |
| 613 | index abfe0e5..7023304 100644 |
| 614 | --- a/src/namespace.rs |
| 615 | +++ b/src/namespace.rs |
| 616 | @@ -11,7 +11,7 @@ use crate::error::Error; |
| 617 | const NAME_REGEXP_MATCH: &str = r"^[a-z0-9]+(?:[._-][a-z0-9]+)*"; |
| 618 | |
| 619 | // TODO: Consider 255 char namespace limit - hostname length per spec docs |
| 620 | - #[derive(Clone, Debug, Hash, PartialEq, Eq)] |
| 621 | + #[derive(Clone, Debug, Hash, PartialEq, Eq, serde::Serialize)] |
| 622 | pub struct Namespace(Vec<String>); |
| 623 | |
| 624 | impl Namespace { |
| 625 | diff --git a/templates/base.html b/templates/base.html |
| 626 | index 4984dde..9cb9954 100644 |
| 627 | --- a/templates/base.html |
| 628 | +++ b/templates/base.html |
| 629 | @@ -11,7 +11,7 @@ |
| 630 | <div class="logo-container"> |
| 631 | <a href="/"><img src="/logo.png" alt="Logo" class="logo"></a> |
| 632 | </div> |
| 633 | - <ul class="nav-list">not authenticated</ul> |
| 634 | + <ul class="nav-list"></ul> |
| 635 | </nav> |
| 636 | </header> |
| 637 | <main> |
| 638 | diff --git a/templates/browser.html b/templates/browser.html |
| 639 | index a516f58..16ae160 100644 |
| 640 | --- a/templates/browser.html |
| 641 | +++ b/templates/browser.html |
| 642 | @@ -1,10 +1,5 @@ |
| 643 | {% extends "base.html" %} |
| 644 | {% block content %} |
| 645 | - <section class="pane"> |
| 646 | - <pre> |
| 647 | - podman pull asdfasdfasdf/asdfasdf |
| 648 | - </pre> |
| 649 | - </section> |
| 650 | <section class="flex"> |
| 651 | <section class="pane tree"> |
| 652 | <ul class="tree"> |
| 653 | @@ -21,10 +16,12 @@ |
| 654 | <section class="pane manifest"> |
| 655 | {% if let Some(manifest) = manifest %} |
| 656 | <section class="pane"> |
| 657 | + <code>podman pull {{ base_url | pull_url(namespace, tag) }}</code> |
| 658 | + </section> |
| 659 | + <section class="pane"> |
| 660 | <header> |
| 661 | - <span class="badge">fuu</span> |
| 662 | - <span class="badge">bar</span> |
| 663 | <h2>Manifest</h2> |
| 664 | + <span> <a href="{{ base_url | manifest_url(namespace, tag) }}">Download</a></span> |
| 665 | </header> |
| 666 | <pre>{{ manifest.pretty | safe }}</pre> |
| 667 | </section> |
| 668 | @@ -42,7 +39,8 @@ |
| 669 | </header> |
| 670 | {% for layer in manifest.layers %} |
| 671 | <section class="pane"> |
| 672 | - {{ layer }} <span class="badge"> 100mb |
| 673 | + {{ layer }} <span class="badge"> Bytes: {{ layer.size }} </span> |
| 674 | + <span> <a href="{{ base_url | digest_url(namespace, layer.digest) }}"> Download </a> </span> |
| 675 | </section> |
| 676 | {% endfor %} |
| 677 | </section> |