diff --git a/rust/common/src/rpc.rs b/rust/common/src/rpc.rs index 1a2a2cd..60b0580 100644 --- a/rust/common/src/rpc.rs +++ b/rust/common/src/rpc.rs @@ -6,54 +6,3 @@ tonic::include_proto!("_"); pub type FrontendClient = rpc_frontend_client::RpcFrontendClient; pub type BackendClient = rpc_backend_client::RpcBackendClient; - -// #[derive(Debug, Clone, Serialize, Deserialize)] -// pub struct RpcSearchResult { -// pub plugin_id: String, -// pub plugin_name: String, -// pub entrypoint_id: String, -// pub entrypoint_name: String, -// pub entrypoint_type: RpcEntrypointType, -// } - -// #[derive(Debug, Clone, Serialize, Deserialize)] -// pub struct RpcPlugin { -// pub plugin_id: String, -// pub plugin_name: String, -// pub enabled: bool, -// pub entrypoints: Vec, -// } - -// #[derive(Debug, Clone, Serialize, Deserialize)] -// pub struct RpcEntrypoint { -// pub entrypoint_id: String, -// pub entrypoint_name: String, -// pub enabled: bool, -// pub entrypoint_type: RpcEntrypointType, -// } -// -// #[derive(Debug, Clone, Deserialize, Serialize)] -// pub struct RpcUiWidget { -// pub widget_id: RpcUiWidgetId, -// pub widget_type: String, -// pub widget_properties: HashMap, -// pub widget_children: Vec, -// } - -// #[derive(Debug, Clone, Deserialize, Serialize)] -// pub struct RpcEventRenderView { -// pub frontend: String, -// pub entrypoint_id: String, -// } -// -// #[derive(Debug, Clone, Deserialize, Serialize)] -// pub struct RpcEventRunCommand { -// pub entrypoint_id: String, -// } - -// #[derive(Debug, Clone, Serialize, Deserialize)] -// pub enum RpcEntrypointType { -// Command, -// View, -// InlineView, -// } diff --git a/rust/management_client/src/ui.rs b/rust/management_client/src/ui.rs index c64007a..9dc0e0c 100644 --- a/rust/management_client/src/ui.rs +++ b/rust/management_client/src/ui.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::rc::Rc; use std::time::Duration; -use iced::{Alignment, Application, Command, Element, executor, font, futures, Length, Padding, Renderer, Settings, Size, Subscription, theme, Theme, time, window, color}; +use iced::{Alignment, Application, color, Command, Element, executor, font, futures, Length, Padding, Renderer, Settings, Size, Subscription, theme, Theme, time, window}; use iced::theme::Palette; use iced::widget::{button, checkbox, column, container, horizontal_space, row, scrollable, Space, text, text_input, vertical_rule}; use iced_aw::graphics::icons; @@ -11,8 +11,9 @@ use iced_table::table; use tonic::Request; use common::model::{EntrypointId, PluginId}; -use common::rpc::{BackendClient, RpcDownloadPluginRequest, RpcDownloadStatus, RpcDownloadStatusRequest, RpcEntrypointType, RpcPluginsRequest, RpcSetEntrypointStateRequest, RpcSetPluginStateRequest}; +use common::rpc::{BackendClient, RpcDownloadPluginRequest, RpcDownloadStatus, RpcDownloadStatusRequest, RpcEntrypointType, RpcPluginPreference, RpcPluginPreferenceUserData, RpcPluginPreferenceValueType, RpcPluginsRequest, RpcSetEntrypointStateRequest, RpcSetPluginStateRequest}; use common::rpc::rpc_backend_client::RpcBackendClient; +use common::rpc::rpc_ui_property_value::Value; pub fn run() { ManagementAppModel::run(Settings { @@ -94,6 +95,8 @@ struct Plugin { show_entrypoints: bool, enabled: bool, entrypoints: HashMap, + preferences: HashMap, + preferences_user_data: HashMap, } #[derive(Debug, Clone)] @@ -102,6 +105,8 @@ struct Entrypoint { entrypoint_name: String, entrypoint_type: EntrypointType, enabled: bool, + preferences: HashMap, + preferences_user_data: HashMap, } #[derive(Debug, Clone)] @@ -111,6 +116,72 @@ pub enum EntrypointType { InlineView, } +#[derive(Debug, Clone)] +pub enum PluginPreferenceUserData { + Number { + value: Option, + }, + String { + value: Option, + }, + Enum { + value: Option, + }, + Bool { + value: Option, + }, + ListOfStrings { + value: Option>, + }, + ListOfNumbers { + value: Option>, + }, + ListOfEnums { + value: Option>, + } +} + +#[derive(Debug, Clone)] +pub enum PluginPreference { + Number { + default: Option, + description: String, + }, + String { + default: Option, + description: String, + }, + Enum { + default: Option, + description: String, + enum_values: Vec, + }, + Bool { + default: Option, + description: String, + }, + ListOfStrings { + default: Option>, + description: String, + }, + ListOfNumbers { + default: Option>, + description: String, + }, + ListOfEnums { + default: Option>, + enum_values: Vec, + description: String, + } +} + +#[derive(Debug, Clone)] +pub struct PreferenceEnumValue { + pub label: String, + pub value: String, +} + + impl Application for ManagementAppModel { type Executor = executor::Default; type Message = ManagementAppMsg; @@ -753,7 +824,13 @@ async fn reload_plugins(mut backend_client: BackendClient) -> HashMap HashMap PluginPreference { + let value_type: RpcPluginPreferenceValueType = value.r#type.try_into().unwrap(); + match value_type { + RpcPluginPreferenceValueType::Number => { + let default = value.default + .map(|value| { + match value.value.unwrap() { + Value::Number(value) => value, + _ => unreachable!() + } + }); + + PluginPreference::Number { + default, + description: value.description, + } + } + RpcPluginPreferenceValueType::String => { + let default = value.default + .map(|value| { + match value.value.unwrap() { + Value::String(value) => value, + _ => unreachable!() + } + }); + + PluginPreference::String { + default, + description: value.description, + } + } + RpcPluginPreferenceValueType::Enum => { + let default = value.default + .map(|value| { + match value.value.unwrap() { + Value::String(value) => value, + _ => unreachable!() + } + }); + + PluginPreference::Enum { + default, + description: value.description, + enum_values: value.enum_values.into_iter() + .map(|value| PreferenceEnumValue { label: value.label, value: value.value }) + .collect() + } + } + RpcPluginPreferenceValueType::Bool => { + let default = value.default + .map(|value| { + match value.value.unwrap() { + Value::Bool(value) => value, + _ => unreachable!() + } + }); + + PluginPreference::Bool { + default, + description: value.description, + } + } + RpcPluginPreferenceValueType::ListOfStrings => { + let default_list = match value.default_list_exists { + true => { + let default_list = value.default_list + .into_iter() + .flat_map(|value| value.value.map(|value| { + match value { + Value::String(value) => value, + _ => unreachable!() + } + })) + .collect(); + + Some(default_list) + }, + false => None + }; + + PluginPreference::ListOfStrings { + default: default_list, + description: value.description, + } + } + RpcPluginPreferenceValueType::ListOfNumbers => { + let default_list = match value.default_list_exists { + true => { + let default_list = value.default_list + .into_iter() + .flat_map(|value| value.value.map(|value| { + match value { + Value::Number(value) => value, + _ => unreachable!() + } + })) + .collect(); + + Some(default_list) + }, + false => None + }; + + PluginPreference::ListOfNumbers { + default: default_list, + description: value.description, + } + } + RpcPluginPreferenceValueType::ListOfEnums => { + let default_list = match value.default_list_exists { + true => { + let default_list = value.default_list + .into_iter() + .flat_map(|value| value.value.map(|value| { + match value { + Value::String(value) => value, + _ => unreachable!() + } + })) + .collect(); + + Some(default_list) + }, + false => None + }; + + PluginPreference::ListOfEnums { + default: default_list, + enum_values: value.enum_values.into_iter() + .map(|value| PreferenceEnumValue { label: value.label, value: value.value }) + .collect(), + description: value.description, + } + } + } +} + +fn plugin_preference_user_data_from_grpc(value: RpcPluginPreferenceUserData) -> PluginPreferenceUserData { + let value_type: RpcPluginPreferenceValueType = value.r#type.try_into().unwrap(); + match value_type { + RpcPluginPreferenceValueType::Number => { + let value = value.value + .map(|value| { + match value.value.unwrap() { + Value::Number(value) => value, + _ => unreachable!() + } + }); + + PluginPreferenceUserData::Number { + value + } + } + RpcPluginPreferenceValueType::String => { + let value = value.value + .map(|value| { + match value.value.unwrap() { + Value::String(value) => value, + _ => unreachable!() + } + }); + + PluginPreferenceUserData::String { + value + } + } + RpcPluginPreferenceValueType::Enum => { + let value = value.value + .map(|value| { + match value.value.unwrap() { + Value::String(value) => value, + _ => unreachable!() + } + }); + + PluginPreferenceUserData::Enum { + value + } + } + RpcPluginPreferenceValueType::Bool => { + let value = value.value + .map(|value| { + match value.value.unwrap() { + Value::Bool(value) => value, + _ => unreachable!() + } + }); + + PluginPreferenceUserData::Bool { + value + } + } + RpcPluginPreferenceValueType::ListOfStrings => { + let value = match value.value_list_exists { + true => { + let value_list = value.value_list + .into_iter() + .flat_map(|value| value.value.map(|value| { + match value { + Value::String(value) => value, + _ => unreachable!() + } + })) + .collect(); + + Some(value_list) + }, + false => None + }; + + PluginPreferenceUserData::ListOfStrings { + value + } + } + RpcPluginPreferenceValueType::ListOfNumbers => { + let value = match value.value_list_exists { + true => { + let value_list = value.value_list + .into_iter() + .flat_map(|value| value.value.map(|value| { + match value { + Value::Number(value) => value, + _ => unreachable!() + } + })) + .collect(); + + Some(value_list) + }, + false => None + }; + + PluginPreferenceUserData::ListOfNumbers { + value + } + } + RpcPluginPreferenceValueType::ListOfEnums => { + let value = match value.value_list_exists { + true => { + let value_list = value.value_list + .into_iter() + .flat_map(|value| value.value.map(|value| { + match value { + Value::String(value) => value, + _ => unreachable!() + } + })) + .collect(); + + Some(value_list) + }, + false => None + }; + + PluginPreferenceUserData::ListOfEnums { + value + } + } + } +} diff --git a/rust/server/src/plugins/config_reader.rs b/rust/server/src/plugins/config_reader.rs index f929aac..987f48a 100644 --- a/rust/server/src/plugins/config_reader.rs +++ b/rust/server/src/plugins/config_reader.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use tracing::{error, info}; use crate::dirs::Dirs; -use crate::plugins::data_db_repository::{DataDbRepository, SavePendingPlugin}; +use crate::plugins::data_db_repository::{DataDbRepository, DbWritePendingPlugin}; pub struct ConfigReader { dirs: Dirs, @@ -25,7 +25,7 @@ impl ConfigReader { if !exists { let pending = self.repository.is_plugin_pending(&plugin.id).await?; if !pending { - let pending_plugin = SavePendingPlugin { + let pending_plugin = DbWritePendingPlugin { id: plugin.id }; self.repository.save_pending_plugin(pending_plugin).await? @@ -62,7 +62,7 @@ impl ConfigReader { #[derive(Debug, Deserialize, Default)] pub struct ApplicationConfig { - // #[serde(default)] + // #[serde(default)] // TODO // configuration_mode: ConfigurationModeConfig, #[serde(default)] plugins: Vec, diff --git a/rust/server/src/plugins/data_db_repository.rs b/rust/server/src/plugins/data_db_repository.rs index 6b35805..ef2f2b4 100644 --- a/rust/server/src/plugins/data_db_repository.rs +++ b/rust/server/src/plugins/data_db_repository.rs @@ -12,7 +12,6 @@ use sqlx::sqlite::SqliteConnectOptions; use sqlx::types::Json; use crate::dirs::Dirs; -use crate::plugins::loader::EnumValue; static MIGRATOR: Migrator = sqlx::migrate!("./db_migrations"); @@ -21,37 +20,24 @@ pub struct DataDbRepository { pool: Pool, } -pub struct GetListPlugin { - pub id: String, - pub name: String, - pub enabled: bool, - pub code: Code, - pub entrypoints: Vec, -} - #[derive(sqlx::FromRow)] -pub struct GetPlugin { +pub struct DbReadPlugin { pub id: String, pub name: String, pub enabled: bool, #[sqlx(json)] - pub code: Code, + pub code: DbCode, #[sqlx(json)] - pub permissions: PluginPermissions, + pub permissions: DbPluginPermissions, pub from_config: bool, #[sqlx(json)] - pub preferences: HashMap, + pub preferences: HashMap, #[sqlx(json)] - pub preferences_user_data: HashMap, + pub preferences_user_data: HashMap, } #[derive(sqlx::FromRow)] -pub struct GetPendingPlugin { - pub id: String, -} - -#[derive(sqlx::FromRow)] -pub struct GetPluginEntrypoint { +pub struct DbReadPluginEntrypoint { pub id: String, pub plugin_id: String, pub name: String, @@ -59,54 +45,38 @@ pub struct GetPluginEntrypoint { #[sqlx(rename = "type")] pub entrypoint_type: String, #[sqlx(json)] - pub preferences: HashMap, + pub preferences: HashMap, #[sqlx(json)] - pub preferences_user_data: HashMap, + pub preferences_user_data: HashMap, } #[derive(Deserialize, Serialize)] -pub struct Code { +pub struct DbCode { pub js: HashMap, } -#[derive(sqlx::FromRow)] -pub struct GetPluginPreferences { - #[sqlx(json)] - pub preferences: HashMap, - #[sqlx(json)] - pub preferences_user_data: HashMap, -} - -#[derive(sqlx::FromRow)] -pub struct GetPluginEntrypointPreferences { - #[sqlx(json)] - pub preferences: HashMap, - #[sqlx(json)] - pub preferences_user_data: HashMap, -} - -pub struct SavePlugin { +pub struct DbWritePlugin { pub id: String, pub name: String, pub enabled: bool, - pub code: Code, - pub entrypoints: Vec, - pub permissions: PluginPermissions, + pub code: DbCode, + pub entrypoints: Vec, + pub permissions: DbPluginPermissions, pub from_config: bool, - pub preferences: HashMap, - pub preferences_user_data: HashMap, + pub preferences: HashMap, + pub preferences_user_data: HashMap, } -pub struct SavePluginEntrypoint { +pub struct DbWritePluginEntrypoint { pub id: String, pub name: String, pub entrypoint_type: String, - pub preferences: HashMap, - pub preferences_user_data: HashMap, + pub preferences: HashMap, + pub preferences_user_data: HashMap, } #[derive(Debug, Deserialize, Serialize)] -pub struct PluginPermissions { +pub struct DbPluginPermissions { pub environment: Vec, pub high_resolution_time: bool, pub network: Vec, @@ -119,7 +89,7 @@ pub struct PluginPermissions { #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "type")] -pub enum PluginPreferenceUserData { +pub enum DbPluginPreferenceUserData { #[serde(rename = "number")] Number { value: Option, @@ -152,7 +122,7 @@ pub enum PluginPreferenceUserData { #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "type")] -pub enum PluginPreference { +pub enum DbPluginPreference { #[serde(rename = "number")] Number { default: Option, @@ -167,7 +137,7 @@ pub enum PluginPreference { Enum { default: Option, description: String, - enum_values: Vec, + enum_values: Vec, }, #[serde(rename = "bool")] Bool { @@ -187,21 +157,27 @@ pub enum PluginPreference { #[serde(rename = "list_of_enums")] ListOfEnums { default: Option>, - enum_values: Vec, + enum_values: Vec, description: String, } } -pub struct SavePendingPlugin { +#[derive(Debug, Deserialize, Serialize)] +pub struct DbPreferenceEnumValue { + pub label: String, + pub value: String, +} + + +#[derive(sqlx::FromRow)] +pub struct DbReadPendingPlugin { pub id: String, } -#[derive(sqlx::FromRow)] -struct PluginEnabled { - pub enabled: bool, +pub struct DbWritePendingPlugin { + pub id: String, } - impl DataDbRepository { pub async fn new(dirs: Dirs) -> anyhow::Result { let conn = SqliteConnectOptions::new() @@ -222,33 +198,34 @@ impl DataDbRepository { }) } - pub async fn list_plugins(&self) -> anyhow::Result> { + pub async fn list_plugins(&self) -> anyhow::Result> { // language=SQLite - let plugins = sqlx::query_as::<_, GetPlugin>("SELECT * FROM plugin") + let plugins = sqlx::query_as::<_, DbReadPlugin>("SELECT * FROM plugin") .fetch_all(&self.pool) .await?; + Ok(plugins) + } + + pub async fn list_plugins_and_entrypoints(&self) -> anyhow::Result)>> { + // language=SQLite + let plugins = self.list_plugins().await?; + let result = futures::stream::iter(plugins) .then(|plugin| async move { let entrypoints = self.get_entrypoints_by_plugin_id(&plugin.id).await?; - Ok::(GetListPlugin { - id: plugin.id, - name: plugin.name, - enabled: plugin.enabled, - code: plugin.code, - entrypoints, - }) + Ok::<(DbReadPlugin, Vec), AnyError>((plugin, entrypoints)) }) - .try_collect::>() + .try_collect::)>>() .await?; Ok(result) } - pub async fn get_plugin_by_id(&self, plugin_id: &str) -> anyhow::Result { + pub async fn get_plugin_by_id(&self, plugin_id: &str) -> anyhow::Result { // language=SQLite - let result = sqlx::query_as::<_, GetPlugin>("SELECT * FROM plugin WHERE id = ?1") + let result = sqlx::query_as::<_, DbReadPlugin>("SELECT * FROM plugin WHERE id = ?1") .bind(plugin_id) .fetch_one(&self.pool) .await?; @@ -256,9 +233,9 @@ impl DataDbRepository { Ok(result) } - pub async fn get_entrypoints_by_plugin_id(&self, plugin_id: &str) -> anyhow::Result> { + pub async fn get_entrypoints_by_plugin_id(&self, plugin_id: &str) -> anyhow::Result> { // language=SQLite - let result = sqlx::query_as::<_, GetPluginEntrypoint>("SELECT * FROM plugin_entrypoint WHERE plugin_id = ?1") + let result = sqlx::query_as::<_, DbReadPluginEntrypoint>("SELECT * FROM plugin_entrypoint WHERE plugin_id = ?1") .bind(plugin_id) .fetch_all(&self.pool) .await?; @@ -266,6 +243,17 @@ impl DataDbRepository { Ok(result) } + pub async fn get_entrypoint_by_id(&self, plugin_id: &str, entrypoint_id: &str) -> anyhow::Result { + // language=SQLite + let result = sqlx::query_as::<_, DbReadPluginEntrypoint>("SELECT * FROM plugin_entrypoint WHERE id = ?1 AND plugin_id = ?2") + .bind(entrypoint_id) + .bind(plugin_id) + .fetch_one(&self.pool) + .await?; + + Ok(result) + } + pub async fn get_inline_view_entrypoint_id_for_plugin(&self, plugin_id: &str) -> anyhow::Result> { // language=SQLite let entrypoint_id = sqlx::query_as::<_, (String, )>("SELECT id FROM plugin_entrypoint WHERE plugin_id = ?1 AND type = 'inline-view'") @@ -277,9 +265,9 @@ impl DataDbRepository { Ok(entrypoint_id) } - pub async fn list_pending_plugins(&self) -> anyhow::Result> { + pub async fn list_pending_plugins(&self) -> anyhow::Result> { // language=SQLite - let plugins = sqlx::query_as::<_, GetPendingPlugin>("SELECT * FROM pending_plugin") + let plugins = sqlx::query_as::<_, DbReadPendingPlugin>("SELECT * FROM pending_plugin") .fetch_all(&self.pool) .await?; @@ -307,8 +295,13 @@ impl DataDbRepository { } pub async fn is_plugin_enabled(&self, plugin_id: &str) -> anyhow::Result { + #[derive(sqlx::FromRow)] + struct DbReadPluginEnabled { + pub enabled: bool, + } + // language=SQLite - let result = sqlx::query_as::<_, PluginEnabled>("SELECT enabled FROM plugin WHERE id = ?1") + let result = sqlx::query_as::<_, DbReadPluginEnabled>("SELECT enabled FROM plugin WHERE id = ?1") .bind(plugin_id) .fetch_one(&self.pool) .await?; @@ -316,27 +309,6 @@ impl DataDbRepository { Ok(result.enabled) } - pub async fn get_plugin_preferences(&self, plugin_id: &str) -> anyhow::Result { - // language=SQLite - let result = sqlx::query_as::<_, GetPluginPreferences>("SELECT * FROM plugin WHERE id = ?1") - .bind(plugin_id) - .fetch_one(&self.pool) - .await?; - - Ok(result) - } - - pub async fn get_plugin_entrypoint_preferences(&self, plugin_id: &str, entrypoint_id: &str) -> anyhow::Result { - // language=SQLite - let result = sqlx::query_as::<_, GetPluginEntrypointPreferences>("SELECT * FROM plugin_entrypoint WHERE id = ?1 AND plugin_id = ?2") - .bind(entrypoint_id) - .bind(plugin_id) - .fetch_one(&self.pool) - .await?; - - Ok(result) - } - pub async fn set_plugin_enabled(&self, plugin_id: &str, enabled: bool) -> anyhow::Result<()> { // language=SQLite sqlx::query("UPDATE plugin SET enabled = ?1 WHERE id = ?2") @@ -360,7 +332,7 @@ impl DataDbRepository { Ok(()) } - pub async fn save_pending_plugin(&self, plugin: SavePendingPlugin) -> anyhow::Result<()> { + pub async fn save_pending_plugin(&self, plugin: DbWritePendingPlugin) -> anyhow::Result<()> { // language=SQLite sqlx::query("INSERT INTO pending_plugin VALUES(?1)") .bind(&plugin.id) @@ -379,7 +351,7 @@ impl DataDbRepository { Ok(()) } - pub async fn save_plugin(&self, plugin: SavePlugin) -> anyhow::Result<()> { + pub async fn save_plugin(&self, plugin: DbWritePlugin) -> anyhow::Result<()> { let mut tx = self.pool.begin().await?; // language=SQLite diff --git a/rust/server/src/plugins/js.rs b/rust/server/src/plugins/js.rs index 64ddd50..d84afab 100644 --- a/rust/server/src/plugins/js.rs +++ b/rust/server/src/plugins/js.rs @@ -27,7 +27,7 @@ use common::rpc::rpc_frontend_server::RpcFrontend; use component_model::{Children, Component, create_component_model, PropertyType}; use crate::model::{from_rpc_to_intermediate_value, IntermediateUiEvent, IntermediateUiWidget, JsPropertyValue, JsRenderLocation, JsUiEvent, JsUiRequestData, JsUiResponseData, JsUiWidget, PreferenceUserData, UiWidgetId}; -use crate::plugins::data_db_repository::{DataDbRepository, GetPluginEntrypointPreferences, GetPluginPreferences, PluginPreference, PluginPreferenceUserData}; +use crate::plugins::data_db_repository::{DataDbRepository, DbPluginPreference, DbPluginPreferenceUserData, DbReadPlugin, DbReadPluginEntrypoint}; use crate::plugins::run_status::RunStatusGuard; pub struct PluginRuntimeData { @@ -492,8 +492,8 @@ fn get_plugin_preferences(state: Rc>) -> anyhow::Result>, entrypoint_id: &str) }; block_on(async { - let GetPluginEntrypointPreferences { preferences, preferences_user_data } = repository - .get_plugin_entrypoint_preferences(&plugin_id.to_string(), entrypoint_id) + let DbReadPluginEntrypoint { preferences, preferences_user_data, .. } = repository + .get_entrypoint_by_id(&plugin_id.to_string(), entrypoint_id) .await?; Ok(preferences_to_js(preferences, preferences_user_data)) @@ -528,31 +528,31 @@ fn get_entrypoint_preferences(state: Rc>, entrypoint_id: &str) } fn preferences_to_js( - preferences: HashMap, - mut preferences_user_data: HashMap + preferences: HashMap, + mut preferences_user_data: HashMap ) -> HashMap { preferences.into_iter() .map(|(name, preference)| { let user_data = match preferences_user_data.remove(&name) { None => { match preference { - PluginPreference::Number { default, .. } => PreferenceUserData::Number(default), - PluginPreference::String { default, .. } => PreferenceUserData::String(default), - PluginPreference::Enum { default, .. } => PreferenceUserData::String(default), - PluginPreference::Bool { default, .. } => PreferenceUserData::Bool(default), - PluginPreference::ListOfStrings { default, .. } => PreferenceUserData::ListOfStrings(default.unwrap_or(vec![])), - PluginPreference::ListOfNumbers { default, .. } => PreferenceUserData::ListOfNumbers(default.unwrap_or(vec![])), - PluginPreference::ListOfEnums { default, .. } => PreferenceUserData::ListOfStrings(default.unwrap_or(vec![])), + DbPluginPreference::Number { default, .. } => PreferenceUserData::Number(default), + DbPluginPreference::String { default, .. } => PreferenceUserData::String(default), + DbPluginPreference::Enum { default, .. } => PreferenceUserData::String(default), + DbPluginPreference::Bool { default, .. } => PreferenceUserData::Bool(default), + DbPluginPreference::ListOfStrings { default, .. } => PreferenceUserData::ListOfStrings(default.unwrap_or(vec![])), + DbPluginPreference::ListOfNumbers { default, .. } => PreferenceUserData::ListOfNumbers(default.unwrap_or(vec![])), + DbPluginPreference::ListOfEnums { default, .. } => PreferenceUserData::ListOfStrings(default.unwrap_or(vec![])), } } Some(user_data) => match user_data { - PluginPreferenceUserData::Number { value } => PreferenceUserData::Number(value), - PluginPreferenceUserData::String { value } => PreferenceUserData::String(value), - PluginPreferenceUserData::Enum { value } => PreferenceUserData::String(value), - PluginPreferenceUserData::Bool { value } => PreferenceUserData::Bool(value), - PluginPreferenceUserData::ListOfStrings { value } => PreferenceUserData::ListOfStrings(value.unwrap_or(vec![])), - PluginPreferenceUserData::ListOfNumbers { value } => PreferenceUserData::ListOfNumbers(value.unwrap_or(vec![])), - PluginPreferenceUserData::ListOfEnums { value } => PreferenceUserData::ListOfStrings(value.unwrap_or(vec![])), + DbPluginPreferenceUserData::Number { value } => PreferenceUserData::Number(value), + DbPluginPreferenceUserData::String { value } => PreferenceUserData::String(value), + DbPluginPreferenceUserData::Enum { value } => PreferenceUserData::String(value), + DbPluginPreferenceUserData::Bool { value } => PreferenceUserData::Bool(value), + DbPluginPreferenceUserData::ListOfStrings { value } => PreferenceUserData::ListOfStrings(value.unwrap_or(vec![])), + DbPluginPreferenceUserData::ListOfNumbers { value } => PreferenceUserData::ListOfNumbers(value.unwrap_or(vec![])), + DbPluginPreferenceUserData::ListOfEnums { value } => PreferenceUserData::ListOfStrings(value.unwrap_or(vec![])), } }; diff --git a/rust/server/src/plugins/loader.rs b/rust/server/src/plugins/loader.rs index 966fdff..e486382 100644 --- a/rust/server/src/plugins/loader.rs +++ b/rust/server/src/plugins/loader.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use common::model::{DownloadStatus, PluginId}; use crate::model::{entrypoint_to_str, PluginEntrypointType}; -use crate::plugins::data_db_repository::{Code, DataDbRepository, PluginPermissions, PluginPreference, PluginPreferenceUserData, SavePlugin, SavePluginEntrypoint}; +use crate::plugins::data_db_repository::{DbCode, DataDbRepository, DbPluginPermissions, DbPluginPreference, DbPluginPreferenceUserData, DbWritePlugin, DbWritePluginEntrypoint, DbPreferenceEnumValue}; use crate::plugins::download_status::DownloadStatusHolder; pub struct PluginLoader { @@ -47,7 +47,7 @@ impl PluginLoader { let plugin_data = PluginLoader::read_plugin_dir(temp_dir.path(), plugin_id.clone()) .await?; - data_db_repository.save_plugin(SavePlugin { + data_db_repository.save_plugin(DbWritePlugin { id: plugin_data.id, name: plugin_data.name, enabled: false, @@ -83,7 +83,7 @@ impl PluginLoader { self.db_repository.remove_plugin(&plugin_data.id).await? } - self.db_repository.save_plugin(SavePlugin { + self.db_repository.save_plugin(DbWritePlugin { id: plugin_data.id, name: plugin_data.name, enabled: true, @@ -125,7 +125,7 @@ impl PluginLoader { Ok(()) } - async fn read_plugin_dir(plugin_dir: &Path, plugin_id: PluginId) -> anyhow::Result { + async fn read_plugin_dir(plugin_dir: &Path, plugin_id: PluginId) -> anyhow::Result { let js_dir = plugin_dir.join("js"); let js_dir_context = js_dir.display().to_string(); @@ -161,7 +161,7 @@ impl PluginLoader { let entrypoints: Vec<_> = plugin_manifest.entrypoint .into_iter() - .map(|entrypoint| SavePluginEntrypoint { + .map(|entrypoint| DbWritePluginEntrypoint { id: entrypoint.id, name: entrypoint.name, entrypoint_type: entrypoint_to_str(match entrypoint.entrypoint_type { @@ -172,13 +172,25 @@ impl PluginLoader { preferences: entrypoint.preferences .into_iter() .map(|preference| match preference { - PluginManifestPreference::Number { name, default, description } => (name, PluginPreference::Number { default, description }), - PluginManifestPreference::String { name, default, description } => (name, PluginPreference::String { default, description }), - PluginManifestPreference::Enum { name, default, description, enum_values } => (name, PluginPreference::Enum { default, description, enum_values }), - PluginManifestPreference::Bool { name, default, description } => (name, PluginPreference::Bool { default, description }), - PluginManifestPreference::ListOfStrings { name, default, description } => (name, PluginPreference::ListOfStrings { default, description }), - PluginManifestPreference::ListOfNumbers { name, default, description } => (name, PluginPreference::ListOfNumbers { default, description }), - PluginManifestPreference::ListOfEnums { name, default, description, enum_values } => (name, PluginPreference::ListOfEnums { default, description, enum_values }), + PluginManifestPreference::Number { name, default, description } => (name, DbPluginPreference::Number { default, description }), + PluginManifestPreference::String { name, default, description } => (name, DbPluginPreference::String { default, description }), + PluginManifestPreference::Enum { name, default, description, enum_values } => { + let enum_values = enum_values.into_iter() + .map(|PluginManifestPreferenceEnumValue { label, value } | DbPreferenceEnumValue { label, value }) + .collect(); + + (name, DbPluginPreference::Enum { default, description, enum_values }) + }, + PluginManifestPreference::Bool { name, default, description } => (name, DbPluginPreference::Bool { default, description }), + PluginManifestPreference::ListOfStrings { name, default, description } => (name, DbPluginPreference::ListOfStrings { default, description }), + PluginManifestPreference::ListOfNumbers { name, default, description } => (name, DbPluginPreference::ListOfNumbers { default, description }), + PluginManifestPreference::ListOfEnums { name, default, description, enum_values } => { + let enum_values = enum_values.into_iter() + .map(|PluginManifestPreferenceEnumValue { label, value } | DbPreferenceEnumValue { label, value }) + .collect(); + + (name, DbPluginPreference::ListOfEnums { default, description, enum_values }) + }, }) .collect(), preferences_user_data: HashMap::new(), @@ -188,17 +200,29 @@ impl PluginLoader { let plugin_preferences = plugin_manifest.preferences .into_iter() .map(|preference| match preference { - PluginManifestPreference::Number { name, default, description } => (name, PluginPreference::Number { default, description }), - PluginManifestPreference::String { name, default, description } => (name, PluginPreference::String { default, description }), - PluginManifestPreference::Enum { name, default, description, enum_values } => (name, PluginPreference::Enum { default, description, enum_values }), - PluginManifestPreference::Bool { name, default, description } => (name, PluginPreference::Bool { default, description }), - PluginManifestPreference::ListOfStrings { name, default, description } => (name, PluginPreference::ListOfStrings { default, description }), - PluginManifestPreference::ListOfNumbers { name, default, description } => (name, PluginPreference::ListOfNumbers { default, description }), - PluginManifestPreference::ListOfEnums { name, default, description, enum_values } => (name, PluginPreference::ListOfEnums { default, description, enum_values }), + PluginManifestPreference::Number { name, default, description } => (name, DbPluginPreference::Number { default, description }), + PluginManifestPreference::String { name, default, description } => (name, DbPluginPreference::String { default, description }), + PluginManifestPreference::Enum { name, default, description, enum_values } => { + let enum_values = enum_values.into_iter() + .map(|PluginManifestPreferenceEnumValue { label, value } | DbPreferenceEnumValue { label, value }) + .collect(); + + (name, DbPluginPreference::Enum { default, description, enum_values }) + }, + PluginManifestPreference::Bool { name, default, description } => (name, DbPluginPreference::Bool { default, description }), + PluginManifestPreference::ListOfStrings { name, default, description } => (name, DbPluginPreference::ListOfStrings { default, description }), + PluginManifestPreference::ListOfNumbers { name, default, description } => (name, DbPluginPreference::ListOfNumbers { default, description }), + PluginManifestPreference::ListOfEnums { name, default, description, enum_values } => { + let enum_values = enum_values.into_iter() + .map(|PluginManifestPreferenceEnumValue { label, value } | DbPreferenceEnumValue { label, value }) + .collect(); + + (name, DbPluginPreference::ListOfEnums { default, description, enum_values }) + }, }) .collect(); - let permissions = PluginPermissions { + let permissions = DbPluginPermissions { environment: plugin_manifest.permissions.environment, high_resolution_time: plugin_manifest.permissions.high_resolution_time, network: plugin_manifest.permissions.network, @@ -209,10 +233,10 @@ impl PluginLoader { system: plugin_manifest.permissions.system, }; - Ok(PluginDirData { + Ok(PluginDownloadData { id: plugin_id.to_string(), name: plugin_name, - code: Code { + code: DbCode { js }, entrypoints, @@ -223,14 +247,14 @@ impl PluginLoader { } } -struct PluginDirData { +struct PluginDownloadData { pub id: String, pub name: String, - pub code: Code, - pub entrypoints: Vec, - pub permissions: PluginPermissions, - pub preferences: HashMap, - pub preferences_user_data: HashMap, + pub code: DbCode, + pub entrypoints: Vec, + pub permissions: DbPluginPermissions, + pub preferences: HashMap, + pub preferences_user_data: HashMap, } #[derive(Debug, Deserialize)] @@ -277,7 +301,7 @@ enum PluginManifestPreference { name: String, default: Option, description: String, - enum_values: Vec, + enum_values: Vec, }, #[serde(rename = "bool")] Bool { @@ -301,15 +325,15 @@ enum PluginManifestPreference { ListOfEnums { name: String, default: Option>, - enum_values: Vec, + enum_values: Vec, description: String, } } #[derive(Debug, Deserialize, Serialize)] -pub struct EnumValue { - label: String, - value: String, +pub struct PluginManifestPreferenceEnumValue { + pub label: String, + pub value: String, } #[derive(Debug, Deserialize)] diff --git a/rust/server/src/plugins/mod.rs b/rust/server/src/plugins/mod.rs index 026f17f..8b065f6 100644 --- a/rust/server/src/plugins/mod.rs +++ b/rust/server/src/plugins/mod.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; -use common::rpc::{RpcEntrypoint, RpcEntrypointType, RpcPlugin}; +use common::rpc::{rpc_ui_property_value, RpcEntrypoint, RpcEntrypointType, RpcEnumValue, RpcPlugin, RpcPluginPreference, RpcPluginPreferenceUserData, RpcPluginPreferenceValueType, RpcUiPropertyValue}; use common::model::{DownloadStatus, EntrypointId, PluginId, PropertyValue}; use crate::dirs::Dirs; use crate::model::{entrypoint_from_str, from_rpc_to_intermediate_value, PluginEntrypointType, UiWidgetId}; use crate::plugins::config_reader::ConfigReader; -use crate::plugins::data_db_repository::DataDbRepository; +use crate::plugins::data_db_repository::{DataDbRepository, DbPluginPreference, DbPluginPreferenceUserData}; use crate::plugins::js::{PluginCode, PluginCommand, OnePluginCommandData, PluginPermissions, PluginRuntimeData, start_plugin_runtime, AllPluginCommandData}; use crate::plugins::loader::PluginLoader; use crate::plugins::run_status::RunStatusHolder; @@ -69,12 +69,12 @@ impl ApplicationManager { } pub async fn plugins(&self) -> anyhow::Result> { - let plugins = self.db_repository.list_plugins().await?; - - let result = plugins + let result = self.db_repository + .list_plugins_and_entrypoints() + .await? .into_iter() - .map(|plugin| { - let entrypoints = plugin.entrypoints + .map(|(plugin, entrypoints)| { + let entrypoints = entrypoints .into_iter() .map(|entrypoint| RpcEntrypoint { enabled: entrypoint.enabled, @@ -84,7 +84,13 @@ impl ApplicationManager { PluginEntrypointType::Command => RpcEntrypointType::Command, PluginEntrypointType::View => RpcEntrypointType::View, PluginEntrypointType::InlineView => RpcEntrypointType::InlineView - }.into() + }.into(), + preferences: entrypoint.preferences.into_iter() + .map(|(key, value)| (key, plugin_preference_to_grpc(value))) + .collect(), + preferences_user_data: entrypoint.preferences_user_data.into_iter() + .map(|(key, value)| (key, plugin_preference_user_data_to_grpc(value))) + .collect(), }) .collect(); @@ -93,6 +99,12 @@ impl ApplicationManager { plugin_name: plugin.name, enabled: plugin.enabled, entrypoints, + preferences: plugin.preferences.into_iter() + .map(|(key, value)| (key, plugin_preference_to_grpc(value))) + .collect(), + preferences_user_data: plugin.preferences_user_data.into_iter() + .map(|(key, value)| (key, plugin_preference_user_data_to_grpc(value))) + .collect(), } }) .collect(); @@ -270,12 +282,12 @@ impl ApplicationManager { async fn reload_search_index(&self) -> anyhow::Result<()> { tracing::info!("Reloading search index"); - let search_items: Vec<_> = self.db_repository.list_plugins() + let search_items: Vec<_> = self.db_repository.list_plugins_and_entrypoints() .await? .into_iter() - .filter(|plugin| plugin.enabled) - .flat_map(|plugin| { - plugin.entrypoints + .filter(|(plugin, _)| plugin.enabled) + .flat_map(|(plugin, entrypoints)| { + entrypoints .into_iter() .filter(|entrypoint| entrypoint.enabled) .map(|entrypoint| { @@ -310,3 +322,130 @@ impl ApplicationManager { self.command_broadcaster.send(command).expect("all respective receivers were closed"); } } + +fn plugin_preference_to_grpc(value: DbPluginPreference) -> RpcPluginPreference { + match value { + DbPluginPreference::Number { default, description } => { + RpcPluginPreference { + r#type: RpcPluginPreferenceValueType::Number.into(), + default: default.map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::Number(value)) }), + description, + ..RpcPluginPreference::default() + } + } + DbPluginPreference::String { default, description } => { + RpcPluginPreference { + r#type: RpcPluginPreferenceValueType::String.into(), + default: default.map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::String(value)) }), + description, + ..RpcPluginPreference::default() + } + } + DbPluginPreference::Enum { default, description, enum_values } => { + RpcPluginPreference { + r#type: RpcPluginPreferenceValueType::Enum.into(), + default: default.map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::String(value)) }), + description, + enum_values: enum_values.into_iter() + .map(|value| RpcEnumValue { label: value.label, value: value.value }) + .collect(), + ..RpcPluginPreference::default() + } + } + DbPluginPreference::Bool { default, description } => { + RpcPluginPreference { + r#type: RpcPluginPreferenceValueType::Bool.into(), + default: default.map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::Bool(value)) }), + description, + ..RpcPluginPreference::default() + } + } + DbPluginPreference::ListOfStrings { default, description } => { + RpcPluginPreference { + r#type: RpcPluginPreferenceValueType::ListOfStrings.into(), + default_list: default.map(|value| value.into_iter().map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::String(value)) }).collect()).unwrap_or(vec![]), + description, + ..RpcPluginPreference::default() + } + } + DbPluginPreference::ListOfNumbers { default, description } => { + RpcPluginPreference { + r#type: RpcPluginPreferenceValueType::ListOfNumbers.into(), + default_list: default.map(|value| value.into_iter().map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::Number(value)) }).collect()).unwrap_or(vec![]), + description, + ..RpcPluginPreference::default() + } + } + DbPluginPreference::ListOfEnums { default, enum_values, description } => { + RpcPluginPreference { + r#type: RpcPluginPreferenceValueType::ListOfEnums.into(), + default_list: default.map(|value| value.into_iter().map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::String(value)) }).collect()).unwrap_or(vec![]), + description, + enum_values: enum_values.into_iter() + .map(|value| RpcEnumValue { label: value.label, value: value.value }) + .collect(), + ..RpcPluginPreference::default() + } + } + } +} + +fn plugin_preference_user_data_to_grpc(value: DbPluginPreferenceUserData) -> RpcPluginPreferenceUserData { + match value { + DbPluginPreferenceUserData::Number { value } => { + RpcPluginPreferenceUserData { + r#type: RpcPluginPreferenceValueType::Number.into(), + value: value.map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::Number(value)) }), + ..RpcPluginPreferenceUserData::default() + } + } + DbPluginPreferenceUserData::String { value } => { + RpcPluginPreferenceUserData { + r#type: RpcPluginPreferenceValueType::String.into(), + value: value.map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::String(value)) }), + ..RpcPluginPreferenceUserData::default() + } + } + DbPluginPreferenceUserData::Enum { value } => { + RpcPluginPreferenceUserData { + r#type: RpcPluginPreferenceValueType::Enum.into(), + value: value.map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::String(value)) }), + ..RpcPluginPreferenceUserData::default() + } + } + DbPluginPreferenceUserData::Bool { value } => { + RpcPluginPreferenceUserData { + r#type: RpcPluginPreferenceValueType::Bool.into(), + value: value.map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::Bool(value)) }), + ..RpcPluginPreferenceUserData::default() + } + } + DbPluginPreferenceUserData::ListOfStrings { value } => { + let exists = value.is_some(); + RpcPluginPreferenceUserData { + r#type: RpcPluginPreferenceValueType::ListOfStrings.into(), + value_list: value.map(|value| value.into_iter().map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::String(value)) }).collect()).unwrap_or(vec![]), + value_list_exists: exists, + ..RpcPluginPreferenceUserData::default() + } + } + DbPluginPreferenceUserData::ListOfNumbers { value } => { + let exists = value.is_some(); + RpcPluginPreferenceUserData { + r#type: RpcPluginPreferenceValueType::ListOfNumbers.into(), + value_list: value.map(|value| value.into_iter().map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::Number(value)) }).collect()).unwrap_or(vec![]), + value_list_exists: exists, + ..RpcPluginPreferenceUserData::default() + } + } + DbPluginPreferenceUserData::ListOfEnums { value } => { + let exists = value.is_some(); + RpcPluginPreferenceUserData { + r#type: RpcPluginPreferenceValueType::ListOfEnums.into(), + value_list: value.map(|value| value.into_iter().map(|value| RpcUiPropertyValue { value: Some(rpc_ui_property_value::Value::String(value)) }).collect()).unwrap_or(vec![]), + value_list_exists: exists, + ..RpcPluginPreferenceUserData::default() + } + } + } +} diff --git a/schema/backend.proto b/schema/backend.proto index 649808e..1a64881 100644 --- a/schema/backend.proto +++ b/schema/backend.proto @@ -14,7 +14,7 @@ service RpcBackend { rpc SendViewEvent (RpcSendViewEventRequest) returns (RpcSendViewEventResponse); // settings - rpc plugins (RpcPluginsRequest) returns (RpcPluginsResponse); + rpc Plugins (RpcPluginsRequest) returns (RpcPluginsResponse); rpc SetPluginState(RpcSetPluginStateRequest) returns (RpcSetPluginStateResponse); @@ -114,6 +114,8 @@ message RpcPlugin { string plugin_name = 2; bool enabled = 3; repeated RpcEntrypoint entrypoints = 4; + map preferences = 5; + map preferences_user_data = 6; } message RpcEntrypoint { @@ -121,6 +123,8 @@ message RpcEntrypoint { string entrypoint_name = 2; bool enabled = 3; RpcEntrypointType entrypoint_type = 4; + map preferences = 5; + map preferences_user_data = 6; } @@ -150,3 +154,41 @@ message RpcDownloadStatusValue { string message = 2; } + +// protobuf is shit, hopefully somebody soon comes up with normal format using wasm wit or something +message RpcPluginPreference { + RpcPluginPreferenceValueType type = 1; + RpcUiPropertyValue default = 2; + repeated RpcUiPropertyValue default_list = 3; + bool default_list_exists = 4; + string description = 5; + repeated RpcEnumValue enum_values = 6; +} + +message RpcEnumValue { + string label = 1; + string value = 2; +} + +message RpcKeyValue { + string key = 1; + string value = 2; +} + +message RpcPluginPreferenceUserData { + RpcPluginPreferenceValueType type = 1; + RpcUiPropertyValue value = 2; + repeated RpcUiPropertyValue value_list = 3; + bool value_list_exists = 4; +} + + +enum RpcPluginPreferenceValueType { + Number = 0; + String = 1; + Enum = 2; + Bool = 3; + ListOfStrings = 4; + ListOfNumbers = 5; + ListOfEnums = 6; +}