update api, fix tests and examples
This commit is contained in:
@@ -26,4 +26,4 @@ wasm-logger = "0.2"
|
||||
uuid = { version = "1.0", features = ["v4", "js"] }
|
||||
|
||||
# Use our new WASM OpenRPC client
|
||||
hero-supervisor-openrpc-client = { path = "../clients/openrpc" }
|
||||
hero-supervisor-openrpc-client = { path = "../openrpc" }
|
||||
|
@@ -1,11 +1,9 @@
|
||||
use yew::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use gloo::console;
|
||||
use hero_supervisor_openrpc_client::wasm::{WasmSupervisorClient, WasmJob};
|
||||
use gloo::timers::callback::Interval;
|
||||
|
||||
|
||||
use crate::sidebar::{Sidebar, SupervisorInfo};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use hero_supervisor_openrpc_client::wasm::{WasmSupervisorClient, WasmJob};
|
||||
use crate::sidebar::{Sidebar, SupervisorInfo, SessionSecretType};
|
||||
use crate::runners::{Runners, RegisterForm};
|
||||
use crate::jobs::Jobs;
|
||||
|
||||
@@ -17,7 +15,7 @@ fn generate_job_id() -> String {
|
||||
#[derive(Clone, Default)]
|
||||
pub struct JobForm {
|
||||
pub payload: String,
|
||||
pub runner_name: String,
|
||||
pub runner: String,
|
||||
pub executor: String,
|
||||
pub secret: String,
|
||||
}
|
||||
@@ -47,7 +45,7 @@ pub struct AppState {
|
||||
pub job_form: JobForm,
|
||||
pub supervisor_info: Option<SupervisorInfo>,
|
||||
pub admin_secret: String,
|
||||
pub ping_states: std::collections::HashMap<String, PingState>, // runner_name -> ping_state
|
||||
pub ping_states: std::collections::HashMap<String, PingState>, // runner -> ping_state
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +66,7 @@ pub fn app() -> Html {
|
||||
},
|
||||
job_form: JobForm {
|
||||
payload: String::new(),
|
||||
runner_name: String::new(),
|
||||
runner: String::new(),
|
||||
executor: String::new(),
|
||||
secret: String::new(),
|
||||
},
|
||||
@@ -340,7 +338,7 @@ pub fn app() -> Html {
|
||||
// Admin secret change callback
|
||||
let on_admin_secret_change = {
|
||||
let state = state.clone();
|
||||
Callback::from(move |admin_secret: String| {
|
||||
Callback::from(move |(admin_secret, _secret_type): (String, SessionSecretType)| {
|
||||
let mut new_state = (*state).clone();
|
||||
new_state.admin_secret = admin_secret;
|
||||
state.set(new_state);
|
||||
@@ -354,7 +352,7 @@ pub fn app() -> Html {
|
||||
let mut new_form = state.job_form.clone();
|
||||
match field.as_str() {
|
||||
"payload" => new_form.payload = value,
|
||||
"runner_name" => new_form.runner_name = value,
|
||||
"runner" => new_form.runner = value,
|
||||
"executor" => new_form.executor = value,
|
||||
"secret" => new_form.secret = value,
|
||||
_ => {}
|
||||
@@ -385,7 +383,7 @@ pub fn app() -> Html {
|
||||
job_id.clone(),
|
||||
job_form.payload.clone(),
|
||||
job_form.executor.clone(),
|
||||
job_form.runner_name.clone(),
|
||||
job_form.runner.clone(),
|
||||
);
|
||||
|
||||
// Immediately add job to the list with "pending" status
|
||||
@@ -591,8 +589,9 @@ pub fn app() -> Html {
|
||||
<Sidebar
|
||||
server_url={state.server_url.clone()}
|
||||
supervisor_info={state.supervisor_info.clone()}
|
||||
admin_secret={state.admin_secret.clone()}
|
||||
on_admin_secret_change={on_admin_secret_change}
|
||||
session_secret={state.admin_secret.clone()}
|
||||
session_secret_type={SessionSecretType::Admin}
|
||||
on_session_secret_change={on_admin_secret_change}
|
||||
on_supervisor_info_loaded={on_supervisor_info_loaded}
|
||||
/>
|
||||
|
||||
|
@@ -207,7 +207,7 @@ pub fn runner_detail(props: &RunnerDetailProps) -> Html {
|
||||
.context_id("test-job")
|
||||
.payload(script)
|
||||
.job_type(JobType::SAL)
|
||||
.runner_name(&runner_id)
|
||||
.runner(&runner_id)
|
||||
.build();
|
||||
|
||||
match job {
|
||||
|
@@ -23,7 +23,7 @@ impl PartialEq for JobsProps {
|
||||
self.jobs.len() == other.jobs.len() &&
|
||||
self.server_url == other.server_url &&
|
||||
self.job_form.payload == other.job_form.payload &&
|
||||
self.job_form.runner_name == other.job_form.runner_name &&
|
||||
self.job_form.runner == other.job_form.runner &&
|
||||
self.job_form.executor == other.job_form.executor &&
|
||||
self.job_form.secret == other.job_form.secret &&
|
||||
self.runners.len() == other.runners.len()
|
||||
@@ -45,7 +45,7 @@ pub fn jobs(props: &JobsProps) -> Html {
|
||||
let on_change = props.on_job_form_change.clone();
|
||||
Callback::from(move |e: Event| {
|
||||
let input: HtmlInputElement = e.target_unchecked_into();
|
||||
on_change.emit(("runner_name".to_string(), input.value()));
|
||||
on_change.emit(("runner".to_string(), input.value()));
|
||||
})
|
||||
};
|
||||
|
||||
@@ -104,13 +104,13 @@ pub fn jobs(props: &JobsProps) -> Html {
|
||||
<td>
|
||||
<select
|
||||
class="form-control table-input"
|
||||
value={props.job_form.runner_name.clone()}
|
||||
value={props.job_form.runner.clone()}
|
||||
onchange={on_runner_name_change}
|
||||
>
|
||||
<option value="" disabled=true>{"-Select Runner-"}</option>
|
||||
{ for props.runners.iter().map(|(name, _status)| {
|
||||
html! {
|
||||
<option value={name.clone()} selected={name == &props.job_form.runner_name}>
|
||||
<option value={name.clone()} selected={name == &props.job_form.runner}>
|
||||
{name}
|
||||
</option>
|
||||
}
|
||||
@@ -155,7 +155,7 @@ pub fn jobs(props: &JobsProps) -> Html {
|
||||
<tr>
|
||||
<td><small class="text-muted">{job_id}</small></td>
|
||||
<td><code class="code">{job.payload()}</code></td>
|
||||
<td>{job.runner_name()}</td>
|
||||
<td>{job.runner()}</td>
|
||||
<td>{job.executor()}</td>
|
||||
<td class="action-cell">
|
||||
<span class="status-badge">{"Queued"}</span>
|
||||
|
@@ -17,12 +17,12 @@ pub struct RunnersProps {
|
||||
pub server_url: String,
|
||||
pub runners: Vec<(String, String)>, // (name, status)
|
||||
pub register_form: RegisterForm,
|
||||
pub ping_states: HashMap<String, PingState>, // runner_name -> ping_state
|
||||
pub ping_states: HashMap<String, PingState>, // runner -> ping_state
|
||||
pub on_register_form_change: Callback<(String, String)>,
|
||||
pub on_register_runner: Callback<()>,
|
||||
pub on_load_runners: Callback<()>,
|
||||
pub on_remove_runner: Callback<String>,
|
||||
pub on_ping_runner: Callback<(String, String)>, // (runner_name, secret)
|
||||
pub on_ping_runner: Callback<(String, String)>, // (runner, secret)
|
||||
}
|
||||
|
||||
#[function_component(Runners)]
|
||||
@@ -55,8 +55,8 @@ pub fn runners(props: &RunnersProps) -> Html {
|
||||
®ister_form.name,
|
||||
®ister_form.name, // queue = name
|
||||
).await {
|
||||
Ok(runner_name) => {
|
||||
console::log!("Runner registered successfully:", runner_name);
|
||||
Ok(runner) => {
|
||||
console::log!("Runner registered successfully:", runner);
|
||||
on_register_runner.emit(());
|
||||
}
|
||||
Err(e) => {
|
||||
|
@@ -103,13 +103,13 @@ impl SupervisorService {
|
||||
}
|
||||
|
||||
/// Queue a job to a runner
|
||||
pub async fn queue_job(&self, runner_name: &str, job: Job) -> ClientResult<()> {
|
||||
self.client.borrow_mut().queue_job_to_runner(runner_name, job).await
|
||||
pub async fn queue_job(&self, runner: &str, job: Job) -> ClientResult<()> {
|
||||
self.client.borrow_mut().queue_job_to_runner(runner, job).await
|
||||
}
|
||||
|
||||
/// Queue a job and wait for result
|
||||
pub async fn queue_and_wait(&self, runner_name: &str, job: Job, timeout_secs: u64) -> ClientResult<Option<String>> {
|
||||
self.client.borrow_mut().queue_and_wait(runner_name, job, timeout_secs).await
|
||||
pub async fn queue_and_wait(&self, runner: &str, job: Job, timeout_secs: u64) -> ClientResult<Option<String>> {
|
||||
self.client.borrow_mut().queue_and_wait(runner, job, timeout_secs).await
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,8 +2,7 @@ use yew::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use gloo::console;
|
||||
use hero_supervisor_openrpc_client::wasm::WasmSupervisorClient;
|
||||
|
||||
use hero_supervisor_openrpc_client::wasm::{WasmSupervisorClient, WasmJob};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct SupervisorInfo {
|
||||
@@ -14,104 +13,167 @@ pub struct SupervisorInfo {
|
||||
pub runners_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum SessionSecretType {
|
||||
None,
|
||||
User,
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct SidebarProps {
|
||||
pub server_url: String,
|
||||
pub supervisor_info: Option<SupervisorInfo>,
|
||||
pub admin_secret: String,
|
||||
pub on_admin_secret_change: Callback<String>,
|
||||
pub session_secret: String,
|
||||
pub session_secret_type: SessionSecretType,
|
||||
pub on_session_secret_change: Callback<(String, SessionSecretType)>,
|
||||
pub on_supervisor_info_loaded: Callback<SupervisorInfo>,
|
||||
}
|
||||
|
||||
#[function_component(Sidebar)]
|
||||
pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
let is_unlocked = use_state(|| false);
|
||||
let unlock_secret = use_state(|| String::new());
|
||||
let session_secret_input = use_state(|| String::new());
|
||||
let payload_input = use_state(|| String::new());
|
||||
let admin_secrets = use_state(|| Vec::<String>::new());
|
||||
let user_secrets = use_state(|| Vec::<String>::new());
|
||||
let register_secrets = use_state(|| Vec::<String>::new());
|
||||
let is_loading = use_state(|| false);
|
||||
|
||||
|
||||
let on_unlock_secret_change = {
|
||||
let unlock_secret = unlock_secret.clone();
|
||||
let on_session_secret_change = {
|
||||
let session_secret_input = session_secret_input.clone();
|
||||
Callback::from(move |e: web_sys::Event| {
|
||||
let input: web_sys::HtmlInputElement = e.target().unwrap().dyn_into().unwrap();
|
||||
unlock_secret.set(input.value());
|
||||
session_secret_input.set(input.value());
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
let on_unlock_submit = {
|
||||
let unlock_secret = unlock_secret.clone();
|
||||
let is_unlocked = is_unlocked.clone();
|
||||
let on_session_secret_submit = {
|
||||
let session_secret_input = session_secret_input.clone();
|
||||
let is_loading = is_loading.clone();
|
||||
let admin_secrets = admin_secrets.clone();
|
||||
let user_secrets = user_secrets.clone();
|
||||
let register_secrets = register_secrets.clone();
|
||||
let server_url = props.server_url.clone();
|
||||
let on_session_secret_change = props.on_session_secret_change.clone();
|
||||
|
||||
Callback::from(move |_: web_sys::MouseEvent| {
|
||||
let unlock_secret = unlock_secret.clone();
|
||||
let is_unlocked = is_unlocked.clone();
|
||||
let is_loading = is_loading.clone();
|
||||
let admin_secrets = admin_secrets.clone();
|
||||
let user_secrets = user_secrets.clone();
|
||||
let register_secrets = register_secrets.clone();
|
||||
let server_url = server_url.clone();
|
||||
let secret_value = (*unlock_secret).clone();
|
||||
|
||||
if secret_value.is_empty() {
|
||||
let secret = (*session_secret_input).clone();
|
||||
if secret.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
is_loading.set(true);
|
||||
let client = WasmSupervisorClient::new(server_url.clone());
|
||||
|
||||
let session_secret_input = session_secret_input.clone();
|
||||
let is_loading = is_loading.clone();
|
||||
let admin_secrets = admin_secrets.clone();
|
||||
let user_secrets = user_secrets.clone();
|
||||
let register_secrets = register_secrets.clone();
|
||||
let on_session_secret_change = on_session_secret_change.clone();
|
||||
|
||||
spawn_local(async move {
|
||||
let client = WasmSupervisorClient::new(server_url);
|
||||
|
||||
// Try to load all secrets
|
||||
match client.list_admin_secrets(&secret_value).await {
|
||||
Ok(secrets) => {
|
||||
admin_secrets.set(secrets);
|
||||
// Try to get admin secrets first to determine if this is an admin secret
|
||||
match client.list_admin_secrets(&secret).await {
|
||||
Ok(admin_secret_list) => {
|
||||
// This is an admin secret
|
||||
admin_secrets.set(admin_secret_list);
|
||||
|
||||
// Load user secrets
|
||||
if let Ok(user_secs) = client.list_user_secrets(&secret_value).await {
|
||||
user_secrets.set(user_secs);
|
||||
// Also load user and register secrets
|
||||
if let Ok(user_secret_list) = client.list_user_secrets(&secret).await {
|
||||
user_secrets.set(user_secret_list);
|
||||
}
|
||||
if let Ok(register_secret_list) = client.list_register_secrets(&secret).await {
|
||||
register_secrets.set(register_secret_list);
|
||||
}
|
||||
|
||||
// Load register secrets
|
||||
if let Ok(reg_secs) = client.list_register_secrets(&secret_value).await {
|
||||
register_secrets.set(reg_secs);
|
||||
}
|
||||
|
||||
is_unlocked.set(true);
|
||||
unlock_secret.set(String::new());
|
||||
console::log!("Secrets unlocked successfully");
|
||||
on_session_secret_change.emit((secret, SessionSecretType::Admin));
|
||||
console::log!("Admin session established");
|
||||
}
|
||||
Err(e) => {
|
||||
console::error!("Failed to unlock secrets:", format!("{:?}", e));
|
||||
Err(_) => {
|
||||
// Try as user secret - just test if we can make any call with it
|
||||
match client.list_runners().await {
|
||||
Ok(_) => {
|
||||
// This appears to be a valid user secret
|
||||
on_session_secret_change.emit((secret, SessionSecretType::User));
|
||||
console::log!("User session established");
|
||||
}
|
||||
Err(e) => {
|
||||
console::log!("Invalid secret:", format!("{:?}", e));
|
||||
on_session_secret_change.emit((String::new(), SessionSecretType::None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is_loading.set(false);
|
||||
session_secret_input.set(String::new());
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
let on_lock_click = {
|
||||
let is_unlocked = is_unlocked.clone();
|
||||
let on_session_clear = {
|
||||
let on_session_secret_change = props.on_session_secret_change.clone();
|
||||
let admin_secrets = admin_secrets.clone();
|
||||
let user_secrets = user_secrets.clone();
|
||||
let register_secrets = register_secrets.clone();
|
||||
|
||||
Callback::from(move |_: web_sys::MouseEvent| {
|
||||
is_unlocked.set(false);
|
||||
on_session_secret_change.emit((String::new(), SessionSecretType::None));
|
||||
admin_secrets.set(Vec::new());
|
||||
user_secrets.set(Vec::new());
|
||||
register_secrets.set(Vec::new());
|
||||
console::log!("Secrets locked");
|
||||
console::log!("Session cleared");
|
||||
})
|
||||
};
|
||||
|
||||
let on_payload_change = {
|
||||
let payload_input = payload_input.clone();
|
||||
Callback::from(move |e: web_sys::Event| {
|
||||
let input: web_sys::HtmlInputElement = e.target().unwrap().dyn_into().unwrap();
|
||||
payload_input.set(input.value());
|
||||
})
|
||||
};
|
||||
|
||||
let on_run_click = {
|
||||
let payload_input = payload_input.clone();
|
||||
let server_url = props.server_url.clone();
|
||||
let session_secret = props.session_secret.clone();
|
||||
let is_loading = is_loading.clone();
|
||||
|
||||
Callback::from(move |_: web_sys::MouseEvent| {
|
||||
let payload = (*payload_input).clone();
|
||||
if payload.is_empty() || session_secret.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
is_loading.set(true);
|
||||
let client = WasmSupervisorClient::new(server_url.clone());
|
||||
|
||||
let payload_input = payload_input.clone();
|
||||
let is_loading = is_loading.clone();
|
||||
let session_secret = session_secret.clone();
|
||||
|
||||
spawn_local(async move {
|
||||
// Create WasmJob object using constructor
|
||||
let job = WasmJob::new(
|
||||
uuid::Uuid::new_v4().to_string(),
|
||||
payload.clone(),
|
||||
"osis".to_string(),
|
||||
"default".to_string(),
|
||||
);
|
||||
|
||||
match client.create_job(session_secret.clone(), job).await {
|
||||
Ok(job_id) => {
|
||||
console::log!("Job created successfully:", job_id);
|
||||
payload_input.set(String::new());
|
||||
}
|
||||
Err(e) => {
|
||||
console::log!("Failed to create job:", format!("{:?}", e));
|
||||
}
|
||||
}
|
||||
is_loading.set(false);
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
@@ -133,46 +195,76 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Secrets Management Section
|
||||
<div class="secrets-section">
|
||||
<div class="secrets-header">
|
||||
<span class="secrets-title">{"Secrets"}</span>
|
||||
if !*is_unlocked {
|
||||
<button
|
||||
class="unlock-btn"
|
||||
onclick={on_unlock_submit}
|
||||
disabled={*is_loading || unlock_secret.is_empty()}
|
||||
>
|
||||
<i class={if *is_loading { "fas fa-spinner fa-spin" } else { "fas fa-unlock" }}></i>
|
||||
</button>
|
||||
} else {
|
||||
<button
|
||||
class="lock-btn"
|
||||
onclick={on_lock_click}
|
||||
>
|
||||
<i class="fas fa-lock"></i>
|
||||
</button>
|
||||
// Session Secret Management Section
|
||||
<div class="session-section">
|
||||
<div class="session-header">
|
||||
<span class="session-title">{"Session"}</span>
|
||||
{
|
||||
match props.session_secret_type {
|
||||
SessionSecretType::Admin => html! {
|
||||
<span class="session-badge admin">{"Admin"}</span>
|
||||
},
|
||||
SessionSecretType::User => html! {
|
||||
<span class="session-badge user">{"User"}</span>
|
||||
},
|
||||
SessionSecretType::None => html! {
|
||||
<span class="session-badge none">{"None"}</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
if !*is_unlocked {
|
||||
<div class="unlock-input-row">
|
||||
if props.session_secret_type == SessionSecretType::None {
|
||||
<div class="session-input-row">
|
||||
<input
|
||||
type="password"
|
||||
class="unlock-input"
|
||||
placeholder="Enter admin secret to unlock"
|
||||
value={(*unlock_secret).clone()}
|
||||
onchange={on_unlock_secret_change}
|
||||
class="session-input"
|
||||
placeholder="Enter secret to establish session"
|
||||
value={(*session_secret_input).clone()}
|
||||
onchange={on_session_secret_change}
|
||||
disabled={*is_loading}
|
||||
/>
|
||||
<button
|
||||
class="session-btn"
|
||||
onclick={on_session_secret_submit}
|
||||
disabled={*is_loading || session_secret_input.is_empty()}
|
||||
>
|
||||
if *is_loading {
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
} else {
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
} else {
|
||||
<div class="session-active">
|
||||
<div class="session-info">
|
||||
<span class="session-secret-preview">
|
||||
{format!("{}...", &props.session_secret[..std::cmp::min(8, props.session_secret.len())])}
|
||||
</span>
|
||||
<button
|
||||
class="session-clear-btn"
|
||||
onclick={on_session_clear}
|
||||
>
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
if *is_unlocked {
|
||||
</div>
|
||||
|
||||
// Secrets Management Section (only visible for admin)
|
||||
if props.session_secret_type == SessionSecretType::Admin {
|
||||
<div class="secrets-section">
|
||||
<div class="secrets-header">
|
||||
<span class="secrets-title">{"Secrets Management"}</span>
|
||||
</div>
|
||||
|
||||
<div class="secrets-content">
|
||||
<div class="secret-group">
|
||||
<div class="secret-header">
|
||||
<span class="secret-title">{"Admin secrets"}</span>
|
||||
<span class="secret-count">{admin_secrets.len()}</span>
|
||||
</div>
|
||||
<div class="secret-list">
|
||||
{ for admin_secrets.iter().enumerate().map(|(i, secret)| {
|
||||
@@ -185,22 +277,13 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
<div class="secret-add-row">
|
||||
<input
|
||||
type="text"
|
||||
class="secret-add-input"
|
||||
placeholder="New admin secret"
|
||||
/>
|
||||
<button class="btn-icon btn-add">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="secret-group">
|
||||
<div class="secret-header">
|
||||
<span class="secret-title">{"User secrets"}</span>
|
||||
<span class="secret-count">{user_secrets.len()}</span>
|
||||
</div>
|
||||
<div class="secret-list">
|
||||
{ for user_secrets.iter().enumerate().map(|(i, secret)| {
|
||||
@@ -213,22 +296,13 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
<div class="secret-add-row">
|
||||
<input
|
||||
type="text"
|
||||
class="secret-add-input"
|
||||
placeholder="New user secret"
|
||||
/>
|
||||
<button class="btn-icon btn-add">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="secret-group">
|
||||
<div class="secret-header">
|
||||
<span class="secret-title">{"Register secrets"}</span>
|
||||
<span class="secret-count">{register_secrets.len()}</span>
|
||||
</div>
|
||||
<div class="secret-list">
|
||||
{ for register_secrets.iter().enumerate().map(|(i, secret)| {
|
||||
@@ -241,27 +315,72 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
<div class="secret-add-row">
|
||||
<input
|
||||
type="text"
|
||||
class="secret-add-input"
|
||||
placeholder="New register secret"
|
||||
/>
|
||||
<button class="btn-icon btn-add">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
// Quick Actions Section
|
||||
<div class="quick-actions">
|
||||
<div class="quick-actions-header">
|
||||
<span class="quick-actions-title">{"Quick Actions"}</span>
|
||||
</div>
|
||||
<div class="quick-actions-content">
|
||||
if props.session_secret_type != SessionSecretType::None {
|
||||
<div class="action-row">
|
||||
<input
|
||||
type="text"
|
||||
class="action-input"
|
||||
placeholder="Enter payload for job"
|
||||
value={(*payload_input).clone()}
|
||||
onchange={on_payload_change}
|
||||
/>
|
||||
<button
|
||||
class="action-btn run-btn"
|
||||
onclick={on_run_click}
|
||||
disabled={payload_input.is_empty() || *is_loading}
|
||||
>
|
||||
if *is_loading {
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
} else {
|
||||
<i class="fas fa-play"></i>
|
||||
}
|
||||
{"Run"}
|
||||
</button>
|
||||
</div>
|
||||
} else {
|
||||
<div class="action-disabled">
|
||||
<span>{"Establish a session to enable quick actions"}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if *is_unlocked {
|
||||
<div class="save-section">
|
||||
<button class="save-changes-btn">
|
||||
{"Save Changes"}
|
||||
</button>
|
||||
|
||||
// Supervisor Info Section
|
||||
if let Some(info) = &props.supervisor_info {
|
||||
<div class="supervisor-info">
|
||||
<div class="supervisor-info-header">
|
||||
<span class="supervisor-info-title">{"Supervisor Info"}</span>
|
||||
</div>
|
||||
<div class="supervisor-info-content">
|
||||
<div class="info-item">
|
||||
<span class="info-label">{"Admin secrets:"}</span>
|
||||
<span class="info-value">{info.admin_secrets_count}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{"User secrets:"}</span>
|
||||
<span class="info-value">{info.user_secrets_count}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{"Register secrets:"}</span>
|
||||
<span class="info-value">{info.register_secrets_count}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{"Runners:"}</span>
|
||||
<span class="info-value">{info.runners_count}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@@ -87,7 +87,7 @@ pub struct Job {
|
||||
pub context_id: String,
|
||||
pub payload: String,
|
||||
pub job_type: JobType,
|
||||
pub runner_name: String,
|
||||
pub runner: String,
|
||||
pub timeout: Option<u64>,
|
||||
pub env_vars: HashMap<String, String>,
|
||||
}
|
||||
@@ -239,9 +239,9 @@ impl WasmSupervisorClient {
|
||||
}
|
||||
|
||||
/// Queue a job to a specific runner
|
||||
pub async fn queue_job_to_runner(&mut self, runner_name: &str, job: Job) -> WasmClientResult<()> {
|
||||
pub async fn queue_job_to_runner(&mut self, runner: &str, job: Job) -> WasmClientResult<()> {
|
||||
let params = json!({
|
||||
"runner_name": runner_name,
|
||||
"runner": runner,
|
||||
"job": job
|
||||
});
|
||||
self.make_request("queue_job_to_runner", params).await
|
||||
@@ -250,12 +250,12 @@ impl WasmSupervisorClient {
|
||||
/// Queue a job to a specific runner and wait for the result
|
||||
pub async fn queue_and_wait(
|
||||
&mut self,
|
||||
runner_name: &str,
|
||||
runner: &str,
|
||||
job: Job,
|
||||
timeout_secs: u64,
|
||||
) -> WasmClientResult<Option<String>> {
|
||||
let params = json!({
|
||||
"runner_name": runner_name,
|
||||
"runner": runner,
|
||||
"job": job,
|
||||
"timeout_secs": timeout_secs
|
||||
});
|
||||
@@ -293,7 +293,7 @@ pub struct JobBuilder {
|
||||
context_id: Option<String>,
|
||||
payload: Option<String>,
|
||||
job_type: Option<JobType>,
|
||||
runner_name: Option<String>,
|
||||
runner: Option<String>,
|
||||
timeout: Option<u64>,
|
||||
env_vars: HashMap<String, String>,
|
||||
}
|
||||
@@ -329,8 +329,8 @@ impl JobBuilder {
|
||||
}
|
||||
|
||||
/// 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());
|
||||
pub fn runner(mut self, runner: impl Into<String>) -> Self {
|
||||
self.runner = Some(runner.into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -368,8 +368,8 @@ impl JobBuilder {
|
||||
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(),
|
||||
runner: self.runner.ok_or_else(|| WasmClientError::Server {
|
||||
message: "runner is required".to_string(),
|
||||
})?,
|
||||
timeout: self.timeout,
|
||||
env_vars: self.env_vars,
|
||||
|
@@ -54,7 +54,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.context_id("example_context")
|
||||
.payload("print('Hello from Hero Supervisor!');")
|
||||
.job_type(JobType::OSIS)
|
||||
.runner_name("my_actor")
|
||||
.runner("my_actor")
|
||||
.timeout(Duration::from_secs(60))
|
||||
.build()?;
|
||||
|
||||
@@ -112,7 +112,7 @@ let job = JobBuilder::new()
|
||||
.context_id("context_id")
|
||||
.payload("script_content")
|
||||
.job_type(JobType::OSIS)
|
||||
.runner_name("target_actor")
|
||||
.runner("target_actor")
|
||||
.timeout(Duration::from_secs(300))
|
||||
.env_var("KEY", "value")
|
||||
.build()?;
|
||||
|
@@ -138,7 +138,7 @@ impl App {
|
||||
description: "Job ID".to_string(),
|
||||
},
|
||||
RpcParam {
|
||||
name: "runner_name".to_string(),
|
||||
name: "runner".to_string(),
|
||||
param_type: "String".to_string(),
|
||||
required: true,
|
||||
description: "Name of the runner to execute the job".to_string(),
|
||||
@@ -374,7 +374,7 @@ impl App {
|
||||
match param.name.as_str() {
|
||||
"secret" => params["secret"] = value,
|
||||
"job_id" => params["job_id"] = value,
|
||||
"runner_name" => params["runner_name"] = value,
|
||||
"runner" => params["runner"] = value,
|
||||
"payload" => params["payload"] = value,
|
||||
_ => {}
|
||||
}
|
||||
@@ -536,10 +536,10 @@ impl App {
|
||||
}
|
||||
}
|
||||
"run_job" => {
|
||||
if let (Some(secret), Some(job_id), Some(runner_name), Some(payload)) = (
|
||||
if let (Some(secret), Some(job_id), Some(runner), Some(payload)) = (
|
||||
params.get("secret").and_then(|v| v.as_str()),
|
||||
params.get("job_id").and_then(|v| v.as_str()),
|
||||
params.get("runner_name").and_then(|v| v.as_str()),
|
||||
params.get("runner").and_then(|v| v.as_str()),
|
||||
params.get("payload").and_then(|v| v.as_str())
|
||||
) {
|
||||
// Create a job object
|
||||
@@ -549,7 +549,7 @@ impl App {
|
||||
"context_id": "cli_context",
|
||||
"payload": payload,
|
||||
"job_type": "SAL",
|
||||
"runner_name": runner_name,
|
||||
"runner": runner,
|
||||
"timeout": 30000000000u64, // 30 seconds in nanoseconds
|
||||
"env_vars": {},
|
||||
"created_at": chrono::Utc::now().to_rfc3339(),
|
||||
@@ -567,7 +567,7 @@ impl App {
|
||||
}
|
||||
} else {
|
||||
Err(hero_supervisor_openrpc_client::ClientError::from(
|
||||
serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Missing required parameters: secret, job_id, runner_name, payload"))
|
||||
serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Missing required parameters: secret, job_id, runner, payload"))
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,6 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use serde_json;
|
||||
use uuid::Uuid;
|
||||
@@ -157,32 +156,34 @@ pub struct RunnerConfig {
|
||||
pub redis_url: String,
|
||||
}
|
||||
|
||||
/// Job type enumeration that maps to runner types
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum JobType {
|
||||
/// SAL job type
|
||||
SAL,
|
||||
/// OSIS job type
|
||||
OSIS,
|
||||
/// V job type
|
||||
V,
|
||||
/// Python job type
|
||||
Python,
|
||||
}
|
||||
|
||||
/// Job status enumeration
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum JobStatus {
|
||||
/// Job has been created but not yet dispatched
|
||||
Created,
|
||||
/// Job has been dispatched to a worker queue
|
||||
Dispatched,
|
||||
/// Job is currently being executed
|
||||
WaitingForPrerequisites,
|
||||
Started,
|
||||
/// Job completed successfully
|
||||
Finished,
|
||||
/// Job completed with an error
|
||||
Error,
|
||||
Stopping,
|
||||
Finished,
|
||||
}
|
||||
|
||||
/// Job result response
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum JobResult {
|
||||
Success { success: String },
|
||||
Error { error: String },
|
||||
}
|
||||
|
||||
/// Job status response
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JobStatusResponse {
|
||||
pub job_id: String,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
pub started_at: Option<String>,
|
||||
pub completed_at: Option<String>,
|
||||
}
|
||||
|
||||
/// Job structure for creating and managing jobs
|
||||
@@ -196,20 +197,18 @@ pub struct Job {
|
||||
pub context_id: String,
|
||||
/// Script content or payload to execute
|
||||
pub payload: String,
|
||||
/// Type of job (determines which actor will process it)
|
||||
pub job_type: JobType,
|
||||
/// Name of the specific runner/actor to execute this job
|
||||
pub runner_name: String,
|
||||
/// Current status of the job
|
||||
pub status: JobStatus,
|
||||
pub runner: String,
|
||||
/// Name of the executor the runner will use to execute this job
|
||||
pub executor: String,
|
||||
/// Job execution timeout (in seconds)
|
||||
pub timeout: u64,
|
||||
/// Environment variables for job execution
|
||||
pub env_vars: HashMap<String, String>,
|
||||
/// Timestamp when the job was created
|
||||
pub created_at: String,
|
||||
/// Timestamp when the job was last updated
|
||||
pub updated_at: String,
|
||||
/// Job execution timeout
|
||||
pub timeout: Duration,
|
||||
/// Environment variables for job execution
|
||||
pub env_vars: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Process status wrapper for OpenRPC serialization (matches server response)
|
||||
@@ -257,7 +256,7 @@ impl SupervisorClient {
|
||||
let server_url = server_url.into();
|
||||
|
||||
let client = HttpClientBuilder::default()
|
||||
.request_timeout(Duration::from_secs(30))
|
||||
.request_timeout(std::time::Duration::from_secs(30))
|
||||
.build(&server_url)
|
||||
.map_err(|e| ClientError::Http(e.to_string()))?;
|
||||
|
||||
@@ -299,15 +298,83 @@ impl SupervisorClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a job on the appropriate runner
|
||||
pub async fn run_job(
|
||||
/// Create a new job without queuing it to a runner
|
||||
pub async fn jobs_create(
|
||||
&self,
|
||||
secret: &str,
|
||||
job: serde_json::Value,
|
||||
) -> ClientResult<Option<String>> {
|
||||
let result: Option<String> = self
|
||||
job: Job,
|
||||
) -> ClientResult<String> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job": job
|
||||
});
|
||||
|
||||
let job_id: String = self
|
||||
.client
|
||||
.request("run_job", rpc_params![secret, job])
|
||||
.request("jobs.create", rpc_params![params])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(job_id)
|
||||
}
|
||||
|
||||
/// List all jobs
|
||||
pub async fn jobs_list(&self) -> ClientResult<Vec<Job>> {
|
||||
let jobs: Vec<Job> = self
|
||||
.client
|
||||
.request("jobs.list", rpc_params![])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(jobs)
|
||||
}
|
||||
|
||||
/// Run a job on the appropriate runner and return the result
|
||||
pub async fn job_run(
|
||||
&self,
|
||||
secret: &str,
|
||||
job: Job,
|
||||
) -> ClientResult<JobResult> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job": job
|
||||
});
|
||||
|
||||
let result: JobResult = self
|
||||
.client
|
||||
.request("job.run", rpc_params![params])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Start a previously created job by queuing it to its assigned runner
|
||||
pub async fn job_start(
|
||||
&self,
|
||||
secret: &str,
|
||||
job_id: &str,
|
||||
) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job_id": job_id
|
||||
});
|
||||
|
||||
let _: () = self
|
||||
.client
|
||||
.request("job.start", rpc_params![params])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the current status of a job
|
||||
pub async fn job_status(&self, job_id: &str) -> ClientResult<JobStatusResponse> {
|
||||
let status: JobStatusResponse = self
|
||||
.client
|
||||
.request("job.status", rpc_params![job_id])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Get the result of a completed job (blocks until result is available)
|
||||
pub async fn job_result(&self, job_id: &str) -> ClientResult<JobResult> {
|
||||
let result: JobResult = self
|
||||
.client
|
||||
.request("job.result", rpc_params![job_id])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(result)
|
||||
}
|
||||
@@ -347,6 +414,15 @@ impl SupervisorClient {
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a runner to the supervisor
|
||||
pub async fn add_runner(&self, config: RunnerConfig, process_manager: ProcessManagerType) -> ClientResult<()> {
|
||||
let _: () = self
|
||||
.client
|
||||
.request("add_runner", rpc_params![config, process_manager])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get status of a specific runner
|
||||
pub async fn get_runner_status(&self, actor_id: &str) -> ClientResult<ProcessStatus> {
|
||||
@@ -408,9 +484,9 @@ impl SupervisorClient {
|
||||
}
|
||||
|
||||
/// Queue a job to a specific runner
|
||||
pub async fn queue_job_to_runner(&self, runner_name: &str, job: Job) -> ClientResult<()> {
|
||||
pub async fn queue_job_to_runner(&self, runner: &str, job: Job) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"runner_name": runner_name,
|
||||
"runner": runner,
|
||||
"job": job
|
||||
});
|
||||
|
||||
@@ -423,9 +499,9 @@ impl SupervisorClient {
|
||||
|
||||
/// Queue a job to a specific runner and wait for the result
|
||||
/// This implements the proper Hero job protocol with BLPOP on reply queue
|
||||
pub async fn queue_and_wait(&self, runner_name: &str, job: Job, timeout_secs: u64) -> ClientResult<Option<String>> {
|
||||
pub async fn queue_and_wait(&self, runner: &str, job: Job, timeout_secs: u64) -> ClientResult<Option<String>> {
|
||||
let params = serde_json::json!({
|
||||
"runner_name": runner_name,
|
||||
"runner": runner,
|
||||
"job": job,
|
||||
"timeout_secs": timeout_secs
|
||||
});
|
||||
@@ -573,6 +649,30 @@ impl SupervisorClient {
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// Stop a running job
|
||||
pub async fn job_stop(&self, secret: &str, job_id: &str) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job_id": job_id
|
||||
});
|
||||
|
||||
self.client
|
||||
.request("job.stop", rpc_params![params])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))
|
||||
}
|
||||
|
||||
/// Delete a job from the system
|
||||
pub async fn job_delete(&self, secret: &str, job_id: &str) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job_id": job_id
|
||||
});
|
||||
|
||||
self.client
|
||||
.request("job.delete", rpc_params![params])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))
|
||||
}
|
||||
|
||||
/// Get supervisor information including secret counts
|
||||
pub async fn get_supervisor_info(&self, admin_secret: &str) -> ClientResult<SupervisorInfo> {
|
||||
let info: SupervisorInfo = self
|
||||
@@ -588,9 +688,9 @@ pub struct JobBuilder {
|
||||
caller_id: String,
|
||||
context_id: String,
|
||||
payload: String,
|
||||
job_type: JobType,
|
||||
runner_name: String,
|
||||
timeout: Duration,
|
||||
runner: String,
|
||||
executor: String,
|
||||
timeout: u64, // timeout in seconds
|
||||
env_vars: HashMap<String, String>,
|
||||
}
|
||||
|
||||
@@ -601,9 +701,9 @@ impl JobBuilder {
|
||||
caller_id: "".to_string(),
|
||||
context_id: "".to_string(),
|
||||
payload: "".to_string(),
|
||||
job_type: JobType::SAL, // default
|
||||
runner_name: "".to_string(),
|
||||
timeout: Duration::from_secs(300), // 5 minutes default
|
||||
runner: "".to_string(),
|
||||
executor: "".to_string(),
|
||||
timeout: 300, // 5 minutes default
|
||||
env_vars: HashMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -626,20 +726,20 @@ impl JobBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the job type
|
||||
pub fn job_type(mut self, job_type: JobType) -> Self {
|
||||
self.job_type = job_type;
|
||||
/// Set the executor for this job
|
||||
pub fn executor(mut self, executor: impl Into<String>) -> Self {
|
||||
self.executor = executor.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the runner name for this job
|
||||
pub fn runner_name(mut self, runner_name: impl Into<String>) -> Self {
|
||||
self.runner_name = runner_name.into();
|
||||
pub fn runner(mut self, runner: impl Into<String>) -> Self {
|
||||
self.runner = runner.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the timeout for job execution
|
||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||
/// Set the timeout for job execution (in seconds)
|
||||
pub fn timeout(mut self, timeout: u64) -> Self {
|
||||
self.timeout = timeout;
|
||||
self
|
||||
}
|
||||
@@ -673,9 +773,14 @@ impl JobBuilder {
|
||||
message: "payload is required".to_string(),
|
||||
});
|
||||
}
|
||||
if self.runner_name.is_empty() {
|
||||
if self.runner.is_empty() {
|
||||
return Err(ClientError::Server {
|
||||
message: "runner_name is required".to_string(),
|
||||
message: "runner is required".to_string(),
|
||||
});
|
||||
}
|
||||
if self.executor.is_empty() {
|
||||
return Err(ClientError::Server {
|
||||
message: "executor is required".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -686,13 +791,12 @@ impl JobBuilder {
|
||||
caller_id: self.caller_id,
|
||||
context_id: self.context_id,
|
||||
payload: self.payload,
|
||||
job_type: self.job_type,
|
||||
runner_name: self.runner_name,
|
||||
status: JobStatus::Created,
|
||||
created_at: now.clone(),
|
||||
updated_at: now,
|
||||
runner: self.runner,
|
||||
executor: self.executor,
|
||||
timeout: self.timeout,
|
||||
env_vars: self.env_vars,
|
||||
created_at: now.clone(),
|
||||
updated_at: now,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -722,9 +826,9 @@ mod tests {
|
||||
.caller_id("test_client")
|
||||
.context_id("test_context")
|
||||
.payload("print('Hello, World!');")
|
||||
.job_type(JobType::OSIS)
|
||||
.runner_name("test_runner")
|
||||
.timeout(Duration::from_secs(60))
|
||||
.executor("osis")
|
||||
.runner("test_runner")
|
||||
.timeout(60)
|
||||
.env_var("TEST_VAR", "test_value")
|
||||
.build();
|
||||
|
||||
@@ -734,11 +838,10 @@ mod tests {
|
||||
assert_eq!(job.caller_id, "test_client");
|
||||
assert_eq!(job.context_id, "test_context");
|
||||
assert_eq!(job.payload, "print('Hello, World!');");
|
||||
assert_eq!(job.job_type, JobType::OSIS);
|
||||
assert_eq!(job.runner_name, "test_runner");
|
||||
assert_eq!(job.timeout, Duration::from_secs(60));
|
||||
assert_eq!(job.executor, "osis");
|
||||
assert_eq!(job.runner, "test_runner");
|
||||
assert_eq!(job.timeout, 60);
|
||||
assert_eq!(job.env_vars.get("TEST_VAR"), Some(&"test_value".to_string()));
|
||||
assert_eq!(job.status, JobStatus::Created);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -747,7 +850,7 @@ mod tests {
|
||||
let result = JobBuilder::new()
|
||||
.context_id("test")
|
||||
.payload("test")
|
||||
.runner_name("test")
|
||||
.runner("test")
|
||||
.build();
|
||||
assert!(result.is_err());
|
||||
|
||||
@@ -755,7 +858,7 @@ mod tests {
|
||||
let result = JobBuilder::new()
|
||||
.caller_id("test")
|
||||
.payload("test")
|
||||
.runner_name("test")
|
||||
.runner("test")
|
||||
.build();
|
||||
assert!(result.is_err());
|
||||
|
||||
@@ -763,15 +866,26 @@ mod tests {
|
||||
let result = JobBuilder::new()
|
||||
.caller_id("test")
|
||||
.context_id("test")
|
||||
.runner_name("test")
|
||||
.runner("test")
|
||||
.executor("test")
|
||||
.build();
|
||||
assert!(result.is_err());
|
||||
|
||||
// Missing runner_name
|
||||
// Missing runner
|
||||
let result = JobBuilder::new()
|
||||
.caller_id("test")
|
||||
.context_id("test")
|
||||
.payload("test")
|
||||
.executor("test")
|
||||
.build();
|
||||
assert!(result.is_err());
|
||||
|
||||
// Missing executor
|
||||
let result = JobBuilder::new()
|
||||
.caller_id("test")
|
||||
.context_id("test")
|
||||
.payload("test")
|
||||
.runner("test")
|
||||
.build();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -885,7 +999,7 @@ mod client_tests {
|
||||
assert_eq!(job.id(), "test-id");
|
||||
assert_eq!(job.payload(), "test payload");
|
||||
assert_eq!(job.job_type(), "SAL");
|
||||
assert_eq!(job.runner_name(), "test-runner");
|
||||
assert_eq!(job.runner(), "test-runner");
|
||||
assert_eq!(job.caller_id(), "wasm_client");
|
||||
assert_eq!(job.context_id(), "wasm_context");
|
||||
assert_eq!(job.timeout_secs(), 30);
|
||||
@@ -940,7 +1054,7 @@ mod client_tests {
|
||||
assert_eq!(job.id(), "func-test-id");
|
||||
assert_eq!(job.payload(), "func test payload");
|
||||
assert_eq!(job.job_type(), "OSIS");
|
||||
assert_eq!(job.runner_name(), "func-test-runner");
|
||||
assert_eq!(job.runner(), "func-test-runner");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
|
@@ -95,7 +95,7 @@ pub struct WasmJob {
|
||||
caller_id: String,
|
||||
context_id: String,
|
||||
payload: String,
|
||||
runner_name: String,
|
||||
runner: String,
|
||||
executor: String,
|
||||
timeout_secs: u64,
|
||||
env_vars: String, // JSON string of HashMap<String, String>
|
||||
@@ -138,8 +138,8 @@ impl WasmSupervisorClient {
|
||||
match self.call_method("register_runner", params).await {
|
||||
Ok(result) => {
|
||||
// Extract the runner name from the result
|
||||
if let Some(runner_name) = result.as_str() {
|
||||
Ok(runner_name.to_string())
|
||||
if let Some(runner) = result.as_str() {
|
||||
Ok(runner.to_string())
|
||||
} else {
|
||||
Err(JsValue::from_str("Invalid response format: expected runner name"))
|
||||
}
|
||||
@@ -159,7 +159,7 @@ impl WasmSupervisorClient {
|
||||
"caller_id": job.caller_id,
|
||||
"context_id": job.context_id,
|
||||
"payload": job.payload,
|
||||
"runner_name": job.runner_name,
|
||||
"runner": job.runner,
|
||||
"executor": job.executor,
|
||||
"timeout": {
|
||||
"secs": job.timeout_secs,
|
||||
@@ -194,7 +194,7 @@ impl WasmSupervisorClient {
|
||||
"caller_id": job.caller_id,
|
||||
"context_id": job.context_id,
|
||||
"payload": job.payload,
|
||||
"runner_name": job.runner_name,
|
||||
"runner": job.runner,
|
||||
"executor": job.executor,
|
||||
"timeout": {
|
||||
"secs": job.timeout_secs,
|
||||
@@ -258,7 +258,7 @@ impl WasmSupervisorClient {
|
||||
let caller_id = job_value.get("caller_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let context_id = job_value.get("context_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let payload = job_value.get("payload").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let runner_name = job_value.get("runner_name").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let runner = job_value.get("runner").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let executor = job_value.get("executor").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let timeout_secs = job_value.get("timeout").and_then(|v| v.get("secs")).and_then(|v| v.as_u64()).unwrap_or(30);
|
||||
let env_vars = job_value.get("env_vars").map(|v| v.to_string()).unwrap_or_else(|| "{}".to_string());
|
||||
@@ -270,7 +270,7 @@ impl WasmSupervisorClient {
|
||||
caller_id,
|
||||
context_id,
|
||||
payload,
|
||||
runner_name,
|
||||
runner,
|
||||
executor,
|
||||
timeout_secs,
|
||||
env_vars,
|
||||
@@ -477,14 +477,14 @@ impl WasmSupervisorClient {
|
||||
impl WasmJob {
|
||||
/// Create a new job with default values
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: String, payload: String, executor: String, runner_name: String) -> Self {
|
||||
pub fn new(id: String, payload: String, executor: String, runner: String) -> Self {
|
||||
let now = js_sys::Date::new_0().to_iso_string().as_string().unwrap();
|
||||
Self {
|
||||
id,
|
||||
caller_id: "wasm_client".to_string(),
|
||||
context_id: "wasm_context".to_string(),
|
||||
payload,
|
||||
runner_name,
|
||||
runner,
|
||||
executor,
|
||||
timeout_secs: 30,
|
||||
env_vars: "{}".to_string(),
|
||||
@@ -555,8 +555,8 @@ impl WasmJob {
|
||||
|
||||
/// Get the runner name
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn runner_name(&self) -> String {
|
||||
self.runner_name.clone()
|
||||
pub fn runner(&self) -> String {
|
||||
self.runner.clone()
|
||||
}
|
||||
|
||||
/// Get the timeout in seconds
|
||||
@@ -657,8 +657,8 @@ pub fn init() {
|
||||
/// Utility function to create a job from JavaScript
|
||||
/// Create a new job (convenience function for JavaScript)
|
||||
#[wasm_bindgen]
|
||||
pub fn create_job(id: String, payload: String, executor: String, runner_name: String) -> WasmJob {
|
||||
WasmJob::new(id, payload, executor, runner_name)
|
||||
pub fn create_job(id: String, payload: String, executor: String, runner: String) -> WasmJob {
|
||||
WasmJob::new(id, payload, executor, runner)
|
||||
}
|
||||
|
||||
/// Utility function to create a client from JavaScript
|
||||
|
Reference in New Issue
Block a user