Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macro config routes & UI #339

Merged
merged 43 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2deb59e
added routes for minecraft instances to invoke config parsing
C0W0 Aug 19, 2023
0b0834c
Merge branch 'dev' of github.com:Lodestone-Team/lodestone into macro_…
C0W0 Aug 19, 2023
9b32213
fixed config desc parsing that pushed empty comments at the beginning
C0W0 Aug 19, 2023
3eb71ba
added a new route to store macro config to local as a json
C0W0 Aug 30, 2023
96cf086
Merge branch 'dev' of github.com:Lodestone-Team/lodestone into macro_…
C0W0 Aug 30, 2023
fc808b0
removed unused import
C0W0 Aug 30, 2023
fa5f008
used macro folder to store the config
C0W0 Aug 31, 2023
205d87c
added get_value for SettingLocalCache
C0W0 Aug 31, 2023
dd1a016
added validation logic in get_macro_config route
C0W0 Aug 31, 2023
4ab6a0a
added validation in run macro
C0W0 Aug 31, 2023
1757eab
renamed match_type to validate_type
C0W0 Aug 31, 2023
89e5230
Merge branch 'dev' of github.com:Lodestone-Team/lodestone into macro_…
C0W0 Dec 29, 2023
13a6ff5
added error type to macro config return result
C0W0 Dec 30, 2023
149eaa0
fixed a potential issue that causes the backend to panic when there i…
C0W0 Dec 30, 2023
d23c579
added some comments
C0W0 Jan 1, 2024
bfeea07
removed prefix in config names
C0W0 Jan 2, 2024
ec82501
implemented config injection code composition
C0W0 Jan 2, 2024
cc55735
updated dashboard/backend to support macro config
jhuang38 Jan 6, 2024
b33660a
added macro config injection on spawn
C0W0 Jan 6, 2024
eae7ff7
implemented storing macro configs
jhuang38 Jan 14, 2024
fa19948
fixed macro config store for bool fields
jhuang38 Jan 14, 2024
b09f4ac
added macro modal popup as part of run macro
jhuang38 Jan 14, 2024
b950c3d
added error message in resulting macro modal
jhuang38 Feb 4, 2024
872fb6f
implemented config code injection after testing out the frontend change
C0W0 Feb 8, 2024
35012f5
Merge branch 'macro_config_routes' into macro_config_frontend
C0W0 Feb 8, 2024
806d86f
removed extra api call
jhuang38 Feb 11, 2024
d249ee6
change macro close text
jhuang38 Feb 18, 2024
9151b5a
Merge branch 'macro_config_frontend' of github.com:Lodestone-Team/lod…
jhuang38 Feb 18, 2024
48995e5
Merge pull request #366 from Lodestone-Team/macro_config_frontend
C0W0 Mar 10, 2024
f574fe9
Merge branch 'dev' of github.com:Lodestone-Team/lodestone into macro_…
C0W0 Mar 10, 2024
d9d1de6
change text in config modal
jhuang38 May 24, 2024
93635b2
added proper close/save functionality for macro modal
jhuang38 May 25, 2024
415d514
clean up imports and comment
jhuang38 May 25, 2024
a290aa7
added exception handler for frontend error
jhuang38 May 26, 2024
932fdf7
fixed the bug that causes the backend to panic when invalid default v…
C0W0 May 26, 2024
b2b5a9d
fixed a bug that causes the validation to pass with config mismatches
C0W0 May 26, 2024
ceb2d04
added potential scroll solution (NOTE: MAY BREAK OTHER VIEWS)
jhuang38 Jun 2, 2024
a98774c
fixed dropdown and styling for macro modal box
jhuang38 Jun 8, 2024
88de959
fixed corners of settings on macro modal
jhuang38 Jun 8, 2024
4ea7631
batch settings changes for macros
jhuang38 Jun 9, 2024
adefdef
removed extraneous state variable
jhuang38 Jun 9, 2024
9bf1dfb
Merge branch 'dev' into macro_config_routes
CheatCod Jun 15, 2024
e5605c8
Merge branch 'dev' into macro_config_routes
CheatCod Jun 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/bindings/GetConfigResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ErrorKind } from "./ErrorKind";
import type { SettingManifest } from "./SettingManifest";

export interface GetConfigResponse { config: Record<string, SettingManifest>, message: string | null, error: ErrorKind | null, }
107 changes: 95 additions & 12 deletions core/src/handlers/instance_macro.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use axum::{
extract::Path,
routing::{get, put},
routing::{get, put, post},
Json, Router,
};

use axum_auth::AuthBearer;
use color_eyre::eyre::eyre;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use ts_rs::TS;

use crate::{
auth::user::UserAction,
Expand All @@ -16,6 +19,15 @@ use crate::{
types::InstanceUuid,
AppState,
};
use crate::traits::t_configurable::manifest::SettingManifest;

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct GetConfigResponse {
pub config: IndexMap<String, SettingManifest>,
pub message: Option<String>,
pub error: Option<ErrorKind>,
}

pub async fn get_instance_task_list(
axum::extract::State(state): axum::extract::State<AppState>,
Expand Down Expand Up @@ -74,17 +86,33 @@ pub async fn run_macro(
kind: ErrorKind::NotFound,
source: eyre!("Instance not found"),
})?;
instance
.run_macro(
&macro_name,
args,
CausedBy::User {
user_id: requester.uid,
user_name: requester.username,
},
)
.await?;
Ok(Json(()))

if let Ok(valid_config) = instance.validate_local_config(&macro_name, None).await {
let valid_config = if valid_config.is_empty() {
None
} else {
Some(valid_config)
};

instance
.run_macro(
&macro_name,
args,
valid_config,
CausedBy::User {
user_id: requester.uid,
user_name: requester.username,
},
)
.await?;

Ok(Json(()))
} else {
Err(Error {
kind: ErrorKind::Internal,
source: eyre!("Config error"),
})
}
}

pub async fn kill_macro(
Expand All @@ -102,11 +130,66 @@ pub async fn kill_macro(
Ok(Json(()))
}

pub async fn get_macro_configs(
Path((uuid, macro_name)): Path<(InstanceUuid, String)>,
axum::extract::State(state): axum::extract::State<AppState>,
AuthBearer(token): AuthBearer,
) -> Result<Json<GetConfigResponse>, Error> {
let requester = state.users_manager.read().await.try_auth_or_err(&token)?;
requester.try_action(&UserAction::AccessMacro(Some(uuid.clone())))?;

let instance = state.instances.get(&uuid).ok_or_else(|| Error {
kind: ErrorKind::NotFound,
source: eyre!("Instance not found"),
})?;

let mut config = instance.get_macro_config(&macro_name).await?;

match instance.validate_local_config(&macro_name, Some(&config)).await {
Ok(local_value) => {
local_value.iter().for_each(|(setting_id, local_cache)| {
config[setting_id].set_optional_value(local_cache.get_value().clone()).unwrap();
});
Ok(Json(GetConfigResponse{ config, message: None, error: None }))
},
Err(e) => {
match e.kind {
ErrorKind::NotFound => {
Ok(Json(GetConfigResponse { config, message: Some("Local config cache not found".to_string()), error: Some(ErrorKind::NotFound) }))
},
_ => {
Ok(Json(GetConfigResponse { config, message: Some("There is a mismatch between a config type and its locally-stored value".to_string()), error: Some(ErrorKind::Internal) }))
}
}
},
}
}

pub async fn store_config_to_local(
Path((uuid, macro_name)): Path<(InstanceUuid, String)>,
axum::extract::State(state): axum::extract::State<AppState>,
AuthBearer(token): AuthBearer,
Json(config_to_store): Json<IndexMap<String, SettingManifest>>,
) -> Result<(), Error> {
let requester = state.users_manager.read().await.try_auth_or_err(&token)?;
requester.try_action(&UserAction::AccessMacro(Some(uuid.clone())))?;

let instance = state.instances.get(&uuid).ok_or_else(|| Error {
kind: ErrorKind::NotFound,
source: eyre!("Instance not found"),
})?;

instance.store_macro_config_to_local(&macro_name, &config_to_store).await?;
Ok(())
}

pub fn get_instance_macro_routes(state: AppState) -> Router {
Router::new()
.route("/instance/:uuid/macro/run/:macro_name", put(run_macro))
.route("/instance/:uuid/macro/kill/:pid", put(kill_macro))
.route("/instance/:uuid/macro/list", get(get_instance_macro_list))
.route("/instance/:uuid/macro/config/get/:macro_name", get(get_macro_configs))
.route("/instance/:uuid/macro/config/store/:macro_name", post(store_config_to_local))
.route("/instance/:uuid/task/list", get(get_instance_task_list))
.route(
"/instance/:uuid/history/list",
Expand Down
3 changes: 3 additions & 0 deletions core/src/implementations/generic/macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ use std::rc::Rc;

use async_trait::async_trait;
use color_eyre::eyre::eyre;
use indexmap::IndexMap;

use crate::error::{Error, ErrorKind};
use crate::events::CausedBy;
use crate::macro_executor::{self, WorkerOptionGenerator};
use crate::traits::t_configurable::manifest::SettingLocalCache;
use crate::traits::t_macro::{HistoryEntry, MacroEntry, TMacro, TaskEntry};

use super::bridge::procedure_call::{
Expand Down Expand Up @@ -67,6 +69,7 @@ impl TMacro for GenericInstance {
&self,
_name: &str,
_args: Vec<String>,
_configs: Option<IndexMap<String, SettingLocalCache>>,
_caused_by: CausedBy,
) -> Result<TaskEntry, Error> {
Err(Error {
Expand Down
3 changes: 3 additions & 0 deletions core/src/implementations/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ impl GenericInstance {
CausedBy::System,
Box::new(GenericMainWorkerGenerator::new(procedure_bridge.clone())),
None,
None,
Some(dot_lodestone_config.uuid().clone()),
)
.await?;
Expand Down Expand Up @@ -180,6 +181,7 @@ impl GenericInstance {
CausedBy::System,
Box::new(GenericMainWorkerGenerator::new(procedure_bridge.clone())),
None,
None,
Some(dot_lodestone_config.uuid().clone()),
)
.await?;
Expand Down Expand Up @@ -240,6 +242,7 @@ impl GenericInstance {
}),
None,
None,
None,
)
.await?;

Expand Down
118 changes: 118 additions & 0 deletions core/src/implementations/minecraft/macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ use std::path::{Path, PathBuf};

use async_trait::async_trait;
use color_eyre::eyre::{eyre, Context};
use indexmap::IndexMap;

use crate::{
error::Error,
events::CausedBy,
macro_executor::{DefaultWorkerOptionGenerator, MacroPID, SpawnResult},
traits::t_macro::{HistoryEntry, MacroEntry, TMacro, TaskEntry},
};
use crate::error::ErrorKind;
use crate::macro_executor::MacroExecutor;
use crate::traits::t_configurable::manifest::{
SettingLocalCache,
SettingManifest,
ConfigurableValue,
};

use super::MinecraftInstance;

Expand Down Expand Up @@ -111,18 +119,51 @@ impl TMacro for MinecraftInstance {
&self,
name: &str,
args: Vec<String>,
configs: Option<IndexMap<String, SettingLocalCache>>,
caused_by: CausedBy,
) -> Result<TaskEntry, Error> {
let path_to_macro = resolve_macro_invocation(&self.path_to_macros, name)
.ok_or_else(|| eyre!("Failed to resolve macro invocation for {}", name))?;

// compose config injection code
let config_code = match configs {
Some(config_map) => {
let tokens: Vec<_> = config_map.get_index(0).unwrap().1.get_identifier().split('|').collect();
let config_var_name = tokens[0];
let mut code_string = format!("let {config_var_name} = {{\r\n");

for (var_name, meta) in config_map {
let value_code = match meta.get_value() {
Some(val) => match val {
ConfigurableValue::String(str_val) => format!("\'{str_val}\'"),
ConfigurableValue::Enum(str_val) => format!("\'{str_val}\'"),
ConfigurableValue::Boolean(b_val) => b_val.to_string(),
ConfigurableValue::Float(num) => num.to_string(),
_ => return Err(Error{
kind: ErrorKind::Internal,
source: eyre!("Unsupported config data type"),
}),
},
None => "undefined".to_string(),
};
code_string.push_str(&format!(" {var_name}: {value_code},\r\n"))
}

code_string.push_str("};\r\n");

Some(code_string)
},
None => None,
};

let SpawnResult { macro_pid: pid, .. } = self
.macro_executor
.spawn(
path_to_macro,
args,
caused_by,
Box::new(DefaultWorkerOptionGenerator),
config_code,
None,
Some(self.uuid.clone()),
)
Expand All @@ -148,4 +189,81 @@ impl TMacro for MinecraftInstance {
self.macro_executor.abort_macro(pid)?;
Ok(())
}

async fn get_macro_config(&self, name: &str) -> Result<IndexMap<String, SettingManifest>, Error> {
let path_to_macro = resolve_macro_invocation(&self.path_to_macros, name)
.ok_or_else(|| eyre!("Failed to resolve macro invocation for {}", name))?;
MacroExecutor::get_config_manifest(&path_to_macro).await
}

async fn store_macro_config_to_local(
&self,
name: &str,
config_to_store: &IndexMap<String, SettingManifest>,
) -> Result<(), Error> {
let mut local_configs: IndexMap<String, SettingLocalCache> = IndexMap::new();
config_to_store.iter().for_each(|(var_name, config)| {
local_configs.insert(var_name.clone(), SettingLocalCache::from(config));
});

let config_file_path = self.path_to_macros.join(name).join(format!("{name}_config")).with_extension("json");
std::fs::write(
config_file_path,
serde_json::to_string_pretty(&local_configs).unwrap(),
).context("failed to create the config file")?;

Ok(())
}

async fn validate_local_config(
&self,
name: &str,
config_to_validate: Option<&IndexMap<String, SettingManifest>>,
) -> Result<IndexMap<String, SettingLocalCache>, Error> {
let path_to_macro = resolve_macro_invocation(&self.path_to_macros, name)
.ok_or_else(|| eyre!("Failed to resolve macro invocation for {}", name))?;

let is_config_needed = match config_to_validate {
None => std::fs::read_to_string(path_to_macro).context("failed to read macro file")?.contains("LodestoneConfig"),
Some(_) => true,
};

// if the macro does not need a config, pass the validation ("vacuously true")
if !is_config_needed {
return Ok(IndexMap::new());
}

let config_file_path = self.path_to_macros.join(name).join(format!("{name}_config")).with_extension("json");
match std::fs::read_to_string(config_file_path) {
Ok(config_string) => {
let local_configs: IndexMap<String, SettingLocalCache> = serde_json::from_str(&config_string)
.context("failed to parse local config cache")?;

let configs = match config_to_validate {
Some(config) => config.clone(),
None => self.get_macro_config(name).await?,
};

let validation_result = local_configs.iter().fold(true, |partial_result, (setting_id, local_cache)| {
if !partial_result {
return false;
}
local_cache.validate_type(configs.get(setting_id))
});

if !validation_result {
return Err(Error {
kind: ErrorKind::Internal,
source: eyre!("There is a mismatch between a config type and its locally-stored value"),
});
}

Ok(local_configs)
},
Err(_) => Err(Error {
kind: ErrorKind::NotFound,
source: eyre!("Local config cache is not found"),
}),
}
}
}
1 change: 1 addition & 0 deletions core/src/implementations/minecraft/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl TServer for MinecraftInstance {
CausedBy::System,
Box::new(DefaultWorkerOptionGenerator),
None,
None,
Some(self.uuid.clone()),
)
.await;
Expand Down
Loading
Loading