Skip to main content

rusticity_term/ecr/
image.rs

1use crate::common::translate_column;
2use crate::common::{format_iso_timestamp, ColumnId, UTC_TIMESTAMP_WIDTH};
3use crate::ui::table::Column as TableColumn;
4use ratatui::prelude::*;
5use std::collections::HashMap;
6
7pub fn init(i18n: &mut HashMap<String, String>) {
8    for col in [
9        Column::Tag,
10        Column::ArtifactType,
11        Column::PushedAt,
12        Column::SizeMb,
13        Column::Uri,
14        Column::Digest,
15        Column::LastPullTime,
16    ] {
17        i18n.entry(col.id().to_string())
18            .or_insert_with(|| col.default_name().to_string());
19    }
20}
21
22#[derive(Debug, Clone)]
23pub struct Image {
24    pub tag: String,
25    pub artifact_type: String,
26    pub pushed_at: String,
27    pub size_bytes: i64,
28    pub uri: String,
29    pub digest: String,
30    pub last_pull_time: String,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum Column {
35    Tag,
36    ArtifactType,
37    PushedAt,
38    SizeMb,
39    Uri,
40    Digest,
41    LastPullTime,
42}
43
44impl Column {
45    const ID_TAG: &'static str = "column.ecr.image.tag";
46    const ID_ARTIFACT_TYPE: &'static str = "column.ecr.image.artifact_type";
47    const ID_PUSHED_AT: &'static str = "column.ecr.image.pushed_at";
48    const ID_SIZE_MB: &'static str = "column.ecr.image.size_mb";
49    const ID_URI: &'static str = "column.ecr.image.uri";
50    const ID_DIGEST: &'static str = "column.ecr.image.digest";
51    const ID_LAST_PULL_TIME: &'static str = "column.ecr.image.last_pull_time";
52
53    pub const fn id(&self) -> &'static str {
54        match self {
55            Column::Tag => Self::ID_TAG,
56            Column::ArtifactType => Self::ID_ARTIFACT_TYPE,
57            Column::PushedAt => Self::ID_PUSHED_AT,
58            Column::SizeMb => Self::ID_SIZE_MB,
59            Column::Uri => Self::ID_URI,
60            Column::Digest => Self::ID_DIGEST,
61            Column::LastPullTime => Self::ID_LAST_PULL_TIME,
62        }
63    }
64
65    pub const fn default_name(&self) -> &'static str {
66        match self {
67            Column::Tag => "Image tag",
68            Column::ArtifactType => "Artifact type",
69            Column::PushedAt => "Pushed at",
70            Column::SizeMb => "Size (MB)",
71            Column::Uri => "Image URI",
72            Column::Digest => "Digest",
73            Column::LastPullTime => "Last recorded pull time",
74        }
75    }
76
77    pub const fn all() -> [Column; 7] {
78        [
79            Column::Tag,
80            Column::ArtifactType,
81            Column::PushedAt,
82            Column::SizeMb,
83            Column::Uri,
84            Column::Digest,
85            Column::LastPullTime,
86        ]
87    }
88
89    pub fn ids() -> Vec<ColumnId> {
90        Self::all().iter().map(|c| c.id()).collect()
91    }
92
93    pub fn from_id(id: &str) -> Option<Self> {
94        match id {
95            Self::ID_TAG => Some(Column::Tag),
96            Self::ID_ARTIFACT_TYPE => Some(Column::ArtifactType),
97            Self::ID_PUSHED_AT => Some(Column::PushedAt),
98            Self::ID_SIZE_MB => Some(Column::SizeMb),
99            Self::ID_URI => Some(Column::Uri),
100            Self::ID_DIGEST => Some(Column::Digest),
101            Self::ID_LAST_PULL_TIME => Some(Column::LastPullTime),
102            _ => None,
103        }
104    }
105
106    pub fn name(&self) -> String {
107        translate_column(self.id(), self.default_name())
108    }
109}
110
111impl TableColumn<Image> for Column {
112    fn name(&self) -> &str {
113        Box::leak(translate_column(self.id(), self.default_name()).into_boxed_str())
114    }
115
116    fn width(&self) -> u16 {
117        let translated = translate_column(self.id(), self.default_name());
118        translated.len().max(match self {
119            Column::Tag => 20,
120            Column::ArtifactType => 20,
121            Column::PushedAt => UTC_TIMESTAMP_WIDTH as usize,
122            Column::SizeMb => 12,
123            Column::Uri => 50,
124            Column::Digest => 20,
125            Column::LastPullTime => UTC_TIMESTAMP_WIDTH as usize,
126        }) as u16
127    }
128
129    fn render(&self, item: &Image) -> (String, Style) {
130        let text = match self {
131            Column::Tag => item.tag.clone(),
132            Column::ArtifactType => item.artifact_type.clone(),
133            Column::PushedAt => format_iso_timestamp(&item.pushed_at),
134            Column::SizeMb => crate::common::format_bytes(item.size_bytes),
135            Column::Uri => item.uri.clone(),
136            Column::Digest => item.digest.clone(),
137            Column::LastPullTime => format_iso_timestamp(&item.last_pull_time),
138        };
139        (text, Style::default())
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_column_ids_have_correct_prefix() {
149        for col in Column::all() {
150            assert!(
151                col.id().starts_with("column.ecr.image."),
152                "Column ID '{}' should start with 'column.ecr.image.'",
153                col.id()
154            );
155        }
156    }
157}