Re: [pve-devel] [PATCH widget-toolkit/manager v2 0/4] Ceph OSD: add detail infos

2022-10-18 Thread Aaron Lauterer




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

2022-10-18 Thread Fabian Grünbichler
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

2022-10-18 Thread Fabian Grünbichler
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

2022-10-18 Thread Fabian Grünbichler
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

2022-10-18 Thread Fabian Grünbichler
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

2022-10-18 Thread Fabian Grünbichler
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

2022-10-18 Thread Fabian Grünbichler
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

2022-10-18 Thread Fabian Grünbichler
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

2022-10-18 Thread 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(+)

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

2022-10-18 Thread Wolfgang Bumiller
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
'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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
'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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Dominik Csapak
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

2022-10-18 Thread Thomas Lamprecht
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

2022-10-18 Thread Thomas Lamprecht
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

2022-10-18 Thread Dominik Csapak

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