Author:
Hash:
Timestamp:
+362 -37 +/-9 browse
Kevin Schoon [me@kevinschoon.com]
50721fa0683a63b18a093b27915a4a5cfe2509c7
Thu, 24 Apr 2025 13:31:39 +0000 (1 month 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> |