rusticity_term/
s3.rs

1use crate::common::{format_bytes, ColumnTrait, UTC_TIMESTAMP_WIDTH};
2use crate::ui::table::Column as TableColumn;
3use ratatui::prelude::*;
4
5pub fn console_url_buckets(region: &str) -> String {
6    format!(
7        "https://{}.console.aws.amazon.com/s3/buckets?region={}",
8        region, region
9    )
10}
11
12pub fn console_url_bucket(region: &str, bucket: &str, prefix: &str) -> String {
13    if prefix.is_empty() {
14        format!(
15            "https://s3.console.aws.amazon.com/s3/buckets/{}?region={}",
16            bucket, region
17        )
18    } else {
19        format!(
20            "https://s3.console.aws.amazon.com/s3/buckets/{}?region={}&prefix={}",
21            bucket,
22            region,
23            urlencoding::encode(prefix)
24        )
25    }
26}
27
28#[derive(Debug, Clone)]
29pub struct Bucket {
30    pub name: String,
31    pub region: String,
32    pub creation_date: String,
33}
34
35#[derive(Debug, Clone)]
36pub struct Object {
37    pub key: String,
38    pub size: i64,
39    pub last_modified: String,
40    pub is_prefix: bool,
41    pub storage_class: String,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq)]
45pub enum BucketColumn {
46    Name,
47    Region,
48    CreationDate,
49}
50
51impl BucketColumn {
52    pub fn name(&self) -> &'static str {
53        match self {
54            BucketColumn::Name => "Name",
55            BucketColumn::Region => "Region",
56            BucketColumn::CreationDate => "Creation date",
57        }
58    }
59
60    pub fn all() -> Vec<BucketColumn> {
61        vec![
62            BucketColumn::Name,
63            BucketColumn::Region,
64            BucketColumn::CreationDate,
65        ]
66    }
67}
68
69impl TableColumn<Bucket> for BucketColumn {
70    fn name(&self) -> &str {
71        BucketColumn::name(self)
72    }
73
74    fn width(&self) -> u16 {
75        self.name().len().max(match self {
76            BucketColumn::Name => 30,
77            BucketColumn::Region => 15,
78            BucketColumn::CreationDate => UTC_TIMESTAMP_WIDTH as usize,
79        }) as u16
80    }
81
82    fn render(&self, item: &Bucket) -> (String, Style) {
83        let text = match self {
84            BucketColumn::Name => item.name.clone(),
85            BucketColumn::Region => item.region.clone(),
86            BucketColumn::CreationDate => item.creation_date.clone(),
87        };
88        (text, Style::default())
89    }
90}
91
92impl ColumnTrait for BucketColumn {
93    fn name(&self) -> &'static str {
94        BucketColumn::name(self)
95    }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq)]
99pub enum ObjectColumn {
100    Name,
101    Type,
102    LastModified,
103    Size,
104    StorageClass,
105}
106
107impl ObjectColumn {
108    pub fn name(&self) -> &'static str {
109        match self {
110            ObjectColumn::Name => "Name",
111            ObjectColumn::Type => "Type",
112            ObjectColumn::LastModified => "Last modified",
113            ObjectColumn::Size => "Size",
114            ObjectColumn::StorageClass => "Storage class",
115        }
116    }
117
118    pub fn all() -> Vec<ObjectColumn> {
119        vec![
120            ObjectColumn::Name,
121            ObjectColumn::Type,
122            ObjectColumn::LastModified,
123            ObjectColumn::Size,
124            ObjectColumn::StorageClass,
125        ]
126    }
127}
128
129impl TableColumn<Object> for ObjectColumn {
130    fn name(&self) -> &str {
131        ObjectColumn::name(self)
132    }
133
134    fn width(&self) -> u16 {
135        self.name().len().max(match self {
136            ObjectColumn::Name => 40,
137            ObjectColumn::Type => 10,
138            ObjectColumn::LastModified => UTC_TIMESTAMP_WIDTH as usize,
139            ObjectColumn::Size => 15,
140            ObjectColumn::StorageClass => 15,
141        }) as u16
142    }
143
144    fn render(&self, item: &Object) -> (String, Style) {
145        let text = match self {
146            ObjectColumn::Name => {
147                let icon = if item.is_prefix { "📁" } else { "📄" };
148                format!("{} {}", icon, item.key)
149            }
150            ObjectColumn::Type => {
151                if item.is_prefix {
152                    "Folder".to_string()
153                } else {
154                    "File".to_string()
155                }
156            }
157            ObjectColumn::LastModified => {
158                if item.last_modified.is_empty() {
159                    String::new()
160                } else {
161                    format!(
162                        "{} (UTC)",
163                        item.last_modified
164                            .split('T')
165                            .next()
166                            .unwrap_or(&item.last_modified)
167                    )
168                }
169            }
170            ObjectColumn::Size => {
171                if item.is_prefix {
172                    String::new()
173                } else {
174                    format_bytes(item.size)
175                }
176            }
177            ObjectColumn::StorageClass => {
178                if item.storage_class.is_empty() {
179                    String::new()
180                } else {
181                    item.storage_class
182                        .chars()
183                        .next()
184                        .unwrap()
185                        .to_uppercase()
186                        .to_string()
187                        + &item.storage_class[1..].to_lowercase()
188                }
189            }
190        };
191        (text, Style::default())
192    }
193}
194
195impl ColumnTrait for ObjectColumn {
196    fn name(&self) -> &'static str {
197        ObjectColumn::name(self)
198    }
199}