From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 2D36E1FF138 for ; Wed, 18 Feb 2026 17:43:27 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 851A9AE1; Wed, 18 Feb 2026 17:44:26 +0100 (CET) From: Shan Shaji To: pdm-devel@lists.proxmox.com Subject: [PATCH datacenter-manager v4 5/6] fix #6914: ui: add remove remote dialog with optional token deletion Date: Wed, 18 Feb 2026 17:41:33 +0100 Message-ID: <20260218164135.413762-6-s.shaji@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260218164135.413762-1-s.shaji@proxmox.com> References: <20260218164135.413762-1-s.shaji@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1771433024322 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.109 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 Message-ID-Hash: MW25FDFBPSH3ZUCPD2RRQ6FNGQOLVYIW X-Message-ID-Hash: MW25FDFBPSH3ZUCPD2RRQ6FNGQOLVYIW X-MailFrom: s.shaji@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: previously, removing a remote did not remove its token, which prevented users from re-adding the same remote later with the same token name. To fix this, add a new checkbox that lets users choose whether to delete the token from remote. Signed-off-by: Shan Shaji --- changes since v3: - use the `ConfirmDialog` widget to create the dialog. - made the token deletion as an opt-out feature. If the user wants to keep the token, then the checkbox needs to checked. - fixed clippy warnings. - changed the checkbox label. - remove the use of `pub` keyword. ui/src/remotes/config.rs | 38 ++++++++---- ui/src/remotes/mod.rs | 2 + ui/src/remotes/remove_remote.rs | 103 ++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 ui/src/remotes/remove_remote.rs diff --git a/ui/src/remotes/config.rs b/ui/src/remotes/config.rs index 0737f1b..0481c78 100644 --- a/ui/src/remotes/config.rs +++ b/ui/src/remotes/config.rs @@ -7,6 +7,7 @@ use anyhow::Error; use proxmox_schema::property_string::PropertyString; use crate::remotes::edit_remote::EditRemote; +use crate::remotes::remove_remote::RemoveRemote; //use pwt::widget::form::{Field, FormContext, InputType}; use pdm_api_types::remotes::Remote; @@ -17,7 +18,7 @@ use proxmox_yew_comp::percent_encoding::percent_encode_component; //use proxmox_schema::api_types::{CERT_FINGERPRINT_SHA256_SCHEMA, DNS_NAME_OR_IP_SCHEMA}; -use serde_json::Value; +use serde_json::{json, Value}; use yew::virtual_dom::{Key, VComp, VNode}; use pwt::prelude::*; @@ -32,7 +33,7 @@ use pwt::widget::{ //use proxmox_yew_comp::EditWindow; use proxmox_yew_comp::{ - ConfirmButton, LoadableComponent, LoadableComponentContext, LoadableComponentMaster, + LoadableComponent, LoadableComponentContext, LoadableComponentMaster, LoadableComponentScopeExt, LoadableComponentState, }; @@ -42,10 +43,13 @@ async fn load_remotes() -> Result, Error> { proxmox_yew_comp::http_get("/remotes/remote", None).await } -async fn delete_item(key: Key) -> Result<(), Error> { +async fn delete_item(key: Key, delete_token: bool) -> Result<(), Error> { let id = key.to_string(); + let param = Some(json!({ + "delete-token": delete_token, + })); let url = format!("/remotes/remote/{}", percent_encode_component(&id)); - proxmox_yew_comp::http_delete(&url, None).await?; + proxmox_yew_comp::http_delete(&url, param).await?; Ok(()) } @@ -100,10 +104,11 @@ impl RemoteConfigPanel { pub enum ViewState { Add(RemoteType), Edit, + Remove, } pub enum Msg { - RemoveItem, + RemoveItem(bool), } pub struct PbsRemoteConfigPanel { @@ -156,11 +161,11 @@ impl LoadableComponent for PbsRemoteConfigPanel { fn update(&mut self, ctx: &LoadableComponentContext, msg: Self::Message) -> bool { match msg { - Msg::RemoveItem => { + Msg::RemoveItem(v) => { if let Some(key) = self.selection.selected_key() { let link = ctx.link().clone(); self.spawn(async move { - if let Err(err) = delete_item(key).await { + if let Err(err) = delete_item(key, v).await { link.show_error(tr!("Unable to delete item"), err, true); } link.send_reload(); @@ -205,10 +210,9 @@ impl LoadableComponent for PbsRemoteConfigPanel { .onclick(link.change_view_callback(|_| Some(ViewState::Edit))), ) .with_child( - ConfirmButton::new(tr!("Remove")) - .confirm_message(tr!("Are you sure you want to remove this remote?")) + Button::new(tr!("Remove")) .disabled(disabled) - .on_activate(link.callback(|_| Msg::RemoveItem)), + .on_activate(link.change_view_callback(|_| Some(ViewState::Remove))), ) .with_flex_spacer() .with_child({ @@ -243,6 +247,7 @@ impl LoadableComponent for PbsRemoteConfigPanel { .selection .selected_key() .map(|key| self.create_edit_dialog(ctx, key)), + ViewState::Remove => Some(self.create_remove_remote_dialog(ctx)), } } } @@ -303,6 +308,19 @@ impl PbsRemoteConfigPanel { .on_done(ctx.link().change_view_callback(|_| None)) .into() } + + fn create_remove_remote_dialog(&self, ctx: &LoadableComponentContext) -> Html { + let link = ctx.link().clone(); + let close = link.change_view_callback(|_| None); + + RemoveRemote::new() + .on_dismiss(close.clone()) + .on_confirm(Callback::from(move |v| { + link.send_message(Msg::RemoveItem(v)); + link.change_view(None); + })) + .into() + } } impl From for VNode { diff --git a/ui/src/remotes/mod.rs b/ui/src/remotes/mod.rs index 603077c..bfe9dc0 100644 --- a/ui/src/remotes/mod.rs +++ b/ui/src/remotes/mod.rs @@ -27,6 +27,8 @@ pub use tasks::RemoteTaskList; mod updates; pub use updates::UpdateTree; +mod remove_remote; + mod firewall; pub use firewall::FirewallTree; diff --git a/ui/src/remotes/remove_remote.rs b/ui/src/remotes/remove_remote.rs new file mode 100644 index 0000000..45cbd18 --- /dev/null +++ b/ui/src/remotes/remove_remote.rs @@ -0,0 +1,103 @@ +use std::rc::Rc; + +use yew::html::IntoEventCallback; +use yew::prelude::*; +use yew::virtual_dom::{VComp, VNode}; + +use pwt::prelude::*; +use pwt::widget::form::Checkbox; +use pwt::widget::{Column, ConfirmDialog}; + +use pwt_macros::builder; + +#[derive(PartialEq, Properties)] +#[builder] +pub struct RemoveRemote { + /// A callback for an action that needs to be confirmed by the user. + #[prop_or_default] + #[builder_cb(IntoEventCallback, into_event_callback, bool)] + pub on_confirm: Option>, + + /// A callback that will trigger if the user dismisses the dialog. + #[prop_or_default] + #[builder_cb(IntoEventCallback, into_event_callback, ())] + pub on_dismiss: Option>, +} + +impl Default for RemoveRemote { + fn default() -> Self { + Self::new() + } +} + +impl RemoveRemote { + pub fn new() -> Self { + yew::props!(Self {}) + } +} + +enum Msg { + SelectCheckBox(bool), +} + +struct PdmRemoveRemote { + keep_api_token: bool, +} + +impl Component for PdmRemoveRemote { + type Message = Msg; + type Properties = RemoveRemote; + + fn create(_ctx: &Context) -> Self { + Self { + keep_api_token: false, + } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::SelectCheckBox(v) => { + self.keep_api_token = v; + true + } + } + } + + fn view(&self, ctx: &Context) -> Html { + let props = ctx.props(); + let keep_api_token = self.keep_api_token; + + let on_confirm = props.on_confirm.clone(); + let on_dismiss = props.on_dismiss.clone(); + + let content = Column::new() + .gap(2) + .with_child(tr!("Are you sure you want to remove this remote?")) + .with_child( + Checkbox::new() + .box_label(tr!("Keep the API token on the remote")) + .checked(keep_api_token) + .on_change(ctx.link().callback(Msg::SelectCheckBox)), + ); + + let mut dialog = ConfirmDialog::default() + .on_confirm(Callback::from(move |_| { + if let Some(on_confirm) = &on_confirm { + on_confirm.emit(!keep_api_token); + } + })) + .on_close(on_dismiss.clone()) + .on_dismiss(on_dismiss); + + dialog.set_confirm_message(content); + + dialog.into() + } +} + +impl From for VNode { + fn from(val: RemoveRemote) -> Self { + let comp = VComp::new::(Rc::new(val), None); + VNode::from(comp) + } +} -- 2.47.3