Compare commits

...

11 Commits

Author SHA1 Message Date
77d634710e bump version to 0.8.7-2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-07-14 12:05:34 +02:00
5c5181a252 d/lintian-overrides: ignore systemd-service-file-refers-to-unusual-wantedby-target
proxmox-backup-banner.service needs getty.target

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-07-14 11:08:36 +02:00
67042466e8 ui: datastore edit: avoid an extra indentation level
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-07-14 10:56:36 +02:00
757d0ccc76 warning fixup
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2020-07-14 10:37:14 +02:00
4a55fa87d5 bump version to 0.8.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2020-07-14 10:25:53 +02:00
032cd1b862 pxar: restore file attributes, improve errors
and use the correct integer types for these operations

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2020-07-14 10:25:45 +02:00
ec2434fe3c ui: buildsys: add lint target
not yet automatically called on build, as it still fails.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-07-14 07:43:01 +02:00
34389132d9 docs: installation: add note where to find the webinterface
As the 8007 vs 8006 port is new and could confuse people, especially
if they did not used the PBS installer.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-07-14 07:35:59 +02:00
78ee20d72d docs: fix typo s/PBS_REPOSTOR/PBS_REPOSITOR/
Reported-by: Piotr Paszkowski aka patefoniQ
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-07-13 19:23:50 +02:00
601e42ac35 ui: running tasks: update limit to 100
else we'll never see the 99+ tasks ..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-07-11 12:53:32 +02:00
e1897b363b docs: add secure-apt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-07-10 14:12:51 +02:00
14 changed files with 352 additions and 185 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "proxmox-backup"
version = "0.8.6"
version = "0.8.7"
authors = ["Dietmar Maurer <dietmar@proxmox.com>"]
edition = "2018"
license = "AGPL-3"

10
debian/changelog vendored
View File

@ -1,3 +1,13 @@
rust-proxmox-backup (0.8.7-2) unstable; urgency=medium
* support restoring file attributes from pxar archives
* docs: additions and fixes
* ui: running tasks: update limit to 100
-- Proxmox Support Team <support@proxmox.com> Tue, 14 Jul 2020 12:05:25 +0200
rust-proxmox-backup (0.8.6-1) unstable; urgency=medium
* ui: add button for easily showing the server fingerprint dashboard

View File

@ -1 +1,2 @@
proxmox-backup-server: package-installs-apt-sources etc/apt/sources.list.d/pbstest-beta.list
proxmox-backup-server: systemd-service-file-refers-to-unusual-wantedby-target lib/systemd/system/proxmox-backup-banner.service getty.target

View File

@ -541,7 +541,7 @@ environment variable ``PBS_REPOSITORY``.
.. code-block:: console
# export PBS_REPOSTORY=backup-server:store1
# export PBS_REPOSITORY=backup-server:store1
After this you can execute all commands without specifying the ``--repository``
option.

View File

@ -83,6 +83,10 @@ In general this is not trivial, especially when LVM_ or ZFS_ is used.
The network configuration is completely up to you as well.
.. note:: You can access the webinterface of the Proxmox Backup Server with
your web browser, using HTTPS on port 8007. For example at
``https://<ip-or-dns-name>:8007``
Install Proxmox Backup server on `Proxmox VE`_
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -99,6 +103,10 @@ After configuring the
server to store backups. Should the hypervisor server fail, you can
still access the backups.
.. note:: You can access the webinterface of the Proxmox Backup Server with
your web browser, using HTTPS on port 8007. For example at
``https://<ip-or-dns-name>:8007``
Client installation
-------------------

View File

@ -33,6 +33,46 @@ During the Proxmox Backup beta phase only one repository (pbstest) will be
available. Once released, a Enterprise repository for production use and a
no-subscription repository will be provided.
SecureApt
~~~~~~~~~
The `Release` files in the repositories are signed with GnuPG. APT is using
these signatures to verify that all packages are from a trusted source.
If you install Proxmox Backup Server from an official ISO image, the key for
verification is already installed.
If you install Proxmox Backup Server on top of Debian, download and install the
key with the following commands:
.. code-block:: console
# wget http://download.proxmox.com/debian/proxmox-ve-release-6.x.gpg -O /etc/apt/trusted.gpg.d/proxmox-ve-release-6.x.gpg
Verify the SHA512 checksum afterwards with:
.. code-block:: console
# sha512sum /etc/apt/trusted.gpg.d/proxmox-ve-release-6.x.gpg
The output should be:
.. code-block:: console
acca6f416917e8e11490a08a1e2842d500b3a5d9f322c6319db0927b2901c3eae23cfb5cd5df6facf2b57399d3cfa52ad7769ebdd75d9b204549ca147da52626 /etc/apt/trusted.gpg.d/proxmox-ve-release-6.x.gpg
and the md5sum:
.. code-block:: console
# md5sum /etc/apt/trusted.gpg.d/proxmox-ve-release-6.x.gpg
Here, the output should be:
.. code-block:: console
f3f6c5a3a67baf38ad178e5ff1ee270c /etc/apt/trusted.gpg.d/proxmox-ve-release-6.x.gpg
.. comment
`Proxmox Backup`_ Enterprise Repository
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -23,6 +23,7 @@ use proxmox::tools::fd::RawFdNum;
use proxmox::tools::vec;
use crate::pxar::catalog::BackupCatalogWriter;
use crate::pxar::metadata::errno_is_unsupported;
use crate::pxar::Flags;
use crate::pxar::tools::assert_single_path_component;
use crate::tools::{acl, fs, xattr, Fd};
@ -698,13 +699,6 @@ fn get_metadata(fd: RawFd, stat: &FileStat, flags: Flags, fs_magic: i64) -> Resu
Ok(meta)
}
fn errno_is_unsupported(errno: Errno) -> bool {
match errno {
Errno::ENOTTY | Errno::ENOSYS | Errno::EBADF | Errno::EOPNOTSUPP | Errno::EINVAL => true,
_ => false,
}
}
fn get_fcaps(meta: &mut Metadata, fd: RawFd, flags: Flags) -> Result<(), Error> {
if flags.contains(Flags::WITH_FCAPS) {
return Ok(());
@ -769,7 +763,7 @@ fn get_xattr_fcaps_acl(
}
fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> {
let mut attr: usize = 0;
let mut attr: libc::c_long = 0;
match unsafe { fs::read_attr_fd(fd, &mut attr) } {
Ok(_) => (),
@ -779,7 +773,7 @@ fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> {
Err(err) => bail!("failed to read file attributes: {}", err),
}
metadata.stat.flags |= Flags::from_chattr(attr as u32).bits();
metadata.stat.flags |= Flags::from_chattr(attr).bits();
Ok(())
}

View File

@ -230,7 +230,8 @@ impl Extractor {
dir.metadata(),
fd,
&CString::new(dir.file_name().as_bytes())?,
)?;
)
.map_err(|err| format_err!("failed to apply directory metadata: {}", err))?;
}
Ok(())
@ -241,7 +242,9 @@ impl Extractor {
}
fn parent_fd(&mut self) -> Result<RawFd, Error> {
self.dir_stack.last_dir_fd(self.allow_existing_dirs)
self.dir_stack
.last_dir_fd(self.allow_existing_dirs)
.map_err(|err| format_err!("failed to get parent directory file descriptor: {}", err))
}
pub fn extract_symlink(
@ -320,10 +323,14 @@ impl Extractor {
file_name,
OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC,
Mode::from_bits(0o600).unwrap(),
)?)
)
.map_err(|err| format_err!("failed to create file {:?}: {}", file_name, err))?)
};
let extracted = io::copy(&mut *contents, &mut file)?;
metadata::apply_initial_flags(self.feature_flags, metadata, file.as_raw_fd())?;
let extracted = io::copy(&mut *contents, &mut file)
.map_err(|err| format_err!("failed to copy file contents: {}", err))?;
if size != extracted {
bail!("extracted {} bytes of a file of {} bytes", extracted, size);
}
@ -345,10 +352,15 @@ impl Extractor {
file_name,
OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC,
Mode::from_bits(0o600).unwrap(),
)?)
)
.map_err(|err| format_err!("failed to create file {:?}: {}", file_name, err))?)
});
let extracted = tokio::io::copy(&mut *contents, &mut file).await?;
metadata::apply_initial_flags(self.feature_flags, metadata, file.as_raw_fd())?;
let extracted = tokio::io::copy(&mut *contents, &mut file)
.await
.map_err(|err| format_err!("failed to copy file contents: {}", err))?;
if size != extracted {
bail!("extracted {} bytes of a file of {} bytes", extracted, size);
}

View File

@ -3,6 +3,8 @@
//! Flags for known supported features for a given filesystem can be derived
//! from the superblocks magic number.
use libc::c_long;
use bitflags::bitflags;
bitflags! {
@ -149,34 +151,54 @@ impl Default for Flags {
}
}
// form /usr/include/linux/fs.h
const FS_APPEND_FL: c_long = 0x0000_0020;
const FS_NOATIME_FL: c_long = 0x0000_0080;
const FS_COMPR_FL: c_long = 0x0000_0004;
const FS_NOCOW_FL: c_long = 0x0080_0000;
const FS_NODUMP_FL: c_long = 0x0000_0040;
const FS_DIRSYNC_FL: c_long = 0x0001_0000;
const FS_IMMUTABLE_FL: c_long = 0x0000_0010;
const FS_SYNC_FL: c_long = 0x0000_0008;
const FS_NOCOMP_FL: c_long = 0x0000_0400;
const FS_PROJINHERIT_FL: c_long = 0x2000_0000;
pub(crate) const INITIAL_FS_FLAGS: c_long =
FS_NOATIME_FL
| FS_COMPR_FL
| FS_NOCOW_FL
| FS_NOCOMP_FL
| FS_PROJINHERIT_FL;
#[rustfmt::skip]
const CHATTR_MAP: [(Flags, c_long); 10] = [
( Flags::WITH_FLAG_APPEND, FS_APPEND_FL ),
( Flags::WITH_FLAG_NOATIME, FS_NOATIME_FL ),
( Flags::WITH_FLAG_COMPR, FS_COMPR_FL ),
( Flags::WITH_FLAG_NOCOW, FS_NOCOW_FL ),
( Flags::WITH_FLAG_NODUMP, FS_NODUMP_FL ),
( Flags::WITH_FLAG_DIRSYNC, FS_DIRSYNC_FL ),
( Flags::WITH_FLAG_IMMUTABLE, FS_IMMUTABLE_FL ),
( Flags::WITH_FLAG_SYNC, FS_SYNC_FL ),
( Flags::WITH_FLAG_NOCOMP, FS_NOCOMP_FL ),
( Flags::WITH_FLAG_PROJINHERIT, FS_PROJINHERIT_FL ),
];
// from /usr/include/linux/msdos_fs.h
const ATTR_HIDDEN: u32 = 2;
const ATTR_SYS: u32 = 4;
const ATTR_ARCH: u32 = 32;
#[rustfmt::skip]
const FAT_ATTR_MAP: [(Flags, u32); 3] = [
( Flags::WITH_FLAG_HIDDEN, ATTR_HIDDEN ),
( Flags::WITH_FLAG_SYSTEM, ATTR_SYS ),
( Flags::WITH_FLAG_ARCHIVE, ATTR_ARCH ),
];
impl Flags {
/// Get a set of feature flags from file attributes.
pub fn from_chattr(attr: u32) -> Flags {
// form /usr/include/linux/fs.h
const FS_APPEND_FL: u32 = 0x0000_0020;
const FS_NOATIME_FL: u32 = 0x0000_0080;
const FS_COMPR_FL: u32 = 0x0000_0004;
const FS_NOCOW_FL: u32 = 0x0080_0000;
const FS_NODUMP_FL: u32 = 0x0000_0040;
const FS_DIRSYNC_FL: u32 = 0x0001_0000;
const FS_IMMUTABLE_FL: u32 = 0x0000_0010;
const FS_SYNC_FL: u32 = 0x0000_0008;
const FS_NOCOMP_FL: u32 = 0x0000_0400;
const FS_PROJINHERIT_FL: u32 = 0x2000_0000;
const CHATTR_MAP: [(Flags, u32); 10] = [
( Flags::WITH_FLAG_APPEND, FS_APPEND_FL ),
( Flags::WITH_FLAG_NOATIME, FS_NOATIME_FL ),
( Flags::WITH_FLAG_COMPR, FS_COMPR_FL ),
( Flags::WITH_FLAG_NOCOW, FS_NOCOW_FL ),
( Flags::WITH_FLAG_NODUMP, FS_NODUMP_FL ),
( Flags::WITH_FLAG_DIRSYNC, FS_DIRSYNC_FL ),
( Flags::WITH_FLAG_IMMUTABLE, FS_IMMUTABLE_FL ),
( Flags::WITH_FLAG_SYNC, FS_SYNC_FL ),
( Flags::WITH_FLAG_NOCOMP, FS_NOCOMP_FL ),
( Flags::WITH_FLAG_PROJINHERIT, FS_PROJINHERIT_FL ),
];
pub fn from_chattr(attr: c_long) -> Flags {
let mut flags = Flags::empty();
for (fe_flag, fs_flag) in &CHATTR_MAP {
@ -188,19 +210,25 @@ impl Flags {
flags
}
/// Get the chattr bit representation of these feature flags.
pub fn to_chattr(self) -> c_long {
let mut flags: c_long = 0;
for (fe_flag, fs_flag) in &CHATTR_MAP {
if self.contains(*fe_flag) {
flags |= *fs_flag;
}
}
flags
}
pub fn to_initial_chattr(self) -> c_long {
self.to_chattr() & INITIAL_FS_FLAGS
}
/// Get a set of feature flags from FAT attributes.
pub fn from_fat_attr(attr: u32) -> Flags {
// from /usr/include/linux/msdos_fs.h
const ATTR_HIDDEN: u32 = 2;
const ATTR_SYS: u32 = 4;
const ATTR_ARCH: u32 = 32;
const FAT_ATTR_MAP: [(Flags, u32); 3] = [
( Flags::WITH_FLAG_HIDDEN, ATTR_HIDDEN ),
( Flags::WITH_FLAG_SYSTEM, ATTR_SYS ),
( Flags::WITH_FLAG_ARCHIVE, ATTR_ARCH ),
];
let mut flags = Flags::empty();
for (fe_flag, fs_flag) in &FAT_ATTR_MAP {
@ -212,6 +240,19 @@ impl Flags {
flags
}
/// Get the fat attribute bit representation of these feature flags.
pub fn to_fat_attr(self) -> u32 {
let mut flags = 0u32;
for (fe_flag, fs_flag) in &FAT_ATTR_MAP {
if self.contains(*fe_flag) {
flags |= *fs_flag;
}
}
flags
}
/// Return the supported *pxar* feature flags based on the magic number of the filesystem.
pub fn from_magic(magic: i64) -> Flags {
use proxmox::sys::linux::magic::*;

View File

@ -79,13 +79,19 @@ pub fn apply_at(
apply(flags, metadata, fd.as_raw_fd(), file_name)
}
pub fn apply_initial_flags(
flags: Flags,
metadata: &Metadata,
fd: RawFd,
) -> Result<(), Error> {
let entry_flags = Flags::from_bits_truncate(metadata.stat.flags);
apply_chattr(fd, entry_flags.to_initial_chattr(), flags.to_initial_chattr())?;
Ok(())
}
pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) -> Result<(), Error> {
let c_proc_path = CString::new(format!("/proc/self/fd/{}", fd)).unwrap();
if metadata.stat.flags != 0 {
todo!("apply flags!");
}
unsafe {
// UID and GID first, as this fails if we lose access anyway.
c_result!(libc::chown(
@ -94,13 +100,15 @@ pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) ->
metadata.stat.gid
))
.map(drop)
.or_else(allow_notsupp)?;
.or_else(allow_notsupp)
.map_err(|err| format_err!("failed to set ownership: {}", err))?;
}
let mut skip_xattrs = false;
apply_xattrs(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs)?;
add_fcaps(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs)?;
apply_acls(flags, &c_proc_path, metadata)?;
apply_acls(flags, &c_proc_path, metadata)
.map_err(|err| format_err!("failed to apply acls: {}", err))?;
apply_quota_project_id(flags, fd, metadata)?;
// Finally mode and time. We may lose access with mode, but the changing the mode also
@ -110,7 +118,12 @@ pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) ->
libc::chmod(c_proc_path.as_ptr(), perms_from_metadata(metadata)?.bits())
})
.map(drop)
.or_else(allow_notsupp)?;
.or_else(allow_notsupp)
.map_err(|err| format_err!("failed to change file mode: {}", err))?;
}
if metadata.stat.flags != 0 {
apply_flags(flags, fd, metadata.stat.flags)?;
}
let res = c_result!(unsafe {
@ -160,7 +173,8 @@ fn add_fcaps(
)
})
.map(drop)
.or_else(|err| allow_notsupp_remember(err, skip_xattrs))?;
.or_else(|err| allow_notsupp_remember(err, skip_xattrs))
.map_err(|err| format_err!("failed to apply file capabilities: {}", err))?;
Ok(())
}
@ -195,7 +209,8 @@ fn apply_xattrs(
)
})
.map(drop)
.or_else(|err| allow_notsupp_remember(err, &mut *skip_xattrs))?;
.or_else(|err| allow_notsupp_remember(err, &mut *skip_xattrs))
.map_err(|err| format_err!("failed to apply extended attributes: {}", err))?;
}
Ok(())
@ -317,3 +332,49 @@ fn apply_quota_project_id(flags: Flags, fd: RawFd, metadata: &Metadata) -> Resul
Ok(())
}
pub(crate) fn errno_is_unsupported(errno: Errno) -> bool {
match errno {
Errno::ENOTTY | Errno::ENOSYS | Errno::EBADF | Errno::EOPNOTSUPP | Errno::EINVAL => true,
_ => false,
}
}
fn apply_chattr(fd: RawFd, chattr: libc::c_long, mask: libc::c_long) -> Result<(), Error> {
if chattr == 0 {
return Ok(());
}
let mut fattr: libc::c_long = 0;
match unsafe { fs::read_attr_fd(fd, &mut fattr) } {
Ok(_) => (),
Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => {
return Ok(());
}
Err(err) => bail!("failed to read file attributes: {}", err),
}
let attr = (chattr & mask) | (fattr & !mask);
match unsafe { fs::write_attr_fd(fd, &attr) } {
Ok(_) => Ok(()),
Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => Ok(()),
Err(err) => bail!("failed to set file attributes: {}", err),
}
}
fn apply_flags(flags: Flags, fd: RawFd, entry_flags: u64) -> Result<(), Error> {
let entry_flags = Flags::from_bits_truncate(entry_flags);
apply_chattr(fd, entry_flags.to_chattr(), flags.to_chattr())?;
let fatattr = (flags & entry_flags).to_fat_attr();
if fatattr != 0 {
match unsafe { fs::write_fat_attr_fd(fd, &fatattr) } {
Ok(_) => (),
Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => (),
Err(err) => bail!("failed to set file attributes: {}", err),
}
}
Ok(())
}

View File

@ -222,11 +222,13 @@ where
// /usr/include/linux/fs.h: #define FS_IOC_GETFLAGS _IOR('f', 1, long)
// read Linux file system attributes (see man chattr)
nix::ioctl_read!(read_attr_fd, b'f', 1, usize);
nix::ioctl_read!(read_attr_fd, b'f', 1, libc::c_long);
nix::ioctl_write_ptr!(write_attr_fd, b'f', 2, libc::c_long);
// /usr/include/linux/msdos_fs.h: #define FAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32)
// read FAT file system attributes
nix::ioctl_read!(read_fat_attr_fd, b'r', 0x10, u32);
nix::ioctl_write_ptr!(write_fat_attr_fd, b'r', 0x11, u32);
// From /usr/include/linux/fs.h
// #define FS_IOC_FSGETXATTR _IOR('X', 31, struct fsxattr)

View File

@ -55,6 +55,10 @@ js/proxmox-backup-gui.js: js OnlineHelpInfo.js ${JSSRC}
cat OnlineHelpInfo.js ${JSSRC} >$@.tmp
mv $@.tmp $@
.PHONY: lint
lint: ${JSSRC}
eslint ${JSSRC}
.PHONY: clean
clean:
find . -name '*~' -exec rm {} ';'

View File

@ -13,7 +13,7 @@ Ext.define('PBS.data.RunningTasksStore', {
proxy: {
type: 'proxmox',
// maybe separate api call?
url: '/api2/json/nodes/localhost/tasks?running=1',
url: '/api2/json/nodes/localhost/tasks?running=1&limit=100',
},
});
me.callParent([config]);

View File

@ -21,134 +21,128 @@ Ext.define('PBS.DataStoreEdit', {
return {};
},
items: [
{
xtype: 'tabpanel',
bodyPadding: 10,
items: [
{
title: gettext('General'),
xtype: 'inputpanel',
column1: [
{
xtype: 'pmxDisplayEditField',
cbind: {
editable: '{isCreate}',
},
name: 'name',
allowBlank: false,
fieldLabel: gettext('Name'),
items: {
xtype: 'tabpanel',
bodyPadding: 10,
items: [
{
title: gettext('General'),
xtype: 'inputpanel',
column1: [
{
xtype: 'pmxDisplayEditField',
cbind: {
editable: '{isCreate}',
},
{
xtype: 'pmxDisplayEditField',
cbind: {
editable: '{isCreate}',
},
name: 'path',
allowBlank: false,
fieldLabel: gettext('Backing Path'),
emptyText: gettext('An absolute path'),
name: 'name',
allowBlank: false,
fieldLabel: gettext('Name'),
},
{
xtype: 'pmxDisplayEditField',
cbind: {
editable: '{isCreate}',
},
],
column2: [
{
xtype: 'proxmoxtextfield',
name: 'gc-schedule',
fieldLabel: gettext("GC Schedule"),
cbind: {
deleteEmpty: '{!isCreate}',
},
name: 'path',
allowBlank: false,
fieldLabel: gettext('Backing Path'),
emptyText: gettext('An absolute path'),
},
],
column2: [
{
xtype: 'proxmoxtextfield',
name: 'gc-schedule',
fieldLabel: gettext("GC Schedule"),
cbind: {
deleteEmpty: '{!isCreate}',
},
{
xtype: 'proxmoxtextfield',
name: 'prune-schedule',
fieldLabel: gettext("Prune Schedule"),
cbind: {
deleteEmpty: '{!isCreate}',
},
},
{
xtype: 'proxmoxtextfield',
name: 'prune-schedule',
fieldLabel: gettext("Prune Schedule"),
cbind: {
deleteEmpty: '{!isCreate}',
},
],
columnB: [
{
xtype: 'textfield',
name: 'comment',
fieldLabel: gettext('Comment'),
},
],
columnB: [
{
xtype: 'textfield',
name: 'comment',
fieldLabel: gettext('Comment'),
},
],
},
{
title: gettext('Prune Options'),
xtype: 'inputpanel',
column1: [
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Last'),
name: 'keep-last',
cbind: {
deleteEmpty: '{!isCreate}',
},
],
},
{
title: gettext('Prune Options'),
xtype: 'inputpanel',
column1: [
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Last'),
name: 'keep-last',
cbind: {
deleteEmpty: '{!isCreate}',
},
minValue: 1,
allowBlank: true,
minValue: 1,
allowBlank: true,
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Daily'),
name: 'keep-daily',
cbind: {
deleteEmpty: '{!isCreate}',
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Daily'),
name: 'keep-daily',
cbind: {
deleteEmpty: '{!isCreate}',
},
minValue: 1,
allowBlank: true,
minValue: 1,
allowBlank: true,
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Monthly'),
name: 'keep-monthly',
cbind: {
deleteEmpty: '{!isCreate}',
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Monthly'),
name: 'keep-monthly',
cbind: {
deleteEmpty: '{!isCreate}',
},
minValue: 1,
allowBlank: true,
minValue: 1,
allowBlank: true,
},
],
column2: [
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Hourly'),
name: 'keep-hourly',
cbind: {
deleteEmpty: '{!isCreate}',
},
],
column2: [
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Hourly'),
name: 'keep-hourly',
cbind: {
deleteEmpty: '{!isCreate}',
},
minValue: 1,
allowBlank: true,
minValue: 1,
allowBlank: true,
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Weekly'),
name: 'keep-weekly',
cbind: {
deleteEmpty: '{!isCreate}',
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Weekly'),
name: 'keep-weekly',
cbind: {
deleteEmpty: '{!isCreate}',
},
minValue: 1,
allowBlank: true,
minValue: 1,
allowBlank: true,
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Yearly'),
name: 'keep-yearly',
cbind: {
deleteEmpty: '{!isCreate}',
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: gettext('Keep Yearly'),
name: 'keep-yearly',
cbind: {
deleteEmpty: '{!isCreate}',
},
minValue: 1,
allowBlank: true,
},
],
}
]
}
],
minValue: 1,
allowBlank: true,
},
],
},
],
},
});