From d67ffbf38762e496f9e6c7c89ee8234cc72fc90d Mon Sep 17 00:00:00 2001 From: Sergio <77530549+sergi0g@users.noreply.github.com> Date: Fri, 6 Sep 2024 21:13:38 +0300 Subject: [PATCH] Add liquid again for static rendering, fix #21 and make some small frontend changes --- Cargo.lock | 154 +++++++++++++++++++++++++++- Cargo.toml | 5 +- src/docker.rs | 2 +- src/server.rs | 29 +++++- web/index.html | 259 +++++++++++++++++++++++++++++++++++++++++++++++- web/src/App.tsx | 24 ++++- 6 files changed, 460 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4b42fb..88972a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,6 +90,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + [[package]] name = "atty" version = "0.2.14" @@ -344,7 +350,7 @@ dependencies = [ [[package]] name = "cup" -version = "2.1.0" +version = "2.2.0" dependencies = [ "bollard", "chrono", @@ -352,6 +358,7 @@ dependencies = [ "http-auth", "indicatif", "json", + "liquid", "once_cell", "rayon", "regex", @@ -381,6 +388,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" version = "1.13.0" @@ -755,6 +768,15 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -776,6 +798,16 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +[[package]] +name = "kstring" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +dependencies = [ + "serde", + "static_assertions", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -788,6 +820,63 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "liquid" +version = "0.26.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cdcc72b82748f47c2933c172313f5a9aea5b2c4eb3fa4c66b4ea55bb60bb4b1" +dependencies = [ + "doc-comment", + "liquid-core", + "liquid-derive", + "liquid-lib", + "serde", +] + +[[package]] +name = "liquid-core" +version = "0.26.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2752e978ffc53670f3f2e8b3ef09f348d6f7b5474a3be3f8a5befe5382e4effb" +dependencies = [ + "anymap2", + "itertools", + "kstring", + "liquid-derive", + "num-traits", + "pest", + "pest_derive", + "regex", + "serde", + "time", +] + +[[package]] +name = "liquid-derive" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b51f1d220e3fa869e24cfd75915efe3164bd09bb11b3165db3f37f57bf673e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "liquid-lib" +version = "0.26.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b1a298d3d2287ee5b1e43840d885b8fdfc37d3f4e90d82aacfd04d021618da" +dependencies = [ + "itertools", + "liquid-core", + "once_cell", + "percent-encoding", + "regex", + "time", + "unicode-segmentation", +] + [[package]] name = "log" version = "0.4.22" @@ -898,6 +987,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -1235,6 +1369,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -1483,6 +1623,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -1504,6 +1650,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "unicode-width" version = "0.1.13" diff --git a/Cargo.toml b/Cargo.toml index bff33c2..75a13b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cup" -version = "2.1.0" +version = "2.2.0" edition = "2021" [dependencies] @@ -10,6 +10,7 @@ tokio = {version = "1.38.0", features = ["rt", "rt-multi-thread", "macros"]} ureq = { version = "2.9.7", features = ["tls"] } rayon = "1.10.0" xitca-web = { version = "0.5.0", optional = true, features = ["logger"] } +liquid = { version = "0.26.6", optional = true } bollard = "0.16.1" once_cell = "1.19.0" http-auth = { version = "0.1.9", features = [] } @@ -20,7 +21,7 @@ json = "0.12.4" [features] default = ["server", "cli"] -server = ["dep:xitca-web", "dep:chrono"] +server = ["dep:xitca-web", "dep:liquid", "dep:chrono"] cli = ["dep:indicatif", "dep:termsize"] [profile.release] diff --git a/src/docker.rs b/src/docker.rs index a4200f3..c8598cb 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -34,7 +34,7 @@ pub async fn get_images_from_docker_daemon(socket: Option) -> Vec }; let mut result: Vec = Vec::new(); for image in images { - if !image.repo_tags.is_empty() && image.repo_digests.len() == 1 { + if !image.repo_tags.is_empty() && !image.repo_digests.is_empty() { for t in &image.repo_tags { let (registry, repository, tag) = split_image(t); result.push(Image { diff --git a/src/server.rs b/src/server.rs index 465076a..c4fcc90 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use chrono::Local; use json::JsonValue; +use liquid::{object, Object}; use tokio::sync::Mutex; use xitca_web::{ body::ResponseBody, @@ -45,7 +46,7 @@ async fn _static(data: StateRef<'_, Arc>>, path: PathRef<'_>) match path.0 { "/" => WebResponse::builder() .header("Content-Type", "text/html") - .body(ResponseBody::from(HTML)) + .body(ResponseBody::from(data.lock().await.template.clone())) .unwrap(), "/assets/index.js" => WebResponse::builder() .header("Content-Type", "text/javascript") @@ -89,6 +90,7 @@ async fn refresh(data: StateRef<'_, Arc>>) -> WebResponse { } struct ServerData { + template: String, raw_updates: Vec<(String, Option)>, json: JsonValue, socket: Option, @@ -100,6 +102,7 @@ impl ServerData { async fn new(socket: Option, config: JsonValue) -> Self { let mut s = Self { socket, + template: String::new(), json: json::object! { metrics: json::object! {}, images: json::object! {}, @@ -116,9 +119,22 @@ impl ServerData { &get_all_updates(self.socket.clone(), &self.config["authentication"]).await, ); self.raw_updates = updates; + let template = liquid::ParserBuilder::with_stdlib() + .build() + .unwrap() + .parse(HTML) + .unwrap(); + let images = self + .raw_updates + .iter() + .map(|(name, has_update)| match has_update { + Some(v) => object!({"name": name, "has_update": v.to_string()}), // Liquid kinda thinks false == nil, so we'll be comparing strings from now on + None => object!({"name": name, "has_update": "null"}), + }) + .collect::>(); self.json = to_json(&self.raw_updates); - let last_updated = Local::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true); - self.json["last_updated"] = last_updated.to_string().into(); + let last_updated = Local::now(); + self.json["last_updated"] = last_updated.to_rfc3339_opts(chrono::SecondsFormat::Secs, true).to_string().into(); self.theme = match &self.config["theme"].as_str() { Some(t) => match *t { "default" => "neutral", @@ -130,5 +146,12 @@ impl ServerData { }, None => "neutral", }; + let globals = object!({ + "metrics": [{"name": "Monitored images", "value": self.json["metrics"]["monitored_images"].as_usize()}, {"name": "Up to date", "value": self.json["metrics"]["up_to_date"].as_usize()}, {"name": "Updates available", "value": self.json["metrics"]["update_available"].as_usize()}, {"name": "Unknown", "value": self.json["metrics"]["unknown"].as_usize()}], + "images": images, + "last_updated": last_updated.format("%Y-%m-%d %H:%M:%S").to_string(), + "theme": &self.theme + }); + self.template = template.render(&globals).unwrap(); } } diff --git a/web/index.html b/web/index.html index 8da247b..c69ad67 100644 --- a/web/index.html +++ b/web/index.html @@ -10,7 +10,264 @@ Cup -
+
+
+
+
+
+

+ Cup +

+ + + + + + + + + + + + + + + + +
+
+
+ {% for metric in metrics %} +
+
+
+ {{ metric.name }} +
+
+
+ {{ metric.value }} +
+ {% if metric.name == 'Monitored images' %} + + + + + {% elsif metric.name == 'Up to date' %} + + + + + {% elsif metric.name == 'Updates available' %} + + + + + {% elsif metric.name == 'Unknown' %} + + + + + {% endif %} +
+
+
+ {% endfor %} +
+
+
+
+

Last checked: {{ last_updated }}

+ +
+
    + {% for image in images %} +
  • + + + + + + + + {{ image.name }} {% if image.has_update == 'false' %} + + + + + {% elsif image.has_update == 'true' %} + + + + + {% elsif image.has_update == 'null' %} + + + + + {% endif %} +
  • + {% endfor %} +
+
+
+
+
+
diff --git a/web/src/App.tsx b/web/src/App.tsx index 3f5ac7e..d1d586c 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -2,7 +2,6 @@ import { MouseEvent, useState } from "react"; import Logo from "./components/Logo"; import Statistic from "./components/Statistic"; import Image from "./components/Image"; -import { IconRefresh } from "@tabler/icons-react"; import { LastChecked } from "./components/LastChecked"; import Loading from "./components/Loading"; import { Data } from "./types"; @@ -16,7 +15,7 @@ function App() { btn.disabled = true; let request = new XMLHttpRequest(); - request.onload = function () { + request.onload = () => { if (request.status === 200) { window.location.reload(); } @@ -24,8 +23,8 @@ function App() { request.open( "GET", process.env.NODE_ENV === "production" - ? "/json" - : `http://${window.location.hostname}:8000/json`, + ? "/refresh" + : `http://${window.location.hostname}:8000/refresh` ); request.send(); }; @@ -58,7 +57,22 @@ function App() { >