Skip to content

Commit

Permalink
feat: added snapshot_list and single view UI
Browse files Browse the repository at this point in the history
  • Loading branch information
sauraww committed Sep 26, 2024
1 parent 6594ee1 commit 28aac48
Show file tree
Hide file tree
Showing 10 changed files with 411 additions and 14 deletions.
28 changes: 28 additions & 0 deletions Dockerfile.dev
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"]
41 changes: 40 additions & 1 deletion crates/frontend/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use leptos::ServerFnError;
use serde_json::Value;

use crate::{
types::{
Config, DefaultConfig, Dimension, ExperimentResponse, ExperimentsResponse,
FetchTypeTemplateResponse, FunctionResponse, ListFilters,
FetchTypeTemplateResponse, FunctionResponse, ListFilters,SnapshotResponse,
},
utils::{
construct_request_headers, get_host, parse_json_response, request,
Expand Down Expand Up @@ -48,6 +49,26 @@ pub async fn fetch_default_config(
.await
.map_err(|e| ServerFnError::new(e.to_string()))?;

Ok(response)
}

pub async fn get_snapshots(
tenant: String,
) -> Result<SnapshotResponse, ServerFnError> {
let client = reqwest::Client::new();
let host = use_host_server();

let url = format!("{host}/config-versions");
let response: SnapshotResponse = client
.get(url)
.header("x-tenant", tenant)
.send()
.await
.map_err(|e| ServerFnError::new(e.to_string()))?
.json()
.await
.map_err(|e| ServerFnError::new(e.to_string()))?;

Ok(response)
}

Expand Down Expand Up @@ -248,3 +269,21 @@ pub async fn fetch_types(
.await
.map_err(err_handler)
}

pub async fn get_config_by_version(tenant: String, version: String) -> Result<Value, ServerFnError> {
let client = reqwest::Client::new();
let host = use_host_server();

let url = format!("{host}/config?version={}", version);
let response: Value = client
.get(url)
.header("x-tenant", tenant)
.send()
.await
.map_err(|e| ServerFnError::new(e.to_string()))?
.json()
.await
.map_err(|e| ServerFnError::new(e.to_string()))?;

Ok(response)
}
27 changes: 26 additions & 1 deletion crates/frontend/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use serde_json::json;

use crate::hoc::layout::Layout;
use crate::pages::dimensions::Dimensions;
use crate::pages::snapshot_list::SnapshotList;
use crate::pages::snapshotconfig::SnapshotConfig;
use crate::pages::experiment_list::ExperimentList;
use crate::pages::function::{
function_create::CreateFunctionView, function_list::FunctionList, FunctionPage,
Expand Down Expand Up @@ -209,7 +211,30 @@ pub fn app(app_envs: Envs) -> impl IntoView {
</Layout>
}
}
/>
/>

<Route
ssr=SsrMode::Async
path="/admin/:tenant/snapshots"
view=move || {
view! {
<Layout>
<SnapshotList/>
</Layout>
}
}
/>
<Route
ssr=SsrMode::Async
path="/admin/:tenant/snapshots/:version"
view=move || {
view! {
<Layout>
<SnapshotConfig/>
</Layout>
}
}
/>

// <Route
// path="/*any"
Expand Down
6 changes: 6 additions & 0 deletions crates/frontend/src/components/side_nav.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ fn create_routes(tenant: &str) -> Vec<AppRoute> {
path: format!("{base}/admin/{tenant}/types"),
icon: "ri-t-box-fill".to_string(),
label: "Type Templates".to_string(),
},
AppRoute {
key: format!("{base}/admin/{tenant}/snapshots"),
path: format!("{base}/admin/{tenant}/snapshots"),
icon: "ri-camera-lens-fill".to_string(),
label: "Snapshots".to_string(),
},
]
}
Expand Down
2 changes: 2 additions & 0 deletions crates/frontend/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ pub mod experiment_list;
pub mod function;
pub mod home;
pub mod not_found;
pub mod snapshot_list;
pub mod snapshotconfig;
221 changes: 221 additions & 0 deletions crates/frontend/src/pages/snapshot_list.rs
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()
},
),
]
}

Loading

0 comments on commit 28aac48

Please sign in to comment.