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