diff --git a/core/startos/bindings/ExposedUI.ts b/core/startos/bindings/ExposedUI.ts index 1354bb576..bef4c8214 100644 --- a/core/startos/bindings/ExposedUI.ts +++ b/core/startos/bindings/ExposedUI.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export interface ExposedUI { path: string, title: string, description: string | null, masked: boolean | null, copyable: boolean | null, qr: boolean | null, } \ No newline at end of file +export type ExposedUI = { "type": "object", value: {[key: string]: ExposedUI}, description: string | null, } | { "type": "string", path: string, description: string | null, masked: boolean, copyable: boolean | null, qr: boolean | null, }; \ No newline at end of file diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index cb4e3a255..97e90b894 100644 --- a/core/startos/src/db/model/package.rs +++ b/core/startos/src/db/model/package.rs @@ -303,7 +303,7 @@ pub struct PackageDataEntry { pub current_dependencies: CurrentDependencies, pub interface_addresses: InterfaceAddressMap, pub hosts: HostInfo, - pub store_exposed_ui: Vec, + pub store_exposed_ui: StoreExposedUI, pub store_exposed_dependents: Vec, } impl AsRef for PackageDataEntry { @@ -322,17 +322,39 @@ pub struct ExposedDependent { copyable: Option, qr: Option, } -#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)] -#[model = "Model"] +#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StoreExposedUI(pub BTreeMap); + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type")] #[ts(export)] -pub struct ExposedUI { - #[ts(type = "string")] - pub path: JsonPointer, - pub title: String, - pub description: Option, - pub masked: Option, - pub copyable: Option, - pub qr: Option, +pub enum ExposedUI { + Object { + #[ts(type = "{[key: string]: ExposedUI}")] + value: BTreeMap, + #[serde(default)] + #[ts(type = "string | null")] + description: String, + }, + String { + #[ts(type = "string")] + path: JsonPointer, + description: Option, + masked: bool, + copyable: Option, + qr: Option, + }, +} + +impl Default for ExposedUI { + fn default() -> Self { + ExposedUI::Object { + value: BTreeMap::new(), + description: "".to_string(), + } + } } #[derive(Debug, Clone, Default, Deserialize, Serialize)] diff --git a/core/startos/src/properties.rs b/core/startos/src/properties.rs index 4f59d4303..060518aff 100644 --- a/core/startos/src/properties.rs +++ b/core/startos/src/properties.rs @@ -1,13 +1,15 @@ +use std::collections::BTreeMap; + use clap::Parser; -use imbl_value::{json, Value}; +use imbl_value::{json, InOMap, InternedString, Value}; use models::PackageId; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; -use crate::context::RpcContext; use crate::db::model::package::ExposedUI; use crate::prelude::*; use crate::Error; +use crate::{context::RpcContext, db::model::package::StoreExposedUI}; pub fn display_properties(response: Value) { println!("{}", response); @@ -16,24 +18,42 @@ pub fn display_properties(response: Value) { trait IntoProperties { fn into_properties(self, store: &Value) -> Value; } -impl IntoProperties for Vec { +impl IntoProperties for ExposedUI { fn into_properties(self, store: &Value) -> Value { - let mut data = json!({}); - for ui in self { - let value = ui.path.get(store); - data[ui.title] = json!({ + match self { + ExposedUI::Object { value, description } => { + json!({ + "type": "object", + "description": description, + "value": value.into_iter().map(|(k, v)| (k, v.into_properties(store))).collect::>() + }) + } + ExposedUI::String { + path, + description, + masked, + copyable, + qr, + } => json!({ "type": "string", - "description": ui.description, - "value": value.map(|x| x.to_string()).unwrap_or_default(), - "copyable": ui.copyable, - "qr": ui.qr, - "masked": ui.masked, - }); + "description": description, + "value": path.get(store).cloned().unwrap_or_default(), + "copyable": copyable, + "qr": qr, + "masked": masked + }), } - json!({ - "version": 2, - "data": data - }) + } +} + +impl IntoProperties for StoreExposedUI { + fn into_properties(self, store: &Value) -> Value { + Value::Object( + self.0 + .into_iter() + .map(|(k, v)| (k, v.into_properties(store))) + .collect::>(), + ) } } diff --git a/core/startos/src/service/service_effect_handler.rs b/core/startos/src/service/service_effect_handler.rs index df332f970..585da0d7d 100644 --- a/core/startos/src/service/service_effect_handler.rs +++ b/core/startos/src/service/service_effect_handler.rs @@ -16,7 +16,9 @@ use serde::{Deserialize, Serialize}; use tokio::process::Command; use ts_rs::TS; -use crate::db::model::package::{CurrentDependencies, CurrentDependencyInfo, ExposedUI}; +use crate::db::model::package::{ + CurrentDependencies, CurrentDependencyInfo, ExposedUI, StoreExposedUI, +}; use crate::disk::mount::filesystem::idmapped::IdMapped; use crate::disk::mount::filesystem::loop_dev::LoopDev; use crate::disk::mount::filesystem::overlayfs::OverlayGuard; @@ -674,6 +676,10 @@ async fn expose_for_dependents( context: EffectContext, ExposeForDependentsParams { paths }: ExposeForDependentsParams, ) -> Result<(), Error> { + Ok(()) +} + +async fn expose_ui(context: EffectContext, params: StoreExposedUI) -> Result<(), Error> { let context = context.deref()?; let package_id = context.id.clone(); context @@ -684,48 +690,12 @@ async fn expose_for_dependents( .as_package_data_mut() .as_idx_mut(&package_id) .or_not_found(&package_id)? - .as_store_exposed_dependents_mut() - .ser(&paths) + .as_store_exposed_ui_mut() + .ser(¶ms) }) .await?; Ok(()) } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] -#[serde(rename_all = "camelCase")] -#[serde(tag = "type")] -#[ts(export)] -enum ExposeUiParams { - Object { - #[ts(type = "{[key: string]: ExposeUiParams}")] - value: OrdMap, - }, - String { - path: String, - description: Option, - masked: bool, - copyable: Option, - qr: Option, - }, -} - -async fn expose_ui(context: EffectContext, params: ExposeUiParams) -> Result<(), Error> { - todo!() - // let context = context.deref()?; - // let package_id = context.id.clone(); - // context - // .ctx - // .db - // .mutate(|db| { - // db.as_public_mut() - // .as_package_data_mut() - // .as_idx_mut(&package_id) - // .or_not_found(&package_id)? - // .as_store_exposed_ui_mut() - // .ser(&paths) - // }) - // .await?; - // Ok(()) -} #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] diff --git a/sdk/lib/inits/setupInit.ts b/sdk/lib/inits/setupInit.ts index 2df688f18..ed7ccaad8 100644 --- a/sdk/lib/inits/setupInit.ts +++ b/sdk/lib/inits/setupInit.ts @@ -26,12 +26,7 @@ export function setupInit( }) const { services, ui } = await setupExports(opts) await opts.effects.exposeForDependents(services) - await opts.effects.exposeUi( - forExpose({ - type: "object", - value: ui, - }), - ) + await opts.effects.exposeUi(forExpose(ui)) }, uninit: async (opts) => { await migrations.uninit(opts) @@ -39,14 +34,23 @@ export function setupInit( }, } } +function forExpose(ui: { [key: string]: ExposeUiPaths }) { + return Object.fromEntries( + Object.entries(ui).map(([key, value]) => [key, forExpose_(value)]), + ) +} -function forExpose(ui: ExposeUiPaths): ExposeUiPathsAll { +function forExpose_(ui: ExposeUiPaths): ExposeUiPathsAll { if (ui.type === ("object" as const)) { return { type: "object" as const, value: Object.fromEntries( - Object.entries(ui.value).map(([key, value]) => [key, forExpose(value)]), + Object.entries(ui.value).map(([key, value]) => [ + key, + forExpose_(value), + ]), ), + description: ui.description ?? null, } } return { diff --git a/sdk/lib/test/startosTypeValidation.test.ts b/sdk/lib/test/startosTypeValidation.test.ts index 5f1be60bb..0bd78defa 100644 --- a/sdk/lib/test/startosTypeValidation.test.ts +++ b/sdk/lib/test/startosTypeValidation.test.ts @@ -24,6 +24,7 @@ import { ExportActionParams } from "../../../core/startos/bindings/ExportActionP import { RemoveActionParams } from "../../../core/startos/bindings/RemoveActionParams" import { ReverseProxyParams } from "../../../core/startos/bindings/ReverseProxyParams" import { MountParams } from "../../../core/startos/bindings/MountParams" +import { ExposedUI } from "../../../core/startos/bindings/ExposedUI" function typeEquality(_a: ExpectedType) {} describe("startosTypeValidation ", () => { test(`checking the params match`, () => { @@ -48,7 +49,7 @@ describe("startosTypeValidation ", () => { setConfigured: {} as SetConfigured, setHealth: {} as SetHealth, exposeForDependents: {} as ExposeForDependentsParams, - exposeUi: {} as ExposeUiParams, + exposeUi: {} as { [key: string]: ExposedUI }, getSslCertificate: {} as GetSslCertificateParams, getSslKey: {} as GetSslKeyParams, getServiceInterface: {} as GetServiceInterfaceParams, diff --git a/sdk/lib/types.ts b/sdk/lib/types.ts index 35e345618..6d6b2d7d7 100644 --- a/sdk/lib/types.ts +++ b/sdk/lib/types.ts @@ -265,6 +265,7 @@ export type ExposeUiPaths = | { type: "object" value: { [k: string]: ExposeUiPaths } + description?: string } | { type: "string" @@ -283,6 +284,7 @@ export type ExposeUiPathsAll = | { type: "object" value: { [k: string]: ExposeUiPathsAll } + description: string | null } | { type: "string" @@ -395,7 +397,7 @@ export type Effects = { exposeForDependents(options: { paths: string[] }): Promise - exposeUi(options: ExposeUiPathsAll): Promise + exposeUi(options: { [key: string]: ExposeUiPathsAll }): Promise /** * There are times that we want to see the addresses that where exported * @param options.addressId If we want to filter the address id