Commit

Author:

Hash:

Timestamp:

+101 -86 +/-8 browse

Kevin Schoon [me@kevinschoon.com]

43ae7b1a3e98a0622276657ed9aa10565d20ba60

Tue, 22 Apr 2025 15:46:53 +0000 (1 month ago)

wip ui improvements
1diff --git a/src/axum/web/router.rs b/src/axum/web/router.rs
2index c9aae33..9ec6bb8 100644
3--- a/src/axum/web/router.rs
4+++ b/src/axum/web/router.rs
5 @@ -13,7 +13,7 @@ use crate::{Namespace, axum::AppState, oci_interface::OciInterface, storage::Sto
6
7 use super::{
8 link_tree,
9- template::{self, LOGO, Repository, RepositoryIndex, STYLESHEET},
10+ template::{self, LOGO, RepositoryIndex, STYLESHEET},
11 };
12
13 pub async fn logo() -> Response {
14 @@ -30,7 +30,7 @@ pub async fn stylesheet() -> Response {
15 res
16 }
17
18- pub async fn index(
19+ pub async fn browser(
20 Extension(namespace): Extension<Option<Namespace>>,
21 current_tag: Option<Path<String>>,
22 State(state): State<Arc<AppState>>,
23 @@ -49,6 +49,7 @@ pub async fn index(
24 tree: &tree,
25 namespace: None,
26 manifest: None,
27+ config: None,
28 tags: Vec::new(),
29 };
30
31 @@ -75,6 +76,11 @@ pub async fn index(
32 .oci
33 .read_manifest(&ns, &crate::TagOrDigest::Tag(current_tag.to_string()))
34 .await?;
35+ let manifest_config_spec = manifest.config();
36+ if manifest_config_spec.media_type() == &oci_spec::image::MediaType::ImageConfig {
37+ let cfg = state.oci.read_config(manifest_config_spec.digest()).await?;
38+ template.config = Some(template::ImageConfig::from(cfg));
39+ };
40 template.manifest = Some(template::Manifest {
41 pretty: manifest.to_string_pretty().unwrap(),
42 created_at: manifest.annotations().as_ref().and_then(|annotations| {
43 @@ -105,9 +111,9 @@ pub async fn index(
44 pub fn router(storage: &Storage) -> Router {
45 let store = Arc::new(storage.inner());
46 Router::new()
47- .route("/", get(index))
48- .route("/index", get(index))
49- .route("/tag/{name}", get(index))
50+ .route("/", get(browser))
51+ .route("/index", get(browser))
52+ .route("/tag/{name}", get(browser))
53 .route("/style.css", get(stylesheet))
54 .route("/logo.png", get(logo))
55 .with_state(Arc::new(AppState {
56 diff --git a/src/axum/web/template.rs b/src/axum/web/template.rs
57index f6f4019..9a258c0 100644
58--- a/src/axum/web/template.rs
59+++ b/src/axum/web/template.rs
60 @@ -1,12 +1,25 @@
61 use std::fmt::Display;
62
63 use askama::Template;
64+ use oci_spec::image::ImageConfiguration;
65 use serde::Serialize;
66
67 pub const STYLESHEET: &[u8] = include_bytes!("../../../templates/style.css");
68 pub const LOGO: &[u8] = include_bytes!("../../../assets/logo.png");
69
70 #[derive(Debug, Serialize)]
71+ pub struct ImageConfig {
72+ pub pretty: String,
73+ }
74+
75+ impl From<ImageConfiguration> for ImageConfig {
76+ fn from(value: ImageConfiguration) -> Self {
77+ let pretty = serde_json::ser::to_string_pretty(&value).unwrap();
78+ Self { pretty }
79+ }
80+ }
81+
82+ #[derive(Debug, Serialize)]
83 pub struct Tag {
84 pub name: String,
85 pub namespace: String,
86 @@ -58,17 +71,12 @@ impl Display for Manifest {
87 }
88
89 #[derive(Template)]
90- #[template(path = "index.html")]
91+ #[template(path = "browser.html")]
92 pub struct RepositoryIndex<'a> {
93 pub title: &'a str,
94 pub tree: &'a str,
95 pub namespace: Option<Namespace>,
96 pub tags: Vec<Tag>,
97 pub manifest: Option<Manifest>,
98- }
99-
100- #[derive(Template)]
101- #[template(path = "repository.html")]
102- pub struct Repository<'a> {
103- pub title: &'a str,
104+ pub config: Option<ImageConfig>,
105 }
106 diff --git a/src/oci_interface.rs b/src/oci_interface.rs
107index 4c1538f..e9f65b9 100644
108--- a/src/oci_interface.rs
109+++ b/src/oci_interface.rs
110 @@ -4,7 +4,7 @@ use bytes::Bytes;
111 use futures::{Stream, StreamExt};
112 use oci_spec::{
113 distribution::{TagList, TagListBuilder},
114- image::{Digest, ImageManifest},
115+ image::{Digest, ImageConfiguration, ImageManifest},
116 };
117 use sha2::{Digest as HashDigest, Sha256};
118 use uuid::Uuid;
119 @@ -267,6 +267,23 @@ impl OciInterface {
120 Ok(manifest)
121 }
122
123+ pub async fn read_config(
124+ &self,
125+ digest: &Digest,
126+ ) -> Result<oci_spec::image::ImageConfiguration, Error> {
127+ let addr = Address::Blob {
128+ digest: digest.clone(),
129+ };
130+ let config_bytes = self
131+ .storage
132+ .read_bytes(&addr)
133+ .await
134+ .map_err(Error::Storage)?;
135+ let config: ImageConfiguration = serde_json::de::from_slice(config_bytes.iter().as_slice())
136+ .expect("invalid configuration");
137+ Ok(config)
138+ }
139+
140 pub async fn has_blob(&self, digest: &Digest) -> Result<bool, Error> {
141 let blob_addr = Address::Blob {
142 digest: digest.clone(),
143 diff --git a/templates/base.html b/templates/base.html
144index 9cb9954..4984dde 100644
145--- a/templates/base.html
146+++ b/templates/base.html
147 @@ -11,7 +11,7 @@
148 <div class="logo-container">
149 <a href="/"><img src="/logo.png" alt="Logo" class="logo"></a>
150 </div>
151- <ul class="nav-list"></ul>
152+ <ul class="nav-list">not authenticated</ul>
153 </nav>
154 </header>
155 <main>
156 diff --git a/templates/browser.html b/templates/browser.html
157new file mode 100644
158index 0000000..a516f58
159--- /dev/null
160+++ b/templates/browser.html
161 @@ -0,0 +1,52 @@
162+ {% extends "base.html" %}
163+ {% block content %}
164+ <section class="pane">
165+ <pre>
166+ podman pull asdfasdfasdf/asdfasdf
167+ </pre>
168+ </section>
169+ <section class="flex">
170+ <section class="pane tree">
171+ <ul class="tree">
172+ {{ tree | safe }}
173+ </ul>
174+ </section>
175+ <section class="pane tags">
176+ <ul>
177+ {% for tag in tags %}
178+ <li> <code class="tag"><a href="/{{tag.namespace}}/tag/{{tag}}">{{ tag }}</a></code> </li>
179+ {% endfor %}
180+ </ul>
181+ </section>
182+ <section class="pane manifest">
183+ {% if let Some(manifest) = manifest %}
184+ <section class="pane">
185+ <header>
186+ <span class="badge">fuu</span>
187+ <span class="badge">bar</span>
188+ <h2>Manifest</h2>
189+ </header>
190+ <pre>{{ manifest.pretty | safe }}</pre>
191+ </section>
192+ {% if let Some(config) = config %}
193+ <section class="pane">
194+ <header>
195+ <h2>Configuration</h2>
196+ </header>
197+ <pre>{{ config.pretty | safe }}</pre>
198+ </section>
199+ {% endif %}
200+ <section class="pane">
201+ <header>
202+ <h2>Layers</h2>
203+ </header>
204+ {% for layer in manifest.layers %}
205+ <section class="pane">
206+ {{ layer }} <span class="badge"> 100mb
207+ </section>
208+ {% endfor %}
209+ </section>
210+ {% endif %}
211+ </section>
212+ </section>
213+ {% endblock %}
214 diff --git a/templates/index.html b/templates/index.html
215deleted file mode 100644
216index 7654879..0000000
217--- a/templates/index.html
218+++ /dev/null
219 @@ -1,45 +0,0 @@
220- {% extends "base.html" %}
221- {% block content %}
222- {% if let Some(manifest) = manifest %}
223- <section class="pane details">
224- <span id="total-size"></span>
225-
226- <p>Created At:</p>
227- <span class="badge">hello</span>
228-
229- <p>Revision:</p>
230- <span id="revision"></span>
231-
232- <p>Url:</p>
233- <span id="url"></span>
234- </section>
235- {% endif %}
236- <section class="flex">
237- <section class="pane tree">
238- <ul class="tree">
239- {{ tree | safe }}
240- </ul>
241- </section>
242- <section class="pane tags">
243- <ul>
244- {% for tag in tags %}
245- <li> <code class="tag"><a href="/{{tag.namespace}}/tag/{{tag}}">{{ tag }}</a></code> </li>
246- {% endfor %}
247- </ul>
248- </section>
249- <section class="pane manifest">
250- {% if let Some(manifest) = manifest %}
251- <h2>Manifest</h2>
252- <section class="pane">
253- <pre>{{ manifest.pretty | safe }}</pre>
254- </section>
255- <h2>Layers</h2>
256- {% for layer in manifest.layers %}
257- <section class="pane">
258- {{ layer }}
259- </section>
260- {% endfor %}
261- {% endif %}
262- </section>
263- </section>
264- {% endblock %}
265 diff --git a/templates/repository.html b/templates/repository.html
266deleted file mode 100644
267index 069a68c..0000000
268--- a/templates/repository.html
269+++ /dev/null
270 @@ -1,21 +0,0 @@
271- {% extends "base.html" %}
272- {% block content %}
273- <table>
274- <thead>
275- <tr>
276- <th>Repository</th>
277- <th>Url</th>
278- <th>Tag Count</th>
279- <th>Updated</th>
280- </tr>
281- </thead>
282- <tbody>
283- <tr>
284- <td>NAME</td>
285- <td>LINK</td>
286- <td>N_TAGS</td>
287- <td>LAST_UPDATED</td>
288- </tr>
289- </tbody>
290- </table>
291- {% endblock %}
292 diff --git a/templates/style.css b/templates/style.css
293index ff23674..f8ba640 100644
294--- a/templates/style.css
295+++ b/templates/style.css
296 @@ -21,7 +21,7 @@ code.tag {
297 font-weight: bold
298 }
299
300- section.pane.manifest >h2 {
301+ section > header > h2 {
302 float:right;
303 }
304
305 @@ -29,7 +29,7 @@ section.pane.manifest >h2 {
306 background-color: #f7dc6f; /* yellow */
307 color: #333;
308 padding: 10px 20px;
309- border-radius: 50%;
310+ border-radius: 10%;
311 font-size: 16px;
312 font-weight: bold;
313 }
314 @@ -45,10 +45,10 @@ section.pane.manifest >h2 {
315 section.flex {
316 display: flex;
317 padding: 2em;
318- margin: 3em auto;
319 }
320
321 section.pane {
322+ margin-top: 20px;
323 margin-right: 10px;
324 padding: 20px;
325 border: 1px solid #ccc;
326 @@ -116,7 +116,7 @@ footer {
327 }
328
329 .logo {
330- height: 10em;
331+ height: 5em;
332 width: auto;
333 margin-right: 0.5rem;
334 }
335 @@ -143,8 +143,6 @@ footer {
336 color: #555;
337 }
338
339-
340-
341 /*
342 Tree structure using CSS:
343 http://stackoverflow.com/questions/14922247/how-to-get-a-tree-in-html-using-pure-css