From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 8F1AB1FF165 for ; Thu, 28 Aug 2025 09:54:27 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 78D909F70; Thu, 28 Aug 2025 09:54:36 +0200 (CEST) Message-ID: Date: Thu, 28 Aug 2025 09:54:32 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Beta To: Proxmox Datacenter Manager development discussion , Stefan Hanreich References: <20250827113427.199253-1-s.hanreich@proxmox.com> <20250827113427.199253-31-s.hanreich@proxmox.com> Content-Language: en-US From: Dominik Csapak In-Reply-To: <20250827113427.199253-31-s.hanreich@proxmox.com> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1756367666362 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.022 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: Re: [pdm-devel] [PATCH proxmox-datacenter-manager 14/16] ui: sdn: add EvpnPanel X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset="us-ascii"; Format="flowed" Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" high level comment: i think reversing the toolbar and grids would be better so having the toolbar below the tabpanel seem much more natural and in line with our remaining ui two possibilities to do this: * here, by adding the toolbar twice to the respective tabpanels * in the grid componen (probably harder because of callbacks and such) one comment inline On 8/27/25 1:35 PM, Stefan Hanreich wrote: > This panel shows an overview of the state of SDN EVPN Zones across > multiple remotes. It includes two different views: a per-remote and a > per-VRF view. For details on the specific views consult the respective > commits. It handles the fetching of data and passing them to the > specific child components and it also handles the dialogues for > creating new EVPN entities. > > Signed-off-by: Stefan Hanreich > --- > ui/src/sdn/evpn/evpn_panel.rs | 224 ++++++++++++++++++++++++++++++++++ > ui/src/sdn/evpn/mod.rs | 3 + > 2 files changed, 227 insertions(+) > create mode 100644 ui/src/sdn/evpn/evpn_panel.rs > > diff --git a/ui/src/sdn/evpn/evpn_panel.rs b/ui/src/sdn/evpn/evpn_panel.rs > new file mode 100644 > index 0000000..930d8e4 > --- /dev/null > +++ b/ui/src/sdn/evpn/evpn_panel.rs > @@ -0,0 +1,224 @@ > +use futures::try_join; > +use std::rc::Rc; > + > +use anyhow::Error; > +use yew::virtual_dom::{VComp, VNode}; > +use yew::{Callback, Html, Properties}; > + > +use pdm_client::types::{ListController, ListControllersType, ListVnet, ListZone, ListZonesType}; > +use proxmox_yew_comp::{LoadableComponent, LoadableComponentContext, LoadableComponentMaster}; > +use pwt::props::{ContainerBuilder, EventSubscriber, StorageLocation, WidgetBuilder}; > +use pwt::state::NavigationContainer; > +use pwt::tr; > +use pwt::widget::menu::{Menu, MenuButton, MenuItem}; > +use pwt::widget::{Button, Column, MiniScrollMode, TabBarItem, TabPanel, Toolbar}; > + > +use crate::pdm_client; > +use crate::sdn::evpn::{AddVnetWindow, AddZoneWindow, RemoteTree, VrfTree}; > + > +#[derive(PartialEq, Properties)] > +pub struct EvpnPanel {} > + > +impl Default for EvpnPanel { > + fn default() -> Self { > + Self::new() > + } > +} > + > +impl EvpnPanel { > + pub fn new() -> Self { > + Self {} > + } > +} > + > +impl From for VNode { > + fn from(value: EvpnPanel) -> Self { > + let comp = VComp::new::>(Rc::new(value), None); > + VNode::from(comp) > + } > +} > + > +pub enum EvpnPanelMsg { > + Reload, > + LoadFinished { > + controllers: Rc>, > + zones: Rc>, > + vnets: Rc>, > + }, > +} > + > +#[derive(Debug, PartialEq)] > +pub enum EvpnPanelViewState { > + AddZone, > + AddVnet, > +} > + > +async fn load_zones() -> Result, Error> { > + let client = pdm_client(); > + let data = client > + .pve_sdn_list_zones(false, true, ListZonesType::Evpn) > + .await?; > + Ok(data) > +} > + > +async fn load_controllers() -> Result, Error> { > + let client = pdm_client(); > + let data = client > + .pve_sdn_list_controllers(false, true, ListControllersType::Evpn) > + .await?; > + Ok(data) > +} > + > +async fn load_vnets() -> Result, Error> { > + let client = pdm_client(); > + let data = client.pve_sdn_list_vnets(false, true).await?; > + Ok(data) > +} > + > +pub struct EvpnPanelComponent { > + controllers: Rc>, > + zones: Rc>, > + vnets: Rc>, > +} > + > +impl LoadableComponent for EvpnPanelComponent { > + type Properties = EvpnPanel; > + type Message = EvpnPanelMsg; > + type ViewState = EvpnPanelViewState; > + > + fn create(_ctx: &LoadableComponentContext) -> Self { > + Self { > + controllers: Default::default(), > + zones: Default::default(), > + vnets: Default::default(), > + } > + } > + > + fn load( > + &self, > + ctx: &LoadableComponentContext, > + ) -> std::pin::Pin>>> { > + let link = ctx.link().clone(); > + > + Box::pin(async move { > + let (controllers, zones, vnets) = > + try_join!(load_controllers(), load_zones(), load_vnets())?; > + > + link.send_message(Self::Message::LoadFinished { > + controllers: Rc::new(controllers), > + zones: Rc::new(zones), > + vnets: Rc::new(vnets), > + }); > + > + Ok(()) > + }) > + } > + > + fn update(&mut self, ctx: &LoadableComponentContext, msg: Self::Message) -> bool { > + match msg { > + Self::Message::LoadFinished { > + controllers, > + zones, > + vnets, > + } => { > + self.controllers = controllers; > + self.zones = zones; > + self.vnets = vnets; > + > + return true; > + } > + Self::Message::Reload => { > + ctx.link().send_reload(); > + } > + } > + > + false > + } > + > + fn main_view(&self, ctx: &LoadableComponentContext) -> Html { > + let add_menu = Menu::new() > + .with_item( > + MenuItem::new(tr!("Zone")).icon_class("fa fa-th").on_select( > + ctx.link() > + .change_view_callback(|_| Some(Self::ViewState::AddZone)), > + ), > + ) > + .with_item( > + MenuItem::new(tr!("VNet")) > + .icon_class("fa fa-sdn-vnet") > + .on_select( > + ctx.link() > + .change_view_callback(|_| Some(Self::ViewState::AddVnet)), > + ), > + ); > + > + let toolbar = Toolbar::new() > + .class("pwt-w-100") > + .class("pwt-overflow-hidden") > + .class("pwt-border-bottom") > + .with_child(MenuButton::new(tr!("Add")).show_arrow(true).menu(add_menu)) > + .with_flex_spacer() > + .with_child( > + Button::refresh(false).onclick(ctx.link().callback(|_| Self::Message::Reload)), > + ); if you change the order of toolbar/tabpanel it's not really relevant, but the loadable component includes a 'fn toolbar' method that you can override, which makes the column at the end unnecessary > + > + let panel = TabPanel::new() > + .state_id(StorageLocation::session("EvpnPanelState")) > + .class(pwt::css::FlexFit) > + .router(true) > + .scroll_mode(MiniScrollMode::Arrow) > + .with_item( > + TabBarItem::new() > + .key("remotes") > + .label(tr!("Remotes")) > + .icon_class("fa fa-server"), > + RemoteTree::new( > + self.zones.clone(), > + self.vnets.clone(), > + self.controllers.clone(), > + ), > + ) > + .with_item( > + TabBarItem::new() > + .key("vrfs") > + .label(tr!("IP-VRFs")) > + .icon_class("fa fa-th"), > + VrfTree::new( > + self.zones.clone(), > + self.vnets.clone(), > + self.controllers.clone(), > + ), > + ); > + > + let navigation_container = NavigationContainer::new().with_child(panel); > + > + Column::new() > + .class(pwt::css::FlexFit) > + .with_child(toolbar) > + .with_child(navigation_container) > + .into() > + } > + > + fn dialog_view( > + &self, > + ctx: &LoadableComponentContext, > + view_state: &Self::ViewState, > + ) -> Option { > + let scope = ctx.link().clone(); > + > + let on_success = Callback::from(move |upid: String| { > + scope.show_task_log(upid, None); > + }); > + > + let on_close = ctx.link().clone().change_view_callback(|_| None); > + > + Some(match view_state { > + EvpnPanelViewState::AddZone => { > + AddZoneWindow::new(self.controllers.clone(), on_success, on_close).into() > + } > + EvpnPanelViewState::AddVnet => { > + AddVnetWindow::new(self.zones.clone(), on_success, on_close).into() > + } > + }) > + } > +} > diff --git a/ui/src/sdn/evpn/mod.rs b/ui/src/sdn/evpn/mod.rs > index adcf272..1948ecf 100644 > --- a/ui/src/sdn/evpn/mod.rs > +++ b/ui/src/sdn/evpn/mod.rs > @@ -1,3 +1,6 @@ > +mod evpn_panel; > +pub use evpn_panel::EvpnPanel; > + > mod remote_tree; > pub use remote_tree::RemoteTree; > _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel