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 3EB6E1FF142 for ; Tue, 05 May 2026 09:32:44 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 208F118235; Tue, 5 May 2026 09:32:44 +0200 (CEST) From: Dominik Csapak To: pdm-devel@lists.proxmox.com Subject: [PATCH datacenter-manager v2 1/4] lib/api/ui: add location property to remote config Date: Tue, 5 May 2026 09:31:56 +0200 Message-ID: <20260505073203.398548-6-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260505073203.398548-1-d.csapak@proxmox.com> References: <20260505073203.398548-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.050 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [remotes.rs,mod.rs] Message-ID-Hash: LKGGGW5MW6WWUVKE5IR2SWANMFRTGKQZ X-Message-ID-Hash: LKGGGW5MW6WWUVKE5IR2SWANMFRTGKQZ X-MailFrom: d.csapak@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: this will be used to show the remote on a map in a custom view. Let's the user simply enter the longitude and latitude in the remote edit window. Signed-off-by: Dominik Csapak --- changes from v1: * use correct 'location' property for gui edit windows of remotes lib/pdm-api-types/src/remotes.rs | 22 +++++++++++++++++++++- server/src/api/pbs/mod.rs | 2 ++ server/src/api/pve/mod.rs | 2 ++ server/src/api/remotes/mod.rs | 9 +++++++++ ui/src/remotes/edit_remote.rs | 30 +++++++++++++++++++++++++----- 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/lib/pdm-api-types/src/remotes.rs b/lib/pdm-api-types/src/remotes.rs index b226d190..d879fcfb 100644 --- a/lib/pdm-api-types/src/remotes.rs +++ b/lib/pdm-api-types/src/remotes.rs @@ -4,7 +4,7 @@ use http::Uri; use serde::{Deserialize, Serialize}; use proxmox_schema::property_string::PropertyString; -use proxmox_schema::{api, ApiType, Schema, StringSchema, Updater}; +use proxmox_schema::{api, ApiType, Schema, StringSchema, Updater, UpdaterType}; use proxmox_section_config::typed::ApiSectionDataEntry; use proxmox_section_config::{SectionConfig, SectionConfigPlugin}; @@ -66,6 +66,17 @@ impl RemoteType { serde_plain::derive_display_from_serialize!(RemoteType); serde_plain::derive_fromstr_from_deserialize!(RemoteType); +#[api] +#[derive(Clone, Debug, Deserialize, Serialize, UpdaterType, PartialEq)] +#[serde(rename_all = "kebab-case")] +/// A location using the geographic coordinate system. +pub struct RemoteLocation { + /// The latitude of the remote location. + pub latitude: f64, + /// The longitude of the remote location. + pub longitude: f64, +} + #[api( properties: { "id": { schema: REMOTE_ID_SCHEMA }, @@ -81,6 +92,10 @@ serde_plain::derive_fromstr_from_deserialize!(RemoteType); type: String, optional: true, }, + "location": { + type: String, + optional: true, + }, }, )] /// The information required to connect to a remote instance. @@ -120,6 +135,11 @@ pub struct Remote { skip_serializing_if = "Option::is_none" )] pub web_url: Option, + + /// The remotes physical location + #[updater(serde(skip_serializing_if = "Option::is_none"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub location: Option>, } impl ApiSectionDataEntry for Remote { diff --git a/server/src/api/pbs/mod.rs b/server/src/api/pbs/mod.rs index 32e6bf84..50161488 100644 --- a/server/src/api/pbs/mod.rs +++ b/server/src/api/pbs/mod.rs @@ -275,6 +275,7 @@ pub async fn scan_remote_pbs( authid: authid.clone(), token, web_url: None, + location: None, }; let _client = connect_or_login(&remote) @@ -327,6 +328,7 @@ pub async fn list_realm_remote_pbs( authid: "root@pam".parse()?, token: String::new(), web_url: None, + location: None, }; let client = connection::make_pbs_client(&remote)?; diff --git a/server/src/api/pve/mod.rs b/server/src/api/pve/mod.rs index 20892f38..96aaf80d 100644 --- a/server/src/api/pve/mod.rs +++ b/server/src/api/pve/mod.rs @@ -460,6 +460,7 @@ pub async fn scan_remote_pve( authid: authid.clone(), token, web_url: None, + location: None, }; let client = connect_or_login(&remote) @@ -551,6 +552,7 @@ pub async fn list_realm_remote_pve( authid: "root@pam".parse()?, token: String::new(), web_url: None, + location: None, }; let client = connection::make_pve_client(&remote)?; diff --git a/server/src/api/remotes/mod.rs b/server/src/api/remotes/mod.rs index a91ec97d..69a4e3af 100644 --- a/server/src/api/remotes/mod.rs +++ b/server/src/api/remotes/mod.rs @@ -340,6 +340,8 @@ pub async fn add_remote(mut entry: Remote, create_token: Option) -> Resu pub enum DeletableProperty { /// Delete the web-url property. WebUrl, + /// Delete the location property. + Location, } // FIXME: Support `OneOf` in schema so we can use a derived Updater for all product types? @@ -390,6 +392,9 @@ pub fn update_remote( DeletableProperty::WebUrl => { entry.web_url = None; } + DeletableProperty::Location => { + entry.location = None; + } } } } @@ -408,6 +413,10 @@ pub fn update_remote( entry.web_url = updater.web_url; } + if updater.location.is_some() { + entry.location = updater.location; + } + pdm_config::remotes::save_config(remotes)?; Ok(()) diff --git a/ui/src/remotes/edit_remote.rs b/ui/src/remotes/edit_remote.rs index 925d11ad..99bc6c1a 100644 --- a/ui/src/remotes/edit_remote.rs +++ b/ui/src/remotes/edit_remote.rs @@ -7,10 +7,12 @@ use yew::virtual_dom::{VComp, VNode}; use pwt::css::FlexFit; use pwt::prelude::*; -use pwt::widget::form::{DisplayField, Field, FormContext, InputType}; +use pwt::widget::form::{DisplayField, Field, FormContext, InputType, Number}; use pwt::widget::{Container, InputPanel}; -use proxmox_yew_comp::form::delete_empty_values; +use proxmox_yew_comp::form::{ + delete_empty_values, flatten_property_string, property_string_from_parts, +}; use proxmox_yew_comp::percent_encoding::percent_encode_component; use proxmox_yew_comp::{EditWindow, SchemaValidation}; @@ -21,6 +23,8 @@ use super::NodeUrlList; use pwt_macros::builder; +use pdm_api_types::remotes::RemoteLocation; + #[derive(PartialEq, Properties)] #[builder] pub struct EditRemote { @@ -42,7 +46,13 @@ impl EditRemote { pub struct PdmEditRemote {} async fn load_remote(url: AttrValue) -> Result, Error> { - proxmox_yew_comp::http_get_full(&*url, None).await + let mut res = proxmox_yew_comp::http_get_full(&*url, None).await; + + if let Ok(data) = res.as_mut() { + flatten_property_string::(&mut data.data, "location")?; + } + + res } impl Component for PdmEditRemote { @@ -76,9 +86,11 @@ impl Component for PdmEditRemote { move |form_ctx: FormContext| { let url = url.clone(); async move { - let data = form_ctx.get_submit_data(); + let mut data = form_ctx.get_submit_data(); + + property_string_from_parts::(&mut data, "location", true)?; - let data = delete_empty_values(&data, &["web-url"], true); + let data = delete_empty_values(&data, &["web-url", "location"], true); proxmox_yew_comp::http_put(&url, Some(data)).await } @@ -120,6 +132,14 @@ fn edit_remote_input_panel(_form_ctx: &FormContext, remote_id: &str) -> Html { .name("web-url") .placeholder(tr!("Use first endpoint.")), ) + .with_field( + tr!("Location Latitude"), + Number::new().name("_lat").min(-90.0).max(90.0), + ) + .with_field( + tr!("Location Longitude"), + Number::new().name("_long").min(-180.0).max(180.0), + ) .with_custom_child( Container::new() .key("nodes-title") -- 2.47.3