Skip to content
22 changes: 16 additions & 6 deletions opendut-lea/opendut-lea-components/src/health.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use leptos::prelude::*;
use crate::tooltip::Tooltip;
use crate::tooltip::{Tooltip, TooltipDirection};

pub struct State {
pub kind: StateKind,
Expand All @@ -15,9 +15,12 @@ pub enum StateKind {
}

#[component]
pub fn Health(state: Signal<State>) -> impl IntoView {
pub fn Health(
state: Signal<State>,
#[prop(optional)] tooltip_direction: TooltipDirection,
) -> impl IntoView {

let classes = move || state.with(|state| {
let health_class = move || state.with(|state| {
match state.kind {
StateKind::Unknown => "health-light",
StateKind::Red => "health-light red",
Expand All @@ -26,14 +29,21 @@ pub fn Health(state: Signal<State>) -> impl IntoView {
}
});

let tool_tip_text = Signal::derive(move || state.with(|state| {
let tooltip_text = Signal::derive(move || state.with(|state| {
Clone::clone(&state.text)
}));

let tooltip_content = Box::new(move || {
view! {
<p> { tooltip_text } </p>

}.into_any()
});

view! {
<div class="is-flex is-justify-content-center">
<Tooltip text=tool_tip_text>
<div class=classes />
<Tooltip text=tooltip_content direction=tooltip_direction>
<div class=health_class />
</Tooltip>
</div>
}
Expand Down
10 changes: 7 additions & 3 deletions opendut-lea/opendut-lea-components/src/tooltip.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use leptos::prelude::*;

#[derive(Default, Clone, Copy)]
pub enum TooltipDirection {
#[default]
Left,
Right,
Up,
Expand All @@ -18,10 +20,12 @@ impl TooltipDirection {
}
}

pub type TooltipContent = Box<dyn FnOnce() -> AnyView + Send>;

#[component]
pub fn Tooltip(
#[prop(into)] text: Signal<String>,
#[prop(into, default=Signal::from(TooltipDirection::Left))] direction: Signal<TooltipDirection>,
text: TooltipContent,
#[prop(into, optional)] direction: Signal<TooltipDirection>,
#[prop(into, default=Signal::from(false))] is_hidden: Signal<bool>,
children: Children
) -> impl IntoView {
Expand All @@ -34,7 +38,7 @@ pub fn Tooltip(
<div class="tooltip-container" class=("is-hidden", move || is_hidden.get())>
<div class="tooltip-content p-0">
<div class="tooltip-item p-3">
{ text }
{ text() }
</div>
</div>
</div>
Expand Down
9 changes: 7 additions & 2 deletions opendut-lea/src/clusters/components/cluster_health.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use leptos::prelude::*;
use opendut_lea_components::health::{self, Health};
use opendut_lea_components::tooltip::TooltipDirection;
use opendut_model::cluster::state::ClusterState;

#[component]
pub fn ClusterHealth(
#[prop(into)] state: Signal<ClusterState>
#[prop(into)] state: Signal<ClusterState>,
#[prop(optional)] tooltip_direction: TooltipDirection
) -> impl IntoView {
let _ = state;

Expand All @@ -17,6 +19,9 @@ pub fn ClusterHealth(
});

view! {
<Health state=health_state />
<Health
state=health_state
tooltip_direction
/>
}
}
8 changes: 7 additions & 1 deletion opendut-lea/src/clusters/components/delete_cluster_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,15 @@ where F: Fn() + Clone + Send + 'static {
!matches!(button_state.get(), ButtonState::Disabled)
});

let tooltip_content = Box::new(move || {
view! {
Cluster can not be deleted while it is deployed.
}.into_any()
});

view! {
<Tooltip
text="Cluster can not be deleted while it is deployed."
text=tooltip_content
direction=TooltipDirection::Right
is_hidden=hide_tooltip
>
Expand Down
170 changes: 151 additions & 19 deletions opendut-lea/src/clusters/components/deploy_toggle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@ use std::sync::Arc;
use leptos::prelude::*;
use tracing::{debug, error};
use opendut_carl_api::carl::ClientError;
use opendut_carl_api::carl::cluster::StoreClusterDeploymentError;
use opendut_lea_components::tooltip::Tooltip;
use opendut_carl_api::carl::cluster::{ListClusterPeerStatesResponse, StoreClusterDeploymentError};
use opendut_lea_components::tooltip::{Tooltip, TooltipDirection};
use opendut_lea_components::{Toggle, ToggleState};
use opendut_model::cluster::{ClusterDeployment, ClusterId};

use opendut_model::peer::PeerId;
use opendut_model::peer::state::PeerMemberState;
use crate::app::use_app_globals;
use crate::clusters::IsDeployed;
use crate::components::{Toast, use_toaster};

#[component]
pub fn DeployToggle<OnDeploymentChanged>(
#[prop(into)] cluster_id: Signal<ClusterId>,
#[prop(into, default=Signal::from(false))] is_new_cluster: Signal<bool>,
#[prop(into)] is_deployed: Signal<IsDeployed>,
on_deployment_changed: OnDeploymentChanged,
#[prop(optional)] tooltip_direction: TooltipDirection,
) -> impl IntoView
where
OnDeploymentChanged: Fn() + Clone + Send + 'static,
Expand Down Expand Up @@ -101,23 +104,152 @@ where
}
};

let tooltip_text = Signal::derive(move || {
if is_deployed.get().0 {
"Deployment requested".to_string()
let blocked_cluster_deployment = {
let carl = globals.client.clone();
let cluster_id = cluster_id.get_untracked();

LocalResource::new(move || {
let mut carl = carl.clone();
let is_new_cluster = is_new_cluster.get();
let _ = is_deployed.get();

async move {
if is_new_cluster {
return None;
}

let states = carl.cluster.list_cluster_peer_states(cluster_id).await
.expect("Failed to request the list of cluster's peer state.");
match states {
ListClusterPeerStatesResponse::Success { peer_states } => {
let invalid_peers = peer_states.iter()
.filter(|(_, peer_state)| {
matches!(&peer_state.member, PeerMemberState::Blocked { .. })
})
.map(|(peer_id, _)| peer_id.to_owned())
.collect::<Vec<_>>();

if invalid_peers.is_empty() {
None
} else {
Some(BlockedDeployment::Peers(invalid_peers))
}
}
ListClusterPeerStatesResponse::Failure { message } => {
Some(BlockedDeployment::Message(message))
}
}
}
})
};

move || {
let on_deploy = on_deploy.clone();
let on_undeploy = on_undeploy.clone();

Suspend::new(async move {
let blocked_deployment = blocked_cluster_deployment.await;

let is_deployed = Signal::derive(move || is_deployed.get().0);

let show_error = Signal::derive({
let blocked_deployment = blocked_deployment.clone();
move || blocked_deployment.is_some() && !is_deployed.get()
});

let toggle_state = Signal::derive(move || {
if show_error.get() || is_new_cluster.get() {
ToggleState::Disabled
} else {
ToggleState::Enabled
}
});

let tooltip_content = Box::new(move || {
view! {
<DeploymentTooltipContent is_new_cluster is_deployed blocked_deployment />
}.into_any()
});

view! {
<Tooltip text=tooltip_content direction=tooltip_direction>
<Toggle
is_active=is_deployed
state=toggle_state
on_action=move || {
if is_deployed.get() { on_undeploy() } else { on_deploy() }
}
/>
</Tooltip>
}
})
}
}

#[derive(Clone)]
enum BlockedDeployment {
Peers(Vec<PeerId>),
Message(String),
}

#[component]
fn DeploymentTooltipContent(
is_new_cluster: Signal<bool>,
is_deployed: Signal<bool>,
blocked_deployment: Option<BlockedDeployment>,
) -> impl IntoView {

move || {
if is_new_cluster.get() {
return view! {
"Save the cluster first."
}.into_any();
}

if !is_deployed.get()
&& let Some(blocked_deployment) = blocked_deployment.as_ref() {
return match blocked_deployment {
BlockedDeployment::Peers(peers) => {
let amount = peers.len();

let message = if amount == 1 {
"1 Peer is already in use:".to_string()
} else {
format!("{amount} Peers are already in use:")
};

let peer_links = peers
.iter()
.enumerate()
.map(|(index, peer_id)| {
let href = format!("/peers/{peer_id}/configure/general");

view! {
<span>
{if index == 0 { "" } else { ", " }}
<a href=href>{peer_id.to_string()}</a>
</span>
}
})
.collect_view();

view! {
<span>{message} " " {peer_links}</span>
}
.into_any()
}

BlockedDeployment::Message(message) => {
view! { { message.to_owned() } }.into_any()
}
};
}


if is_deployed.get() {
view! { "Deployment requested" }.into_any()
} else {
"Undeployed".to_string()
view! { "Undeployed" }.into_any()
}
});

view! {
<Tooltip text=tooltip_text>
<Toggle
is_active=Signal::derive(move || is_deployed.get().0)
state=ToggleState::Enabled
on_action=move || {
if is_deployed.get().0 { on_undeploy() } else { on_deploy() }
}
/>
</Tooltip>
}
}
20 changes: 14 additions & 6 deletions opendut-lea/src/clusters/configurator/components/controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,22 @@ where
navigate_to(WellKnownRoutes::ClustersOverview, use_navigate.clone());
}
};

let tooltip_direction = TooltipDirection::Right;

view! {
<div class="is-flex is-align-items-center">
<div class=("is-hidden", move || is_new_cluster.get())>
<div class="is-flex is-align-items-center">
<DeployToggle
cluster_id
is_new_cluster
is_deployed=deployed_signal
on_deployment_changed
tooltip_direction
/>
</div>
<div class=("is-hidden", move || is_new_cluster.get())>
<div class="px-2" />
</div>
<ClusterHealth state=cluster_state />
<div class="px-2" />
<ClusterHealth state=cluster_state tooltip_direction />
<div class="px-2" />
<SaveClusterButton
cluster_descriptor
Expand Down Expand Up @@ -145,9 +147,15 @@ fn SaveClusterButton(
!deployed_signal.get().0
});

let tooltip_content = Box::new(move || {
view! {
Cluster can not be updated while it is deployed.
}.into_any()
});

view! {
<Tooltip
text="Cluster can not be updated while it is deployed."
text=tooltip_content
direction=TooltipDirection::Right
is_hidden=hide_tooltip
>
Expand Down
8 changes: 7 additions & 1 deletion opendut-lea/src/peers/components/delete_peer_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,15 @@ where F: Fn() + Clone + Send + 'static {
!matches!(button_state.get(), ButtonState::Disabled)
});

let tooltip_content = Box::new(move || {
view! {
Peer can not be deleted while it is configured in a cluster.
}.into_any()
});

view! {
<Tooltip
text="Peer can not be deleted while it is configured in a cluster."
text=tooltip_content
direction=TooltipDirection::Right
is_hidden=hide_tooltip
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ where
<ReadOnlyInput label="ID" value=device_id_string />
<DeviceNameInput device_configuration />
<DeviceInterfaceInput user_peer_descriptor device_configuration />
<DeviceInterfaceInput user_peer_descriptor device_configuration />
<DeviceTagInput device_configuration />
<DeviceDescriptionInput device_configuration />
</div>
Expand Down
Loading
Loading