Add config Change tracking

This commit is contained in:
Samuel Lorch 2023-10-30 18:26:09 +01:00
parent 56e5bf1f2c
commit 18ef592c55
4 changed files with 71 additions and 27 deletions

View file

@ -9,9 +9,8 @@ let loading = $ref(false);
const columns = [ const columns = [
{heading: 'Path', path: 'path'}, {heading: 'Path', path: 'path'},
{heading: 'Type', path: 'type'}, {heading: 'Action', path: 'action'},
{heading: 'From', path: 'from'}, {heading: 'ID', path: 'id'},
{heading: 'To', path: 'to'},
]; ];
const displayData = $computed(() => { const displayData = $computed(() => {
@ -20,9 +19,8 @@ const displayData = $computed(() => {
for (const change of changelog) { for (const change of changelog) {
data.push({ data.push({
path: change.path, path: change.path,
type: change.type, action: change.action,
from: change.from, id: change.id,
to: change.to,
}); });
} }
return data; return data;

View file

@ -1,12 +1,13 @@
use jsonrpsee::types::Params; use jsonrpsee::types::Params;
use crate::config_manager::Change;
use crate::state::RpcState; use crate::state::RpcState;
use super::ApiError; use super::ApiError;
use super::ApiError::ConfigError; use super::ApiError::ConfigError;
pub fn get_pending_changelog(_: Params, state: &RpcState) -> Result<(), ApiError> { pub fn get_pending_changelog(_: Params, state: &RpcState) -> Result<Vec<Change>, ApiError> {
Err(ApiError::NotImplemented) Ok(state.config_manager.clone().get_pending_changelog())
} }
pub fn apply_pending_changes(_: Params, state: &RpcState) -> Result<(), ApiError> { pub fn apply_pending_changes(_: Params, state: &RpcState) -> Result<(), ApiError> {

View file

@ -1,3 +1,6 @@
use crate::config_manager::{
Change, ChangeAction::Create, ChangeAction::Delete, ChangeAction::Update,
};
use crate::{definitions::system::User, state::RpcState}; use crate::{definitions::system::User, state::RpcState};
use jsonrpsee::types::Params; use jsonrpsee::types::Params;
use pwhash::sha512_crypt; use pwhash::sha512_crypt;
@ -10,6 +13,8 @@ use ApiError::ParameterDeserialize;
use super::{ApiError, GetStringID}; use super::{ApiError, GetStringID};
const USER_CHANGE_PATH: &str = "system.user";
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
pub struct GetUser { pub struct GetUser {
name: String, name: String,
@ -68,11 +73,11 @@ pub fn create_user(p: Params, state: &RpcState) -> Result<(), ApiError> {
let mut tx = cm.start_transaction(); let mut tx = cm.start_transaction();
if tx if tx
.changes .config
.system .system
.users .users
.insert( .insert(
u.name, u.name.clone(),
User { User {
comment: match u.comment { comment: match u.comment {
Some(c) => c, Some(c) => c,
@ -83,8 +88,12 @@ pub fn create_user(p: Params, state: &RpcState) -> Result<(), ApiError> {
) )
.is_none() .is_none()
{ {
tx.commit().map_err(ConfigError)?; tx.commit(Change {
Ok(()) action: Create,
path: USER_CHANGE_PATH,
id: u.name,
})
.map_err(ConfigError)
} else { } else {
tx.revert(); tx.revert();
Err(NotFound) 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 cm = state.config_manager.clone();
let mut tx = cm.start_transaction(); let mut tx = cm.start_transaction();
match tx.changes.system.users.get(&u.name) { match tx.config.system.users.get(&u.name) {
Some(user) => { Some(user) => {
// Only Update Password if field is not empty // Only Update Password if field is not empty
let hash = if u.password == "" { 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)? sha512_crypt::hash(u.password).map_err(HashError)?
}; };
tx.changes.system.users.insert( tx.config.system.users.insert(
u.name, u.name.clone(),
User { User {
comment: match u.comment { comment: match u.comment {
Some(c) => c, Some(c) => c,
@ -123,7 +132,12 @@ pub fn update_user(p: Params, state: &RpcState) -> Result<(), ApiError> {
hash, hash,
}, },
); );
Ok(()) tx.commit(Change {
action: Update,
path: USER_CHANGE_PATH,
id: u.name,
})
.map_err(ConfigError)
} }
None => Err(NotFound), 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 cm = state.config_manager.clone();
let mut tx = cm.start_transaction(); let mut tx = cm.start_transaction();
match tx.changes.system.users.remove(&u.name) { match tx.config.system.users.remove(&u.name) {
Some(_) => tx.commit().map_err(ConfigError), Some(_) => tx
.commit(Change {
action: Delete,
path: USER_CHANGE_PATH,
id: u.name,
})
.map_err(ConfigError),
None => { None => {
tx.revert(); tx.revert();
Err(NotFound) Err(NotFound)

View file

@ -1,3 +1,4 @@
use serde::Serialize;
use validator::Validate; use validator::Validate;
use super::definitions::config::Config; use super::definitions::config::Config;
@ -35,9 +36,30 @@ pub struct ConfigManager {
shared_data: Arc<Mutex<SharedData>>, shared_data: Arc<Mutex<SharedData>>,
} }
pub struct ConfigTransaction<'a> {
finished: bool,
shared_data: MutexGuard<'a, SharedData>,
pub config: Config,
}
struct SharedData { struct SharedData {
current_config: Config, current_config: Config,
pending_config: Config, pending_config: Config,
changelog: Vec<Change>,
}
#[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 // 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)?, current_config: read_file_to_config(CURRENT_CONFIG_PATH)?,
// TODO Dont Fail if pending config is missing, use current instead // TODO Dont Fail if pending config is missing, use current instead
pending_config: read_file_to_config(PENDING_CONFIG_PATH)?, 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() self.shared_data.lock().unwrap().pending_config.clone()
} }
pub fn get_pending_changelog(&self) -> Vec<Change> {
self.shared_data.lock().unwrap().changelog.clone()
}
pub fn apply_pending_changes(&mut self) -> Result<(), ConfigError> { pub fn apply_pending_changes(&mut self) -> Result<(), ConfigError> {
let mut data = self.shared_data.lock().unwrap(); let mut data = self.shared_data.lock().unwrap();
// TODO run Apply functions // TODO run Apply functions
@ -69,6 +97,7 @@ impl ConfigManager {
// TODO revert if config save fails // TODO revert if config save fails
// TODO Remove Pending Config File // TODO Remove Pending Config File
data.current_config = data.pending_config.clone(); data.current_config = data.pending_config.clone();
data.changelog = Vec::new();
Ok(()) Ok(())
} }
@ -77,6 +106,7 @@ impl ConfigManager {
// TODO Remove Pending Config File // TODO Remove Pending Config File
data.pending_config = data.current_config.clone(); data.pending_config = data.current_config.clone();
data.changelog = Vec::new();
Ok(()) Ok(())
} }
@ -85,23 +115,18 @@ impl ConfigManager {
ConfigTransaction { ConfigTransaction {
finished: false, finished: false,
changes: data.pending_config.clone(), config: data.pending_config.clone(),
shared_data: data, shared_data: data,
} }
} }
} }
pub struct ConfigTransaction<'a> {
finished: bool,
shared_data: MutexGuard<'a, SharedData>,
pub changes: Config,
}
impl<'a> ConfigTransaction<'a> { impl<'a> ConfigTransaction<'a> {
pub fn commit(mut self) -> Result<(), ConfigError> { pub fn commit(mut self, change: Change) -> Result<(), ConfigError> {
let ch = self.changes.clone(); let ch = self.config.clone();
ch.validate()?; ch.validate()?;
self.shared_data.pending_config = ch.clone(); self.shared_data.pending_config = ch.clone();
self.shared_data.changelog.push(change);
write_config_to_file(PENDING_CONFIG_PATH, ch.clone())?; write_config_to_file(PENDING_CONFIG_PATH, ch.clone())?;
Ok(()) Ok(())
} }