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}