-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added snapshot_list and single view UI
- Loading branch information
Showing
10 changed files
with
411 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Use the official Rust image as the base | ||
FROM rust:latest | ||
|
||
# Set the working directory inside the container | ||
WORKDIR /usr/src/app | ||
|
||
# Install necessary tools | ||
RUN apt-get update && apt-get install -y libssl-dev pkg-config && \ | ||
rustup component add rustfmt && \ | ||
cargo install cargo-watch | ||
|
||
# Copy only the Cargo.toml and Cargo.lock to cache dependencies | ||
COPY Cargo.toml Cargo.lock ./ | ||
|
||
# Create a dummy main.rs to allow the initial build | ||
RUN mkdir src && echo "fn main() { println!(\"Hello, world!\"); }" > src/main.rs | ||
|
||
# Build dependencies to cache them | ||
RUN cargo build --release || true | ||
|
||
# Clean up the dummy file | ||
RUN rm -rf src | ||
|
||
# Expose the application port | ||
EXPOSE 8080 | ||
|
||
# Command to run when the container starts | ||
CMD ["cargo", "watch", "-x", "run"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
use leptos::*; | ||
|
||
use chrono::{DateTime, TimeZone, Utc}; | ||
use leptos_router::A; | ||
use serde::{Deserialize, Serialize}; | ||
use serde_json::{json, Map, Value}; | ||
|
||
use crate::components::skeleton::Skeleton; | ||
use crate::components::stat::Stat; | ||
use crate::components::table::types::TablePaginationProps; | ||
use crate::components::table::{types::Column, Table}; | ||
use crate::types::{ListFilters, Snapshot}; | ||
|
||
use crate::api::get_snapshots; | ||
|
||
#[derive(Serialize, Deserialize, Clone, Debug)] | ||
struct CombinedResource { | ||
snapshots: Vec<Snapshot>, | ||
} | ||
|
||
#[component] | ||
pub fn snapshot_list() -> impl IntoView { | ||
let tenant_rs = use_context::<ReadSignal<String>>().unwrap(); | ||
|
||
// Signals for filters | ||
let (filters, set_filters) = create_signal(ListFilters { | ||
status: None, | ||
from_date: Utc.timestamp_opt(0, 0).single(), | ||
to_date: Utc.timestamp_opt(4130561031, 0).single(), | ||
page: Some(1), | ||
count: Some(10), // Limit of 10 items per page | ||
}); | ||
|
||
let table_columns = create_memo(move |_| snapshot_table_columns(tenant_rs.get_untracked())); | ||
|
||
let combined_resource: Resource<String, CombinedResource> = create_blocking_resource( | ||
move || tenant_rs.get(), | ||
|current_tenant| async move { | ||
|
||
match get_snapshots(current_tenant.to_string()).await { | ||
Ok(response) => CombinedResource { | ||
snapshots: response.data, | ||
}, | ||
Err(_) => CombinedResource { | ||
snapshots: vec![], | ||
}, | ||
} | ||
}, | ||
); | ||
|
||
let handle_next_click = Callback::new(move |total_pages: i64| { | ||
set_filters.update(|f| { | ||
f.page = match f.page { | ||
Some(p) if p < total_pages => Some(p + 1), | ||
Some(p) => Some(p), | ||
None => Some(1), | ||
} | ||
}); | ||
}); | ||
|
||
let handle_prev_click = Callback::new(move |_| { | ||
set_filters.update(|f| { | ||
f.page = match f.page { | ||
Some(p) if p > 1 => Some(p - 1), | ||
Some(_) => Some(1), | ||
None => Some(1), | ||
} | ||
}); | ||
}); | ||
|
||
view! { | ||
<div class="p-8"> | ||
<Suspense fallback=move || view! { <Skeleton/> }> | ||
<div class="pb-4"> | ||
{move || { | ||
let snapshot_res = combined_resource.get(); | ||
let total_items = match snapshot_res { | ||
Some(snapshot_resp) => snapshot_resp.snapshots.len().to_string(), | ||
_ => "0".to_string(), | ||
}; | ||
view! { | ||
<Stat heading="Snapshots" icon="ri-camera-lens-fill" number=total_items/> | ||
} | ||
}} | ||
</div> | ||
<div class="card rounded-xl w-full bg-base-100 shadow"> | ||
<div class="card-body"> | ||
<div class="flex justify-between"> | ||
<h2 class="card-title">Snapshots</h2> | ||
</div> | ||
<div> | ||
{move || { | ||
let value = combined_resource.get(); | ||
let filters = filters.get(); | ||
match value { | ||
Some(v) => { | ||
// Pagination calculations | ||
let page = filters.page.unwrap_or(1); | ||
let count = filters.count.unwrap_or(10); | ||
let total_items = v.snapshots.len(); | ||
let total_pages = (total_items as f64 / count as f64).ceil() as i64; | ||
|
||
// Get the items for the current page | ||
let start = ((page - 1) * count as i64) as usize; | ||
let end = std::cmp::min(start + count as usize, total_items); | ||
|
||
let data = v | ||
.snapshots[start..end] | ||
.iter() | ||
.map(|snapshot| { | ||
let mut snapshot_map = json!(snapshot) | ||
.as_object() | ||
.unwrap() | ||
.to_owned(); | ||
|
||
if let Some(id_value) = snapshot_map.get("id") { | ||
let id_string = match id_value { | ||
Value::Number(num) => num.to_string(), | ||
Value::String(s) => s.clone(), | ||
_ => "".to_string(), | ||
}; | ||
snapshot_map.insert("id".to_string(), Value::String(id_string)); | ||
} | ||
|
||
// Format the created_at field | ||
if let Some(created_at_str) = snapshot_map.get("created_at").and_then(|v| v.as_str()) { | ||
if let Ok(created_at_dt) = DateTime::parse_from_rfc3339(created_at_str) { | ||
snapshot_map.insert( | ||
"created_at".to_string(), | ||
json!(created_at_dt.format("%v").to_string()), | ||
); | ||
} | ||
} | ||
|
||
snapshot_map | ||
}) | ||
.collect::<Vec<Map<String, Value>>>() | ||
.to_owned(); | ||
|
||
let pagination_props = TablePaginationProps { | ||
enabled: true, | ||
count, | ||
current_page: page, | ||
total_pages, | ||
on_next: handle_next_click, | ||
on_prev: handle_prev_click, | ||
}; | ||
|
||
view! { | ||
<Table | ||
cell_class="".to_string() | ||
rows=data | ||
key_column="id".to_string() | ||
columns=table_columns.get() | ||
pagination=pagination_props | ||
/> | ||
} | ||
} | ||
None => view! { <div>Loading....</div> }.into_view(), | ||
} | ||
}} | ||
</div> | ||
</div> | ||
</div> | ||
</Suspense> | ||
</div> | ||
} | ||
} | ||
|
||
pub fn snapshot_table_columns(tenant: String) -> Vec<Column> { | ||
vec![ | ||
Column::new( | ||
"id".to_string(), | ||
None, | ||
|value: &str, _row: &Map<String, Value>| { | ||
let id = value.to_string(); | ||
view! { | ||
<div>{id}</div> | ||
} | ||
.into_view() | ||
}, | ||
), | ||
Column::new( | ||
"config".to_string(), | ||
None, | ||
move |_value: &str, row: &Map<String, Value>| { | ||
let id = row.get("id").and_then(|v| v.as_str()).unwrap_or_default(); | ||
let href = format!("/admin/{}/snapshots/{}", tenant, id); | ||
view! { | ||
<div> | ||
<A href=href class="btn-link"> | ||
"View Config" | ||
</A> | ||
</div> | ||
} | ||
.into_view() | ||
}, | ||
), | ||
Column::default("config_hash".to_string()), | ||
Column::default("created_at".to_string()), | ||
Column::new( | ||
"tags".to_string(), | ||
None, | ||
|_value: &str, row: &Map<String, Value>| { | ||
let tags = row.get("tags").and_then(|v| v.as_array()); | ||
match tags { | ||
Some(arr) => { | ||
let tags_str = arr.iter() | ||
.map(|v| v.as_str().unwrap_or("")) | ||
.collect::<Vec<&str>>() | ||
.join(", "); | ||
view! { <span>{tags_str}</span> } | ||
} | ||
None => view! { <span>"-"</span> }, | ||
} | ||
.into_view() | ||
}, | ||
), | ||
] | ||
} | ||
|
Oops, something went wrong.