initial commit

This commit is contained in:
Timur Gordon
2025-08-26 14:49:21 +02:00
commit 767c66fb6a
66 changed files with 22035 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
use yew::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use gloo::console;
use hero_supervisor_openrpc_client::wasm::WasmSupervisorClient;
#[derive(Clone, PartialEq)]
pub struct SupervisorInfo {
pub server_url: String,
pub admin_secrets_count: usize,
pub user_secrets_count: usize,
pub register_secrets_count: usize,
pub runners_count: usize,
}
#[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 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 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();
Callback::from(move |e: web_sys::Event| {
let input: web_sys::HtmlInputElement = e.target().unwrap().dyn_into().unwrap();
unlock_secret.set(input.value());
})
};
let on_unlock_submit = {
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 = props.server_url.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() {
return;
}
is_loading.set(true);
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);
// Load user secrets
if let Ok(user_secs) = client.list_user_secrets(&secret_value).await {
user_secrets.set(user_secs);
}
// 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");
}
Err(e) => {
console::error!("Failed to unlock secrets:", format!("{:?}", e));
}
}
is_loading.set(false);
});
})
};
let on_lock_click = {
let is_unlocked = is_unlocked.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);
admin_secrets.set(Vec::new());
user_secrets.set(Vec::new());
register_secrets.set(Vec::new());
console::log!("Secrets locked");
})
};
html! {
<div class="sidebar">
<div class="sidebar-header">
<h2>{"Supervisor"}</h2>
</div>
<div class="sidebar-content">
<div class="sidebar-sections">
// Server Info Section
<div class="server-info">
<div class="server-header">
<h3 class="supervisor-title">{"Hero Supervisor"}</h3>
</div>
<div class="server-url">
<span class="connection-indicator connected"></span>
<span class="url-text">{props.server_url.clone()}</span>
</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>
}
</div>
if !*is_unlocked {
<div class="unlock-input-row">
<input
type="password"
class="unlock-input"
placeholder="Enter admin secret to unlock"
value={(*unlock_secret).clone()}
onchange={on_unlock_secret_change}
disabled={*is_loading}
/>
</div>
}
if *is_unlocked {
<div class="secrets-content">
<div class="secret-group">
<div class="secret-header">
<span class="secret-title">{"Admin secrets"}</span>
</div>
<div class="secret-list">
{ for admin_secrets.iter().enumerate().map(|(i, secret)| {
html! {
<div class="secret-item" key={i}>
<div class="secret-value">{secret.clone()}</div>
<button class="btn-icon btn-remove">
<i class="fas fa-minus"></i>
</button>
</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>
</div>
<div class="secret-list">
{ for user_secrets.iter().enumerate().map(|(i, secret)| {
html! {
<div class="secret-item" key={i}>
<div class="secret-value">{secret.clone()}</div>
<button class="btn-icon btn-remove">
<i class="fas fa-minus"></i>
</button>
</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>
</div>
<div class="secret-list">
{ for register_secrets.iter().enumerate().map(|(i, secret)| {
html! {
<div class="secret-item" key={i}>
<div class="secret-value">{secret.clone()}</div>
<button class="btn-icon btn-remove">
<i class="fas fa-minus"></i>
</button>
</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>
if *is_unlocked {
<div class="save-section">
<button class="save-changes-btn">
{"Save Changes"}
</button>
</div>
}
</div>
</div>
// Documentation Links at Bottom
<div class="sidebar-footer">
<div class="docs-section">
<h5>{"Documentation"}</h5>
<div class="docs-links">
<a href="https://github.com/herocode/supervisor" target="_blank" class="doc-link">
{"📖 User Guide"}
</a>
<a href="https://github.com/herocode/supervisor/blob/main/README.md" target="_blank" class="doc-link">
{"🚀 Getting Started"}
</a>
<a href="https://github.com/herocode/supervisor/issues" target="_blank" class="doc-link">
{"🐛 Report Issues"}
</a>
<a href="https://github.com/herocode/supervisor/wiki" target="_blank" class="doc-link">
{"📚 API Reference"}
</a>
</div>
</div>
</div>
</div>
}
}