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 972BF1FF15E for ; Mon, 1 Sep 2025 15:45:12 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 19C422FC52; Mon, 1 Sep 2025 15:45:25 +0200 (CEST) Message-ID: Date: Mon, 1 Sep 2025 15:44:50 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Beta To: Proxmox Datacenter Manager development discussion , Stefan Hanreich References: <20250829145313.329114-1-s.hanreich@proxmox.com> <20250829145313.329114-30-s.hanreich@proxmox.com> Content-Language: en-US From: Dominik Csapak In-Reply-To: <20250829145313.329114-30-s.hanreich@proxmox.com> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1756734278374 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.021 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 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. 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 v2 14/15] ui: sdn: add evpn overview panel 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" one question: couldn't have the toolbar stayed the same as before? as in, create the toolbar in line in the `main_view` and just `clone()` it once for each panel? not that i'm totally against factoring such things out, but the `EvpnToolbar` does not do anything special FWICT, so creating the toolbar inline would have been fine... On 8/29/25 4:53 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 (zones, vnets). > > Signed-off-by: Stefan Hanreich > --- > ui/src/sdn/evpn/evpn_panel.rs | 275 ++++++++++++++++++++++++++++++++++ > ui/src/sdn/evpn/mod.rs | 3 + > 2 files changed, 278 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..29c3ee9 > --- /dev/null > +++ b/ui/src/sdn/evpn/evpn_panel.rs > @@ -0,0 +1,275 @@ > +use futures::try_join; > +use std::rc::Rc; > + > +use anyhow::Error; > +use yew::virtual_dom::{VComp, VNode}; > +use yew::{function_component, Callback, Component, Html, MouseEvent, 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, MenuEvent, MenuItem}; > +use pwt::widget::{Button, Column, MiniScrollMode, TabBarItem, TabPanel, Toolbar}; > +use pwt_macros::widget; > + > +use crate::pdm_client; > +use crate::sdn::evpn::{AddVnetWindow, AddZoneWindow, RemoteTree, VrfTree}; > + > +#[widget(comp=EvpnToolbarComponent)] > +#[derive(Properties, PartialEq, Clone)] > +struct EvpnToolbar { > + on_add_zone: Callback, > + on_add_vnet: Callback, > + on_refresh: Callback, > +} > + > +impl EvpnToolbar { > + pub fn new( > + on_add_zone: Callback, > + on_add_vnet: Callback, > + on_refresh: Callback, > + ) -> Self { > + yew::props!(Self { > + on_add_zone, > + on_add_vnet, > + on_refresh, > + }) > + } > +} > + > +struct EvpnToolbarComponent {} > + > +impl Component for EvpnToolbarComponent { > + type Message = (); > + type Properties = EvpnToolbar; > + > + fn create(_ctx: &yew::Context) -> Self { > + Self {} > + } > + > + fn view(&self, ctx: &yew::Context) -> Html { > + let add_menu = Menu::new() > + .with_item( > + MenuItem::new(tr!("Zone")) > + .icon_class("fa fa-th") > + .on_select(ctx.props().on_add_zone.clone()), > + ) > + .with_item( > + MenuItem::new(tr!("VNet")) > + .icon_class("fa fa-sdn-vnet") > + .on_select(ctx.props().on_add_vnet.clone()), > + ); > + > + 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.props().on_refresh.clone())) > + .into() > + } > +} > + > +#[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 on_add_zone = ctx > + .link() > + .change_view_callback(|_| Some(Self::ViewState::AddZone)); > + > + let on_add_vnet = ctx > + .link() > + .change_view_callback(|_| Some(Self::ViewState::AddVnet)); > + > + let on_refresh = ctx.link().callback(|_| Self::Message::Reload); > + > + 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"), > + Column::new() > + .class(pwt::css::FlexFit) > + .with_child(EvpnToolbar::new( > + on_add_zone.clone(), > + on_add_vnet.clone(), > + on_refresh.clone(), > + )) > + .with_child(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"), > + Column::new() > + .class(pwt::css::FlexFit) > + .with_child(EvpnToolbar::new(on_add_zone, on_add_vnet, on_refresh)) > + .with_child(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(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