Skip to content

Commit

Permalink
LEA -> Fix ClusterConfigurator not properly loading information from …
Browse files Browse the repository at this point in the history
…backend.

Visually, the Leader radio button did not get selected, but it was more broken.
  • Loading branch information
mbfm committed Jan 17, 2025
1 parent 8000109 commit ba648fb
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 118 deletions.
4 changes: 2 additions & 2 deletions opendut-lea/src/about/overview.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use leptos::prelude::*;
use crate::app::use_app_globals;
use crate::components::BasePageContainer;
use crate::components::{BasePageContainer, LoadingSpinner};

use shadow_rs::shadow;
use opendut_types::proto::util::VersionInfo;
Expand Down Expand Up @@ -28,7 +28,7 @@ pub fn AboutOverview() -> impl IntoView {
controls=view! { <> }
>
<div class="mt-4">
<Transition fallback=move || view! { <p>"Loading..."</p> }>
<Transition fallback=LoadingSpinner>
{ move || Suspend::new(async move {
let metadata = metadata.await;
view! {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use opendut_types::topology::{DeviceDescriptor, DeviceId};
use opendut_types::util::net::NetworkInterfaceDescriptor;
use crate::clusters::configurator::components::{get_all_peers, get_all_selected_devices};
use crate::clusters::configurator::types::UserClusterConfiguration;
use crate::components::{ButtonColor, ButtonSize, ButtonState, FontAwesomeIcon, IconButton};
use crate::components::{ButtonColor, ButtonSize, ButtonState, FontAwesomeIcon, IconButton, LoadingSpinner};
use crate::util::{Ior, NON_BREAKING_SPACE};
use crate::util::net::UserNetworkInterfaceConfiguration;

Expand Down Expand Up @@ -119,8 +119,8 @@ pub fn DeviceSelector(cluster_configuration: RwSignal<UserClusterConfiguration>)
</tr>
</thead>
<tbody>
<Suspense
fallback=move || view! { <p>"Loading..."</p> }
<Transition
fallback=LoadingSpinner
>
{move || Suspend::new(async move {
let peer_descriptors = peer_descriptors.await;
Expand All @@ -129,7 +129,7 @@ pub fn DeviceSelector(cluster_configuration: RwSignal<UserClusterConfiguration>)
{ render_peer_descriptors(peer_descriptors, selected_devices, getter, setter) }
}
})}
</Suspense>
</Transition>
</tbody>
</table>
</div>
Expand Down
92 changes: 53 additions & 39 deletions opendut-lea/src/clusters/configurator/components/leader_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ use opendut_types::topology::DeviceId;

use crate::clusters::configurator::components::{get_all_peers, get_all_selected_devices};
use crate::clusters::configurator::types::UserClusterConfiguration;
use crate::components::LoadingSpinner;
use crate::util::{Ior, NON_BREAKING_SPACE};

pub type LeaderSelectionError = String;
pub type LeaderSelection = Ior<LeaderSelectionError, PeerId>;

#[component]
pub fn LeaderSelector(cluster_configuration: RwSignal<UserClusterConfiguration>) -> impl IntoView {

let peer_descriptors = get_all_peers();

let getter_selected_devices = create_read_slice(cluster_configuration, |config| {
Expand All @@ -38,7 +40,7 @@ pub fn LeaderSelector(cluster_configuration: RwSignal<UserClusterConfiguration>)
})
};

let rows = LocalResource::new(move || async move {
let peers = LocalResource::new(move || async move {
let selected_devices = selected_devices();

let mut peers = peer_descriptors.await;
Expand All @@ -53,9 +55,11 @@ pub fn LeaderSelector(cluster_configuration: RwSignal<UserClusterConfiguration>)
peers.clone().into_iter()
.filter(|peer_descriptor| {
let mut peer_devices: HashSet<DeviceId> = HashSet::new();

for device in &peer_descriptor.topology.devices {
peer_devices.insert(device.id);
}

if selected_devices.len() < 2 {
setter_leader.set(LeaderSelection::Left(String::from("Please select at least two devices first.")));
}
Expand All @@ -72,42 +76,9 @@ pub fn LeaderSelector(cluster_configuration: RwSignal<UserClusterConfiguration>)
setter_leader.set(LeaderSelection::Left(String::from("Select a leader.")));
}
}

!peer_devices.is_disjoint(&selected_devices)
})
.map(|peer| {
view! {
<tr>
<td>
{ peer.name.to_string().into_view() }
</td>
<td>
{ peer.id.to_string().into_view() }
</td>
<td>
{ peer.location.unwrap_or_default().to_string().into_view() }
</td>
<td class="is-narrow" style="text-align: center">
<div class="control">
<label class="radio">
<input
type = "radio"
name = "answer"
checked = move || {
match getter_leader.get() {
LeaderSelection::Right(leader) => peer.id == leader,
LeaderSelection::Left(_) | LeaderSelection::Both(_, _) => false,
}
}
on:click = move |_| {
setter_leader.set(LeaderSelection::Right(peer.id));
}
/>
</label>
</div>
</td>
</tr>
}
})
.collect::<Vec<_>>()
});

Expand All @@ -124,13 +95,56 @@ pub fn LeaderSelector(cluster_configuration: RwSignal<UserClusterConfiguration>)
</tr>
</thead>
<tbody>
<Suspense
fallback=move || view! { <p>"Loading..."</p> }
<Transition
fallback=LoadingSpinner
>
{move || Suspend::new(async move {
rows.await
let peers = peers.await;

let is_leader = move |peer: PeerId| {
match getter_leader.get() {
LeaderSelection::Right(leader) => peer == leader,
LeaderSelection::Left(_) | LeaderSelection::Both(_, _) => false,
}
};

view! {
<For
each=move || peers.clone()
key=|peer| peer.id
children=move |peer| {
view! {
<tr>
<td>
{ peer.name.to_string() }
</td>
<td>
{ peer.id.to_string() }
</td>
<td>
{ peer.location.clone().unwrap_or_default().to_string() }
</td>
<td class="is-narrow" style="text-align: center">
<div class="control">
<label class="radio">
<input
type = "radio"
name = "answer"
checked = is_leader(peer.id)
on:click = move |_| {
setter_leader.set(LeaderSelection::Right(peer.id));
}
/>
</label>
</div>
</td>
</tr>
}
}
/>
}
})}
</Suspense>
</Transition>
</tbody>
</table>
</div>
Expand Down
106 changes: 63 additions & 43 deletions opendut-lea/src/clusters/configurator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use leptos::either::Either;
use leptos::prelude::*;
use leptos_router::hooks::{use_navigate, use_params_map};
use opendut_types::cluster::ClusterId;
Expand All @@ -8,8 +9,8 @@ use crate::clusters::configurator::components::Controls;
use crate::clusters::configurator::tabs::{DevicesTab, GeneralTab, LeaderTab, TabIdentifier};
use crate::clusters::configurator::types::UserClusterConfiguration;
use crate::clusters::overview::IsDeployed;
use crate::components::{BasePageContainer, Breadcrumb, use_active_tab};
use crate::components::{UserInputError, UserInputValue};
use crate::components::{use_active_tab, BasePageContainer, Breadcrumb, LoadingSpinner};
use crate::components::UserInputValue;
use crate::routing::{navigate_to, WellKnownRoutes};

mod types;
Expand All @@ -22,55 +23,72 @@ pub fn ClusterConfigurator() -> impl IntoView {
let globals = use_app_globals();
let params = use_params_map();

let active_tab = use_active_tab::<TabIdentifier>();
let load_cluster_configuration = {
let carl = globals.client.clone();

LocalResource::new(move || {
let mut carl = carl.clone();

let cluster_configuration = {
let cluster_id = {
let cluster_id = params.with_untracked(|params| {
let maybe_cluster_id = params.with(|params| {
params.get("id").and_then(|id| ClusterId::try_from(id.as_str()).ok())
});
match cluster_id {
None => {
let use_navigate = use_navigate();

navigate_to(WellKnownRoutes::ErrorPage {
title: String::from("Invalid ClusterId"),
text: String::from("Could not parse the provided value as ClusterId!"),
details: None,
}, use_navigate);

ClusterId::default()
}
Some(cluster_id) => {
cluster_id
async move {
match maybe_cluster_id {
Some(cluster_id) => {
carl.cluster.get_cluster_configuration(cluster_id).await
.map(|configuration| {
UserClusterConfiguration {
id: cluster_id,
name: UserInputValue::Right(configuration.name.value()),
devices: DeviceSelection::Right(configuration.devices),
leader: LeaderSelection::Right(configuration.leader),
}
})
.map_err(|error| format!("Error while loading cluster configuration: {error}"))
}
None => {
Err(String::from("Could not parse the provided value as ClusterId!"))
}
}
}
};
})
};

let user_configuration = RwSignal::new(UserClusterConfiguration {
id: cluster_id,
name: UserInputValue::Left(UserInputError::from("Enter a valid cluster name.")),
devices: DeviceSelection::Left(String::from("Select at least two devices.")),
leader: LeaderSelection::Left(String::from("Select a leader.")),
});
view! {
<Transition
fallback=LoadingSpinner
>
{move || Suspend::new(async move {
let cluster_configuration = load_cluster_configuration.await;

let carl = globals.client.clone();
match cluster_configuration {
Ok(cluster_configuration) => Either::Right(view! {
<LoadedClusterConfigurator loaded=cluster_configuration />
}),

LocalResource::new(move || {
let mut carl = carl.clone();
async move {
if let Ok(configuration) = carl.cluster.get_cluster_configuration(cluster_id).await {
user_configuration.update(|user_configuration| {
user_configuration.name = UserInputValue::Right(configuration.name.value());
user_configuration.devices = DeviceSelection::Right(configuration.devices);
user_configuration.leader = LeaderSelection::Right(configuration.leader);
});
}
Err(error) => Either::Left({
let use_navigate = use_navigate();

navigate_to(WellKnownRoutes::ErrorPage {
title: String::from("Error while loading Cluster overview"),
text: String::from(error),
details: None,
}, use_navigate);
})
}
});
})}
</Transition>
}
}

user_configuration
};
#[component]
fn LoadedClusterConfigurator(
loaded: UserClusterConfiguration,
) -> impl IntoView {
let globals = use_app_globals();

let cluster_configuration = RwSignal::new(loaded);

let cluster_id = create_read_slice(cluster_configuration, |config| config.id);

Expand All @@ -83,6 +101,8 @@ pub fn ClusterConfigurator() -> impl IntoView {
]
});

let active_tab = use_active_tab::<TabIdentifier>();

let cluster_deployments = {
let carl = globals.client.clone();
LocalResource::new(move || {
Expand Down Expand Up @@ -144,13 +164,13 @@ pub fn ClusterConfigurator() -> impl IntoView {
</div>
<div class="container">
<div class=("is-hidden", move || TabIdentifier::General != active_tab.get())>
<GeneralTab cluster_configuration=cluster_configuration />
<GeneralTab cluster_configuration />
</div>
<div class=("is-hidden", move || TabIdentifier::Devices != active_tab.get())>
<DevicesTab cluster_configuration=cluster_configuration />
<DevicesTab cluster_configuration />
</div>
<div class=("is-hidden", move || TabIdentifier::Leader != active_tab.get())>
<LeaderTab cluster_configuration=cluster_configuration />
<LeaderTab cluster_configuration />
</div>
</div>
</fieldset>
Expand Down
4 changes: 2 additions & 2 deletions opendut-lea/src/clusters/configurator/tabs/leader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub fn LeaderTab(cluster_configuration: RwSignal<UserClusterConfiguration>) -> i

view! {
<div>
<LeaderSelector cluster_configuration=cluster_configuration/>
<LeaderSelector cluster_configuration />
</div>
}
}
}
4 changes: 2 additions & 2 deletions opendut-lea/src/clusters/overview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use opendut_types::cluster::{ClusterConfiguration, ClusterDeployment, ClusterId}

use crate::app::use_app_globals;
use crate::clusters::components::CreateClusterButton;
use crate::components::{BasePageContainer, Breadcrumb, ButtonColor, ButtonSize, ButtonState, FontAwesomeIcon, health, IconButton, Toast, use_toaster};
use crate::components::{health, use_toaster, BasePageContainer, Breadcrumb, ButtonColor, ButtonSize, ButtonState, FontAwesomeIcon, IconButton, LoadingSpinner, Toast};
use crate::components::health::Health;

#[component]
Expand Down Expand Up @@ -165,7 +165,7 @@ pub fn ClustersOverview() -> impl IntoView {
}
>
<Suspense
fallback = move || view! { <p>"Loading..."</p> }
fallback=LoadingSpinner
>
{move || {
let rows = rows.clone();
Expand Down
Loading

0 comments on commit ba648fb

Please sign in to comment.