Re: [pve-devel] [PATCH widget-toolkit/manager v2 0/4] Ceph OSD: add detail infos
On 10/17/22 16:29, Dominik Csapak wrote: high level looks mostly good, a small question: is there a special reason why we ignore pre-lvm osds here? AFAICS, we simply error out for osds that don't live on lvm (though we can add additional types later i guess) Mainly because with a recent Ceph version, you shouldn't have any pre Bluestore OSDs anymore. With Quincy, they have officially been deprecated. And even before, for quite a few versions, every new OSD would be a bluestore one. So I did not want to do the work for the old filestore OSDs. Once a new generation of OSDs becomes available, we will need to handle them as well though. comments in the individual patches ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH proxmox-offline-mirror 2/4] mirror: implement source packages mirroring
similar to the binary package one, but with one additional layer since each source package consists of 2-3 files, not a single .deb file. Signed-off-by: Fabian Grünbichler --- Notes: requires proxmox-apt with source index support src/mirror.rs | 158 +++--- 1 file changed, 150 insertions(+), 8 deletions(-) diff --git a/src/mirror.rs b/src/mirror.rs index 22dc716..37dca97 100644 --- a/src/mirror.rs +++ b/src/mirror.rs @@ -22,6 +22,7 @@ use crate::{ use proxmox_apt::{ deb822::{ CheckSums, CompressionType, FileReference, FileReferenceType, PackagesFile, ReleaseFile, +SourcesFile, }, repositories::{APTRepository, APTRepositoryPackageType}, }; @@ -598,10 +599,15 @@ pub fn create_snapshot( let mut packages_size = 0_usize; let mut packages_indices = HashMap::new(); + +let mut source_packages_indices = HashMap::new(); + let mut failed_references = Vec::new(); for (component, references) in per_component { println!("\nFetching indices for component '{component}'"); let mut component_deb_size = 0; +let mut component_dsc_size = 0; + let mut fetch_progress = Progress::new(); for basename in references { @@ -642,21 +648,49 @@ pub fn create_snapshot( fetch_progress.update(&res); if package_index_data.is_none() && reference.file_type.is_package_index() { -package_index_data = Some(res.data()); +package_index_data = Some((&reference.file_type, res.data())); } } -if let Some(data) = package_index_data { -let packages: PackagesFile = data[..].try_into()?; -let size: usize = packages.files.iter().map(|p| p.size).sum(); -println!("\t{} packages totalling {size}", packages.files.len()); -component_deb_size += size; - -packages_indices.entry(basename).or_insert(packages); +if let Some((reference_type, data)) = package_index_data { +match reference_type { +FileReferenceType::Packages(_, _) => { +let packages: PackagesFile = data[..].try_into()?; +let size: usize = packages.files.iter().map(|p| p.size).sum(); +println!("\t{} packages totalling {size}", packages.files.len()); +component_deb_size += size; + +packages_indices.entry(basename).or_insert(packages); +} +FileReferenceType::Sources(_) => { +let source_packages: SourcesFile = data[..].try_into()?; +let size: usize = source_packages +.source_packages +.iter() +.map(|s| s.size()) +.sum(); +println!( +"\t{} source packages totalling {size}", +source_packages.source_packages.len() +); +component_dsc_size += size; +source_packages_indices +.entry(basename) +.or_insert(source_packages); +} +unknown => { +eprintln!("Unknown package index '{unknown:?}', skipping processing..") +} +} } println!("Progress: {fetch_progress}"); } + println!("Total deb size for component: {component_deb_size}"); packages_size += component_deb_size; + +println!("Total dsc size for component: {component_dsc_size}"); +packages_size += component_dsc_size; + total_progress += fetch_progress; } println!("Total deb size: {packages_size}"); @@ -782,6 +816,114 @@ pub fn create_snapshot( } } +for (basename, references) in source_packages_indices { +let total_source_packages = references.source_packages.len(); +if total_source_packages == 0 { +println!("\n{basename} - no files, skipping."); +continue; +} else { +println!("\n{basename} - {total_source_packages} total source package(s)"); +} + +let mut fetch_progress = Progress::new(); +let mut skipped_count = 0usize; +let mut skipped_bytes = 0usize; +for package in references.source_packages { +if let Some(ref sections) = &config.skip.skip_sections { +if sections +.iter() +.any(|section| package.section.as_ref() == Some(section)) +{ +println!( +"\tskipping {} - {}b (section '{}')", +packa
[pve-devel] [PATCH proxmox-offline-mirror 3/4] fix #4264: only require either Release or InRelease
strictly speaking InRelease is required, and Release optional, but that might not be true for older repositories. treat failure to fetch either as non-fatal, provided the other is available. Signed-off-by: Fabian Grünbichler --- src/mirror.rs | 70 +-- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/src/mirror.rs b/src/mirror.rs index 37dca97..39b7f47 100644 --- a/src/mirror.rs +++ b/src/mirror.rs @@ -144,40 +144,61 @@ fn fetch_repo_file( /// Helper to fetch InRelease (`detached` == false) or Release/Release.gpg (`detached` == true) files from repository. /// -/// Verifies the contained/detached signature, stores all fetched files under `prefix`, and returns the verified raw release file data. +/// Verifies the contained/detached signature and stores all fetched files under `prefix`. +/// +/// Returns the verified raw release file data, or None if the "fetch" part itself fails. fn fetch_release( config: &ParsedMirrorConfig, prefix: &Path, detached: bool, dry_run: bool, -) -> Result { +) -> Result, Error> { let (name, fetched, sig) = if detached { println!("Fetching Release/Release.gpg files"); -let sig = fetch_repo_file( +let sig = match fetch_repo_file( &config.client, &get_dist_url(&config.repository, "Release.gpg"), 1024 * 1024, None, config.auth.as_deref(), -)?; -let mut fetched = fetch_repo_file( +) { +Ok(res) => res, +Err(err) => { +eprintln!("Release.gpg fetch failure: {err}"); +return Ok(None); +} +}; + +let mut fetched = match fetch_repo_file( &config.client, &get_dist_url(&config.repository, "Release"), 256 * 1024 * 1024, None, config.auth.as_deref(), -)?; +) { +Ok(res) => res, +Err(err) => { +eprintln!("Release fetch failure: {err}"); +return Ok(None); +} +}; fetched.fetched += sig.fetched; ("Release(.gpg)", fetched, Some(sig.data())) } else { println!("Fetching InRelease file"); -let fetched = fetch_repo_file( +let fetched = match fetch_repo_file( &config.client, &get_dist_url(&config.repository, "InRelease"), 256 * 1024 * 1024, None, config.auth.as_deref(), -)?; +) { +Ok(res) => res, +Err(err) => { +eprintln!("InRelease fetch failure: {err}"); +return Ok(None); +} +}; ("InRelease", fetched, None) }; @@ -193,10 +214,10 @@ fn fetch_release( }; if dry_run { -return Ok(FetchResult { +return Ok(Some(FetchResult { data: verified, fetched: fetched.fetched, -}); +})); } let locked = &config.pool.lock()?; @@ -230,10 +251,10 @@ fn fetch_release( )?; } -Ok(FetchResult { +Ok(Some(FetchResult { data: verified, fetched: fetched.fetched, -}) +})) } /// Helper to fetch an index file referenced by a `ReleaseFile`. @@ -510,14 +531,25 @@ pub fn create_snapshot( Ok(parsed) }; -// we want both on-disk for compat reasons -let res = fetch_release(&config, prefix, true, dry_run)?; -total_progress.update(&res); -let _release = parse_release(res, "Release")?; +// we want both on-disk for compat reasons, if both are available +let release = fetch_release(&config, prefix, true, dry_run)? +.map(|res| { +total_progress.update(&res); +parse_release(res, "Release") +}) +.transpose()?; + +let in_release = fetch_release(&config, prefix, false, dry_run)? +.map(|res| { +total_progress.update(&res); +parse_release(res, "InRelease") +}) +.transpose()?; -let res = fetch_release(&config, prefix, false, dry_run)?; -total_progress.update(&res); -let release = parse_release(res, "InRelease")?; +// at least one must be available to proceed +let release = release +.or(in_release) +.ok_or_else(|| format_err!("Neither Release(.gpg) nor InRelease available!"))?; let mut per_component = HashMap::new(); let mut others = Vec::new(); -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH-SERIES 0/6] proxmox-offline-mirror filtering & deb-src support
this series implements filtering based on package section (exact match) or package name (glob), and extends mirroring support to source packages/deb-src repositories. technically the first patch in proxmox-apt is a breaking change, but the only user of the changed struct is proxmox-offline-mirror, which doesn't do any incompatible initializations. proxmox-apt: Fabian Grünbichler (2): packages file: add section field deb822: source index support src/deb822/mod.rs | 3 + src/deb822/packages_file.rs | 2 + src/deb822/release_file.rs| 2 +- src/deb822/sources_file.rs|255 + ..._debian_dists_bullseye_main_source_Sources | 858657 +++ 5 files changed, 858918 insertions(+), 1 deletion(-) create mode 100644 src/deb822/sources_file.rs create mode 100644 tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources proxmox-offline-mirror: Fabian Grünbichler (4): mirror: add exclusion of packages/sections mirror: implement source packages mirroring fix #4264: only require either Release or InRelease mirror: refactor fetch_binary/source_packages Cargo.toml| 1 + debian/control| 2 + src/bin/proxmox-offline-mirror.rs | 4 +- src/bin/proxmox_offline_mirror_cmds/config.rs | 8 + src/config.rs | 40 +- src/mirror.rs | 483 ++ 6 files changed, 437 insertions(+), 101 deletions(-) -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH proxmox-apt 1/2] packages file: add section field
Signed-off-by: Fabian Grünbichler --- Notes: technically a breaking change, but the only user (pom) doesn't care. not bumping to an incompatible version would avoid the need to bump the dep in proxmox-perl-rs src/deb822/packages_file.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/deb822/packages_file.rs b/src/deb822/packages_file.rs index a51f71e..90b21c6 100644 --- a/src/deb822/packages_file.rs +++ b/src/deb822/packages_file.rs @@ -57,6 +57,7 @@ pub struct PackageEntry { pub size: usize, pub installed_size: Option, pub checksums: CheckSums, +pub section: String, } #[derive(Debug, Default, PartialEq, Eq)] @@ -83,6 +84,7 @@ impl TryFrom for PackageEntry { size: value.size.parse::()?, installed_size, checksums: CheckSums::default(), +section: value.section, }; if let Some(md5) = value.md5_sum { -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH proxmox-offline-mirror 1/4] mirror: add exclusion of packages/sections
to keep the size of mirror snapshots down by excluding unnecessary files (e.g., games data, browsers, debug packages, ..). Signed-off-by: Fabian Grünbichler --- Notes: requires proxmox-apt with 'section' field we could suggest excluding sections like 'games' in the wizard/docs.. Cargo.toml| 1 + debian/control| 2 + src/bin/proxmox-offline-mirror.rs | 4 +- src/bin/proxmox_offline_mirror_cmds/config.rs | 8 +++ src/config.rs | 40 - src/mirror.rs | 59 ++- 6 files changed, 111 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76791c8..b2bb188 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ anyhow = "1.0" base64 = "0.13" bzip2 = "0.4" flate2 = "1.0.22" +globset = "0.4.8" hex = "0.4.3" lazy_static = "1.4" nix = "0.24" diff --git a/debian/control b/debian/control index 0741a7b..9fe6605 100644 --- a/debian/control +++ b/debian/control @@ -10,6 +10,7 @@ Build-Depends: debhelper (>= 12), librust-base64-0.13+default-dev, librust-bzip2-0.4+default-dev, librust-flate2-1+default-dev (>= 1.0.22-~~), + librust-globset-0.4+default-dev (>= 0.4.8-~~), librust-hex-0.4+default-dev (>= 0.4.3-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-nix-0.24+default-dev, @@ -57,6 +58,7 @@ Depends: librust-base64-0.13+default-dev, librust-bzip2-0.4+default-dev, librust-flate2-1+default-dev (>= 1.0.22-~~), + librust-globset-0.4+default-dev (>= 0.4.8-~~), librust-hex-0.4+default-dev (>= 0.4.3-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-nix-0.24+default-dev, diff --git a/src/bin/proxmox-offline-mirror.rs b/src/bin/proxmox-offline-mirror.rs index 522056b..07b6ce6 100644 --- a/src/bin/proxmox-offline-mirror.rs +++ b/src/bin/proxmox-offline-mirror.rs @@ -13,7 +13,7 @@ use proxmox_offline_mirror::helpers::tty::{ read_bool_from_tty, read_selection_from_tty, read_string_from_tty, }; use proxmox_offline_mirror::{ -config::{save_config, MediaConfig, MirrorConfig}, +config::{save_config, MediaConfig, MirrorConfig, SkipConfig}, mirror, types::{ProductType, MEDIA_ID_SCHEMA, MIRROR_ID_SCHEMA}, }; @@ -387,6 +387,7 @@ fn action_add_mirror(config: &SectionConfigData) -> Result, Er base_dir: base_dir.clone(), use_subscription: None, ignore_errors: false, +skip: SkipConfig::default(), // TODO sensible default? }); } } @@ -401,6 +402,7 @@ fn action_add_mirror(config: &SectionConfigData) -> Result, Er base_dir, use_subscription, ignore_errors: false, +skip: SkipConfig::default(), }; configs.push(main_config); diff --git a/src/bin/proxmox_offline_mirror_cmds/config.rs b/src/bin/proxmox_offline_mirror_cmds/config.rs index 5ebf6d5..3ebf4ad 100644 --- a/src/bin/proxmox_offline_mirror_cmds/config.rs +++ b/src/bin/proxmox_offline_mirror_cmds/config.rs @@ -266,6 +266,14 @@ pub fn update_mirror( data.ignore_errors = ignore_errors } +if let Some(skip_packages) = update.skip.skip_packages { +data.skip.skip_packages = Some(skip_packages); +} + +if let Some(skip_sections) = update.skip.skip_sections { +data.skip.skip_sections = Some(skip_sections); +} + config.set_data(&id, "mirror", &data)?; proxmox_offline_mirror::config::save_config(&config_file, &config)?; diff --git a/src/config.rs b/src/config.rs index be8f96b..39b1193 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,38 @@ use crate::types::{ PROXMOX_SUBSCRIPTION_KEY_SCHEMA, }; +/// Skip Configuration +#[api( +properties: { +"skip-sections": { +type: Array, +optional: true, +items: { +type: String, +description: "Section name", +}, +}, +"skip-packages": { +type: Array, +optional: true, +items: { +type: String, +description: "Package name", +}, +}, +}, +)] +#[derive(Default, Serialize, Deserialize, Updater, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct SkipConfig { +/// Sections which should be skipped +#[serde(skip_serializing_if = "Option::is_none")] +pub skip_sections: Option>, +/// Packages which should be skipped, supports globbing +#[serde(skip_serializing_if = "Option::is_none")] +pub skip_packages: Option>, +} + #[api( properties: { id: { @@ -46,6 +78,9 @@ use crate::types::{ optional: true, default: false, }, +"skip": { +type: SkipConfig, +}, } )] #[derive(Clone, Debug, Serialize, Deserialize, Updater)] @@ -73,6 +108,9 @@ pub struct MirrorConfig { /// W
[pve-devel] [PATCH proxmox-apt 2/2] deb822: source index support
Signed-off-by: Fabian Grünbichler --- the test file needs to be downloaded from the referenced URL and uncompressed (it's too big to send as patch). its SHA256sum is ec1d305f5e0a31bcf2fe26e955436986edb5c211c03a362c7d557c899349 src/deb822/mod.rs | 3 + src/deb822/release_file.rs| 2 +- src/deb822/sources_file.rs|255 + ..._debian_dists_bullseye_main_source_Sources | 858657 +++ 4 files changed, 858916 insertions(+), 1 deletion(-) create mode 100644 src/deb822/sources_file.rs create mode 100644 tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources diff --git a/src/deb822/mod.rs b/src/deb822/mod.rs index 7a1bb0e..59e7c21 100644 --- a/src/deb822/mod.rs +++ b/src/deb822/mod.rs @@ -5,6 +5,9 @@ pub use release_file::{CompressionType, FileReference, FileReferenceType, Releas mod packages_file; pub use packages_file::PackagesFile; +mod sources_file; +pub use sources_file::SourcesFile; + #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct CheckSums { pub md5: Option<[u8; 16]>, diff --git a/src/deb822/release_file.rs b/src/deb822/release_file.rs index c50c095..85d3436 100644 --- a/src/deb822/release_file.rs +++ b/src/deb822/release_file.rs @@ -245,7 +245,7 @@ impl FileReferenceType { } pub fn is_package_index(&self) -> bool { -matches!(self, FileReferenceType::Packages(_, _)) +matches!(self, FileReferenceType::Packages(_, _) | FileReferenceType::Sources(_)) } } diff --git a/src/deb822/sources_file.rs b/src/deb822/sources_file.rs new file mode 100644 index 000..a13d84f --- /dev/null +++ b/src/deb822/sources_file.rs @@ -0,0 +1,255 @@ +use std::collections::HashMap; + +use anyhow::{bail, Error, format_err}; +use rfc822_like::de::Deserializer; +use serde::Deserialize; +use serde_json::Value; + +use super::CheckSums; +//Uploaders +// +//Homepage +// +//Version Control System (VCS) fields +// +//Testsuite +// +//Dgit +// +//Standards-Version (mandatory) +// +//Build-Depends et al +// +//Package-List (recommended) +// +//Checksums-Sha1 and Checksums-Sha256 (mandatory) +// +//Files (mandatory) + + + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct SourcesFileRaw { +pub format: String, +pub package: String, +pub binary: Option>, +pub version: String, +pub section: Option, +pub priority: Option, +pub maintainer: String, +pub uploaders: Option, +pub architecture: Option, +pub directory: String, +pub files: String, +#[serde(rename = "Checksums-Sha256")] +pub sha256: Option, +#[serde(rename = "Checksums-Sha512")] +pub sha512: Option, +#[serde(flatten)] +pub extra_fields: HashMap, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SourcePackageEntry { +pub format: String, +pub package: String, +pub binary: Option>, +pub version: String, +pub architecture: Option, +pub section: Option, +pub priority: Option, +pub maintainer: String, +pub uploaders: Option, +pub directory: String, +pub files: HashMap, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SourcePackageFileReference { +pub file: String, +pub size: usize, +pub checksums: CheckSums, +} + +impl SourcePackageEntry { +pub fn size(&self) -> usize { +self.files.values().map(|f| f.size).sum() +} +} + +#[derive(Debug, Default, PartialEq, Eq)] +/// A parsed representation of a Release file +pub struct SourcesFile { +pub source_packages: Vec, +} + +impl TryFrom for SourcePackageEntry { +type Error = Error; + +fn try_from(value: SourcesFileRaw) -> Result { +let mut parsed = SourcePackageEntry { +package: value.package, +binary: value.binary, +version: value.version, +architecture: value.architecture, +files: HashMap::new(), +format: value.format, +section: value.section, +priority: value.priority, +maintainer: value.maintainer, +uploaders: value.uploaders, +directory: value.directory, +}; + +for file_reference in value.files.lines() { +let (file_name, size, md5) = parse_file_reference(file_reference, 16)?; +let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()}); +entry.checksums.md5 = Some(md5.try_into().map_err(|_|format_err!("unexpected checksum length"))?); +if entry.size != size { +bail!("Size mismatch: {} != {}", entry.size, size); +} +} + +if let Some(sha256) = value.sha256 { +for line in sha256.lines() { +let (file_name, size, sha256) = parse_file_reference(line, 32)?; +let entry = parsed.files.en
[pve-devel] [PATCH proxmox-offline-mirror 4/4] mirror: refactor fetch_binary/source_packages
and pull out some of the progress variables into a struct. Signed-off-by: Fabian Grünbichler --- src/mirror.rs | 520 -- 1 file changed, 287 insertions(+), 233 deletions(-) diff --git a/src/mirror.rs b/src/mirror.rs index 39b7f47..f19 100644 --- a/src/mirror.rs +++ b/src/mirror.rs @@ -7,7 +7,7 @@ use std::{ use anyhow::{bail, format_err, Error}; use flate2::bufread::GzDecoder; -use globset::{Glob, GlobSetBuilder}; +use globset::{Glob, GlobSet, GlobSetBuilder}; use nix::libc; use proxmox_http::{client::sync::Client, HttpClient, HttpOptions}; use proxmox_sys::fs::file_get_contents; @@ -145,7 +145,7 @@ fn fetch_repo_file( /// Helper to fetch InRelease (`detached` == false) or Release/Release.gpg (`detached` == true) files from repository. /// /// Verifies the contained/detached signature and stores all fetched files under `prefix`. -/// +/// /// Returns the verified raw release file data, or None if the "fetch" part itself fails. fn fetch_release( config: &ParsedMirrorConfig, @@ -474,6 +474,259 @@ pub fn list_snapshots(config: &MirrorConfig) -> Result, Error> { Ok(list) } +struct MirrorProgress { +warnings: Vec, +dry_run: Progress, +total: Progress, +skip_count: usize, +skip_bytes: usize, +} + +fn convert_to_globset(config: &ParsedMirrorConfig) -> Result, Error> { +Ok(if let Some(skipped_packages) = &config.skip.skip_packages { +let mut globs = GlobSetBuilder::new(); +for glob in skipped_packages { +let glob = Glob::new(glob)?; +globs.add(glob); +} +let globs = globs.build()?; +Some(globs) +} else { +None +}) +} + +fn fetch_binary_packages( +config: &ParsedMirrorConfig, +packages_indices: HashMap<&String, PackagesFile>, +dry_run: bool, +prefix: &Path, +progress: &mut MirrorProgress, +) -> Result<(), Error> { +let skipped_package_globs = convert_to_globset(config)?; + +for (basename, references) in packages_indices { +let total_files = references.files.len(); +if total_files == 0 { +println!("\n{basename} - no files, skipping."); +continue; +} else { +println!("\n{basename} - {total_files} total file(s)"); +} + +let mut fetch_progress = Progress::new(); +let mut skip_count = 0usize; +let mut skip_bytes = 0usize; +for package in references.files { +if let Some(ref sections) = &config.skip.skip_sections { +if sections.iter().any(|section| package.section == *section) { +println!( +"\tskipping {} - {}b (section '{}')", +package.package, package.size, package.section +); +skip_count += 1; +skip_bytes += package.size; +continue; +} +} +if let Some(skipped_package_globs) = &skipped_package_globs { +let matches = skipped_package_globs.matches(&package.package); +if !matches.is_empty() { +// safety, skipped_package_globs is set based on this +let globs = config.skip.skip_packages.as_ref().unwrap(); +let matches: Vec = matches.iter().map(|i| globs[*i].clone()).collect(); +println!( +"\tskipping {} - {}b (package glob(s): {})", +package.package, +package.size, +matches.join(", ") +); +skip_count += 1; +skip_bytes += package.size; +continue; +} +} +let url = get_repo_url(&config.repository, &package.file); + +if dry_run { +if config.pool.contains(&package.checksums) { +fetch_progress.update(&FetchResult { +data: vec![], +fetched: 0, +}); +} else { +println!("\t(dry-run) GET missing '{url}' ({}b)", package.size); +fetch_progress.update(&FetchResult { +data: vec![], +fetched: package.size, +}); +} +} else { +let mut full_path = PathBuf::from(prefix); +full_path.push(&package.file); + +match fetch_plain_file( +config, +&url, +&full_path, +package.size, +&package.checksums, +false, +dry_run, +) { +Ok(res) => fetch_progress.update(&res), +Err(err) if config.ignore_errors => {
[pve-devel] [PATCH manager] report: add arcstat
One of the infos, that can sometimes be usable. Signed-off-by: Aaron Lauterer --- PVE/Report.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/PVE/Report.pm b/PVE/Report.pm index b8bceb24..90b7cb1c 100644 --- a/PVE/Report.pm +++ b/PVE/Report.pm @@ -124,6 +124,7 @@ my $init_report_cmds = sub { 'zpool status', 'zpool list -v', 'zfs list', + 'arcstat', ; } -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] applied: [PATCH qemu] savevm async IO channel: channel writev: fix return value in error case
applied, thanks ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH widget-toolkit v8 2/2] Toolkit: add override for Ext.dd.DragDropManager
to fix selection behavior for Ext.dd.DragZone. Signed-off-by: Dominik Csapak --- src/Toolkit.js | 16 1 file changed, 16 insertions(+) diff --git a/src/Toolkit.js b/src/Toolkit.js index c730374..20b6eba 100644 --- a/src/Toolkit.js +++ b/src/Toolkit.js @@ -681,6 +681,22 @@ Ext.define('Proxmox.view.DragZone', { }, }); +// Fix text selection on drag when using DragZone, +// see https://forum.sencha.com/forum/showthread.php?335100 +Ext.define('Proxmox.dd.DragDropManager', { +override: 'Ext.dd.DragDropManager', + +stopEvent: function(e) { + if (this.stopPropagation) { + e.stopPropagation(); + } + + if (this.preventDefault) { + e.preventDefault(); + } +}, +}); + // force alert boxes to be rendered with an Error Icon // since Ext.Msg is an object and not a prototype, we need to override it // after the framework has been initiated -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH cluster v8 2/4] Cluster: add get_guest_config_properties
akin to get_guest_config_property, but with a list of properties. uses the new CFS_IPC_GET_GUEST_CONFIG_PROPERTIES also adds the same NOTEs regarding parsing/permissions to the comment of get_guest_config_property Signed-off-by: Dominik Csapak --- data/PVE/Cluster.pm | 27 +++ 1 file changed, 27 insertions(+) diff --git a/data/PVE/Cluster.pm b/data/PVE/Cluster.pm index abcc46d..99e7975 100644 --- a/data/PVE/Cluster.pm +++ b/data/PVE/Cluster.pm @@ -339,10 +339,37 @@ sub get_node_kv { return $res; } +# properties: an array-ref of config properties you want to get, e.g., this +# is perfect to get multiple properties of a guest _fast_ +# (>100 faster than manual parsing here) +# vmid: optional, if a valid is passed we only check that one, else return all +# NOTE: does *not* searches snapshot and PENDING entries sections! +# NOTE: returns the guest config lines (excluding trailing whitespace) as is, +# so for non-trivial properties, checking the validity must be done +# NOTE: no permission check is done, that is the responsibilty of the caller +sub get_guest_config_properties { +my ($properties, $vmid) = @_; + +die "properties required" if !defined($properties); + +my $num_props = scalar(@$properties); +die "only up to 255 properties supported" if $num_props > 255; +my $bindata = pack "VC", $vmid // 0, $num_props; +for my $property (@$properties) { + $bindata .= pack "Z*", $property; +} +my $res = $ipcc_send_rec_json->(CFS_IPC_GET_GUEST_CONFIG_PROPERTIES, $bindata); + +return $res; +} + # property: a config property you want to get, e.g., this is perfect to get # the 'lock' entry of a guest _fast_ (>100 faster than manual parsing here) # vmid: optional, if a valid is passed we only check that one, else return all # NOTE: does *not* searches snapshot and PENDING entries sections! +# NOTE: returns the guest config lines (excluding trailing whitespace) as is, +# so for non-trivial properties, checking the validity must be done +# NOTE: no permission check is done, that is the responsibilty of the caller sub get_guest_config_property { my ($property, $vmid) = @_; -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH manager v8 03/12] ui: call '/ui-options' and save the result in PVE.UIOptions
and move the use of the console from VersionInfo to here, since this will be the future place for ui related backend options. Signed-off-by: Dominik Csapak --- www/manager6/Utils.js | 2 +- www/manager6/Workspace.js | 13 + www/manager6/dc/OptionView.js | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 7ca6a271..3c8c1417 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1332,7 +1332,7 @@ Ext.define('PVE.Utils', { allowSpice = consoles.spice; allowXtermjs = !!consoles.xtermjs; } - let dv = PVE.VersionInfo.console || (type === 'kvm' ? 'vv' : 'xtermjs'); + let dv = PVE.UIOptions.console || (type === 'kvm' ? 'vv' : 'xtermjs'); if (dv === 'vv' && !allowSpice) { dv = allowXtermjs ? 'xtermjs' : 'html5'; } else if (dv === 'xtermjs' && !allowXtermjs) { diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js index 2bb502e0..7e17964e 100644 --- a/www/manager6/Workspace.js +++ b/www/manager6/Workspace.js @@ -158,6 +158,14 @@ Ext.define('PVE.StdWorkspace', { }, }); + Proxmox.Utils.API2Request({ + url: '/ui-options', + method: 'GET', + success: function(response) { + me.updateUIOptions(response.result.data); + }, + }); + Proxmox.Utils.API2Request({ url: '/cluster/sdn', method: 'GET', @@ -213,6 +221,11 @@ Ext.define('PVE.StdWorkspace', { ui.updateLayout(); }, +updateUIOptions: function(data) { + let me = this; + PVE.UIOptions = data ?? {}; +}, + initComponent: function() { let me = this; diff --git a/www/manager6/dc/OptionView.js b/www/manager6/dc/OptionView.js index 5a2be182..ff96351d 100644 --- a/www/manager6/dc/OptionView.js +++ b/www/manager6/dc/OptionView.js @@ -343,9 +343,9 @@ Ext.define('PVE.dc.OptionView', { } var rec = store.getById('console'); - PVE.VersionInfo.console = rec.data.value; + PVE.UIOptions.console = rec.data.value; if (rec.data.value === '__default__') { - delete PVE.VersionInfo.console; + delete PVE.UIOptions.console; } }); -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH cluster v8 3/4] datacenter.cfg: add option for tag-style
its a property string containing 'tree-shape' and 'colors' the colors are formatted like this: :[:] Signed-off-by: Dominik Csapak --- data/PVE/DataCenterConfig.pm | 37 1 file changed, 37 insertions(+) diff --git a/data/PVE/DataCenterConfig.pm b/data/PVE/DataCenterConfig.pm index 6a2adee..bb29d26 100644 --- a/data/PVE/DataCenterConfig.pm +++ b/data/PVE/DataCenterConfig.pm @@ -131,6 +131,29 @@ sub pve_verify_mac_prefix { return $mac_prefix; } +my $COLOR_RE = '[0-9a-fA-F]{6}'; +my $TAG_COLOR_OVERRIDE_RE = "(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)"; + +my $tag_style_format = { +'tree-shape' => { + optional => 1, + type => 'string', + enum => ['full', 'circle', 'dense', 'none'], + default => 'circle', + description => "Tag style in tree. 'full' draws the full tag. 'circle' ". + "draws only a circle with the background color. 'dense' only draws ". + "a small rectancle (useful when many tags are assigned to each guest).". + "'none' disables showing the tags.", +}, +'colors' => { + optional => 1, + type => 'string', + pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*", + typetext => ':[:][;=...]', + description => "Manual color mapping for tags (semicolon separated).", +}, +}; + my $datacenter_schema = { type => "object", additionalProperties => 0, @@ -256,6 +279,12 @@ my $datacenter_schema = { maxLength => 64 * 1024, optional => 1, }, + 'tag-style' => { + optional => 1, + type => 'string', + description => "Tag style options.", + format => $tag_style_format, + }, }, }; @@ -300,6 +329,10 @@ sub parse_datacenter_config { $res->{webauthn} = parse_property_string($webauthn_format, $webauthn); } +if (my $tag_style = $res->{'tag-style'}) { + $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style); +} + # for backwards compatibility only, new migration property has precedence if (defined($res->{migration_unsecure})) { if (defined($res->{migration}->{type})) { @@ -359,6 +392,10 @@ sub write_datacenter_config { $cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format); } +if (ref(my $tag_style = $cfg->{'tag-style'})) { + $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format); +} + my $comment = ''; # add description as comment to top of file my $description = $cfg->{description} || ''; -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH manager v8 05/12] ui: tree/ResourceTree: collect tags on update
into a global list, so that we have it avaiable anywhere also add the tags from the tagOverrides on update into the list Signed-off-by: Dominik Csapak --- www/manager6/Utils.js | 7 +++ www/manager6/data/ResourceStore.js | 6 ++ www/manager6/tree/ResourceTree.js | 16 ++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index e32c679a..bc808c68 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1804,6 +1804,13 @@ Ext.define('PVE.Utils', { notesTemplateVars: ['cluster', 'guestname', 'node', 'vmid'], +tagList: new Set(), + +updateTagList: function(tags) { + let override_tags = Object.keys(PVE.Utils.tagOverrides); + PVE.Utils.tagList = [...new Set([...tags, ...override_tags])].sort(); +}, + parseTagOverrides: function(overrides) { let colors = {}; (overrides || "").split(';').forEach(color => { diff --git a/www/manager6/data/ResourceStore.js b/www/manager6/data/ResourceStore.js index c7b72306..b18f7dd8 100644 --- a/www/manager6/data/ResourceStore.js +++ b/www/manager6/data/ResourceStore.js @@ -293,6 +293,12 @@ Ext.define('PVE.data.ResourceStore', { sortable: true, width: 100, }, + tags: { + header: gettext('Tags'), + type: 'string', + hidden: true, + sortable: true, + }, }; let fields = []; diff --git a/www/manager6/tree/ResourceTree.js b/www/manager6/tree/ResourceTree.js index be90d4f7..139defab 100644 --- a/www/manager6/tree/ResourceTree.js +++ b/www/manager6/tree/ResourceTree.js @@ -226,6 +226,10 @@ Ext.define('PVE.tree.ResourceTree', { let stateid = 'rid'; + const changedFields = [ + 'text', 'running', 'template', 'status', 'qmpstatus', 'hastate', 'lock', 'tags', + ]; + let updateTree = function() { store.suspendEvents(); @@ -261,7 +265,7 @@ Ext.define('PVE.tree.ResourceTree', { } // tree item has been updated - for (const field of ['text', 'running', 'template', 'status', 'qmpstatus', 'hastate', 'lock']) { + for (const field of changedFields) { if (item.data[field] !== olditem.data[field]) { changed = true; break; @@ -294,7 +298,14 @@ Ext.define('PVE.tree.ResourceTree', { } } - rstore.each(function(item) { // add new items + let tags = new Set(); + + rstore.each(function(item) { // add new items and collect tags + if (item.data.tags) { + item.data.tags.split(/[,; ]/).filter(t => !!t).forEach((tag) => { + tags.add(tag); + }); + } let olditem = index[item.data.id]; if (olditem) { return; @@ -310,6 +321,7 @@ Ext.define('PVE.tree.ResourceTree', { } }); + PVE.Utils.updateTagList(tags); store.resumeEvents(); store.fireEvent('refresh', store); -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH manager v8 11/12] ui: tree/ResourceTree: show Tags in tree
Signed-off-by: Dominik Csapak --- www/manager6/lxc/Config.js| 4 +++- www/manager6/qemu/Config.js | 4 +++- www/manager6/tree/ResourceTree.js | 4 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js index 9b3017ad..f3339051 100644 --- a/www/manager6/lxc/Config.js +++ b/www/manager6/lxc/Config.js @@ -206,8 +206,10 @@ Ext.define('PVE.lxc.Config', { }, }); + let vm_text = `${vm.vmid} (${vm.name})`; + Ext.apply(me, { - title: Ext.String.format(gettext("Container {0} on node '{1}'"), vm.text, nodename), + title: Ext.String.format(gettext("Container {0} on node '{1}'"), vm_text, nodename), hstateid: 'lxctab', tbarSpacing: false, tbar: [statusTxt, tagsContainer, '->', startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn], diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js index 2cd6d856..5c8fa620 100644 --- a/www/manager6/qemu/Config.js +++ b/www/manager6/qemu/Config.js @@ -242,8 +242,10 @@ Ext.define('PVE.qemu.Config', { }, }); + let vm_text = `${vm.vmid} (${vm.name})`; + Ext.apply(me, { - title: Ext.String.format(gettext("Virtual Machine {0} on node '{1}'"), vm.text, nodename), + title: Ext.String.format(gettext("Virtual Machine {0} on node '{1}'"), vm_text, nodename), hstateid: 'kvmtab', tbarSpacing: false, tbar: [statusTxt, tagsContainer, '->', resumeBtn, startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn], diff --git a/www/manager6/tree/ResourceTree.js b/www/manager6/tree/ResourceTree.js index 139defab..d41721b9 100644 --- a/www/manager6/tree/ResourceTree.js +++ b/www/manager6/tree/ResourceTree.js @@ -5,6 +5,8 @@ Ext.define('PVE.tree.ResourceTree', { extend: 'Ext.tree.TreePanel', alias: ['widget.pveResourceTree'], +userCls: 'proxmox-tags-circle', + statics: { typeDefaults: { node: { @@ -114,6 +116,8 @@ Ext.define('PVE.tree.ResourceTree', { } } + info.text += PVE.Utils.renderTags(info.tags, PVE.Utils.tagOverrides); + info.text = status + info.text; }, -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH widget-toolkit v8 1/2] add tag related helpers
helpers to * generate a color from a string consistently * generate a html tag for a tag * related css classes contrast is calculated according to SAPC draft: https://github.com/Myndex/SAPC-APCA which is likely to become a w3c guideline in the future and seems to be a better algorithm for this Signed-off-by: Dominik Csapak --- src/Utils.js | 88 src/css/ext6-pmx.css | 45 ++ 2 files changed, 133 insertions(+) diff --git a/src/Utils.js b/src/Utils.js index 6a03057..f491fd1 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -1272,6 +1272,94 @@ utilities: { .map(val => val.charCodeAt(0)), ); }, + +stringToRGB: function(string) { + let hash = 0; + if (!string) { + return hash; + } + string += 'prox'; // give short strings more variance + for (let i = 0; i < string.length; i++) { + hash = string.charCodeAt(i) + ((hash << 5) - hash); + hash = hash & hash; // to int + } + + let alpha = 0.7; // make the color a bit brighter + let bg = 255; // assume white background + + return [ + (hash & 255) * alpha + bg * (1 - alpha), + ((hash >> 8) & 255) * alpha + bg * (1 - alpha), + ((hash >> 16) & 255) * alpha + bg * (1 - alpha), + ]; +}, + +rgbToCss: function(rgb) { + return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`; +}, + +rgbToHex: function(rgb) { + let r = Math.round(rgb[0]).toString(16); + let g = Math.round(rgb[1]).toString(16); + let b = Math.round(rgb[2]).toString(16); + return `${r}${g}${b}`; +}, + +hexToRGB: function(hex) { + if (!hex) { + return undefined; + } + if (hex.length === 7) { + hex = hex.slice(1); + } + let r = parseInt(hex.slice(0, 2), 16); + let g = parseInt(hex.slice(2, 4), 16); + let b = parseInt(hex.slice(4, 6), 16); + return [r, g, b]; +}, + +// optimized & simplified SAPC function +// https://github.com/Myndex/SAPC-APCA +getTextContrastClass: function(rgb) { + const blkThrs = 0.022; + const blkClmp = 1.414; + + // linearize & gamma correction + let r = (rgb[0] / 255) ** 2.4; + let g = (rgb[1] / 255) ** 2.4; + let b = (rgb[2] / 255) ** 2.4; + + // relative luminance sRGB + let bg = r * 0.2126729 + g * 0.7151522 + b * 0.0721750; + + // black clamp + bg = bg > blkThrs ? bg : bg + (blkThrs - bg) ** blkClmp; + + // SAPC with white text + let contrastLight = bg ** 0.65 - 1; + // SAPC with black text + let contrastDark = bg ** 0.56 - 0.046134502; + + if (Math.abs(contrastLight) >= Math.abs(contrastDark)) { + return 'light'; + } else { + return 'dark'; + } +}, + +getTagElement: function(string, color_overrides) { + let rgb = color_overrides?.[string] || Proxmox.Utils.stringToRGB(string); + let style = `background-color: ${Proxmox.Utils.rgbToCss(rgb)};`; + let cls; + if (rgb.length > 3) { + style += `color: ${Proxmox.Utils.rgbToCss([rgb[3], rgb[4], rgb[5]])}`; + cls = "proxmox-tag-dark"; + } else { + let txtCls = Proxmox.Utils.getTextContrastClass(rgb); + cls = `proxmox-tag-${txtCls}`; + } + return `${string}`; +}, }, singleton: true, diff --git a/src/css/ext6-pmx.css b/src/css/ext6-pmx.css index 231b4ce..bb85ffb 100644 --- a/src/css/ext6-pmx.css +++ b/src/css/ext6-pmx.css @@ -6,6 +6,51 @@ background-color: LightYellow; } +.proxmox-tags-full .proxmox-tag-light, +.proxmox-tags-full .proxmox-tag-dark { +border-radius: 3px; +padding: 1px 6px; +margin: 0px 1px; +} + +.proxmox-tags-circle .proxmox-tag-light, +.proxmox-tags-circle .proxmox-tag-dark { +margin: 0px 1px; +position: relative; +top: 2px; +border-radius: 6px; +height: 12px; +width: 12px; +display: inline-block; +color: transparent !important; +overflow: hidden; +} + +.proxmox-tags-none .proxmox-tag-light, +.proxmox-tags-none .proxmox-tag-dark { +display: none; +} + +.proxmox-tags-dense .proxmox-tag-light, +.proxmox-tags-dense .proxmox-tag-dark { +width: 6px; +margin-right: 1px; +display: inline-block; +color: transparent !important; +overflow: hidden; +vertical-align: bottom; +} + +.proxmox-tags-full .proxmox-tag-light { +color: #fff; +background-color: #383838; +} + +.proxmox-tags-full .proxmox-tag-dark { +color: #000; +background-color: #f0f0f0; +} + .x-mask-msg-text { text-align: center; } -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH manager v8 08/12] ui: add form/Tag
displays a single tag, with the ability to edit inline on click (when the mode is set to editable). This brings up a list of globally available tags for simple selection. Signed-off-by: Dominik Csapak --- www/manager6/Makefile| 1 + www/manager6/form/Tag.js | 233 +++ 2 files changed, 234 insertions(+) create mode 100644 www/manager6/form/Tag.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 60ae421e..9d610f71 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -74,6 +74,7 @@ JSSRC= \ form/ViewSelector.js\ form/iScsiProviderSelector.js \ form/TagColorGrid.js\ + form/Tag.js \ grid/BackupView.js \ grid/FirewallAliases.js \ grid/FirewallOptions.js \ diff --git a/www/manager6/form/Tag.js b/www/manager6/form/Tag.js new file mode 100644 index ..aa6ae867 --- /dev/null +++ b/www/manager6/form/Tag.js @@ -0,0 +1,233 @@ +Ext.define('Proxmox.Tag', { +extend: 'Ext.Component', +alias: 'widget.pmxTag', + +mode: 'editable', + +icons: { + editable: 'fa fa-minus-square', + normal: '', + inEdit: 'fa fa-check-square', +}, + +tag: '', +cls: 'pve-edit-tag', + +tpl: [ + '', + '{tag}', + '', +], + +// we need to do this in mousedown, because that triggers before +// focusleave (which triggers before click) +onMouseDown: function(event) { + let me = this; + if (event.target.tagName !== 'I' || event.target.classList.contains('handle')) { + return; + } + switch (me.mode) { + case 'editable': + me.setVisible(false); + me.setTag(''); + break; + case 'inEdit': + me.setTag(me.tagEl().innerHTML); + me.setMode('editable'); + break; + default: break; + } +}, + +onClick: function(event) { + let me = this; + if (event.target.tagName !== 'SPAN' || me.mode !== 'editable') { + return; + } + me.setMode('inEdit'); + + // select text in the element + let tagEl = me.tagEl(); + tagEl.contentEditable = true; + let range = document.createRange(); + range.selectNodeContents(tagEl); + let sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + + me.showPicker(); +}, + +showPicker: function() { + let me = this; + if (!me.picker) { + me.picker = Ext.widget({ + xtype: 'boundlist', + minWidth: 70, + scrollable: true, + floating: true, + hidden: true, + userCls: 'proxmox-tags-full', + displayField: 'tag', + itemTpl: [ + '{[Proxmox.Utils.getTagElement(values.tag, PVE.Utils.tagOverrides)]}', + ], + store: [], + listeners: { + select: function(picker, rec) { + me.setTag(rec.data.tag); + me.setMode('editable'); + me.picker.hide(); + }, + }, + }); + } + me.picker.getStore()?.clearFilter(); + let taglist = PVE.Utils.tagList.map(v => ({ tag: v })); + if (taglist.length < 1) { + return; + } + me.picker.getStore().setData(taglist); + me.picker.showBy(me, 'tl-bl'); + me.picker.setMaxHeight(200); +}, + +setMode: function(mode) { + let me = this; + if (me.icons[mode] === undefined) { + throw "invalid mode"; + } + let tagEl = me.tagEl(); + if (tagEl) { + tagEl.contentEditable = mode === 'inEdit'; + } + me.removeCls(me.mode); + me.addCls(mode); + me.mode = mode; + me.updateData(); +}, + +onKeyPress: function(event) { + let me = this; + let key = event.browserEvent.key; + switch (key) { + case 'Enter': + if (me.tagEl().innerHTML !== '') { + me.setTag(me.tagEl().innerHTML); + me.setMode('editable'); + return; + } + break; + case 'Escape': + me.cancelEdit(); + return; + case 'Backspace': + case 'Delete': + return; + default: + if (key.match(PVE.Utils.tagCharRegex)) { + return; + } + } + event.browserEvent.preventDefault(); + event.browserEvent.stopPropagation(); +}, + +beforeInput: function(event) { +
[pve-devel] [PATCH container v8] check_ct_modify_config_perm: improve tag privilege check
'normal' tags require 'VM.Config.Options' on '/vm/', but not allowed tags (either limited with 'user-tag-privileges' or 'admin-tags' in the datacenter config) require 'Sys.Modify' on '/' this patch implements the proper checks on adding/editing/deleting these permissions Signed-off-by: Dominik Csapak --- src/PVE/LXC.pm | 39 +++ 1 file changed, 39 insertions(+) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index fe63087..c31c95c 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -1333,6 +1333,45 @@ sub check_ct_modify_config_perm { } elsif ($opt eq 'hookscript') { # For now this is restricted to root@pam raise_perm_exc("changing the hookscript is only allowed for root\@pam"); + } elsif ($opt eq 'tags') { + my $user_tags; + my $admin_tags; + + my $check_tag_perms = sub { + my ($tag) = @_; + if ($rpcenv->check($authuser, '/', ['Sys.Modify'], 1)) { + return; + } + + $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Options']); + + if (!defined($user_tags) && !defined($admin_tags)) { + ($user_tags, $admin_tags) = PVE::DataCenterConfig::get_user_admin_tags(); + } + + if ((defined($user_tags) && !$user_tags->{$tag}) || + (defined($admin_tags) && $admin_tags->{$tag}) + ) { + $rpcenv->check($authuser, '/', ['Sys.Modify']); + } + }; + + my $old_tags = {}; + my $new_tags = {}; + + map { $old_tags->{$_} += 1 } PVE::Tools::split_list($oldconf->{$opt} // ''); + map { $new_tags->{$_} += 1 } PVE::Tools::split_list($newconf->{$opt}); + + my $check_tags = sub { + my ($a, $b) = @_; + foreach my $tag (keys %$a) { + next if ($b->{$tag} // 0) == ($a->{$tag} // 0); + $check_tag_perms->($tag); + } + }; + + $check_tags->($old_tags, $new_tags); + $check_tags->($new_tags, $old_tags); } else { $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']); } -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH manager v8 09/12] ui: add form/TagEdit.js
this is a wrapper container for holding a list of (editable) tags intended to be used in the lxc/qemu status toolbar to add a new tag, we reuse the 'pmxTag' class, but overwrite some of its behaviour so that it properly adds tags Signed-off-by: Dominik Csapak --- www/manager6/Makefile| 1 + www/manager6/form/TagEdit.js | 316 +++ 2 files changed, 317 insertions(+) create mode 100644 www/manager6/form/TagEdit.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 9d610f71..eb4be4c5 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -75,6 +75,7 @@ JSSRC= \ form/iScsiProviderSelector.js \ form/TagColorGrid.js\ form/Tag.js \ + form/TagEdit.js \ grid/BackupView.js \ grid/FirewallAliases.js \ grid/FirewallOptions.js \ diff --git a/www/manager6/form/TagEdit.js b/www/manager6/form/TagEdit.js new file mode 100644 index ..1d832728 --- /dev/null +++ b/www/manager6/form/TagEdit.js @@ -0,0 +1,316 @@ +Ext.define('PVE.panel.TagEditContainer', { +extend: 'Ext.container.Container', +alias: 'widget.pveTagEditContainer', + +layout: { + type: 'hbox', + align: 'stretch', +}, + +controller: { + xclass: 'Ext.app.ViewController', + + loadTags: function(tagstring = '', force = false) { + let me = this; + let view = me.getView(); + + if (me.oldTags === tagstring && !force) { + return; + } + + view.suspendLayout = true; + me.forEachTag((tag) => { + view.remove(tag); + }); + me.getViewModel().set('tagCount', 0); + let newtags = tagstring.split(/[;, ]/).filter((t) => !!t) || []; + newtags.forEach((tag) => { + me.addTag(tag); + }); + view.suspendLayout = false; + view.updateLayout(); + if (!force) { + me.oldTags = tagstring; + } + }, + + onRender: function(v) { + let me = this; + let view = me.getView(); + view.dragzone = Ext.create('Ext.dd.DragZone', v.getEl(), { + getDragData: function(e) { + let source = e.getTarget('.handle'); + if (!source) { + return undefined; + } + let sourceId = source.parentNode.id; + let cmp = Ext.getCmp(sourceId); + let ddel = document.createElement('div'); + ddel.classList.add('proxmox-tags-full'); + ddel.innerHTML = Proxmox.Utils.getTagElement(cmp.tag, PVE.Utils.tagOverrides); + let repairXY = Ext.fly(source).getXY(); + cmp.setDisabled(true); + ddel.id = Ext.id(); + return { + ddel, + repairXY, + sourceId, + }; + }, + onMouseUp: function(target, e, id) { + let cmp = Ext.getCmp(this.dragData.sourceId); + if (cmp && !cmp.isDestroyed) { + cmp.setDisabled(false); + } + }, + getRepairXY: function() { + return this.dragData.repairXY; + }, + beforeInvalidDrop: function(target, e, id) { + let cmp = Ext.getCmp(this.dragData.sourceId); + if (cmp && !cmp.isDestroyed) { + cmp.setDisabled(false); + } + }, + }); + view.dropzone = Ext.create('Ext.dd.DropZone', v.getEl(), { + getTargetFromEvent: function(e) { + return e.getTarget('.proxmox-tag-dark,.proxmox-tag-light'); + }, + getIndicator: function() { + if (!view.indicator) { + view.indicator = Ext.create('Ext.Component', { + floating: true, + html: '', + hidden: true, + shadow: false, + }); + } + return view.indicator; + }, + onContainerOver: function() { + this.getIndicator().setVisible(false); + }, + notifyOut: function() { + this.getIndicator().setVisible(false); + }, + onNodeOver: function(target, dd, e, data) { + let indicator = this.getIndicator(); + indicator.setVisible(true
[pve-devel] [PATCH qemu-server v8] api: update: improve tag privilege check
'normal' tags require 'VM.Config.Options' on '/vm/', but not allowed tags (either limited with 'user-tag-privileges' or 'admin-tags' in the datacenter config) require 'Sys.Modify' on '/' this patch implements the proper checks on adding/editing/deleting these permissions Signed-off-by: Dominik Csapak --- PVE/API2/Qemu.pm | 51 +++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 3ec31c2..27fbdcc 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -538,7 +538,6 @@ my $generaloptions = { 'startup' => 1, 'tdf' => 1, 'template' => 1, -'tags' => 1, }; my $vmpoweroptions = { @@ -608,6 +607,7 @@ my $check_vm_modify_config_perm = sub { next if PVE::QemuServer::is_valid_drivename($opt); next if $opt eq 'cdrom'; next if $opt =~ m/^(?:unused|serial|usb)\d+$/; + next if $opt eq 'tags'; if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) { @@ -1520,6 +1520,29 @@ my $update_vm_api = sub { } }; + my $user_tags; + my $admin_tags; + + my $check_tag_perms = sub { + my ($tag) = @_; + + if ($rpcenv->check($authuser, '/', ['Sys.Modify'], 1)) { + return; + } + + $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Options']); + + if (!defined($user_tags) && !defined($admin_tags)) { + ($user_tags, $admin_tags) = PVE::DataCenterConfig::get_user_admin_tags(); + } + + if ((defined($user_tags) && !$user_tags->{$tag}) || + (defined($admin_tags) && $admin_tags->{$tag}) + ) { + $rpcenv->check($authuser, '/', ['Sys.Modify']); + } + }; + foreach my $opt (@delete) { $modified->{$opt} = 1; $conf = PVE::QemuConfig->load_config($vmid); # update/reload @@ -1578,6 +1601,13 @@ my $update_vm_api = sub { } PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); + } elsif ($opt eq 'tags') { + foreach my $tag (PVE::Tools::split_list($val)) { + check_tag_perms->($tag); + } + + delete $conf->{$opt}; + PVE::QemuConfig->write_config($vmid, $conf); } else { PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); @@ -1637,6 +1667,25 @@ my $update_vm_api = sub { } elsif ($authuser ne 'root@pam') { die "only root can modify '$opt' config for real devices\n"; } + $conf->{pending}->{$opt} = $param->{$opt}; + } elsif ($opt eq 'tags') { + my $old_tags = {}; + my $new_tags = {}; + + map { $old_tags->{$_} += 1 } PVE::Tools::split_list($conf->{$opt} // ''); + map { $new_tags->{$_} += 1 } PVE::Tools::split_list($param->{$opt}); + + my $check_tags = sub { + my ($a, $b) = @_; + foreach my $tag (keys %$a) { + next if ($b->{$tag} // 0) == ($a->{$tag} // 0); + $check_tag_perms->($tag); + } + }; + + $check_tags->($old_tags, $new_tags); + $check_tags->($new_tags, $old_tags); + $conf->{pending}->{$opt} = $param->{$opt}; } else { $conf->{pending}->{$opt} = $param->{$opt}; -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH cluster v8 4/4] DataCenterConfig: add tag rights control to the datacenter config
by adding a 'user-tag-privileges' and 'admin-tags' option. The first sets the policy by which "normal" users (with 'VM.Config.Options' on the respective guest) can create/delete tags and the second is a list of tags only settable by 'admins' ('Sys.Modify' on '/') also add a helper 'get_user_admin_tags' that returns two hashmaps that determines the allowed user tags and admin tags that require elevated permissions Signed-off-by: Dominik Csapak --- data/PVE/DataCenterConfig.pm | 93 1 file changed, 93 insertions(+) diff --git a/data/PVE/DataCenterConfig.pm b/data/PVE/DataCenterConfig.pm index bb29d26..e2140ff 100644 --- a/data/PVE/DataCenterConfig.pm +++ b/data/PVE/DataCenterConfig.pm @@ -154,6 +154,26 @@ my $tag_style_format = { }, }; +my $user_tag_privs_format = { +'usable' => { + optional => 1, + type => 'string', + enum => ['none', 'list', 'existing', 'free'], + default => 'free', + dscription => "Determines which tags a user without Sys.Modify on '/' can set and delete. ". + "'none' means no tags are settable.'list' allows tags from the given list. ". + "'existing' means only already existing tags or from the given list. ". + "And 'free' means users can assign any tags." +}, +'list' => { + optional => 1, + type => 'string', + pattern => "${PVE::JSONSchema::PVE_TAG_RE}(?:\;${PVE::JSONSchema::PVE_TAG_RE})*", + typetext => "[;=...]", + description => "List of tags users are allowd to set and delete (semicolon separated).", +}, +}; + my $datacenter_schema = { type => "object", additionalProperties => 0, @@ -285,12 +305,60 @@ my $datacenter_schema = { description => "Tag style options.", format => $tag_style_format, }, + 'user-tag-privileges' => { + optional => 1, + type => 'string', + description => "Privilege options for user settable tags", + format => $user_tag_privs_format, + }, + 'admin-tags' => { + optional => 1, + type => 'string', + description => "A list of tags only admins (Sys.Modify on '/') are allowed to set/delete", + pattern => "(?:${PVE::JSONSchema::PVE_TAG_RE};)*${PVE::JSONSchema::PVE_TAG_RE}", + }, }, }; # make schema accessible from outside (for documentation) sub get_datacenter_schema { return $datacenter_schema }; +# returns two hashmaps of tags, the first is the list of tags that can +# be used by users with 'VM.Config.Options', and the second is a list +# that needs 'Sys.Modify' on '/' +# +# If the first map is 'undef', it means there is generally no restriction +# besides the tags defined in the second map. +# +# CAUTION: this function may include tags from *all* guest configs, +# regardless of the current authuser +sub get_user_admin_tags { +my $user_tags = {}; +my $admin_tags = {}; + +my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg'); +if (my $user_tag_privs = $dc->{'user-tag-privileges'}) { + my $usable = $user_tag_privs->{usable} // 'free'; + if ($usable eq 'free') { + $user_tags = undef; + } elsif ($usable eq 'existing') { + map { $user_tags->{$_} = 1 } ($user_tag_privs->{list} // [])->@*; + my $props = PVE::Cluster::get_guest_config_properties(['tags']); + for my $vmid (keys $props->%*) { + map { $user_tags->{$_} = 1 } PVE::Tools::split_list($props->{$vmid}->{tags}); + } + } elsif ($usable eq 'list') { + map { $user_tags->{$_} = 1 } ($user_tag_privs->{list} // [])->@*; + } +} +if (my $tags = $dc->{'admin-tags'}) { + $admin_tags = {}; + map { $admin_tags->{$_} = 1 } $tags->@*; +} + +return ($user_tags, $admin_tags); +} + sub parse_datacenter_config { my ($filename, $raw) = @_; @@ -333,6 +401,19 @@ sub parse_datacenter_config { $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style); } +if (my $user_tag_privs = $res->{'user-tag-privileges'}) { + $res->{'user-tag-privileges'} = + parse_property_string($user_tag_privs_format, $user_tag_privs); + + if (my $user_tags = $res->{'user-tag-privileges'}->{list}) { + $res->{'user-tag-privileges'}->{list} = [split(';', $user_tags)]; + } +} + +if (my $admin_tags = $res->{'admin-tags'}) { + $res->{'admin-tags'} = [split(';', $admin_tags)]; +} + # for backwards compatibility only, new migration property has precedence if (defined($res->{migration_unsecure})) { if (defined($res->{migration}->{type})) { @@ -396,6 +477,18 @@ sub write_datacenter_config { $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format); } +if (ref(my $user_tag_privs = $cfg->{'user-tag-privileges'})) { + if (my $us
[pve-devel] [PATCH manager v8 02/12] api: add /ui-options api call
which contains ui relevant options, like the console preference and tag-style Signed-off-by: Dominik Csapak --- PVE/API2.pm | 43 +++ 1 file changed, 43 insertions(+) diff --git a/PVE/API2.pm b/PVE/API2.pm index a4256160..2acdecdb 100644 --- a/PVE/API2.pm +++ b/PVE/API2.pm @@ -118,6 +118,7 @@ __PACKAGE__->register_method ({ my $res = {}; + # TODO remove with next major release my $datacenter_confg = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {}; for my $k (qw(console)) { $res->{$k} = $datacenter_confg->{$k} if exists $datacenter_confg->{$k}; @@ -130,4 +131,46 @@ __PACKAGE__->register_method ({ return $res; }}); +__PACKAGE__->register_method ({ +name => 'ui-options', +path => 'ui-options', +method => 'GET', +permissions => { user => 'all' }, +description => "Global options regarding the UI.", +parameters => { + additionalProperties => 0, + properties => {}, +}, +returns => { + type => "object", + properties => { + console => { + type => 'string', + enum => ['applet', 'vv', 'html5', 'xtermjs'], + optional => 1, + description => 'The default console viewer to use.', + }, + 'tag-style' => { + type => 'string', + optional => 1, + description => 'Cluster wide tag style overrides', + }, + }, +}, +code => sub { + my ($param) = @_; + + my $res = {}; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $datacenter_confg = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {}; + for my $k (qw(console tag-style)) { + $res->{$k} = $datacenter_confg->{$k} if exists $datacenter_confg->{$k}; + } + + return $res; +}}); + 1; -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH manager v8 01/12] api: /cluster/resources: add tags to returned properties
by querying 'lock' and 'tags' with 'get_guest_config_properties' Signed-off-by: Dominik Csapak --- PVE/API2/Cluster.pm | 9 ++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/PVE/API2/Cluster.pm b/PVE/API2/Cluster.pm index d6b405e2..bc327e76 100644 --- a/PVE/API2/Cluster.pm +++ b/PVE/API2/Cluster.pm @@ -360,7 +360,8 @@ __PACKAGE__->register_method({ # we try to generate 'numbers' by using "$X + 0" if (!$param->{type} || $param->{type} eq 'vm') { - my $locked_vms = PVE::Cluster::get_guest_config_property('lock'); + my $prop_list = [qw(lock tags)]; + my $props = PVE::Cluster::get_guest_config_properties($prop_list); for my $vmid (sort keys %$idlist) { @@ -392,8 +393,10 @@ __PACKAGE__->register_method({ # only skip now to next to ensure that the pool stats above are filled, if eligible next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1); - if (defined(my $lock = $locked_vms->{$vmid}->{lock})) { - $entry->{lock} = $lock; + for my $prop (@$prop_list) { + if (defined(my $value = $props->{$vmid}->{$prop})) { + $entry->{$prop} = $value; + } } if (defined($entry->{pool}) && -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH cluster/qemu-server/container/wt/manager v8] add tags to ui
this series brings the already existing 'tags' for ct/vms to the gui: * tags can be edited in the status toolbar of the guest * existing tags will be shown in the tree/global search/resource grids * when editing a tag, a list of existing tags will be shown * by default, the color is (consistently) autogenerated based on the text * that color can be overriden in datacenter -> options (cluster wide) (edit window for browser local storage is TBD) * by default, the text color is either white or black, depending which provides the greater contrast (according to SAPC) * this text color can also be overridden * there are multiple shapes available for the tree * implements some permission control in datacenter.cfg with 'user-tag-privileges' and 'admin-tags' with that it's possible to have better control over what tags a user can actually add to its guests i intentionally left out the gui for those for now, but they shouldn't be that hard to add, should we go this way changes from v7: * rebase on master * changed admin tags from special syntax to datacenter.cfg option * add 'user-tag-privleges' option and according api permission checks * fixed some small bugs with permission checks (e.g. now we check the tag count, so that users cannot add tags multiple times that already exist but they have no privileges for) * completely reworked the form/Tag and form/TagEdit, their implmementation is now much cleaner imho (squashed the drag&drop changes into the intial patches) * squashed some patches together that fit together * fixed some drag&drop bugs, so it should now work much better changes from v6: * reworded some commit messages * added small benchmark result to CFS_IPC_GET_GUEST_CONFIG_PROPERTIES commit msg * reshaped datacenter.cfg format into a property-string (also combined the gui edit window for shape+color override) * refactored the pve-tags regex in pve-common/JSONSchema * added admin tags ('+tag' syntax) with priv checks in qemu-sever/pve-container and subtle highlighting in the gui (bold-text) * added tag rendering in resource grids * add patches for drag&drop support when editing pve-cluster: Dominik Csapak (4): add CFS_IPC_GET_GUEST_CONFIG_PROPERTIES method Cluster: add get_guest_config_properties datacenter.cfg: add option for tag-style DataCenterConfig: add tag rights control to the datacenter config data/PVE/Cluster.pm | 27 ++ data/PVE/DataCenterConfig.pm | 130 + data/src/cfs-ipc-ops.h | 2 + data/src/server.c| 64 + data/src/status.c| 177 --- data/src/status.h| 3 + 6 files changed, 347 insertions(+), 56 deletions(-) qemu-server: Dominik Csapak (1): api: update: improve tag privilege check PVE/API2/Qemu.pm | 51 +++- 1 file changed, 50 insertions(+), 1 deletion(-) pve-container: Dominik Csapak (1): check_ct_modify_config_perm: improve tag privilege check src/PVE/LXC.pm | 39 +++ 1 file changed, 39 insertions(+) proxmox-widget-toolkit: Dominik Csapak (2): add tag related helpers Toolkit: add override for Ext.dd.DragDropManager src/Toolkit.js | 16 src/Utils.js | 88 src/css/ext6-pmx.css | 45 ++ 3 files changed, 149 insertions(+) pve-manager: Dominik Csapak (12): api: /cluster/resources: add tags to returned properties api: add /ui-options api call ui: call '/ui-options' and save the result in PVE.UIOptions ui: parse and save tag color overrides from /ui-options ui: tree/ResourceTree: collect tags on update ui: add form/TagColorGrid ui: dc/OptionView: add editors for tag settings ui: add form/Tag ui: add form/TagEdit.js ui: {lxc,qemu}/Config: show Tags and make them editable ui: tree/ResourceTree: show Tags in tree ui: add tags to ResourceGrid and GlobalSearchField PVE/API2.pm| 43 +++ PVE/API2/Cluster.pm| 9 +- www/css/ext6-pve.css | 5 + www/manager6/Makefile | 3 + www/manager6/Utils.js | 71 - www/manager6/Workspace.js | 22 ++ www/manager6/data/ResourceStore.js | 7 + www/manager6/dc/OptionView.js | 88 +- www/manager6/form/GlobalSearchField.js | 20 +- www/manager6/form/Tag.js | 233 www/manager6/form/TagColorGrid.js | 357 + www/manager6/form/TagEdit.js | 316 ++ www/manager6/grid/ResourceGrid.js | 1 + www/manager6/lxc/Config.js | 36 ++- www/manager6/qemu/Config.js| 35 ++- www/manager6/tree/ResourceTree.js | 20 +- 16 files changed, 1247 insertions(+), 19 deletions(-) create mode 100644 www/manager6/form/Tag.js create mode 100644 www/manager
[pve-devel] [PATCH manager v8 04/12] ui: parse and save tag color overrides from /ui-options
into a global list of overrides. on update, also parse the values from the browser localstore. Also emits a GlobalEvent 'loadedUiOptions' so that e.g. the tags can listen to that and refresh their colors Signed-off-by: Dominik Csapak --- www/manager6/Utils.js | 40 +++ www/manager6/Workspace.js | 9 + 2 files changed, 49 insertions(+) diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 3c8c1417..e32c679a 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1803,6 +1803,46 @@ Ext.define('PVE.Utils', { }, notesTemplateVars: ['cluster', 'guestname', 'node', 'vmid'], + +parseTagOverrides: function(overrides) { + let colors = {}; + (overrides || "").split(';').forEach(color => { + if (!color) { + return; + } + let [tag, color_hex, font_hex] = color.split(':'); + let r = parseInt(color_hex.slice(0, 2), 16); + let g = parseInt(color_hex.slice(2, 4), 16); + let b = parseInt(color_hex.slice(4, 6), 16); + colors[tag] = [r, g, b]; + if (font_hex) { + colors[tag].push(parseInt(font_hex.slice(0, 2), 16)); + colors[tag].push(parseInt(font_hex.slice(2, 4), 16)); + colors[tag].push(parseInt(font_hex.slice(4, 6), 16)); + } + }); + return colors; +}, + +tagOverrides: {}, + +updateTagOverrides: function(colors) { + let sp = Ext.state.Manager.getProvider(); + let color_state = sp.get('colors', ''); + let browser_colors = PVE.Utils.parseTagOverrides(color_state); + PVE.Utils.tagOverrides = Ext.apply({}, browser_colors, colors); + Ext.GlobalEvents.fireEvent('tag-color-override'); +}, + +updateTagSettings: function(overrides, style) { + PVE.Utils.updateTagOverrides(PVE.Utils.parseTagOverrides(overrides ?? "")); + + if (style === undefined || style === '__default__') { + style = 'circle'; + } + + Ext.ComponentQuery.query('pveResourceTree')[0].setUserCls(`proxmox-tags-${style}`); +}, }, singleton: true, diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js index 7e17964e..616f127f 100644 --- a/www/manager6/Workspace.js +++ b/www/manager6/Workspace.js @@ -224,6 +224,15 @@ Ext.define('PVE.StdWorkspace', { updateUIOptions: function(data) { let me = this; PVE.UIOptions = data ?? {}; + let colors = data?.['tag-style']?.colors; + let shape = data?.['tag-style']?.['tree-shape']; + + PVE.Utils.updateTagSettings(colors, shape); + if (colors) { + // refresh tree once + PVE.data.ResourceStore.fireEvent('load'); + Ext.GlobalEvents.fireEvent('loadedUiOptions'); + } }, initComponent: function() { -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH manager v8 07/12] ui: dc/OptionView: add editors for tag settings
namely for 'tag-tree-style' and 'tag-colors'. display the tag overrides directly as they will appear as tags Signed-off-by: Dominik Csapak --- www/manager6/Utils.js | 20 + www/manager6/dc/OptionView.js | 84 +++ 2 files changed, 104 insertions(+) diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index ba276ebe..c2a98523 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1851,6 +1851,26 @@ Ext.define('PVE.Utils', { Ext.ComponentQuery.query('pveResourceTree')[0].setUserCls(`proxmox-tags-${style}`); }, +tagTreeStyles: { + '__default__': Proxmox.Utils.defaultText, + 'full': gettext('Full'), + 'circle': gettext('Circle'), + 'dense': gettext('Dense'), + 'none': Proxmox.Utils.NoneText, +}, + +renderTags: function(tagstext, overrides) { + let text = ''; + if (tagstext) { + let tags = (tagstext.split(/[,; ]/) || []).filter(t => !!t); + text += ' '; + tags.forEach((tag) => { + text += Proxmox.Utils.getTagElement(tag, overrides); + }); + } + return text; +}, + tagCharRegex: /^[a-z0-9+_.-]$/i, }, diff --git a/www/manager6/dc/OptionView.js b/www/manager6/dc/OptionView.js index ff96351d..cb1b1dc7 100644 --- a/www/manager6/dc/OptionView.js +++ b/www/manager6/dc/OptionView.js @@ -5,6 +5,7 @@ Ext.define('PVE.dc.OptionView', { onlineHelp: 'datacenter_configuration_file', monStoreErrors: true, +userCls: 'proxmox-tags-full', add_inputpanel_row: function(name, text, opts) { var me = this; @@ -312,6 +313,86 @@ Ext.define('PVE.dc.OptionView', { submitValue: true, }], }); + me.rows['tag-style'] = { + required: true, + renderer: (value) => { + if (value === undefined) { + return gettext('No Overrides'); + } + let colors = PVE.Utils.parseTagOverrides(value.colors); + let shape = value['tree-shape']; + let shapeText = PVE.Utils.tagTreeStyles[shape] ?? Proxmox.Utils.defaultText; + let txt = Ext.String.format(gettext("Tree Shape: {0}"), shapeText); + if (Object.keys(colors).length > 0) { + txt += ', '; + } + for (const tag of Object.keys(colors)) { + txt += Proxmox.Utils.getTagElement(tag, colors); + } + return txt; + }, + header: gettext('Tag Style Override'), + editor: { + xtype: 'proxmoxWindowEdit', + width: 800, + subject: gettext('Tag Color Override'), + fieldDefaults: { + labelWidth: 100, + }, + url: '/api2/extjs/cluster/options', + items: [ + { + xtype: 'inputpanel', + setValues: function(values) { + if (values === undefined) { + return undefined; + } + values = values?.['tag-style'] ?? {}; + values['tree-shape'] = values?.['tree-shape'] || '__default__'; + return Proxmox.panel.InputPanel.prototype.setValues.call(this, values); + }, + onGetValues: function(values) { + let style = {}; + if (values.colors) { + style.colors = values.colors; + } + if (values['tree-shape']) { + style['tree-shape'] = values['tree-shape']; + } + let value = PVE.Parser.printPropertyString(style); + if (value === '') { + return { + 'delete': 'tag-style', + }; + } + return { + 'tag-style': value, + }; + }, + items: [ + { + name: 'tree-shape', + xtype: 'proxmoxKVComboBox', + fieldLabel: gettext('Tree Shape'), + comboItems: Object.entries(PVE.Utils.tagTreeStyles), + defaultValue: '__default__', + deleteEmpty: true, + }, + { + xtype: 'displayfield', + fieldLabel: gettext('Color Overrides'), + }, +
[pve-devel] [PATCH cluster v8 1/4] add CFS_IPC_GET_GUEST_CONFIG_PROPERTIES method
for getting multiple properties from the in memory config of the guests in one go. Keep the existing IPC call as is for backward compatibility and add this as separate, new one. It basically behaves the same as CFS_IPC_GET_GUEST_CONFIG_PROPERTY, but takes a list of properties instead and returns multiple properties per guest. The old way of getting a single property is now also done by the new function, since it basically performs identically. Here a short benchmark: Setup: PVE in a VM with cpu type host (12700k) and 4 cores 1 typical configs with both 'lock' and 'tags' set at the end and fairly long tags ('test-tag1,test-tag2,test-tag3') (normal vm with a snapshot, ~1KiB) i let it run 100 times each, times in ms old code: total time time per iteration 1054.2 10.2 new code: num props total time time per iter function 2 1332.2 13.2 get_properties 1 1051.2 10.2 get_properties 2 2082.2 20.2 get_property (2 calls to get_property) 1 1034.2 10.2 get_property so a call with the new code for one property is the same as with the old code, and adding a second property only adds a bit of additional time (in this case ~30%) Signed-off-by: Dominik Csapak --- data/src/cfs-ipc-ops.h | 2 + data/src/server.c | 64 +++ data/src/status.c | 177 - data/src/status.h | 3 + 4 files changed, 190 insertions(+), 56 deletions(-) diff --git a/data/src/cfs-ipc-ops.h b/data/src/cfs-ipc-ops.h index 003e233..249308d 100644 --- a/data/src/cfs-ipc-ops.h +++ b/data/src/cfs-ipc-ops.h @@ -43,4 +43,6 @@ #define CFS_IPC_VERIFY_TOKEN 12 +#define CFS_IPC_GET_GUEST_CONFIG_PROPERTIES 13 + #endif diff --git a/data/src/server.c b/data/src/server.c index 549788a..ced9cfc 100644 --- a/data/src/server.c +++ b/data/src/server.c @@ -89,6 +89,13 @@ typedef struct { char property[]; } cfs_guest_config_propery_get_request_header_t; +typedef struct { + struct qb_ipc_request_header req_header; + uint32_t vmid; + uint8_t num_props; + char props[]; /* list of \0 terminated properties */ +} cfs_guest_config_properties_get_request_header_t; + typedef struct { struct qb_ipc_request_header req_header; char token[]; @@ -348,6 +355,63 @@ static int32_t s1_msg_process_fn( result = cfs_create_guest_conf_property_msg(outbuf, memdb, rh->property, rh->vmid); } + } else if (request_id == CFS_IPC_GET_GUEST_CONFIG_PROPERTIES) { + + cfs_guest_config_properties_get_request_header_t *rh = + (cfs_guest_config_properties_get_request_header_t *) data; + + size_t remaining = request_size - G_STRUCT_OFFSET(cfs_guest_config_properties_get_request_header_t, props); + + result = 0; + if (rh->vmid < 100 && rh->vmid != 0) { + cfs_debug("vmid out of range %u", rh->vmid); + result = -EINVAL; + } else if (rh->vmid >= 100 && !vmlist_vm_exists(rh->vmid)) { + result = -ENOENT; + } else if (rh->num_props == 0) { + cfs_debug("num_props == 0"); + result = -EINVAL; + } else if (remaining <= 1) { + cfs_debug("property length <= 1, %ld", remaining); + result = -EINVAL; + } else { + const char **properties = malloc(sizeof(char*) * rh->num_props); + char *current = (rh->props); + for (uint8_t i = 0; i < rh->num_props; i++) { + size_t proplen = strnlen(current, remaining); + if (proplen == 0) { + cfs_debug("property length 0"); + result = -EINVAL; + break; + } + if (proplen == remaining) { + cfs_debug("property not \\0 terminated"); + result = -EINVAL; + break; + } + if (current[0] < 'a' || current[0] > 'z') { + cfs_debug("property does not start with [a-z]"); + result = -EINVAL; + break; + } + properties[i] = current; + current[proplen] = '\0'; // ensure property is 0 terminated + remaining -= (proplen + 1); + current += proplen + 1; + } + + if (remaining != 0) { + cfs_debug("leftover data after parsing %u properties", rh->num_props); +
[pve-devel] [PATCH manager v8 06/12] ui: add form/TagColorGrid
this provides a basic grid to edit a list of tag color overrides. We'll use this for editing the datacenter.cfg overrides and the browser storage overrides. Signed-off-by: Dominik Csapak --- www/css/ext6-pve.css | 5 + www/manager6/Makefile | 1 + www/manager6/Utils.js | 2 + www/manager6/form/TagColorGrid.js | 357 ++ 4 files changed, 365 insertions(+) create mode 100644 www/manager6/form/TagColorGrid.js diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css index dadb84a9..f7d0c420 100644 --- a/www/css/ext6-pve.css +++ b/www/css/ext6-pve.css @@ -651,3 +651,8 @@ table.osds td:first-of-type { background-color: rgb(245, 245, 245); color: #000; } + +.x-pveColorPicker-default-cell > .x-grid-cell-inner { +padding-top: 0px; +padding-bottom: 0px; +} diff --git a/www/manager6/Makefile b/www/manager6/Makefile index d16770b1..60ae421e 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -73,6 +73,7 @@ JSSRC= \ form/VNCKeyboardSelector.js \ form/ViewSelector.js\ form/iScsiProviderSelector.js \ + form/TagColorGrid.js\ grid/BackupView.js \ grid/FirewallAliases.js \ grid/FirewallOptions.js \ diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index bc808c68..ba276ebe 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1850,6 +1850,8 @@ Ext.define('PVE.Utils', { Ext.ComponentQuery.query('pveResourceTree')[0].setUserCls(`proxmox-tags-${style}`); }, + +tagCharRegex: /^[a-z0-9+_.-]$/i, }, singleton: true, diff --git a/www/manager6/form/TagColorGrid.js b/www/manager6/form/TagColorGrid.js new file mode 100644 index ..3ad8e07f --- /dev/null +++ b/www/manager6/form/TagColorGrid.js @@ -0,0 +1,357 @@ +Ext.define('PVE.form.ColorPicker', { +extend: 'Ext.form.FieldContainer', +alias: 'widget.pveColorPicker', + +defaultBindProperty: 'value', + +config: { + value: null, +}, + +height: 24, + +layout: { + type: 'hbox', + align: 'stretch', +}, + +getValue: function() { + return this.realvalue.slice(1); +}, + +setValue: function(value) { + let me = this; + me.setColor(value); + if (value && value.length === 6) { + me.picker.value = value[0] !== '#' ? `#${value}` : value; + } +}, + +setColor: function(value) { + let me = this; + let oldValue = me.realvalue; + me.realvalue = value; + let color = value.length === 6 ? `#${value}` : undefined; + me.down('#picker').setStyle('background-color', color); + me.down('#text').setValue(value ?? ""); + me.fireEvent('change', me, me.realvalue, oldValue); +}, + +initComponent: function() { + let me = this; + me.picker = document.createElement('input'); + me.picker.type = 'color'; + me.picker.style = `opacity: 0; border: 0px; width: 100%; height: ${me.height}px`; + me.picker.value = `${me.value}`; + + me.items = [ + { + xtype: 'textfield', + itemId: 'text', + minLength: !me.allowBlank ? 6 : undefined, + maxLength: 6, + enforceMaxLength: true, + allowBlank: me.allowBlank, + emptyText: me.allowBlank ? gettext('Automatic') : undefined, + maskRe: /[a-f0-9]/i, + regex: /^[a-f0-9]{6}$/i, + flex: 1, + listeners: { + change: function(field, value) { + me.setValue(value); + }, + }, + }, + { + xtype: 'box', + style: { + 'margin-left': '1px', + border: '1px solid #cfcfcf', + }, + itemId: 'picker', + width: 24, + contentEl: me.picker, + }, + ]; + + me.callParent(); + me.picker.oninput = function() { + me.setColor(me.picker.value.slice(1)); + }; +}, +}); + +Ext.define('PVE.form.TagColorGrid', { +extend: 'Ext.grid.Panel', +alias: 'widget.pveTagColorGrid', + +mixins: [ + 'Ext.form.field.Field', +], + +allowBlank: true, +selectAll: false, +isFormField: true, +deleteEmpty: false, +selModel: 'checkboxmodel', + +config: { + deleteEmpty: false, +}, + +emptyText: gettext('No Overrides'), +viewConfig: { + deferEmptyText: false, +}, + +setValue: function(value) { + let me = this; + let colors; + if (Ext.isObject(value)) { + colors = value.colors; + } else { +
[pve-devel] [PATCH manager v8 10/12] ui: {lxc, qemu}/Config: show Tags and make them editable
add the tags in the status line, and add a button for adding new ones Signed-off-by: Dominik Csapak --- www/manager6/lxc/Config.js | 32 ++-- www/manager6/qemu/Config.js | 31 +-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js index 93f385db..9b3017ad 100644 --- a/www/manager6/lxc/Config.js +++ b/www/manager6/lxc/Config.js @@ -4,6 +4,8 @@ Ext.define('PVE.lxc.Config', { onlineHelp: 'chapter_pct', +userCls: 'proxmox-tags-full', + initComponent: function() { var me = this; var vm = me.pveSelNode.data; @@ -182,12 +184,33 @@ Ext.define('PVE.lxc.Config', { ], }); + let tagsContainer = Ext.create('PVE.panel.TagEditContainer', { + tags: vm.tags, + listeners: { + change: function(tags) { + Proxmox.Utils.API2Request({ + url: base_url + '/config', + method: 'PUT', + params: { + tags, + }, + success: function() { + me.statusStore.load(); + }, + failure: function(response) { + Ext.Msg.alert('Error', response.htmlStatus); + me.statusStore.load(); + }, + }); + }, + }, + }); Ext.apply(me, { title: Ext.String.format(gettext("Container {0} on node '{1}'"), vm.text, nodename), hstateid: 'lxctab', tbarSpacing: false, - tbar: [statusTxt, '->', startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn], + tbar: [statusTxt, tagsContainer, '->', startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn], defaults: { statusStore: me.statusStore }, items: [ { @@ -344,10 +367,12 @@ Ext.define('PVE.lxc.Config', { me.mon(me.statusStore, 'load', function(s, records, success) { var status; var lock; + var rec; + if (!success) { status = 'unknown'; } else { - var rec = s.data.get('status'); + rec = s.data.get('status'); status = rec ? rec.data.value : 'unknown'; rec = s.data.get('template'); template = rec ? rec.data.value : false; @@ -357,6 +382,9 @@ Ext.define('PVE.lxc.Config', { statusTxt.update({ lock: lock }); + rec = s.data.get('tags'); + tagsContainer.loadTags(rec?.data?.value); + startBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'running' || template); shutdownBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running'); me.down('#removeBtn').setDisabled(!caps.vms['VM.Allocate'] || status !== 'stopped'); diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js index 9fe933df..2cd6d856 100644 --- a/www/manager6/qemu/Config.js +++ b/www/manager6/qemu/Config.js @@ -3,6 +3,7 @@ Ext.define('PVE.qemu.Config', { alias: 'widget.PVE.qemu.Config', onlineHelp: 'chapter_virtual_machines', +userCls: 'proxmox-tags-full', initComponent: function() { var me = this; @@ -219,11 +220,33 @@ Ext.define('PVE.qemu.Config', { ], }); + let tagsContainer = Ext.create('PVE.panel.TagEditContainer', { + tags: vm.tags, + listeners: { + change: function(tags) { + Proxmox.Utils.API2Request({ + url: base_url + '/config', + method: 'PUT', + params: { + tags, + }, + success: function() { + me.statusStore.load(); + }, + failure: function(response) { + Ext.Msg.alert('Error', response.htmlStatus); + me.statusStore.load(); + }, + }); + }, + }, + }); + Ext.apply(me, { title: Ext.String.format(gettext("Virtual Machine {0} on node '{1}'"), vm.text, nodename), hstateid: 'kvmtab', tbarSpacing: false, - tbar: [statusTxt, '->', resumeBtn, startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn], + tbar: [statusTxt, tagsContainer, '->', resumeBtn, startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn], defaults: { statusStore: me.statusStore }, items: [ { @@ -382,11 +405,12 @@ Ext.define('PVE.qemu.Config', { var spice = false; var xtermjs = false; var lock; + var rec;
[pve-devel] [PATCH manager v8 12/12] ui: add tags to ResourceGrid and GlobalSearchField
also allows to search for tags in the GlobalSearchField where each tag is treated like a seperate field, so it weighs more if the user searches for the exact string of a single tag Signed-off-by: Dominik Csapak ui: ResourceGrid: render tags with the 'full' styling Signed-off-by: Dominik Csapak --- www/manager6/data/ResourceStore.js | 1 + www/manager6/form/GlobalSearchField.js | 20 +++- www/manager6/grid/ResourceGrid.js | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/www/manager6/data/ResourceStore.js b/www/manager6/data/ResourceStore.js index b18f7dd8..ed1f4699 100644 --- a/www/manager6/data/ResourceStore.js +++ b/www/manager6/data/ResourceStore.js @@ -295,6 +295,7 @@ Ext.define('PVE.data.ResourceStore', { }, tags: { header: gettext('Tags'), + renderer: (value) => PVE.Utils.renderTags(value, PVE.Utils.tagOverrides), type: 'string', hidden: true, sortable: true, diff --git a/www/manager6/form/GlobalSearchField.js b/www/manager6/form/GlobalSearchField.js index 267a480d..8e815d4f 100644 --- a/www/manager6/form/GlobalSearchField.js +++ b/www/manager6/form/GlobalSearchField.js @@ -15,6 +15,7 @@ Ext.define('PVE.form.GlobalSearchField', { grid: { xtype: 'gridpanel', + userCls: 'proxmox-tags-full', focusOnToFront: false, floating: true, emptyText: Proxmox.Utils.noneText, @@ -23,7 +24,7 @@ Ext.define('PVE.form.GlobalSearchField', { scrollable: { xtype: 'scroller', y: true, - x: false, + x: true, }, store: { model: 'PVEResources', @@ -78,6 +79,11 @@ Ext.define('PVE.form.GlobalSearchField', { text: gettext('Description'), flex: 1, dataIndex: 'text', + renderer: function(value, mD, rec) { + let overrides = PVE.Utils.tagOverrides; + let tags = PVE.Utils.renderTags(rec.data.tags, overrides); + return `${value}${tags}`; + }, }, { text: gettext('Node'), @@ -104,16 +110,20 @@ Ext.define('PVE.form.GlobalSearchField', { 'storage': ['type', 'pool', 'node', 'storage'], 'default': ['name', 'type', 'node', 'pool', 'vmid'], }; - let fieldArr = fieldMap[item.data.type] || fieldMap.default; + let fields = fieldMap[item.data.type] || fieldMap.default; + let fieldArr = fields.map(field => item.data[field]?.toString().toLowerCase()); + if (item.data.tags) { + let tags = item.data.tags.split(/[;, ]/); + fieldArr.push(...tags); + } let filterWords = me.filterVal.split(/\s+/); // all text is case insensitive and each split-out word is searched for separately. // a row gets 1 point for every partial match, and and additional point for every exact match let match = 0; - for (let field of fieldArr) { - let fieldValue = item.data[field]?.toString().toLowerCase(); - if (fieldValue === undefined) { + for (let fieldValue of fieldArr) { + if (fieldValue === undefined || fieldValue === "") { continue; } for (let filterWord of filterWords) { diff --git a/www/manager6/grid/ResourceGrid.js b/www/manager6/grid/ResourceGrid.js index 29906a37..9376bcc2 100644 --- a/www/manager6/grid/ResourceGrid.js +++ b/www/manager6/grid/ResourceGrid.js @@ -7,6 +7,7 @@ Ext.define('PVE.grid.ResourceGrid', { property: 'type', direction: 'ASC', }, +userCls: 'proxmox-tags-full', initComponent: function() { let me = this; -- 2.30.2 ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
Re: [pve-devel] [PATCH manager 1/2] fix #3719: gui: expose MTU option for containers in web UI
the implementation looks mostly OK and works, but some comments on limits and also a workflow nit that require a v2 high level: The CT implementation currently doesn't checks limits like qemu-server does, like setting (most of the time) bogus values <576 (see below) and guaranteed not working values like > $bridge_mtu. So I'd also add the overall limit of 2^16-1 there as upper limit in the schema and also assert <= $bridge_mtu on CT-start/veth-hotplug and possibly also on config change (to early catch it for stopped CTs already, as IMO its better to push the user towards setting the bridge MTU earlier than the ones of its connected ports. work flow nit: why bother adding this to the non advanced section only to move it immediately? I'd directly add it to advancedColumn1, if you want to move out rate limit field before or after is Am 17/10/2022 um 15:56 schrieb Daniel Tschlatscher:> The option to set the mtu parameter for lxc containers already exists > in the backend. It only had to be exposed in the web UI as well. > > Signed-off-by: Daniel Tschlatscher > --- > www/manager6/lxc/Network.js | 14 ++ > 1 file changed, 14 insertions(+) > > diff --git a/www/manager6/lxc/Network.js b/www/manager6/lxc/Network.js > index 7b6437c5..d6a35a49 100644 > --- a/www/manager6/lxc/Network.js > +++ b/www/manager6/lxc/Network.js > @@ -138,6 +138,14 @@ Ext.define('PVE.lxc.NetworkInputPanel', { > name: 'firewall', > value: cdata.firewall, > }, > + { > + xtype: 'proxmoxintegerfield', > + fieldLabel: gettext('MTU'), wouldn't gettext that, this is a technical abbreviation that is known widely enough to be language agnostic. > + name: 'mtu', > + value: cdata.mtu, needs an emptyText with the default behavior if not set, something like: "Same as Bridge" > + minValue: 64, IP requires that hosts must be able to process IP datagrams of at least 576 bytes (for IPv4) or 1280 bytes (for IPv6). And while yes, one may use something different than IP for sending ethernet link stuff it seems rather unlikely in practice, so maybe go for 576 as min, if someone complains we can still lower that and then have an actual known use case. The backend can stay at 64 for now, as there we would need to wait for the next major release with a check for < 576 added to the pve7to8 upgrade helper script, as long as the frontend is restrictive from the beginning I think there won't be many cases that run into this check though. ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] applied: [PATCH manager] report: add arcstat
Am 18/10/2022 um 14:11 schrieb Aaron Lauterer: > One of the infos, that can sometimes be usable. > > Signed-off-by: Aaron Lauterer > --- > PVE/Report.pm | 1 + > 1 file changed, 1 insertion(+) > > applied, thanks! ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
Re: [pve-devel] [PATCH widget-toolkit/manager v2 0/4] Ceph OSD: add detail infos
On 10/18/22 11:14, Aaron Lauterer wrote: On 10/17/22 16:29, Dominik Csapak wrote: high level looks mostly good, a small question: is there a special reason why we ignore pre-lvm osds here? AFAICS, we simply error out for osds that don't live on lvm (though we can add additional types later i guess) Mainly because with a recent Ceph version, you shouldn't have any pre Bluestore OSDs anymore. With Quincy, they have officially been deprecated. And even before, for quite a few versions, every new OSD would be a bluestore one. So I did not want to do the work for the old filestore OSDs. Once a new generation of OSDs becomes available, we will need to handle them as well though. just to have record about what we talked off-list yesterday: i didn't mean 'pre-bluestore' osds, but bluestore osds that don't use lvm iow. created with ceph-disk instead of ceph-volume (from luminous/pve5) also imho we don't have to show any useful infos there (if we even had some) but maybe show a little less 'scary' warning like 'cannot show lvm info for osd's created before pve 6/ceph nautilus' or similar ___ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel