update api, fix tests and examples
This commit is contained in:
@@ -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>
|
||||
|
Reference in New Issue
Block a user