diff --git a/Cargo.toml b/Cargo.toml index c027601..255ede5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,10 @@ tracing = { version = "0.1" } tracing-subscriber = { version = "0.3" } tracing-subscriber-wasm = { version = "0.1" } url = { version = "2.5" } -uuid = { version = "1.10", default-features = false, features = ["serde"] } +uuid = { version = "1.10", default-features = false, features = [ + "serde", + "v4", +] } wasm-streams = { version = "0.4" } web-sys = { version = "0.3", features = [ "FileList", diff --git a/Justfile b/Justfile index 2f58578..572cff0 100644 --- a/Justfile +++ b/Justfile @@ -59,13 +59,13 @@ build *ARGS: ( _trunk "build" ARGS ) run *ARGS: ( _trunk "serve" ARGS ) -run-examples *ARGS: ( _trunk "serve" "--features" "examples" ARGS ) +run-examples *ARGS: ( _trunk "serve" "--features" "examples,full,mock-release" ARGS ) run-gateway *ARGS: - cargo watch -s 'clear && cargo run --package cassette-gateway' + cargo watch -s 'clear && cargo run --package cassette-gateway -- {{ ARGS }}' run-operator *ARGS: - cargo watch -s 'clear && cargo run --package cassette-operator' + cargo watch -s 'clear && cargo run --package cassette-operator -- {{ ARGS }}' _oci-build file oci_suffix *ARGS: mkdir -p "${OCI_BUILD_LOG_DIR}" diff --git a/crates/cassette-core/Cargo.toml b/crates/cassette-core/Cargo.toml index 5799b4a..f1d994d 100644 --- a/crates/cassette-core/Cargo.toml +++ b/crates/cassette-core/Cargo.toml @@ -25,7 +25,7 @@ api = ["dep:actix-web"] ui = ["dep:gloo-net", "dep:patternfly-yew", "dep:tracing", "dep:yew"] # net -stream = ["dep:anyhow", "dep:wasm-streams"] +stream = ["dep:wasm-streams"] # for demo ONLY examples = [] @@ -38,7 +38,7 @@ cdl = [] [dependencies] actix-web = { workspace = true, optional = true } -anyhow = { workspace = true, optional = true } +anyhow = { workspace = true } csv = { workspace = true } garde = { workspace = true } gloo-net = { workspace = true, optional = true } diff --git a/crates/cassette-core/src/components/error.rs b/crates/cassette-core/src/components/error.rs index a3e9c63..cb9c442 100644 --- a/crates/cassette-core/src/components/error.rs +++ b/crates/cassette-core/src/components/error.rs @@ -12,7 +12,9 @@ pub fn error(props: &Props) -> Html { html! { -

{ msg }

+ + { msg.clone() } +
} } diff --git a/crates/cassette-core/src/data/csv.rs b/crates/cassette-core/src/data/csv.rs index 4ad0ebf..9b9f25b 100644 --- a/crates/cassette-core/src/data/csv.rs +++ b/crates/cassette-core/src/data/csv.rs @@ -5,7 +5,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct CsvTable { pub headers: Vec, pub records: Vec>, diff --git a/crates/cassette-core/src/data/table.rs b/crates/cassette-core/src/data/table.rs index 3dcf13f..a725c95 100644 --- a/crates/cassette-core/src/data/table.rs +++ b/crates/cassette-core/src/data/table.rs @@ -31,6 +31,7 @@ impl Default for DataTableLog { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(tag = "kind", content = "data", rename_all = "SCREAMING_SNAKE_CASE")] pub enum DataTableSource { + #[cfg(feature = "cdl")] Cdl(super::cdl::CdlTable), Csv(super::csv::CsvTable), Raw(Vec), @@ -39,6 +40,7 @@ pub enum DataTableSource { impl DataTableSource { pub fn columns(&self) -> Result> { match self { + #[cfg(feature = "cdl")] DataTableSource::Cdl(data) => Ok(data.columns()), DataTableSource::Csv(data) => Ok(data.columns()), DataTableSource::Raw(_) => bail!("Raw data table has no columns"), @@ -47,6 +49,7 @@ impl DataTableSource { pub fn records(self) -> Result>> { match self { + #[cfg(feature = "cdl")] DataTableSource::Cdl(data) => Ok(data.records()), DataTableSource::Csv(data) => Ok(data.records()), DataTableSource::Raw(_) => bail!("Raw data table has no records"), @@ -55,6 +58,7 @@ impl DataTableSource { pub fn is_empty(&self) -> bool { match self { + #[cfg(feature = "cdl")] DataTableSource::Cdl(data) => data.is_empty(), DataTableSource::Csv(data) => data.is_empty(), DataTableSource::Raw(data) => data.is_empty(), @@ -63,6 +67,7 @@ impl DataTableSource { pub fn len(&self) -> usize { match self { + #[cfg(feature = "cdl")] DataTableSource::Cdl(data) => data.len(), DataTableSource::Csv(data) => data.len(), DataTableSource::Raw(data) => data.len(), @@ -104,9 +109,3 @@ impl DataTableSourceType { } } } - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct DataTableEntry { - pub index: usize, - pub values: Vec, -} diff --git a/crates/cassette-core/src/lib.rs b/crates/cassette-core/src/lib.rs index 892ba32..3a7659d 100644 --- a/crates/cassette-core/src/lib.rs +++ b/crates/cassette-core/src/lib.rs @@ -1,6 +1,5 @@ pub mod cassette; pub mod components; -#[cfg(feature = "ui")] pub mod data; pub mod document; pub mod net; diff --git a/crates/cassette-gateway/Cargo.toml b/crates/cassette-gateway/Cargo.toml index 901098f..7779a7c 100644 --- a/crates/cassette-gateway/Cargo.toml +++ b/crates/cassette-gateway/Cargo.toml @@ -40,7 +40,7 @@ rustls-tls = [ # plugins ## Connected Data Lake (CDL) -cdl = ["dep:cassette-plugin-cdl-api"] +cdl = ["cassette-core/cdl", "dep:cassette-plugin-cdl-api"] ## Kubernetes kubernetes = ["dep:cassette-plugin-kubernetes-api"] diff --git a/crates/cassette-gateway/src/actix.rs b/crates/cassette-gateway/src/actix.rs index 6f3c3f8..16c9a32 100644 --- a/crates/cassette-gateway/src/actix.rs +++ b/crates/cassette-gateway/src/actix.rs @@ -97,7 +97,7 @@ where { let scope = web::scope(base_url.unwrap_or("")); let scope = build_core_services(scope); - let scope = build_plugin_servicess(scope); + let scope = build_plugin_services(scope); app.route( base_url.filter(|&path| !path.is_empty()).unwrap_or("/"), @@ -114,12 +114,11 @@ fn build_core_services(scope: Scope) -> Scope { .service(crate::routes::cassette::list) } -fn build_plugin_servicess(scope: Scope) -> Scope { +fn build_plugin_services(scope: Scope) -> Scope { + #[cfg(feature = "cdl")] + let scope = ::cassette_plugin_cdl_api::build_services(scope); #[cfg(feature = "kubernetes")] - let scope = scope.route( - "/kube/{path:.*}", - ::actix_web::web::route().to(::cassette_plugin_kubernetes_api::handle), - ); + let scope = ::cassette_plugin_kubernetes_api::build_services(scope); scope } diff --git a/crates/cassette-plugin-cdl-api/Cargo.toml b/crates/cassette-plugin-cdl-api/Cargo.toml index bdd0a2d..9ba9004 100644 --- a/crates/cassette-plugin-cdl-api/Cargo.toml +++ b/crates/cassette-plugin-cdl-api/Cargo.toml @@ -29,8 +29,10 @@ rustls-tls = ["dash-pipe-provider/rustls-tls"] [dependencies] cassette-core = { path = "../cassette-core", features = ["api"] } +cassette-plugin-kubernetes-api = { path = "../cassette-plugin-kubernetes-api" } actix-web = { workspace = true } anyhow = { workspace = true } dash-pipe-provider = { workspace = true, features = ["kafka", "storage"] } +serde = { workspace = true } tracing = { workspace = true } diff --git a/crates/cassette-plugin-cdl-api/src/lib.rs b/crates/cassette-plugin-cdl-api/src/lib.rs index 7d12d9a..1b2a9de 100644 --- a/crates/cassette-plugin-cdl-api/src/lib.rs +++ b/crates/cassette-plugin-cdl-api/src/lib.rs @@ -1,14 +1,7 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} +mod zone; -#[cfg(test)] -mod tests { - use super::*; +use actix_web::Scope; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } +pub fn build_services(scope: Scope) -> Scope { + scope.service(self::zone::list) } diff --git a/crates/cassette-plugin-cdl-api/src/zone.rs b/crates/cassette-plugin-cdl-api/src/zone.rs new file mode 100644 index 0000000..1c1c8fc --- /dev/null +++ b/crates/cassette-plugin-cdl-api/src/zone.rs @@ -0,0 +1,39 @@ +use actix_web::{get, HttpRequest, HttpResponse, Responder}; +use anyhow::Result; +use cassette_core::{ + data::{ + csv::CsvTable, + table::{DataTable, DataTableLog, DataTableSource}, + }, + result::Result as HttpResult, +}; +use cassette_plugin_kubernetes_api::{load_client, Client}; +use serde::Serialize; + +#[get("/cdl/zone")] +pub async fn list(request: HttpRequest) -> impl Responder { + async fn try_handle(client: Client) -> Result { + Ok(DataTable { + name: "cdl-zones".into(), + data: DataTableSource::Csv(CsvTable::default()), + log: DataTableLog::default(), + }) + } + + match load_client(&request).await { + Ok(client) => response(try_handle(client).await), + Err(error) => HttpResponse::Unauthorized().json(HttpResult::<()>::Err(error.to_string())), + } +} + +fn response(result: Result) -> HttpResponse +where + T: Serialize, +{ + match result { + Ok(data) => HttpResponse::Ok().json(HttpResult::Ok(data)), + Err(error) => { + HttpResponse::MethodNotAllowed().json(HttpResult::::Err(error.to_string())) + } + } +} diff --git a/crates/cassette-plugin-cdl-zone/src/actor.rs b/crates/cassette-plugin-cdl-zone/src/actor.rs new file mode 100644 index 0000000..c764b34 --- /dev/null +++ b/crates/cassette-plugin-cdl-zone/src/actor.rs @@ -0,0 +1,136 @@ +use cassette_core::{ + cassette::CassetteContext, + components::ComponentRenderer, + task::{TaskResult, TaskState}, +}; +use patternfly_yew::prelude::*; +use serde::{Deserialize, Serialize}; +use yew::prelude::*; + +#[derive(Clone, Debug, PartialEq, Deserialize, Properties)] +#[serde(rename_all = "camelCase")] +pub struct Spec {} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct State {} + +impl ComponentRenderer for State { + fn render(self, _ctx: &mut CassetteContext, spec: Spec) -> TaskResult> { + let Spec {} = spec; + + Ok(TaskState::Continue { + body: html! { }, + state: Some(Self {}), + }) + } +} + +#[function_component(Manager)] +fn manager() -> Html { + const TOTAL_ENTRIES: usize = 394; + + let offset = use_state_eq(|| 0); + let limit = use_state_eq(|| 5); + + let entries = use_memo((*offset, *limit), |(offset, limit)| { + (*offset..(offset + limit).clamp(0, TOTAL_ENTRIES)) + .map(ExampleEntry) + .collect::>() + }); + + let (entries, _) = use_table_data(MemoizedTableModel::new(entries)); + + let header = html_nested! { + > + index={Columns::Select} /> + label="Decimal" index={Columns::Decimal} /> + label="Hex" index={Columns::Hex} /> + label="Button" index={Columns::Button} /> + > + }; + + let total_entries = Some(TOTAL_ENTRIES); + + let limit_callback = use_callback(limit.clone(), |number, limit| limit.set(number)); + + let nav_callback = use_callback( + (offset.clone(), *limit), + |page: Navigation, (offset, limit)| { + let o = match page { + Navigation::First => 0, + Navigation::Last => ((TOTAL_ENTRIES - 1) / limit) * limit, + Navigation::Previous => **offset - limit, + Navigation::Next => **offset + limit, + Navigation::Page(n) => n * limit, + }; + offset.set(o); + }, + ); + + html! ( + <> + + + // FIXME: add bulk-select support: https://www.patternfly.org/components/table/react-demos/bulk-select/ + + + + + + >> + mode={TableMode::Compact} + {header} + {entries} + /> + + + ) +} + +#[derive(Clone, Eq, PartialEq)] +pub enum Columns { + Select, + Decimal, + Hex, + Button, +} + +#[derive(Clone)] +struct ExampleEntry(usize); + +impl TableEntryRenderer for ExampleEntry { + fn render_cell(&self, context: CellContext<'_, Columns>) -> Cell { + match context.column { + Columns::Select => html! { }, + Columns::Decimal => html!(self.0.to_string()), + Columns::Hex => html!(format!("{:x}", self.0)), + Columns::Button => html! { + + + + + + + + + }, + } + .into() + } +} diff --git a/crates/cassette-plugin-cdl-zone/src/hooks.rs b/crates/cassette-plugin-cdl-zone/src/hooks.rs new file mode 100644 index 0000000..53089d9 --- /dev/null +++ b/crates/cassette-plugin-cdl-zone/src/hooks.rs @@ -0,0 +1,32 @@ +use std::rc::Rc; + +use cassette_core::{ + cassette::{CassetteContext, CassetteTaskHandle}, + data::table::DataTable, + net::{ + fetch::{FetchRequestWithoutBody, FetchState, Method}, + gateway::get_gateway, + }, +}; + +pub fn use_fetch( + ctx: &mut CassetteContext, + base_url: Option, + force: bool, +) -> CassetteTaskHandle>> { + let handler_name = "chat completions"; + let state = ctx.use_state(handler_name, force, || FetchState::Pending); + { + let state = state.clone(); + let base_url = base_url.unwrap_or(get_gateway()); + let request = FetchRequestWithoutBody { + method: Method::GET, + name: handler_name, + url: "/cdl/zone", + body: None, + }; + + request.try_fetch(&base_url, state) + } + state +} diff --git a/crates/cassette-plugin-cdl-zone/src/lib.rs b/crates/cassette-plugin-cdl-zone/src/lib.rs index c764b34..8222724 100644 --- a/crates/cassette-plugin-cdl-zone/src/lib.rs +++ b/crates/cassette-plugin-cdl-zone/src/lib.rs @@ -1,136 +1,3 @@ -use cassette_core::{ - cassette::CassetteContext, - components::ComponentRenderer, - task::{TaskResult, TaskState}, -}; -use patternfly_yew::prelude::*; -use serde::{Deserialize, Serialize}; -use yew::prelude::*; - -#[derive(Clone, Debug, PartialEq, Deserialize, Properties)] -#[serde(rename_all = "camelCase")] -pub struct Spec {} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct State {} - -impl ComponentRenderer for State { - fn render(self, _ctx: &mut CassetteContext, spec: Spec) -> TaskResult> { - let Spec {} = spec; - - Ok(TaskState::Continue { - body: html! { }, - state: Some(Self {}), - }) - } -} - -#[function_component(Manager)] -fn manager() -> Html { - const TOTAL_ENTRIES: usize = 394; - - let offset = use_state_eq(|| 0); - let limit = use_state_eq(|| 5); - - let entries = use_memo((*offset, *limit), |(offset, limit)| { - (*offset..(offset + limit).clamp(0, TOTAL_ENTRIES)) - .map(ExampleEntry) - .collect::>() - }); - - let (entries, _) = use_table_data(MemoizedTableModel::new(entries)); - - let header = html_nested! { - > - index={Columns::Select} /> - label="Decimal" index={Columns::Decimal} /> - label="Hex" index={Columns::Hex} /> - label="Button" index={Columns::Button} /> - > - }; - - let total_entries = Some(TOTAL_ENTRIES); - - let limit_callback = use_callback(limit.clone(), |number, limit| limit.set(number)); - - let nav_callback = use_callback( - (offset.clone(), *limit), - |page: Navigation, (offset, limit)| { - let o = match page { - Navigation::First => 0, - Navigation::Last => ((TOTAL_ENTRIES - 1) / limit) * limit, - Navigation::Previous => **offset - limit, - Navigation::Next => **offset + limit, - Navigation::Page(n) => n * limit, - }; - offset.set(o); - }, - ); - - html! ( - <> - - - // FIXME: add bulk-select support: https://www.patternfly.org/components/table/react-demos/bulk-select/ - - - - - - >> - mode={TableMode::Compact} - {header} - {entries} - /> - - - ) -} - -#[derive(Clone, Eq, PartialEq)] -pub enum Columns { - Select, - Decimal, - Hex, - Button, -} - -#[derive(Clone)] -struct ExampleEntry(usize); - -impl TableEntryRenderer for ExampleEntry { - fn render_cell(&self, context: CellContext<'_, Columns>) -> Cell { - match context.column { - Columns::Select => html! { }, - Columns::Decimal => html!(self.0.to_string()), - Columns::Hex => html!(format!("{:x}", self.0)), - Columns::Button => html! { - - - - - - - - - }, - } - .into() - } -} +pub mod actor; +pub mod hooks; +pub mod load; diff --git a/crates/cassette-plugin-cdl-zone/src/load.rs b/crates/cassette-plugin-cdl-zone/src/load.rs new file mode 100644 index 0000000..247576f --- /dev/null +++ b/crates/cassette-plugin-cdl-zone/src/load.rs @@ -0,0 +1,53 @@ +use std::rc::Rc; + +use cassette_core::cassette::GenericCassetteTaskHandle; +use cassette_core::prelude::*; +use cassette_core::{ + cassette::CassetteContext, + components::ComponentRenderer, + data::table::DataTable, + net::fetch::FetchState, + task::{TaskResult, TaskState}, +}; +use serde::{Deserialize, Serialize}; +use yew::prelude::*; + +#[derive(Clone, Debug, PartialEq, Deserialize, Properties)] +#[serde(rename_all = "camelCase")] +pub struct Spec { + #[serde(default)] + base_url: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct State { + #[serde(default, flatten)] + data: Option>, +} + +impl ComponentRenderer for State { + fn render(self, ctx: &mut CassetteContext, spec: Spec) -> TaskResult> { + let Spec { base_url } = spec; + + let force_init = false; + + match crate::hooks::use_fetch(ctx, base_url, force_init).get() { + FetchState::Pending | FetchState::Fetching => Ok(TaskState::Break { + body: html! { }, + state: Some(Self { data: None }), + }), + FetchState::Collecting(content) | FetchState::Completed(content) => { + Ok(TaskState::Skip { + state: Some(Self { + data: Some((**content).clone()), + }), + }) + } + FetchState::Error(msg) => Ok(TaskState::Break { + body: html! { }, + state: Some(Self { data: None }), + }), + } + } +} diff --git a/crates/cassette-plugin-kubernetes-api/src/lib.rs b/crates/cassette-plugin-kubernetes-api/src/lib.rs index 7deb1a2..2d948c2 100644 --- a/crates/cassette-plugin-kubernetes-api/src/lib.rs +++ b/crates/cassette-plugin-kubernetes-api/src/lib.rs @@ -4,33 +4,36 @@ use actix_web::{ body::BoxBody, http::StatusCode, web::{Path, Payload, Query}, - HttpRequest, HttpResponse, Responder, + HttpRequest, HttpResponse, Responder, Scope, }; use anyhow::{anyhow, Result}; use cassette_plugin_jwt::get_authorization_token; use http::Request; -use kube::{config::AuthInfo, core::ErrorResponse, Client, Config}; +pub use kube::Client; +use kube::{config::AuthInfo, core::ErrorResponse, Config}; use url::Url; -pub async fn handle( +pub fn build_services(scope: Scope) -> Scope { + scope.route("/kube/{path:.*}", ::actix_web::web::route().to(handle)) +} + +async fn handle( request: HttpRequest, payload: Payload, uri: Path, queries: Query>, ) -> impl Responder { - let token = match get_authorization_token(&request) { - Ok(token) => token, - Err(error) => return response_error(StatusCode::UNAUTHORIZED, error), - }; - - try_handle(&request, token, uri.into_inner(), queries.0, payload) - .await - .unwrap_or_else(|error| response_error(StatusCode::METHOD_NOT_ALLOWED, error)) + match load_client(&request).await { + Ok(client) => try_handle(&request, client, uri.into_inner(), queries.0, payload) + .await + .unwrap_or_else(|error| response_error(StatusCode::METHOD_NOT_ALLOWED, error)), + Err(error) => response_error(StatusCode::UNAUTHORIZED, error), + } } async fn try_handle( request: &HttpRequest, - token: &str, + client: Client, uri: String, queries: BTreeMap, payload: Payload, @@ -53,16 +56,7 @@ async fn try_handle( } let request = builder.body(body)?; - let config = Config { - auth_info: AuthInfo { - token: Some(token.to_string().into()), - ..Default::default() - }, - ..Config::incluster()? - }; - let client = Client::try_from(config)?; let response = client.send(request).await?; - let status = StatusCode::from_u16(response.status().as_u16())?; let (parts, body) = response.into_parts(); let headers = parts.headers; @@ -76,6 +70,20 @@ async fn try_handle( Ok(response.body(body)) } +pub async fn load_client(request: &HttpRequest) -> Result { + let token = get_authorization_token(request)?; + let config = Config { + auth_info: AuthInfo { + token: Some(token.to_string().into()), + ..Default::default() + }, + ..Config::incluster() + .map_err(|error| anyhow!("failed to create a kubernetes config: {error}"))? + }; + Client::try_from(config) + .map_err(|error| anyhow!("failed to create a kubernetes client: {error}")) +} + fn response_error(code: StatusCode, error: impl ToString) -> HttpResponse { let reason: String = code.canonical_reason().unwrap_or("Unknown").into(); let response = ErrorResponse { diff --git a/crates/cassette-plugin-openai-chat/src/hooks.rs b/crates/cassette-plugin-openai-chat/src/hooks.rs index 30a5f1b..3b0234d 100644 --- a/crates/cassette-plugin-openai-chat/src/hooks.rs +++ b/crates/cassette-plugin-openai-chat/src/hooks.rs @@ -25,7 +25,7 @@ pub fn use_fetch( let stream = request.options.stream; let request = FetchRequest { method: Method::POST, - name: "chat completions", + name: handler_name, url: "/chat/completions", body: Some(Body::Json(request.clone())), }; diff --git a/crates/cassette/Cargo.toml b/crates/cassette/Cargo.toml index 9dea0c1..4f72191 100644 --- a/crates/cassette/Cargo.toml +++ b/crates/cassette/Cargo.toml @@ -20,8 +20,9 @@ workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["full"] -full = ["cdl", "openai"] +default = ["full-local"] +full = ["cdl", "kubernetes", "openai"] +full-local = ["cdl", "openai"] experimental = [] # for demo ONLY diff --git a/crates/cassette/src/components/mod.rs b/crates/cassette/src/components/mod.rs index 9178a9e..8cd2dee 100644 --- a/crates/cassette/src/components/mod.rs +++ b/crates/cassette/src/components/mod.rs @@ -37,7 +37,9 @@ impl TaskRenderer for RootCassetteTask<'_> { ::cassette_plugin_cdl_dataset_stream_reader::State::render_with(ctx, spec) } #[cfg(feature = "cdl-zone")] - "CdlZone" => ::cassette_plugin_cdl_zone::State::render_with(ctx, spec), + "CdlZoneActor" => ::cassette_plugin_cdl_zone::actor::State::render_with(ctx, spec), + #[cfg(feature = "cdl-zone")] + "CdlZoneLoad" => ::cassette_plugin_cdl_zone::load::State::render_with(ctx, spec), "FileUpload" => self::file_upload::State::render_with(ctx, spec), #[cfg(feature = "kubernetes-list")] "KubernetesList" => ::cassette_plugin_kubernetes_list::State::render_with(ctx, spec), diff --git a/crates/cassette/src/components/table.rs b/crates/cassette/src/components/table.rs index 7b1be42..f18a948 100644 --- a/crates/cassette/src/components/table.rs +++ b/crates/cassette/src/components/table.rs @@ -1,5 +1,5 @@ use cassette_core::cassette::CassetteTaskHandle; -use cassette_core::data::table::{DataTableEntry, DataTableLog}; +use cassette_core::data::table::DataTableLog; use cassette_core::prelude::*; use cassette_core::{ cassette::CassetteContext, @@ -22,7 +22,7 @@ pub struct Spec { #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct State { - entries: Vec, + entries: Vec>, } impl ComponentRenderer for State { @@ -51,6 +51,13 @@ impl ComponentRenderer for State { } }; + if records.is_empty() { + return Ok(TaskState::Break { + body: html!("empty"), + state: None, + }); + } + let handler_name = "select"; let force_init = false; let num_records = records.len(); @@ -60,12 +67,7 @@ impl ComponentRenderer for State { .iter() .enumerate() .filter(|(_, selected)| **selected) - .filter_map(|(index, _)| { - Some(DataTableEntry { - index, - values: records.get(index).cloned()?, - }) - }) + .filter_map(|(index, _)| records.get(index).cloned()) .collect(); let body = html! { diff --git a/examples/cdl_zone.yaml b/examples/cdl_zone.yaml index 2363b68..efed47c 100644 --- a/examples/cdl_zone.yaml +++ b/examples/cdl_zone.yaml @@ -13,5 +13,15 @@ metadata: name: cdl-zone spec: tasks: - - name: manage - kind: CdlZone + - name: all_zones + kind: CdlZoneLoad + + - name: zones + kind: Table + spec: + table: :/all_zones + + - name: act-zones + kind: CdlZoneActor + spec: + values: :/zones/entries diff --git a/examples/data_csv_table.yaml b/examples/data_csv_table.yaml index 85de107..0b7927f 100644 --- a/examples/data_csv_table.yaml +++ b/examples/data_csv_table.yaml @@ -18,7 +18,12 @@ spec: spec: type: CSV - - name: show + - name: table kind: Table spec: table: :/csv + + - name: show + kind: Text + spec: + msg: :/table/entries diff --git a/examples/hello_world.yaml b/examples/hello_world.yaml index dae360a..89fe9ab 100644 --- a/examples/hello_world.yaml +++ b/examples/hello_world.yaml @@ -22,4 +22,4 @@ spec: - name: show-hello-message kind: Text spec: - msg: ":/init/msg" + msg: :/init/msg diff --git a/examples/kubernetes_list.yaml b/examples/kubernetes_list.yaml index 468ebf4..3e52b7b 100644 --- a/examples/kubernetes_list.yaml +++ b/examples/kubernetes_list.yaml @@ -40,4 +40,4 @@ spec: - name: show kind: Text spec: - msg: ":/list/content" + msg: :/list/content