initial commit
This commit is contained in:
378
clients/admin-ui/src/wasm_client.rs
Normal file
378
clients/admin-ui/src/wasm_client.rs
Normal file
@@ -0,0 +1,378 @@
|
||||
use gloo::net::http::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// WASM-compatible client for Hero Supervisor OpenRPC server
|
||||
#[derive(Clone)]
|
||||
pub struct WasmSupervisorClient {
|
||||
server_url: String,
|
||||
request_id: u64,
|
||||
}
|
||||
|
||||
/// Error types for client operations
|
||||
#[derive(Error, Debug)]
|
||||
pub enum WasmClientError {
|
||||
#[error("HTTP request error: {0}")]
|
||||
Http(String),
|
||||
|
||||
#[error("JSON serialization error: {0}")]
|
||||
Serialization(#[from] serde_json::Error),
|
||||
|
||||
#[error("Server error: {message}")]
|
||||
Server { message: String },
|
||||
}
|
||||
|
||||
/// Result type for client operations
|
||||
pub type WasmClientResult<T> = Result<T, WasmClientError>;
|
||||
|
||||
/// Types of runners supported by the supervisor
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum RunnerType {
|
||||
SALRunner,
|
||||
OSISRunner,
|
||||
VRunner,
|
||||
}
|
||||
|
||||
impl Default for RunnerType {
|
||||
fn default() -> Self {
|
||||
RunnerType::SALRunner
|
||||
}
|
||||
}
|
||||
|
||||
/// Process manager types
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ProcessManagerType {
|
||||
Simple,
|
||||
Tmux,
|
||||
}
|
||||
|
||||
impl Default for ProcessManagerType {
|
||||
fn default() -> Self {
|
||||
ProcessManagerType::Simple
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for an actor runner
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RunnerConfig {
|
||||
pub actor_id: String,
|
||||
pub runner_type: RunnerType,
|
||||
pub binary_path: PathBuf,
|
||||
pub script_type: String,
|
||||
pub args: Vec<String>,
|
||||
pub env_vars: HashMap<String, String>,
|
||||
pub working_dir: Option<PathBuf>,
|
||||
pub restart_policy: String,
|
||||
pub health_check_command: Option<String>,
|
||||
pub dependencies: Vec<String>,
|
||||
}
|
||||
|
||||
/// Job type enumeration
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum JobType {
|
||||
SAL,
|
||||
OSIS,
|
||||
V,
|
||||
}
|
||||
|
||||
/// Job structure for creating and managing jobs
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Job {
|
||||
pub id: String,
|
||||
pub caller_id: String,
|
||||
pub context_id: String,
|
||||
pub payload: String,
|
||||
pub job_type: JobType,
|
||||
pub runner_name: String,
|
||||
pub timeout: Option<u64>,
|
||||
pub env_vars: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Process status information
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ProcessStatus {
|
||||
Running,
|
||||
Stopped,
|
||||
Starting,
|
||||
Stopping,
|
||||
Failed,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Log information structure
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LogInfo {
|
||||
pub timestamp: String,
|
||||
pub level: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl WasmSupervisorClient {
|
||||
/// Create a new supervisor client
|
||||
pub fn new(server_url: impl Into<String>) -> Self {
|
||||
Self {
|
||||
server_url: server_url.into(),
|
||||
request_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the server URL
|
||||
pub fn server_url(&self) -> &str {
|
||||
&self.server_url
|
||||
}
|
||||
|
||||
/// Make a JSON-RPC request
|
||||
async fn make_request<T>(&mut self, method: &str, params: Value) -> WasmClientResult<T>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
self.request_id += 1;
|
||||
|
||||
let request_body = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params,
|
||||
"id": self.request_id
|
||||
});
|
||||
|
||||
let response = Request::post(&self.server_url)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&request_body)
|
||||
.map_err(|e| WasmClientError::Http(e.to_string()))?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| WasmClientError::Http(e.to_string()))?;
|
||||
|
||||
if !response.ok() {
|
||||
return Err(WasmClientError::Http(format!(
|
||||
"HTTP error: {} {}",
|
||||
response.status(),
|
||||
response.status_text()
|
||||
)));
|
||||
}
|
||||
|
||||
let response_text = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|e| WasmClientError::Http(e.to_string()))?;
|
||||
|
||||
let response_json: Value = serde_json::from_str(&response_text)?;
|
||||
|
||||
if let Some(error) = response_json.get("error") {
|
||||
return Err(WasmClientError::Server {
|
||||
message: error.get("message")
|
||||
.and_then(|m| m.as_str())
|
||||
.unwrap_or("Unknown server error")
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let result = response_json
|
||||
.get("result")
|
||||
.ok_or_else(|| WasmClientError::Server {
|
||||
message: "No result in response".to_string(),
|
||||
})?;
|
||||
|
||||
serde_json::from_value(result.clone()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add a new runner to the supervisor
|
||||
pub async fn add_runner(
|
||||
&mut self,
|
||||
config: RunnerConfig,
|
||||
process_manager_type: ProcessManagerType,
|
||||
) -> WasmClientResult<()> {
|
||||
let params = json!({
|
||||
"config": config,
|
||||
"process_manager_type": process_manager_type
|
||||
});
|
||||
|
||||
self.make_request("add_runner", params).await
|
||||
}
|
||||
|
||||
/// Remove a runner from the supervisor
|
||||
pub async fn remove_runner(&mut self, actor_id: &str) -> WasmClientResult<()> {
|
||||
let params = json!({ "actor_id": actor_id });
|
||||
self.make_request("remove_runner", params).await
|
||||
}
|
||||
|
||||
/// List all runner IDs
|
||||
pub async fn list_runners(&mut self) -> WasmClientResult<Vec<String>> {
|
||||
self.make_request("list_runners", json!({})).await
|
||||
}
|
||||
|
||||
/// Start a specific runner
|
||||
pub async fn start_runner(&mut self, actor_id: &str) -> WasmClientResult<()> {
|
||||
let params = json!({ "actor_id": actor_id });
|
||||
self.make_request("start_runner", params).await
|
||||
}
|
||||
|
||||
/// Stop a specific runner
|
||||
pub async fn stop_runner(&mut self, actor_id: &str, force: bool) -> WasmClientResult<()> {
|
||||
let params = json!({ "actor_id": actor_id, "force": force });
|
||||
self.make_request("stop_runner", params).await
|
||||
}
|
||||
|
||||
/// Get status of a specific runner
|
||||
pub async fn get_runner_status(&mut self, actor_id: &str) -> WasmClientResult<ProcessStatus> {
|
||||
let params = json!({ "actor_id": actor_id });
|
||||
self.make_request("get_runner_status", params).await
|
||||
}
|
||||
|
||||
/// Get logs for a specific runner
|
||||
pub async fn get_runner_logs(
|
||||
&mut self,
|
||||
actor_id: &str,
|
||||
lines: Option<usize>,
|
||||
follow: bool,
|
||||
) -> WasmClientResult<Vec<LogInfo>> {
|
||||
let params = json!({
|
||||
"actor_id": actor_id,
|
||||
"lines": lines,
|
||||
"follow": follow
|
||||
});
|
||||
self.make_request("get_runner_logs", params).await
|
||||
}
|
||||
|
||||
/// Queue a job to a specific runner
|
||||
pub async fn queue_job_to_runner(&mut self, runner_name: &str, job: Job) -> WasmClientResult<()> {
|
||||
let params = json!({
|
||||
"runner_name": runner_name,
|
||||
"job": job
|
||||
});
|
||||
self.make_request("queue_job_to_runner", params).await
|
||||
}
|
||||
|
||||
/// Queue a job to a specific runner and wait for the result
|
||||
pub async fn queue_and_wait(
|
||||
&mut self,
|
||||
runner_name: &str,
|
||||
job: Job,
|
||||
timeout_secs: u64,
|
||||
) -> WasmClientResult<Option<String>> {
|
||||
let params = json!({
|
||||
"runner_name": runner_name,
|
||||
"job": job,
|
||||
"timeout_secs": timeout_secs
|
||||
});
|
||||
self.make_request("queue_and_wait", params).await
|
||||
}
|
||||
|
||||
/// Get job result by job ID
|
||||
pub async fn get_job_result(&mut self, job_id: &str) -> WasmClientResult<Option<String>> {
|
||||
let params = json!({ "job_id": job_id });
|
||||
self.make_request("get_job_result", params).await
|
||||
}
|
||||
|
||||
/// Get status of all runners
|
||||
pub async fn get_all_runner_status(&mut self) -> WasmClientResult<Vec<(String, ProcessStatus)>> {
|
||||
self.make_request("get_all_runner_status", json!({})).await
|
||||
}
|
||||
|
||||
/// Start all runners
|
||||
pub async fn start_all(&mut self) -> WasmClientResult<Vec<(String, bool)>> {
|
||||
self.make_request("start_all", json!({})).await
|
||||
}
|
||||
|
||||
/// Stop all runners
|
||||
pub async fn stop_all(&mut self, force: bool) -> WasmClientResult<Vec<(String, bool)>> {
|
||||
let params = json!({ "force": force });
|
||||
self.make_request("stop_all", params).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for creating jobs with a fluent API
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct JobBuilder {
|
||||
id: Option<String>,
|
||||
caller_id: Option<String>,
|
||||
context_id: Option<String>,
|
||||
payload: Option<String>,
|
||||
job_type: Option<JobType>,
|
||||
runner_name: Option<String>,
|
||||
timeout: Option<u64>,
|
||||
env_vars: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl JobBuilder {
|
||||
/// Create a new job builder
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set the caller ID for this job
|
||||
pub fn caller_id(mut self, caller_id: impl Into<String>) -> Self {
|
||||
self.caller_id = Some(caller_id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the context ID for this job
|
||||
pub fn context_id(mut self, context_id: impl Into<String>) -> Self {
|
||||
self.context_id = Some(context_id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the payload (script content) for this job
|
||||
pub fn payload(mut self, payload: impl Into<String>) -> Self {
|
||||
self.payload = Some(payload.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the job type
|
||||
pub fn job_type(mut self, job_type: JobType) -> Self {
|
||||
self.job_type = Some(job_type);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the runner name for this job
|
||||
pub fn runner_name(mut self, runner_name: impl Into<String>) -> Self {
|
||||
self.runner_name = Some(runner_name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the timeout for job execution
|
||||
pub fn timeout(mut self, timeout_secs: u64) -> Self {
|
||||
self.timeout = Some(timeout_secs);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a single environment variable
|
||||
pub fn env_var(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
self.env_vars.insert(key.into(), value.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set multiple environment variables from a HashMap
|
||||
pub fn env_vars(mut self, env_vars: HashMap<String, String>) -> Self {
|
||||
self.env_vars = env_vars;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the job
|
||||
pub fn build(self) -> WasmClientResult<Job> {
|
||||
Ok(Job {
|
||||
id: self.id.unwrap_or_else(|| Uuid::new_v4().to_string()),
|
||||
caller_id: self.caller_id.ok_or_else(|| WasmClientError::Server {
|
||||
message: "caller_id is required".to_string(),
|
||||
})?,
|
||||
context_id: self.context_id.ok_or_else(|| WasmClientError::Server {
|
||||
message: "context_id is required".to_string(),
|
||||
})?,
|
||||
payload: self.payload.ok_or_else(|| WasmClientError::Server {
|
||||
message: "payload is required".to_string(),
|
||||
})?,
|
||||
job_type: self.job_type.ok_or_else(|| WasmClientError::Server {
|
||||
message: "job_type is required".to_string(),
|
||||
})?,
|
||||
runner_name: self.runner_name.ok_or_else(|| WasmClientError::Server {
|
||||
message: "runner_name is required".to_string(),
|
||||
})?,
|
||||
timeout: self.timeout,
|
||||
env_vars: self.env_vars,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user