//! A repository's Artifacts menu (oxygenhub#254): every run's build //! artifacts in one list, the runner images its jobs use, and its //! Terraform state. Releases and reports keep their own pages. use super::{filters, lookup, Error, Page, RepoShell, Shell}; use crate::auth::Viewer; use crate::db::flow as fdb; use crate::{db, Shared}; use askama::Template; use axum::extract::{Path, State}; /// At most this many artifacts are listed, newest first. const ARTIFACTS_SHOWN: i64 = 200; /// Images used within this many days are listed. const IMAGE_DAYS: i64 = 90; #[derive(Template)] #[template(path = "repo_artifacts.html")] pub struct ArtifactsPage { shell: Shell, repo: RepoShell, artifacts: Vec, limit: i64, } /// `GET /{owner}/{name}/artifacts`. pub async fn artifacts( State(app): State, viewer: Viewer, Path((owner, name)): Path<(String, String)>, ) -> Result, Error> { let shell = Shell::new(&app, &format!("/{owner}/{name}/artifacts"), &viewer); let (_repo, row, role) = lookup(&app, &shell, &viewer, &owner, &name).await?; let artifacts = fdb::repo_artifacts(&app.db, row.id, ARTIFACTS_SHOWN).await.map_err(|e| Error::from_db(shell.clone(), e))?; Ok(Page(ArtifactsPage { shell, repo: RepoShell::new(&app, &row, "artifacts").with_role(role), artifacts, limit: ARTIFACTS_SHOWN })) } /// One image this repository's jobs ran on. pub struct ImageView { /// The image's BLAKE3, or what the pipeline named when it is not a hash. pub image: String, pub hashed: bool, pub jobs: i64, pub last_ms: i64, /// Kept by the hub: its size and architecture. pub kept: Option<(i64, String)>, } #[derive(Template)] #[template(path = "repo_images.html")] pub struct ImagesPage { shell: Shell, repo: RepoShell, images: Vec, days: i64, } /// `GET /{owner}/{name}/images`. pub async fn images( State(app): State, viewer: Viewer, Path((owner, name)): Path<(String, String)>, ) -> Result, Error> { let shell = Shell::new(&app, &format!("/{owner}/{name}/images"), &viewer); let (_repo, row, role) = lookup(&app, &shell, &viewer, &owner, &name).await?; let e = |e| Error::from_db(shell.clone(), e); let uses = crate::images::uses_of(&app.db, row.id, db::now_ms() - IMAGE_DAYS * 24 * 3600 * 1000).await.map_err(e)?; let kept = crate::images::kept(&app.db).await.map_err(e)?; let images = uses .into_iter() .map(|u| { let kept = kept.iter().find(|k| k.hash.eq_ignore_ascii_case(&u.image)).map(|k| (k.size, k.arch.clone())); ImageView { hashed: jet::images::is_hash(&u.image), image: u.image, jobs: u.jobs, last_ms: u.last_ms, kept } }) .collect(); Ok(Page(ImagesPage { shell, repo: RepoShell::new(&app, &row, "images").with_role(role), images, days: IMAGE_DAYS })) } #[derive(Template)] #[template(path = "repo_terraform.html")] pub struct TerraformPage { shell: Shell, repo: RepoShell, tf_states: Vec<(db::terraform::TfState, i64)>, } /// `GET /{owner}/{name}/terraform`: the states the hub keeps for this /// repository (oxygenhub#252), and the backend block that uses them. pub async fn terraform( State(app): State, viewer: Viewer, Path((owner, name)): Path<(String, String)>, ) -> Result, Error> { let shell = Shell::new(&app, &format!("/{owner}/{name}/terraform"), &viewer); let (_repo, row, role) = lookup(&app, &shell, &viewer, &owner, &name).await?; let e = |e| Error::from_db(shell.clone(), e); let mut tf_states = Vec::new(); for s in db::terraform::of_repo(&app.db, row.id).await.map_err(e)? { let n = db::terraform::versions(&app.db, row.id, &s.name).await.map_err(e)?; tf_states.push((s, n)); } Ok(Page(TerraformPage { shell, repo: RepoShell::new(&app, &row, "terraform").with_role(role), tf_states })) }