From 18ef592c557ded2eb95f5e91a95915d74ffd4943 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Mon, 30 Oct 2023 18:26:09 +0100 Subject: [PATCH] Add config Change tracking --- client/src/pages/config/config.vue | 10 +++---- src/api/config.rs | 5 ++-- src/api/system.rs | 40 ++++++++++++++++++++------- src/config_manager.rs | 43 +++++++++++++++++++++++------- 4 files changed, 71 insertions(+), 27 deletions(-) diff --git a/client/src/pages/config/config.vue b/client/src/pages/config/config.vue index dae3f16..94005f2 100644 --- a/client/src/pages/config/config.vue +++ b/client/src/pages/config/config.vue @@ -9,9 +9,8 @@ let loading = $ref(false); const columns = [ {heading: 'Path', path: 'path'}, - {heading: 'Type', path: 'type'}, - {heading: 'From', path: 'from'}, - {heading: 'To', path: 'to'}, + {heading: 'Action', path: 'action'}, + {heading: 'ID', path: 'id'}, ]; const displayData = $computed(() => { @@ -20,9 +19,8 @@ const displayData = $computed(() => { for (const change of changelog) { data.push({ path: change.path, - type: change.type, - from: change.from, - to: change.to, + action: change.action, + id: change.id, }); } return data; diff --git a/src/api/config.rs b/src/api/config.rs index e4fb7ae..4b8a14d 100644 --- a/src/api/config.rs +++ b/src/api/config.rs @@ -1,12 +1,13 @@ use jsonrpsee::types::Params; +use crate::config_manager::Change; use crate::state::RpcState; use super::ApiError; use super::ApiError::ConfigError; -pub fn get_pending_changelog(_: Params, state: &RpcState) -> Result<(), ApiError> { - Err(ApiError::NotImplemented) +pub fn get_pending_changelog(_: Params, state: &RpcState) -> Result, ApiError> { + Ok(state.config_manager.clone().get_pending_changelog()) } pub fn apply_pending_changes(_: Params, state: &RpcState) -> Result<(), ApiError> { diff --git a/src/api/system.rs b/src/api/system.rs index 3aacedf..138c9f5 100644 --- a/src/api/system.rs +++ b/src/api/system.rs @@ -1,3 +1,6 @@ +use crate::config_manager::{ + Change, ChangeAction::Create, ChangeAction::Delete, ChangeAction::Update, +}; use crate::{definitions::system::User, state::RpcState}; use jsonrpsee::types::Params; use pwhash::sha512_crypt; @@ -10,6 +13,8 @@ use ApiError::ParameterDeserialize; use super::{ApiError, GetStringID}; +const USER_CHANGE_PATH: &str = "system.user"; + #[derive(Serialize, Clone)] pub struct GetUser { name: String, @@ -68,11 +73,11 @@ pub fn create_user(p: Params, state: &RpcState) -> Result<(), ApiError> { let mut tx = cm.start_transaction(); if tx - .changes + .config .system .users .insert( - u.name, + u.name.clone(), User { comment: match u.comment { Some(c) => c, @@ -83,8 +88,12 @@ pub fn create_user(p: Params, state: &RpcState) -> Result<(), ApiError> { ) .is_none() { - tx.commit().map_err(ConfigError)?; - Ok(()) + tx.commit(Change { + action: Create, + path: USER_CHANGE_PATH, + id: u.name, + }) + .map_err(ConfigError) } else { tx.revert(); Err(NotFound) @@ -104,7 +113,7 @@ pub fn update_user(p: Params, state: &RpcState) -> Result<(), ApiError> { let mut cm = state.config_manager.clone(); let mut tx = cm.start_transaction(); - match tx.changes.system.users.get(&u.name) { + match tx.config.system.users.get(&u.name) { Some(user) => { // Only Update Password if field is not empty let hash = if u.password == "" { @@ -113,8 +122,8 @@ pub fn update_user(p: Params, state: &RpcState) -> Result<(), ApiError> { sha512_crypt::hash(u.password).map_err(HashError)? }; - tx.changes.system.users.insert( - u.name, + tx.config.system.users.insert( + u.name.clone(), User { comment: match u.comment { Some(c) => c, @@ -123,7 +132,12 @@ pub fn update_user(p: Params, state: &RpcState) -> Result<(), ApiError> { hash, }, ); - Ok(()) + tx.commit(Change { + action: Update, + path: USER_CHANGE_PATH, + id: u.name, + }) + .map_err(ConfigError) } None => Err(NotFound), } @@ -140,8 +154,14 @@ pub fn delete_user(p: Params, state: &RpcState) -> Result<(), ApiError> { let mut cm = state.config_manager.clone(); let mut tx = cm.start_transaction(); - match tx.changes.system.users.remove(&u.name) { - Some(_) => tx.commit().map_err(ConfigError), + match tx.config.system.users.remove(&u.name) { + Some(_) => tx + .commit(Change { + action: Delete, + path: USER_CHANGE_PATH, + id: u.name, + }) + .map_err(ConfigError), None => { tx.revert(); Err(NotFound) diff --git a/src/config_manager.rs b/src/config_manager.rs index ac85578..ec6584e 100644 --- a/src/config_manager.rs +++ b/src/config_manager.rs @@ -1,3 +1,4 @@ +use serde::Serialize; use validator::Validate; use super::definitions::config::Config; @@ -35,9 +36,30 @@ pub struct ConfigManager { shared_data: Arc>, } +pub struct ConfigTransaction<'a> { + finished: bool, + shared_data: MutexGuard<'a, SharedData>, + pub config: Config, +} + struct SharedData { current_config: Config, pending_config: Config, + changelog: Vec, +} + +#[derive(Clone, Serialize)] +pub struct Change { + pub action: ChangeAction, + pub path: &'static str, + pub id: String, +} + +#[derive(Clone, Serialize)] +pub enum ChangeAction { + Create, + Update, + Delete, } // Note, using unwarp on a mutex lock is ok since that only errors with mutex poisoning @@ -49,6 +71,8 @@ impl ConfigManager { current_config: read_file_to_config(CURRENT_CONFIG_PATH)?, // TODO Dont Fail if pending config is missing, use current instead pending_config: read_file_to_config(PENDING_CONFIG_PATH)?, + // TODO Figure out how to restore changes + changelog: Vec::new(), })), }) } @@ -61,6 +85,10 @@ impl ConfigManager { self.shared_data.lock().unwrap().pending_config.clone() } + pub fn get_pending_changelog(&self) -> Vec { + self.shared_data.lock().unwrap().changelog.clone() + } + pub fn apply_pending_changes(&mut self) -> Result<(), ConfigError> { let mut data = self.shared_data.lock().unwrap(); // TODO run Apply functions @@ -69,6 +97,7 @@ impl ConfigManager { // TODO revert if config save fails // TODO Remove Pending Config File data.current_config = data.pending_config.clone(); + data.changelog = Vec::new(); Ok(()) } @@ -77,6 +106,7 @@ impl ConfigManager { // TODO Remove Pending Config File data.pending_config = data.current_config.clone(); + data.changelog = Vec::new(); Ok(()) } @@ -85,23 +115,18 @@ impl ConfigManager { ConfigTransaction { finished: false, - changes: data.pending_config.clone(), + config: data.pending_config.clone(), shared_data: data, } } } -pub struct ConfigTransaction<'a> { - finished: bool, - shared_data: MutexGuard<'a, SharedData>, - pub changes: Config, -} - impl<'a> ConfigTransaction<'a> { - pub fn commit(mut self) -> Result<(), ConfigError> { - let ch = self.changes.clone(); + pub fn commit(mut self, change: Change) -> Result<(), ConfigError> { + let ch = self.config.clone(); ch.validate()?; self.shared_data.pending_config = ch.clone(); + self.shared_data.changelog.push(change); write_config_to_file(PENDING_CONFIG_PATH, ch.clone())?; Ok(()) }