Merge 8.2.4
This commit is contained in:
commit
d5867bc8a1
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,3 +9,5 @@ dest/
|
|||||||
/www/mobile/pvemanager-mobile.js
|
/www/mobile/pvemanager-mobile.js
|
||||||
/www/touch/touch-[0-9]*/
|
/www/touch/touch-[0-9]*/
|
||||||
/pve-manager-[0-9]*/
|
/pve-manager-[0-9]*/
|
||||||
|
/test/.mocked_*
|
||||||
|
/test/*.tmp
|
||||||
|
2
Makefile
2
Makefile
@ -10,7 +10,7 @@ DSC=$(PACKAGE)_$(DEB_VERSION).dsc
|
|||||||
DEB=$(PACKAGE)_$(DEB_VERSION)_$(DEB_HOST_ARCH).deb
|
DEB=$(PACKAGE)_$(DEB_VERSION)_$(DEB_HOST_ARCH).deb
|
||||||
|
|
||||||
DESTDIR=
|
DESTDIR=
|
||||||
SUBDIRS = aplinfo PVE bin www services configs network-hooks test
|
SUBDIRS = aplinfo PVE bin www services configs network-hooks test templates
|
||||||
|
|
||||||
all: $(SUBDIRS)
|
all: $(SUBDIRS)
|
||||||
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i; done
|
set -e && for i in $(SUBDIRS); do $(MAKE) -C $$i; done
|
||||||
|
@ -238,12 +238,6 @@ __PACKAGE__->register_method({
|
|||||||
return $pkglist;
|
return $pkglist;
|
||||||
}});
|
}});
|
||||||
|
|
||||||
my $updates_available_subject_template = "New software packages available ({{hostname}})";
|
|
||||||
my $updates_available_body_template = <<EOT;
|
|
||||||
The following updates are available:
|
|
||||||
{{table updates}}
|
|
||||||
EOT
|
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'update_database',
|
name => 'update_database',
|
||||||
path => 'update',
|
path => 'update',
|
||||||
@ -358,8 +352,7 @@ __PACKAGE__->register_method({
|
|||||||
};
|
};
|
||||||
|
|
||||||
PVE::Notify::info(
|
PVE::Notify::info(
|
||||||
$updates_available_subject_template,
|
"package-updates",
|
||||||
$updates_available_body_template,
|
|
||||||
$template_data,
|
$template_data,
|
||||||
$metadata_fields,
|
$metadata_fields,
|
||||||
);
|
);
|
||||||
@ -759,6 +752,7 @@ __PACKAGE__->register_method({
|
|||||||
push @list, sort $byver grep { /^(?:pve|proxmox)-kernel-/ && $cache->{$_}->{CurrentState} eq 'Installed' } keys %$cache;
|
push @list, sort $byver grep { /^(?:pve|proxmox)-kernel-/ && $cache->{$_}->{CurrentState} eq 'Installed' } keys %$cache;
|
||||||
|
|
||||||
my @opt_pack = qw(
|
my @opt_pack = qw(
|
||||||
|
amd64-microcode
|
||||||
ceph
|
ceph
|
||||||
criu
|
criu
|
||||||
dnsmasq
|
dnsmasq
|
||||||
@ -770,6 +764,7 @@ __PACKAGE__->register_method({
|
|||||||
libpve-network-perl
|
libpve-network-perl
|
||||||
openvswitch
|
openvswitch
|
||||||
proxmox-backup-file-restore
|
proxmox-backup-file-restore
|
||||||
|
pve-firewall
|
||||||
pve-zsync
|
pve-zsync
|
||||||
zfs-utils
|
zfs-utils
|
||||||
);
|
);
|
||||||
|
@ -56,7 +56,7 @@ my $vzdump_job_id_prop = {
|
|||||||
|
|
||||||
# NOTE: also used by the vzdump API call.
|
# NOTE: also used by the vzdump API call.
|
||||||
sub assert_param_permission_common {
|
sub assert_param_permission_common {
|
||||||
my ($rpcenv, $user, $param) = @_;
|
my ($rpcenv, $user, $param, $is_delete) = @_;
|
||||||
return if $user eq 'root@pam'; # always OK
|
return if $user eq 'root@pam'; # always OK
|
||||||
|
|
||||||
for my $key (qw(tmpdir dumpdir script)) {
|
for my $key (qw(tmpdir dumpdir script)) {
|
||||||
@ -66,6 +66,12 @@ sub assert_param_permission_common {
|
|||||||
if (grep { defined($param->{$_}) } qw(bwlimit ionice performance)) {
|
if (grep { defined($param->{$_}) } qw(bwlimit ionice performance)) {
|
||||||
$rpcenv->check($user, "/", [ 'Sys.Modify' ]);
|
$rpcenv->check($user, "/", [ 'Sys.Modify' ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($param->{fleecing} && !$is_delete) {
|
||||||
|
my $fleecing = PVE::VZDump::parse_fleecing($param) // {};
|
||||||
|
$rpcenv->check($user, "/storage/$fleecing->{storage}", [ 'Datastore.AllocateSpace' ])
|
||||||
|
if $fleecing->{storage};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my sub assert_param_permission_create {
|
my sub assert_param_permission_create {
|
||||||
@ -84,7 +90,7 @@ my sub assert_param_permission_update {
|
|||||||
return if $user eq 'root@pam'; # always OK
|
return if $user eq 'root@pam'; # always OK
|
||||||
|
|
||||||
assert_param_permission_common($rpcenv, $user, $update);
|
assert_param_permission_common($rpcenv, $user, $update);
|
||||||
assert_param_permission_common($rpcenv, $user, $delete);
|
assert_param_permission_common($rpcenv, $user, $delete, 1);
|
||||||
|
|
||||||
if ($update->{storage}) {
|
if ($update->{storage}) {
|
||||||
$rpcenv->check($user, "/storage/$update->{storage}", [ 'Datastore.Allocate' ])
|
$rpcenv->check($user, "/storage/$update->{storage}", [ 'Datastore.Allocate' ])
|
||||||
|
@ -192,6 +192,11 @@ __PACKAGE__->register_method ({
|
|||||||
PVE::Ceph::Tools::check_ceph_installed('ceph_bin');
|
PVE::Ceph::Tools::check_ceph_installed('ceph_bin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $pve_ceph_cfgdir = PVE::Ceph::Tools::get_config('pve_ceph_cfgdir');
|
||||||
|
if (! -d $pve_ceph_cfgdir) {
|
||||||
|
File::Path::make_path($pve_ceph_cfgdir);
|
||||||
|
}
|
||||||
|
|
||||||
my $auth = $param->{disable_cephx} ? 'none' : 'cephx';
|
my $auth = $param->{disable_cephx} ? 'none' : 'cephx';
|
||||||
|
|
||||||
# simply load old config if it already exists
|
# simply load old config if it already exists
|
||||||
|
@ -451,6 +451,14 @@ __PACKAGE__->register_method ({
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
warn "$@" if $@;
|
warn "$@" if $@;
|
||||||
|
|
||||||
|
print "Configuring keyring for ceph-crash.service\n";
|
||||||
|
eval {
|
||||||
|
PVE::Ceph::Tools::create_or_update_crash_keyring_file();
|
||||||
|
$cfg->{'client.crash'}->{keyring} = '/etc/pve/ceph/$cluster.$name.keyring';
|
||||||
|
cfs_write_file('ceph.conf', $cfg);
|
||||||
|
};
|
||||||
|
warn "Unable to configure keyring for ceph-crash.service: $@" if $@;
|
||||||
}
|
}
|
||||||
|
|
||||||
eval { PVE::Ceph::Services::ceph_service_cmd('enable', $monsection) };
|
eval { PVE::Ceph::Services::ceph_service_cmd('enable', $monsection) };
|
||||||
|
@ -107,6 +107,7 @@ __PACKAGE__->register_method ({
|
|||||||
my $result = [
|
my $result = [
|
||||||
{ name => 'gotify' },
|
{ name => 'gotify' },
|
||||||
{ name => 'sendmail' },
|
{ name => 'sendmail' },
|
||||||
|
{ name => 'smtp' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
@ -143,7 +144,7 @@ __PACKAGE__->register_method ({
|
|||||||
'type' => {
|
'type' => {
|
||||||
description => 'Type of the target.',
|
description => 'Type of the target.',
|
||||||
type => 'string',
|
type => 'string',
|
||||||
enum => [qw(sendmail gotify)],
|
enum => [qw(sendmail gotify smtp)],
|
||||||
},
|
},
|
||||||
'comment' => {
|
'comment' => {
|
||||||
description => 'Comment',
|
description => 'Comment',
|
||||||
|
@ -1017,7 +1017,8 @@ my $get_vnc_connection_info = sub {
|
|||||||
my ($remip, $family);
|
my ($remip, $family);
|
||||||
if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
|
if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
|
||||||
($remip, $family) = PVE::Cluster::remote_node_ip($node);
|
($remip, $family) = PVE::Cluster::remote_node_ip($node);
|
||||||
$remote_cmd = ['/usr/bin/ssh', '-e', 'none', '-t', $remip , '--'];
|
$remote_cmd = PVE::SSHInfo::ssh_info_to_command({ ip => $remip, name => $node }, ('-t'));
|
||||||
|
push @$remote_cmd, '--';
|
||||||
} else {
|
} else {
|
||||||
$family = PVE::Tools::get_host_address_family($node);
|
$family = PVE::Tools::get_host_address_family($node);
|
||||||
}
|
}
|
||||||
|
@ -92,23 +92,6 @@ my sub _should_mail_at_failcount {
|
|||||||
return $i * 48 == $fail_count;
|
return $i * 48 == $fail_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
my $replication_error_subject_template = "Replication Job: '{{job-id}}' failed";
|
|
||||||
my $replication_error_body_template = <<EOT;
|
|
||||||
{{#verbatim}}
|
|
||||||
Replication job '{{job-id}}' with target '{{job-target}}' and schedule '{{job-schedule}}' failed!
|
|
||||||
|
|
||||||
Last successful sync: {{timestamp last-sync}}
|
|
||||||
Next sync try: {{timestamp next-sync}}
|
|
||||||
Failure count: {{failure-count}}
|
|
||||||
|
|
||||||
{{#if (eq failure-count 3)}}
|
|
||||||
Note: The system will now reduce the frequency of error reports, as the job
|
|
||||||
appears to be stuck.
|
|
||||||
{{/if}}
|
|
||||||
Error:
|
|
||||||
{{verbatim-monospaced error}}
|
|
||||||
{{/verbatim}}
|
|
||||||
EOT
|
|
||||||
|
|
||||||
my sub _handle_job_err {
|
my sub _handle_job_err {
|
||||||
my ($job, $err, $mail) = @_;
|
my ($job, $err, $mail) = @_;
|
||||||
@ -146,8 +129,7 @@ my sub _handle_job_err {
|
|||||||
|
|
||||||
eval {
|
eval {
|
||||||
PVE::Notify::error(
|
PVE::Notify::error(
|
||||||
$replication_error_subject_template,
|
"replication",
|
||||||
$replication_error_body_template,
|
|
||||||
$template_data,
|
$template_data,
|
||||||
$metadata_fields
|
$metadata_fields
|
||||||
);
|
);
|
||||||
|
@ -41,10 +41,11 @@ __PACKAGE__->register_method ({
|
|||||||
description => "Create backup.",
|
description => "Create backup.",
|
||||||
permissions => {
|
permissions => {
|
||||||
description => "The user needs 'VM.Backup' permissions on any VM, and "
|
description => "The user needs 'VM.Backup' permissions on any VM, and "
|
||||||
."'Datastore.AllocateSpace' on the backup storage. The 'tmpdir', 'dumpdir' and "
|
."'Datastore.AllocateSpace' on the backup storage (and fleecing storage when fleecing "
|
||||||
."'script' parameters are restricted to the 'root\@pam' user. The 'maxfiles' and "
|
."is used). The 'tmpdir', 'dumpdir' and 'script' parameters are restricted to the "
|
||||||
."'prune-backups' settings require 'Datastore.Allocate' on the backup storage. The "
|
."'root\@pam' user. The 'maxfiles' and 'prune-backups' settings require "
|
||||||
."'bwlimit', 'performance' and 'ionice' parameters require 'Sys.Modify' on '/'. ",
|
."'Datastore.Allocate' on the backup storage. The 'bwlimit', 'performance' and "
|
||||||
|
."'ionice' parameters require 'Sys.Modify' on '/'.",
|
||||||
user => 'all',
|
user => 'all',
|
||||||
},
|
},
|
||||||
protected => 1,
|
protected => 1,
|
||||||
|
@ -116,7 +116,7 @@ sub proxy_handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my @ssh_tunnel_cmd = ('ssh', '-o', 'BatchMode=yes', "root\@$remip");
|
my $ssh_tunnel_cmd = PVE::SSHInfo::ssh_info_to_command({ ip => $remip, name => $node });
|
||||||
|
|
||||||
my @pvesh_cmd = ('pvesh', '--noproxy', $cmd, $path, '--output-format', 'json');
|
my @pvesh_cmd = ('pvesh', '--noproxy', $cmd, $path, '--output-format', 'json');
|
||||||
if (scalar(@$args)) {
|
if (scalar(@$args)) {
|
||||||
@ -126,7 +126,7 @@ sub proxy_handler {
|
|||||||
|
|
||||||
my $res = '';
|
my $res = '';
|
||||||
PVE::Tools::run_command(
|
PVE::Tools::run_command(
|
||||||
[ @ssh_tunnel_cmd, '--', @pvesh_cmd ],
|
[ $ssh_tunnel_cmd->@*, '--', @pvesh_cmd ],
|
||||||
errmsg => "proxy handler failed",
|
errmsg => "proxy handler failed",
|
||||||
outfunc => sub { $res .= shift },
|
outfunc => sub { $res .= shift },
|
||||||
);
|
);
|
||||||
|
@ -18,7 +18,9 @@ my $ccname = 'ceph'; # ceph cluster name
|
|||||||
my $ceph_cfgdir = "/etc/ceph";
|
my $ceph_cfgdir = "/etc/ceph";
|
||||||
my $pve_ceph_cfgpath = "/etc/pve/$ccname.conf";
|
my $pve_ceph_cfgpath = "/etc/pve/$ccname.conf";
|
||||||
my $ceph_cfgpath = "$ceph_cfgdir/$ccname.conf";
|
my $ceph_cfgpath = "$ceph_cfgdir/$ccname.conf";
|
||||||
|
my $pve_ceph_cfgdir = "/etc/pve/ceph";
|
||||||
|
|
||||||
|
my $pve_ceph_crash_key_path = "$pve_ceph_cfgdir/$ccname.client.crash.keyring";
|
||||||
my $pve_mon_key_path = "/etc/pve/priv/$ccname.mon.keyring";
|
my $pve_mon_key_path = "/etc/pve/priv/$ccname.mon.keyring";
|
||||||
my $pve_ckeyring_path = "/etc/pve/priv/$ccname.client.admin.keyring";
|
my $pve_ckeyring_path = "/etc/pve/priv/$ccname.client.admin.keyring";
|
||||||
my $ckeyring_path = "/etc/ceph/ceph.client.admin.keyring";
|
my $ckeyring_path = "/etc/ceph/ceph.client.admin.keyring";
|
||||||
@ -37,12 +39,14 @@ my $ceph_service = {
|
|||||||
|
|
||||||
my $config_values = {
|
my $config_values = {
|
||||||
ccname => $ccname,
|
ccname => $ccname,
|
||||||
|
pve_ceph_cfgdir => $pve_ceph_cfgdir,
|
||||||
ceph_mds_data_dir => $ceph_mds_data_dir,
|
ceph_mds_data_dir => $ceph_mds_data_dir,
|
||||||
long_rados_timeout => 60,
|
long_rados_timeout => 60,
|
||||||
};
|
};
|
||||||
|
|
||||||
my $config_files = {
|
my $config_files = {
|
||||||
pve_ceph_cfgpath => $pve_ceph_cfgpath,
|
pve_ceph_cfgpath => $pve_ceph_cfgpath,
|
||||||
|
pve_ceph_crash_key_path => $pve_ceph_crash_key_path,
|
||||||
pve_mon_key_path => $pve_mon_key_path,
|
pve_mon_key_path => $pve_mon_key_path,
|
||||||
pve_ckeyring_path => $pve_ckeyring_path,
|
pve_ckeyring_path => $pve_ckeyring_path,
|
||||||
ceph_bootstrap_osd_keyring => $ceph_bootstrap_osd_keyring,
|
ceph_bootstrap_osd_keyring => $ceph_bootstrap_osd_keyring,
|
||||||
@ -186,8 +190,14 @@ sub check_ceph_inited {
|
|||||||
|
|
||||||
return undef if !check_ceph_installed('ceph_mon', $noerr);
|
return undef if !check_ceph_installed('ceph_mon', $noerr);
|
||||||
|
|
||||||
if (! -f $pve_ceph_cfgpath) {
|
my @errors;
|
||||||
die "pveceph configuration not initialized\n" if !$noerr;
|
|
||||||
|
push(@errors, "missing '$pve_ceph_cfgpath'") if ! -f $pve_ceph_cfgpath;
|
||||||
|
push(@errors, "missing '$pve_ceph_cfgdir'") if ! -d $pve_ceph_cfgdir;
|
||||||
|
|
||||||
|
if (@errors) {
|
||||||
|
my $err = 'pveceph configuration not initialized - ' . join(', ', @errors) . "\n";
|
||||||
|
die $err if !$noerr;
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,6 +422,39 @@ sub get_or_create_admin_keyring {
|
|||||||
return $pve_ckeyring_path;
|
return $pve_ckeyring_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# is also used in `pve-init-ceph-crash` helper
|
||||||
|
sub create_or_update_crash_keyring_file {
|
||||||
|
my ($rados) = @_;
|
||||||
|
|
||||||
|
if (!defined($rados)) {
|
||||||
|
$rados = PVE::RADOS->new();
|
||||||
|
}
|
||||||
|
|
||||||
|
my $output = $rados->mon_command({
|
||||||
|
prefix => 'auth get-or-create',
|
||||||
|
entity => 'client.crash',
|
||||||
|
caps => [
|
||||||
|
mon => 'profile crash',
|
||||||
|
mgr => 'profile crash',
|
||||||
|
],
|
||||||
|
format => 'plain',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (-f $pve_ceph_crash_key_path) {
|
||||||
|
my $contents = PVE::Tools::file_get_contents($pve_ceph_crash_key_path);
|
||||||
|
|
||||||
|
if ($contents ne $output) {
|
||||||
|
PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
# get ceph-volume managed osds
|
# get ceph-volume managed osds
|
||||||
sub ceph_volume_list {
|
sub ceph_volume_list {
|
||||||
my $result = {};
|
my $result = {};
|
||||||
|
@ -31,12 +31,15 @@ my $init_report_cmds = sub {
|
|||||||
cmds => [
|
cmds => [
|
||||||
'hostname',
|
'hostname',
|
||||||
'date -R',
|
'date -R',
|
||||||
|
'cat /proc/cmdline',
|
||||||
'pveversion --verbose',
|
'pveversion --verbose',
|
||||||
'cat /etc/hosts',
|
'cat /etc/hosts',
|
||||||
'pvesubscription get',
|
'pvesubscription get',
|
||||||
'cat /etc/apt/sources.list',
|
'cat /etc/apt/sources.list',
|
||||||
sub { dir2text('/etc/apt/sources.list.d/', '.*list') },
|
sub { dir2text('/etc/apt/sources.list.d/', '.+\.list') },
|
||||||
sub { dir2text('/etc/apt/sources.list.d/', '.*sources') },
|
sub { dir2text('/etc/apt/sources.list.d/', '.+\.sources') },
|
||||||
|
'apt-cache policy | grep -vP "^ +origin "',
|
||||||
|
'apt-mark showhold',
|
||||||
'lscpu',
|
'lscpu',
|
||||||
'pvesh get /cluster/resources --type node --output-format=yaml',
|
'pvesh get /cluster/resources --type node --output-format=yaml',
|
||||||
],
|
],
|
||||||
@ -64,9 +67,9 @@ my $init_report_cmds = sub {
|
|||||||
order => 40,
|
order => 40,
|
||||||
cmds => [
|
cmds => [
|
||||||
'qm list',
|
'qm list',
|
||||||
sub { dir2text('/etc/pve/qemu-server/', '\d.*conf') },
|
sub { dir2text('/etc/pve/qemu-server/', '\d+\.conf') },
|
||||||
'pct list',
|
'pct list',
|
||||||
sub { dir2text('/etc/pve/lxc/', '\d.*conf') },
|
sub { dir2text('/etc/pve/lxc/', '\d+\.conf') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
network => {
|
network => {
|
||||||
@ -83,7 +86,7 @@ my $init_report_cmds = sub {
|
|||||||
firewall => {
|
firewall => {
|
||||||
order => 50,
|
order => 50,
|
||||||
cmds => [
|
cmds => [
|
||||||
sub { dir2text('/etc/pve/firewall/', '.*fw') },
|
sub { dir2text('/etc/pve/firewall/', '.+\.fw') },
|
||||||
'cat /etc/pve/local/host.fw',
|
'cat /etc/pve/local/host.fw',
|
||||||
'iptables-save -c | column -t -l4 -o" "',
|
'iptables-save -c | column -t -l4 -o" "',
|
||||||
],
|
],
|
||||||
@ -98,6 +101,12 @@ my $init_report_cmds = sub {
|
|||||||
'cat /etc/pve/datacenter.cfg',
|
'cat /etc/pve/datacenter.cfg',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
jobs => {
|
||||||
|
order => 65,
|
||||||
|
cmds => [
|
||||||
|
'cat /etc/pve/jobs.cfg',
|
||||||
|
],
|
||||||
|
},
|
||||||
hardware => {
|
hardware => {
|
||||||
order => 70,
|
order => 70,
|
||||||
cmds => [
|
cmds => [
|
||||||
|
@ -677,8 +677,3 @@ our $cmddef = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,13 +130,37 @@ my $generate_notes = sub {
|
|||||||
return $notes_template;
|
return $notes_template;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sub parse_fleecing {
|
||||||
|
my ($param) = @_;
|
||||||
|
|
||||||
|
if (defined(my $fleecing = $param->{fleecing})) {
|
||||||
|
return $fleecing if ref($fleecing) eq 'HASH'; # already parsed
|
||||||
|
$param->{fleecing} = PVE::JSONSchema::parse_property_string('backup-fleecing', $fleecing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $param->{fleecing};
|
||||||
|
}
|
||||||
|
|
||||||
my sub parse_performance {
|
my sub parse_performance {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
|
|
||||||
if (defined(my $perf = $param->{performance})) {
|
if (defined(my $perf = $param->{performance})) {
|
||||||
return if ref($perf) eq 'HASH'; # already parsed
|
return $perf if ref($perf) eq 'HASH'; # already parsed
|
||||||
$param->{performance} = PVE::JSONSchema::parse_property_string('backup-performance', $perf);
|
$param->{performance} = PVE::JSONSchema::parse_property_string('backup-performance', $perf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $param->{performance};
|
||||||
|
}
|
||||||
|
|
||||||
|
my sub merge_performance {
|
||||||
|
my ($prefer, $fallback) = @_;
|
||||||
|
|
||||||
|
my $res = {};
|
||||||
|
for my $opt (keys PVE::JSONSchema::get_format('backup-performance')->%*) {
|
||||||
|
$res->{$opt} = $prefer->{$opt} // $fallback->{$opt}
|
||||||
|
if defined($prefer->{$opt}) || defined($fallback->{$opt});
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $parse_prune_backups_maxfiles = sub {
|
my $parse_prune_backups_maxfiles = sub {
|
||||||
@ -149,7 +173,7 @@ my $parse_prune_backups_maxfiles = sub {
|
|||||||
if defined($maxfiles) && defined($prune_backups);
|
if defined($maxfiles) && defined($prune_backups);
|
||||||
|
|
||||||
if (defined($prune_backups)) {
|
if (defined($prune_backups)) {
|
||||||
return if ref($prune_backups) eq 'HASH'; # already parsed
|
return $prune_backups if ref($prune_backups) eq 'HASH'; # already parsed
|
||||||
$param->{'prune-backups'} = PVE::JSONSchema::parse_property_string(
|
$param->{'prune-backups'} = PVE::JSONSchema::parse_property_string(
|
||||||
'prune-backups',
|
'prune-backups',
|
||||||
$prune_backups
|
$prune_backups
|
||||||
@ -161,6 +185,8 @@ my $parse_prune_backups_maxfiles = sub {
|
|||||||
$param->{'prune-backups'} = { 'keep-all' => 1 };
|
$param->{'prune-backups'} = { 'keep-all' => 1 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $param->{'prune-backups'};
|
||||||
};
|
};
|
||||||
|
|
||||||
sub storage_info {
|
sub storage_info {
|
||||||
@ -277,8 +303,21 @@ sub read_vzdump_defaults {
|
|||||||
defined($default) ? ($_ => $default) : ()
|
defined($default) ? ($_ => $default) : ()
|
||||||
} keys %$confdesc_for_defaults
|
} keys %$confdesc_for_defaults
|
||||||
};
|
};
|
||||||
|
my $performance_fmt = PVE::JSONSchema::get_format('backup-performance');
|
||||||
|
$defaults->{performance} = {
|
||||||
|
map {
|
||||||
|
my $default = $performance_fmt->{$_}->{default};
|
||||||
|
defined($default) ? ($_ => $default) : ()
|
||||||
|
} keys $performance_fmt->%*
|
||||||
|
};
|
||||||
|
my $fleecing_fmt = PVE::JSONSchema::get_format('backup-fleecing');
|
||||||
|
$defaults->{fleecing} = {
|
||||||
|
map {
|
||||||
|
my $default = $fleecing_fmt->{$_}->{default};
|
||||||
|
defined($default) ? ($_ => $default) : ()
|
||||||
|
} keys $fleecing_fmt->%*
|
||||||
|
};
|
||||||
$parse_prune_backups_maxfiles->($defaults, "defaults in VZDump schema");
|
$parse_prune_backups_maxfiles->($defaults, "defaults in VZDump schema");
|
||||||
parse_performance($defaults);
|
|
||||||
|
|
||||||
my $raw;
|
my $raw;
|
||||||
eval { $raw = PVE::Tools::file_get_contents($fn); };
|
eval { $raw = PVE::Tools::file_get_contents($fn); };
|
||||||
@ -304,10 +343,15 @@ sub read_vzdump_defaults {
|
|||||||
$res->{mailto} = [ @mailto ];
|
$res->{mailto} = [ @mailto ];
|
||||||
}
|
}
|
||||||
$parse_prune_backups_maxfiles->($res, "options in '$fn'");
|
$parse_prune_backups_maxfiles->($res, "options in '$fn'");
|
||||||
|
parse_fleecing($res);
|
||||||
parse_performance($res);
|
parse_performance($res);
|
||||||
|
|
||||||
foreach my $key (keys %$defaults) {
|
for my $key (keys $defaults->%*) {
|
||||||
$res->{$key} = $defaults->{$key} if !defined($res->{$key});
|
if (!defined($res->{$key})) {
|
||||||
|
$res->{$key} = $defaults->{$key};
|
||||||
|
} elsif ($key eq 'performance') {
|
||||||
|
$res->{$key} = merge_performance($res->{$key}, $defaults->{$key});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined($res->{storage}) && defined($res->{dumpdir})) {
|
if (defined($res->{storage}) && defined($res->{dumpdir})) {
|
||||||
@ -433,20 +477,6 @@ my sub get_hostname {
|
|||||||
return $hostname;
|
return $hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $subject_template = "vzdump backup status ({{hostname}}): {{status-text}}";
|
|
||||||
|
|
||||||
my $body_template = <<EOT;
|
|
||||||
{{error-message}}
|
|
||||||
{{heading-1 "Details"}}
|
|
||||||
{{table guest-table}}
|
|
||||||
{{#verbatim}}
|
|
||||||
Total running time: {{duration total-time}}
|
|
||||||
Total size: {{human-bytes total-size}}
|
|
||||||
{{/verbatim}}
|
|
||||||
{{heading-1 "Logs"}}
|
|
||||||
{{verbatim-monospaced logs}}
|
|
||||||
EOT
|
|
||||||
|
|
||||||
use constant MAX_LOG_SIZE => 1024*1024;
|
use constant MAX_LOG_SIZE => 1024*1024;
|
||||||
|
|
||||||
sub send_notification {
|
sub send_notification {
|
||||||
@ -544,8 +574,7 @@ sub send_notification {
|
|||||||
|
|
||||||
PVE::Notify::notify(
|
PVE::Notify::notify(
|
||||||
$severity,
|
$severity,
|
||||||
$subject_template,
|
"vzdump",
|
||||||
$body_template,
|
|
||||||
$notification_props,
|
$notification_props,
|
||||||
$fields,
|
$fields,
|
||||||
$notification_config
|
$notification_config
|
||||||
@ -556,8 +585,7 @@ sub send_notification {
|
|||||||
# no email addresses were configured.
|
# no email addresses were configured.
|
||||||
PVE::Notify::notify(
|
PVE::Notify::notify(
|
||||||
$severity,
|
$severity,
|
||||||
$subject_template,
|
"vzdump",
|
||||||
$body_template,
|
|
||||||
$notification_props,
|
$notification_props,
|
||||||
$fields,
|
$fields,
|
||||||
);
|
);
|
||||||
@ -592,8 +620,10 @@ sub new {
|
|||||||
if ($k eq 'dumpdir' || $k eq 'storage') {
|
if ($k eq 'dumpdir' || $k eq 'storage') {
|
||||||
$opts->{$k} = $defaults->{$k} if !defined ($opts->{dumpdir}) &&
|
$opts->{$k} = $defaults->{$k} if !defined ($opts->{dumpdir}) &&
|
||||||
!defined ($opts->{storage});
|
!defined ($opts->{storage});
|
||||||
} else {
|
} elsif (!defined($opts->{$k})) {
|
||||||
$opts->{$k} = $defaults->{$k} if !defined ($opts->{$k});
|
$opts->{$k} = $defaults->{$k};
|
||||||
|
} elsif ($k eq 'performance') {
|
||||||
|
$opts->{$k} = merge_performance($opts->{$k}, $defaults->{$k});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1273,6 +1303,7 @@ sub exec_backup_task {
|
|||||||
debugmsg ('info', "Failed at " . strftime("%F %H:%M:%S", localtime()));
|
debugmsg ('info', "Failed at " . strftime("%F %H:%M:%S", localtime()));
|
||||||
|
|
||||||
eval { $self->run_hook_script ('backup-abort', $task, $logfd); };
|
eval { $self->run_hook_script ('backup-abort', $task, $logfd); };
|
||||||
|
debugmsg('warn', $@) if $@; # message already contains command with phase name
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$task->{state} = 'ok';
|
$task->{state} = 'ok';
|
||||||
@ -1304,6 +1335,7 @@ sub exec_backup_task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eval { $self->run_hook_script ('log-end', $task); };
|
eval { $self->run_hook_script ('log-end', $task); };
|
||||||
|
debugmsg('warn', $@) if $@; # message already contains command with phase name
|
||||||
|
|
||||||
die $err if $err && $err =~ m/^interrupted by signal$/;
|
die $err if $err && $err =~ m/^interrupted by signal$/;
|
||||||
}
|
}
|
||||||
@ -1453,6 +1485,7 @@ sub verify_vzdump_parameters {
|
|||||||
if defined($param->{'prune-backups'}) && defined($param->{maxfiles});
|
if defined($param->{'prune-backups'}) && defined($param->{maxfiles});
|
||||||
|
|
||||||
$parse_prune_backups_maxfiles->($param, 'CLI parameters');
|
$parse_prune_backups_maxfiles->($param, 'CLI parameters');
|
||||||
|
parse_fleecing($param);
|
||||||
parse_performance($param);
|
parse_performance($param);
|
||||||
|
|
||||||
if (my $template = $param->{'notes-template'}) {
|
if (my $template = $param->{'notes-template'}) {
|
||||||
|
@ -30,6 +30,19 @@ sha512sum: f9d19b4a6d3c6201cf7a4624baf2d16ef08e7668adec0b7ea1a9c0ab8d854c8a9d11d
|
|||||||
Infopage: https://linuxcontainers.org
|
Infopage: https://linuxcontainers.org
|
||||||
Description: LXC default image for alpine 3.18 (20230607)
|
Description: LXC default image for alpine 3.18 (20230607)
|
||||||
|
|
||||||
|
Package: alpine-3.19-default
|
||||||
|
Version: 20240207
|
||||||
|
Type: lxc
|
||||||
|
OS: alpine
|
||||||
|
Section: system
|
||||||
|
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||||
|
Architecture: amd64
|
||||||
|
Location: system/alpine-3.19-default_20240207_amd64.tar.xz
|
||||||
|
md5sum: fe35133232231ed5918c3828e91c4069
|
||||||
|
sha512sum: dec171b608802827a2b47ae6c473f71fdea5fae0064a7928749fc5a854a7220a3244e405fb5ad14d09f52e5325a399c903169a075cd2f6787326972094561f0a
|
||||||
|
Infopage: https://linuxcontainers.org
|
||||||
|
Description: LXC default image for alpine 3.19 (20240207)
|
||||||
|
|
||||||
Package: archlinux-base
|
Package: archlinux-base
|
||||||
Version: 20230608-1
|
Version: 20230608-1
|
||||||
Type: lxc
|
Type: lxc
|
||||||
@ -164,6 +177,20 @@ sha512sum: f3a6785c347da3867d074345b68db9c99ec2b269e454f715d234935014ca1dc9f7239
|
|||||||
Infopage: https://linuxcontainers.org
|
Infopage: https://linuxcontainers.org
|
||||||
Description: LXC default image for opensuse 15.5 (20231118)
|
Description: LXC default image for opensuse 15.5 (20231118)
|
||||||
|
|
||||||
|
Package: proxmox-mail-gateway-8.1-standard
|
||||||
|
Version: 8.1-1
|
||||||
|
Type: lxc
|
||||||
|
OS: debian-12
|
||||||
|
Section: mail
|
||||||
|
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||||
|
Architecture: amd64
|
||||||
|
Location: mail/proxmox-mail-gateway-8.1-standard_8.1-1_amd64.tar.zst
|
||||||
|
md5sum: f227d87298985adf3e8aa1e946437a0e
|
||||||
|
sha512sum: 9739d43874faaa8670e8b0cc593476e6a4bc49f8f641e2ef5b6b24366b2df34d752d98be87c4ffc22d5a3d08a75414e708a7769a1c3054ef60a90323b032a50b
|
||||||
|
Infopage: https://www.proxmox.com/en/proxmox-mail-gateway/overview
|
||||||
|
Description: Proxmox Mail Gateway 8.1
|
||||||
|
A full featured mail proxy for spam and virus filtering, optimized for container environment.
|
||||||
|
|
||||||
Package: proxmox-mailgateway-7.3-standard
|
Package: proxmox-mailgateway-7.3-standard
|
||||||
Version: 7.3-1
|
Version: 7.3-1
|
||||||
Type: lxc
|
Type: lxc
|
||||||
@ -178,20 +205,6 @@ Infopage: https://www.proxmox.com/de/proxmox-mail-gateway
|
|||||||
Description: Proxmox Mailgateway 7.3
|
Description: Proxmox Mailgateway 7.3
|
||||||
A full featured mail proxy for spam and virus filtering, optimized for container environment.
|
A full featured mail proxy for spam and virus filtering, optimized for container environment.
|
||||||
|
|
||||||
Package: proxmox-mailgateway-8.0-standard
|
|
||||||
Version: 8.0-1
|
|
||||||
Type: lxc
|
|
||||||
OS: debian-12
|
|
||||||
Section: mail
|
|
||||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
|
||||||
Architecture: amd64
|
|
||||||
Location: mail/proxmox-mailgateway-8.0-standard_8.0-1_amd64.tar.zst
|
|
||||||
md5sum: 7d321e5dfc6e1005231586d1871e3625
|
|
||||||
sha512sum: be5efcb8ee97f2bb1c638360191eda19f49e2063acb88da55c948c90c091063972cc9ea29e6aeaa4a85733e0fb2c99ea905d665ac693cb2bf06b091c4baf781f
|
|
||||||
Infopage: https://www.proxmox.com/de/proxmox-mail-gateway
|
|
||||||
Description: Proxmox Mailgateway 8.0
|
|
||||||
A full featured mail proxy for spam and virus filtering, optimized for container environment.
|
|
||||||
|
|
||||||
Package: rockylinux-9-default
|
Package: rockylinux-9-default
|
||||||
Version: 20221109
|
Version: 20221109
|
||||||
Type: lxc
|
Type: lxc
|
||||||
@ -260,3 +273,17 @@ sha512sum: 84bcb7348ba86026176ed35e5b798f89cb64b0bcbe27081e3f97e3002a6ec34d836bb
|
|||||||
Infopage: https://pve.proxmox.com/wiki/Linux_Container#pct_supported_distributions
|
Infopage: https://pve.proxmox.com/wiki/Linux_Container#pct_supported_distributions
|
||||||
Description: Ubuntu 23.10 Mantic (standard)
|
Description: Ubuntu 23.10 Mantic (standard)
|
||||||
A small Ubuntu 23.10 Mantic Minotaur system including all standard packages.
|
A small Ubuntu 23.10 Mantic Minotaur system including all standard packages.
|
||||||
|
|
||||||
|
Package: ubuntu-24.04-standard
|
||||||
|
Version: 24.04-2
|
||||||
|
Type: lxc
|
||||||
|
OS: ubuntu-24.04
|
||||||
|
Section: system
|
||||||
|
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||||
|
Architecture: amd64
|
||||||
|
Location: system/ubuntu-24.04-standard_24.04-2_amd64.tar.zst
|
||||||
|
md5sum: 4030982618eeae70854e8f9711adbd09
|
||||||
|
sha512sum: 45c2978e6b97fe292ada95fe06834276015e5739a594db4de2fdfd830fa0c37942e8ae118fc1e32ffd9154b3f9378b592738b668ea3957db41f2907b86f219de
|
||||||
|
Infopage: https://pve.proxmox.com/wiki/Linux_Container#pct_supported_distributions
|
||||||
|
Description: Ubuntu 24.04 Noble (standard)
|
||||||
|
A small Ubuntu 24.04 Noble Numbat system including all standard packages.
|
||||||
|
@ -25,6 +25,10 @@ SCRIPTS = \
|
|||||||
pveperf \
|
pveperf \
|
||||||
pvereport
|
pvereport
|
||||||
|
|
||||||
|
HELPERS = \
|
||||||
|
pve-startall-delay \
|
||||||
|
pve-init-ceph-crash
|
||||||
|
|
||||||
SERVICE_MANS = $(addsuffix .8, $(SERVICES))
|
SERVICE_MANS = $(addsuffix .8, $(SERVICES))
|
||||||
|
|
||||||
CLI_MANS = \
|
CLI_MANS = \
|
||||||
@ -82,7 +86,7 @@ install: $(SCRIPTS) $(CLI_MANS) $(SERVICE_MANS) $(BASH_COMPLETIONS) $(ZSH_COMPLE
|
|||||||
install -d $(BINDIR)
|
install -d $(BINDIR)
|
||||||
install -m 0755 $(SCRIPTS) $(BINDIR)
|
install -m 0755 $(SCRIPTS) $(BINDIR)
|
||||||
install -d $(USRSHARE)/helpers
|
install -d $(USRSHARE)/helpers
|
||||||
install -m 0755 pve-startall-delay $(USRSHARE)/helpers
|
install -m 0755 $(HELPERS) $(USRSHARE)/helpers
|
||||||
install -d $(MAN1DIR)
|
install -d $(MAN1DIR)
|
||||||
install -m 0644 $(CLI_MANS) $(MAN1DIR)
|
install -m 0644 $(CLI_MANS) $(MAN1DIR)
|
||||||
install -d $(MAN8DIR)
|
install -d $(MAN8DIR)
|
||||||
|
150
bin/pve-init-ceph-crash
Executable file
150
bin/pve-init-ceph-crash
Executable file
@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use List::Util qw(first);
|
||||||
|
|
||||||
|
use PVE::Ceph::Tools;
|
||||||
|
use PVE::Cluster;
|
||||||
|
use PVE::RADOS;
|
||||||
|
use PVE::RPCEnvironment;
|
||||||
|
|
||||||
|
my $ceph_cfg_file = 'ceph.conf';
|
||||||
|
my $keyring_value = '/etc/pve/ceph/$cluster.$name.keyring';
|
||||||
|
|
||||||
|
|
||||||
|
sub try_adapt_cfg {
|
||||||
|
my ($cfg) = @_;
|
||||||
|
|
||||||
|
my $entity = 'client.crash';
|
||||||
|
my $removed_key = 0;
|
||||||
|
|
||||||
|
print("Checking whether the configuration for '$entity' needs to be updated.\n");
|
||||||
|
|
||||||
|
my $add_keyring = sub {
|
||||||
|
print("Setting keyring path to '$keyring_value'.\n");
|
||||||
|
$cfg->{$entity}->{keyring} = $keyring_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!exists($cfg->{$entity})) {
|
||||||
|
print("Adding missing section for '$entity'.\n");
|
||||||
|
$add_keyring->();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists($cfg->{$entity}->{key})) {
|
||||||
|
print("Removing existing usage of key.\n");
|
||||||
|
delete($cfg->{$entity}->{key});
|
||||||
|
$removed_key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exists($cfg->{$entity}->{keyring})) {
|
||||||
|
print("Keyring path is missing from configuration.\n");
|
||||||
|
$add_keyring->();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $current_keyring_value = $cfg->{$entity}->{keyring};
|
||||||
|
if ($current_keyring_value ne $keyring_value) {
|
||||||
|
print("Current keyring path differs from expected path.\n");
|
||||||
|
$add_keyring->();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $removed_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub main {
|
||||||
|
# PVE::RADOS expects an active RPC Environment because it forks itself
|
||||||
|
# and may want to clean up after
|
||||||
|
my $rpcenv = PVE::RPCEnvironment->setup_default_cli_env();
|
||||||
|
|
||||||
|
if (!PVE::Ceph::Tools::check_ceph_installed('ceph_bin', 1)) {
|
||||||
|
print("Ceph is not installed. No action required.\n");
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $ceph_cfg_path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath');
|
||||||
|
if (PVE::Ceph::Tools::check_ceph_installed('ceph_mon', 1) && -f $ceph_cfg_path) {
|
||||||
|
my $pve_ceph_cfgdir = PVE::Ceph::Tools::get_config('pve_ceph_cfgdir');
|
||||||
|
if (! -d $pve_ceph_cfgdir) {
|
||||||
|
File::Path::make_path($pve_ceph_cfgdir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eval {
|
||||||
|
PVE::Ceph::Tools::check_ceph_inited();
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
print("Ceph is not initialized. No action required.\n");
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $rados = eval { PVE::RADOS->new() };
|
||||||
|
my $ceph_crash_key_path = PVE::Ceph::Tools::get_config('pve_ceph_crash_key_path');
|
||||||
|
|
||||||
|
my $inner_err = '';
|
||||||
|
|
||||||
|
my $rval = PVE::Cluster::cfs_lock_file($ceph_cfg_file, undef, sub {
|
||||||
|
eval {
|
||||||
|
my $cfg = PVE::Cluster::cfs_read_file($ceph_cfg_file);
|
||||||
|
|
||||||
|
if (!defined($rados)) {
|
||||||
|
my $has_mon_host = defined($cfg->{global}) && defined($cfg->{global}->{mon_host});
|
||||||
|
if ($has_mon_host && $cfg->{global}->{mon_host} ne '') {
|
||||||
|
die "Connection to RADOS failed even though a monitor is configured.\n" .
|
||||||
|
"Please verify whether your configuration in '$ceph_cfg_file' is correct.\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Connection to RADOS failed and no monitor is configured in '$ceph_cfg_file'.\n".
|
||||||
|
"Assuming that things are fine. No action required.\n"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $updated_keyring = PVE::Ceph::Tools::create_or_update_crash_keyring_file($rados);
|
||||||
|
|
||||||
|
if ($updated_keyring) {
|
||||||
|
print("Keyring file '$ceph_crash_key_path' was updated.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
my $changed = try_adapt_cfg($cfg);
|
||||||
|
|
||||||
|
if ($changed) {
|
||||||
|
print("Committing updated configuration to '$ceph_cfg_file'.\n");
|
||||||
|
PVE::Cluster::cfs_write_file($ceph_cfg_file, $cfg);
|
||||||
|
print("Successfully updated configuration for 'ceph-crash.service'.\n");
|
||||||
|
} else {
|
||||||
|
print("Configuration in '$ceph_cfg_file' does not need to be updated.\n");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$inner_err = $@;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
# cfs_lock_file sets $@ explicitly to undef
|
||||||
|
my $err = $@ // '';
|
||||||
|
|
||||||
|
my $has_err = !defined($rval) || $inner_err || $err;
|
||||||
|
|
||||||
|
if ($has_err) {
|
||||||
|
$err =~ s/\n*$//;
|
||||||
|
$inner_err =~ s/\n*$//;
|
||||||
|
|
||||||
|
if (!defined($rval)) {
|
||||||
|
warn("Error while acquiring or releasing lock for '$ceph_cfg_file'.\n");
|
||||||
|
warn("Error: $err\n") if $err ne '';
|
||||||
|
}
|
||||||
|
|
||||||
|
warn("Failed to configure keyring for 'ceph-crash.service'.\nError: $inner_err\n")
|
||||||
|
if $inner_err ne '';
|
||||||
|
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
@ -76,7 +76,7 @@ __END__
|
|||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
pveversion - Proxmox VE version info
|
pveversion - Proxmox VE version info
|
||||||
|
|
||||||
=head1 SYNOPSIS
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
@ -16,3 +16,4 @@
|
|||||||
#exclude-path: PATHLIST
|
#exclude-path: PATHLIST
|
||||||
#pigz: N
|
#pigz: N
|
||||||
#notes-template: {{guestname}}
|
#notes-template: {{guestname}}
|
||||||
|
#pbs-change-detection-mode: legacy|data|metadata
|
||||||
|
141
debian/changelog
vendored
141
debian/changelog
vendored
@ -1,3 +1,144 @@
|
|||||||
|
pve-manager (8.2.4) bookworm; urgency=medium
|
||||||
|
|
||||||
|
* pvestatd: clear trailing newlines
|
||||||
|
|
||||||
|
* www: advanced backup: add pbs change detection mode selector
|
||||||
|
|
||||||
|
* vzdump: add pbs-change-detection-mode to config template
|
||||||
|
|
||||||
|
-- Proxmox Support Team <support@proxmox.com> Mon, 10 Jun 2024 13:59:52 +0200
|
||||||
|
|
||||||
|
pve-manager (8.2.3) bookworm; urgency=medium
|
||||||
|
|
||||||
|
* update shipped appliance info index
|
||||||
|
|
||||||
|
* api: add proxmox-firewall to versions pkg list
|
||||||
|
|
||||||
|
* gitignore: ignore any test artifacts
|
||||||
|
|
||||||
|
* tests: remove vzdump_notification test
|
||||||
|
|
||||||
|
* notifications: use named templates instead of in-code templates
|
||||||
|
|
||||||
|
-- Proxmox Support Team <support@proxmox.com> Tue, 04 Jun 2024 11:08:41 +0200
|
||||||
|
|
||||||
|
pve-manager (8.2.2) bookworm; urgency=medium
|
||||||
|
|
||||||
|
* ui: fix form-reset behavior of backup job editor
|
||||||
|
|
||||||
|
* ui: backup jobs: fix fleecing parameters for 'run now' button
|
||||||
|
|
||||||
|
* ui: mobile: fix login for users that have a TOTP based second factor set up
|
||||||
|
|
||||||
|
* ui: mobile: show is setup is lacking the best recommendations for
|
||||||
|
enterprise production set ups
|
||||||
|
|
||||||
|
* vzdump: also warn when hook script fails for backup-abort or log-end phase
|
||||||
|
|
||||||
|
* ui: user edit: protect user's TFA settings for new factor variants
|
||||||
|
|
||||||
|
* fix #5251: ui: login: set autocomplete on password and user
|
||||||
|
|
||||||
|
* ui: esxi importer: try to better convey what live-import does
|
||||||
|
|
||||||
|
-- Proxmox Support Team <support@proxmox.com> Tue, 23 Apr 2024 21:33:31 +0200
|
||||||
|
|
||||||
|
pve-manager (8.2.1) bookworm; urgency=medium
|
||||||
|
|
||||||
|
* d/control: add proxmox-firewall as recommended dependency
|
||||||
|
|
||||||
|
* ui: backup job: correctly align descriptions with fields in advanced
|
||||||
|
options
|
||||||
|
|
||||||
|
* ui: backup job: rework empty-text for advanced fields again, mention schema
|
||||||
|
default values at the end of the descriptions
|
||||||
|
|
||||||
|
* ui: acme eab: handle missing meta field in directory response
|
||||||
|
|
||||||
|
* ui: qemu: add clipboard selection as advanced option to display editor
|
||||||
|
|
||||||
|
* ui: qemu: allow one to enable a vIOMMU (emulated IOMMU) in the machine
|
||||||
|
editor
|
||||||
|
|
||||||
|
* ui: backup job: allow setting up fleecing for a job in advanced config
|
||||||
|
|
||||||
|
-- Proxmox Support Team <support@proxmox.com> Mon, 22 Apr 2024 19:36:21 +0200
|
||||||
|
|
||||||
|
pve-manager (8.2.0) bookworm; urgency=medium
|
||||||
|
|
||||||
|
* node: firewall options: add setting to select new nftables-based firewall
|
||||||
|
implementation tech preview
|
||||||
|
|
||||||
|
* ui: virtual machines: add Windows Server 2025 to OS types
|
||||||
|
|
||||||
|
* ui: browser local settings: add new edit-notes-on-double-click option
|
||||||
|
|
||||||
|
* fix #4474: ui: guest stop: offer to overrule active shutdown tasks
|
||||||
|
|
||||||
|
* ui: backup job: disable 'mailtnotification' field if the notification mode
|
||||||
|
is set to 'auto'
|
||||||
|
|
||||||
|
* ui: backup job: switch order of 'mailto' and 'mailnotification' field
|
||||||
|
|
||||||
|
* ui: sdn: QinQ: vlan: properly validate bridge name
|
||||||
|
|
||||||
|
* ui: sdn: vlan: fix indentation in vlan edit dialogue
|
||||||
|
|
||||||
|
-- Proxmox Support Team <support@proxmox.com> Sun, 21 Apr 2024 13:03:54 +0200
|
||||||
|
|
||||||
|
pve-manager (8.1.11) bookworm; urgency=medium
|
||||||
|
|
||||||
|
* fix #4136: backup: implement fleecing option for improved guest
|
||||||
|
stability when the backup target is slow
|
||||||
|
|
||||||
|
* api: notifications: add missing 'smtp' to index
|
||||||
|
|
||||||
|
* use SSH command helper for pvesh and to get VNC connection info to benefit
|
||||||
|
from improvements like known host key pinning
|
||||||
|
|
||||||
|
* system report: list held-back and pinned APT packages and priorities of APT
|
||||||
|
sources
|
||||||
|
|
||||||
|
* system report: avoid printing the contents of certain wrongly named
|
||||||
|
configuration files
|
||||||
|
|
||||||
|
* system report: output cluster-wide job configuration
|
||||||
|
|
||||||
|
* system report: output kernel command line from current boot
|
||||||
|
|
||||||
|
* ui: acl: allow searching by typing in the group selector
|
||||||
|
|
||||||
|
* api: apt versions: track optional amd64-/intel-microcode and
|
||||||
|
pve-esxi-import-tools packages
|
||||||
|
|
||||||
|
* ui: acme: add External Account Binding (EAB) related fields
|
||||||
|
|
||||||
|
* fix #5093: ui: acme: expose custom directory option
|
||||||
|
|
||||||
|
* d/postinst: make deb-systemd-invoke non-fatal to avoid potentially
|
||||||
|
breaking an update for unrelated reasons
|
||||||
|
|
||||||
|
* fix #4513: ui: backup job: add tab for advanced options
|
||||||
|
|
||||||
|
* vzdump: improve handling performance settings, by using a per-property
|
||||||
|
fallback and honoring the schema defaults
|
||||||
|
|
||||||
|
* ui: lxc: add edit window for device passthrough
|
||||||
|
|
||||||
|
* ui: lxc: add firewall log view filtering
|
||||||
|
|
||||||
|
* fix #4963: ui: firewall: fix edge case where editing firewall rules using
|
||||||
|
ips / cidrs would be broken
|
||||||
|
|
||||||
|
* fix #1905: ui: VM: allow moving unused disks
|
||||||
|
|
||||||
|
* fix #4759: ceph: configure ceph-crash.service and its key
|
||||||
|
|
||||||
|
* sdn: evpn: allow empty primary exit node in zone form which was broken by a
|
||||||
|
change in libpve-network-perl
|
||||||
|
|
||||||
|
-- Proxmox Support Team <support@proxmox.com> Fri, 19 Apr 2024 16:24:37 +0200
|
||||||
|
|
||||||
pve-manager (8.1.10) bookworm; urgency=medium
|
pve-manager (8.1.10) bookworm; urgency=medium
|
||||||
|
|
||||||
* guest import: allow setting VLAN-tag for network devices in advanced tab
|
* guest import: allow setting VLAN-tag for network devices in advanced tab
|
||||||
|
35
debian/control
vendored
35
debian/control
vendored
@ -11,8 +11,8 @@ Build-Depends: debhelper-compat (= 13),
|
|||||||
libpve-access-control (>= 8.0.7),
|
libpve-access-control (>= 8.0.7),
|
||||||
libpve-cluster-api-perl,
|
libpve-cluster-api-perl,
|
||||||
libpve-cluster-perl (>= 6.1-6),
|
libpve-cluster-perl (>= 6.1-6),
|
||||||
libpve-common-perl (>= 7.2-6),
|
libpve-common-perl (>= 8.1.2),
|
||||||
libpve-guest-common-perl (>= 5.0.2),
|
libpve-guest-common-perl (>= 5.1.1),
|
||||||
libpve-http-server-perl (>= 2.0-12),
|
libpve-http-server-perl (>= 2.0-12),
|
||||||
libpve-notify-perl,
|
libpve-notify-perl,
|
||||||
libpve-rs-perl (>= 0.7.1),
|
libpve-rs-perl (>= 0.7.1),
|
||||||
@ -60,12 +60,12 @@ Depends: apt (>= 1.5~),
|
|||||||
libpve-access-control (>= 8.1.3),
|
libpve-access-control (>= 8.1.3),
|
||||||
libpve-cluster-api-perl (>= 7.0-5),
|
libpve-cluster-api-perl (>= 7.0-5),
|
||||||
libpve-cluster-perl (>= 7.2-3),
|
libpve-cluster-perl (>= 7.2-3),
|
||||||
libpve-common-perl (>= 7.2-7),
|
libpve-common-perl (>= 8.2.0),
|
||||||
libpve-guest-common-perl (>= 5.0.6),
|
libpve-guest-common-perl (>= 5.1.0),
|
||||||
libpve-http-server-perl (>= 4.1-1),
|
libpve-http-server-perl (>= 4.1-1),
|
||||||
libpve-notify-perl (>= 8.0.5),
|
libpve-notify-perl (>= 8.0.5),
|
||||||
libpve-rs-perl (>= 0.7.1),
|
libpve-rs-perl (>= 0.7.1),
|
||||||
libpve-storage-perl (>= 8.1.3),
|
libpve-storage-perl (>= 8.1.5),
|
||||||
librados2-perl (>= 1.3-1),
|
librados2-perl (>= 1.3-1),
|
||||||
libtemplate-perl,
|
libtemplate-perl,
|
||||||
libterm-readline-gnu-perl,
|
libterm-readline-gnu-perl,
|
||||||
@ -74,37 +74,36 @@ Depends: apt (>= 1.5~),
|
|||||||
libwww-perl (>= 6.04-1),
|
libwww-perl (>= 6.04-1),
|
||||||
logrotate,
|
logrotate,
|
||||||
lzop,
|
lzop,
|
||||||
zstd,
|
|
||||||
novnc-pve (>= 1.2.0-2~),
|
novnc-pve (>= 1.2.0-2~),
|
||||||
pciutils,
|
pciutils,
|
||||||
perl (>= 5.10.0-19),
|
perl (>= 5.10.0-19),
|
||||||
postfix | mail-transport-agent,
|
postfix | mail-transport-agent,
|
||||||
proxmox-mail-forward,
|
proxmox-mail-forward,
|
||||||
proxmox-mini-journalreader (>= 1.3-1),
|
proxmox-mini-journalreader (>= 1.3-1),
|
||||||
proxmox-widget-toolkit (>= 4.1.5),
|
proxmox-widget-toolkit (>= 4.2.0),
|
||||||
pve-cluster (>= 8.0.5),
|
pve-cluster (>= 8.0.5),
|
||||||
pve-container (>= 5.0.5),
|
pve-container (>= 5.1.11),
|
||||||
pve-docs (>= 8.0~~),
|
pve-docs (>= 8.0~~),
|
||||||
pve-firewall,
|
pve-firewall,
|
||||||
pve-ha-manager,
|
pve-ha-manager,
|
||||||
pve-i18n (>= 3.2.0~),
|
pve-i18n (>= 3.2.0~),
|
||||||
pve-xtermjs (>= 4.7.0-1),
|
pve-xtermjs (>= 4.7.0-1),
|
||||||
qemu-server (>= 8.0.4),
|
qemu-server (>= 8.1.2),
|
||||||
rsync,
|
rsync,
|
||||||
spiceterm,
|
spiceterm,
|
||||||
systemd,
|
systemd,
|
||||||
vncterm,
|
vncterm,
|
||||||
wget,
|
wget,
|
||||||
|
zstd,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${perl:Depends},
|
${perl:Depends},
|
||||||
${shlibs:Depends}
|
${shlibs:Depends},
|
||||||
Recommends: proxmox-offline-mirror-helper, libpve-network-perl (>= 0.9~)
|
Recommends: libpve-network-perl (>= 0.9~),
|
||||||
Conflicts: vlan,
|
proxmox-firewall,
|
||||||
vzdump,
|
proxmox-offline-mirror-helper,
|
||||||
Replaces: vlan,
|
Conflicts: vlan, vzdump,
|
||||||
vzdump,
|
Replaces: vlan, vzdump,
|
||||||
Provides: vlan,
|
Provides: vlan, vzdump,
|
||||||
vzdump,
|
Breaks: libpve-network-perl (<< 0.5-1),
|
||||||
Breaks: libpve-network-perl (<< 0.5-1)
|
|
||||||
Description: Proxmox Virtual Environment Management Tools
|
Description: Proxmox Virtual Environment Management Tools
|
||||||
This package contains the Proxmox Virtual Environment management tools.
|
This package contains the Proxmox Virtual Environment management tools.
|
||||||
|
2
debian/copyright
vendored
2
debian/copyright
vendored
@ -1,4 +1,4 @@
|
|||||||
Copyright (C) 2010-2023 Proxmox Server Solutions GmbH
|
Copyright (C) 2010-2024 Proxmox Server Solutions GmbH
|
||||||
|
|
||||||
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
|
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
|
||||||
|
|
||||||
|
28
debian/postinst
vendored
28
debian/postinst
vendored
@ -80,6 +80,18 @@ EOF
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_ceph_conf() {
|
||||||
|
UNIT='ceph-crash.service'
|
||||||
|
|
||||||
|
# Don't fail in case user has "exotic" configuration where RADOS
|
||||||
|
# isn't available on all nodes for some reason
|
||||||
|
/usr/share/pve-manager/helpers/pve-init-ceph-crash || true
|
||||||
|
|
||||||
|
if systemctl -q is-enabled "$UNIT" 2> /dev/null; then
|
||||||
|
deb-systemd-invoke restart "$UNIT" || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
migrate_apt_auth_conf() {
|
migrate_apt_auth_conf() {
|
||||||
output=""
|
output=""
|
||||||
removed=""
|
removed=""
|
||||||
@ -123,11 +135,11 @@ case "$1" in
|
|||||||
# the ExecStartPre doesn't triggers on service reload, so just in case
|
# the ExecStartPre doesn't triggers on service reload, so just in case
|
||||||
pvecm updatecerts --silent || true
|
pvecm updatecerts --silent || true
|
||||||
|
|
||||||
deb-systemd-invoke reload-or-try-restart pvedaemon.service
|
deb-systemd-invoke reload-or-try-restart pvedaemon.service || true
|
||||||
deb-systemd-invoke reload-or-try-restart pvestatd.service
|
deb-systemd-invoke reload-or-try-restart pvestatd.service || true
|
||||||
deb-systemd-invoke reload-or-try-restart pveproxy.service
|
deb-systemd-invoke reload-or-try-restart pveproxy.service || true
|
||||||
deb-systemd-invoke reload-or-try-restart spiceproxy.service
|
deb-systemd-invoke reload-or-try-restart spiceproxy.service || true
|
||||||
deb-systemd-invoke reload-or-try-restart pvescheduler.service
|
deb-systemd-invoke reload-or-try-restart pvescheduler.service || true
|
||||||
|
|
||||||
exit 0;;
|
exit 0;;
|
||||||
|
|
||||||
@ -190,6 +202,10 @@ case "$1" in
|
|||||||
|
|
||||||
set_lvm_conf
|
set_lvm_conf
|
||||||
|
|
||||||
|
if test -n "$2" && dpkg --compare-versions "$2" 'lt' '8.1.11'; then
|
||||||
|
update_ceph_conf
|
||||||
|
fi
|
||||||
|
|
||||||
if test ! -e /proxmox_install_mode; then
|
if test ! -e /proxmox_install_mode; then
|
||||||
# modeled after code generated by dh_start
|
# modeled after code generated by dh_start
|
||||||
for unit in ${UNITS}; do
|
for unit in ${UNITS}; do
|
||||||
@ -199,7 +215,7 @@ case "$1" in
|
|||||||
dh_action="start"
|
dh_action="start"
|
||||||
fi
|
fi
|
||||||
if systemctl -q is-enabled "$unit"; then
|
if systemctl -q is-enabled "$unit"; then
|
||||||
deb-systemd-invoke $dh_action "$unit"
|
deb-systemd-invoke $dh_action "$unit" || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
24
templates/Makefile
Normal file
24
templates/Makefile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
NOTIFICATION_TEMPLATES= \
|
||||||
|
default/test-subject.txt.hbs \
|
||||||
|
default/test-body.txt.hbs \
|
||||||
|
default/test-body.html.hbs \
|
||||||
|
default/vzdump-subject.txt.hbs \
|
||||||
|
default/vzdump-body.txt.hbs \
|
||||||
|
default/vzdump-body.html.hbs \
|
||||||
|
default/replication-subject.txt.hbs \
|
||||||
|
default/replication-body.txt.hbs \
|
||||||
|
default/replication-body.html.hbs \
|
||||||
|
default/package-updates-subject.txt.hbs \
|
||||||
|
default/package-updates-body.txt.hbs \
|
||||||
|
default/package-updates-body.html.hbs \
|
||||||
|
|
||||||
|
all:
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install:
|
||||||
|
install -dm 0755 $(DESTDIR)/usr/share/pve-manager/templates/default
|
||||||
|
$(foreach i,$(NOTIFICATION_TEMPLATES), \
|
||||||
|
install -m644 $(i) $(DESTDIR)/usr/share/pve-manager/templates/$(i) ;)
|
||||||
|
|
||||||
|
|
||||||
|
clean:
|
6
templates/default/package-updates-body.html.hbs
Normal file
6
templates/default/package-updates-body.html.hbs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
The following updates are available:
|
||||||
|
{{table updates}}
|
||||||
|
</body>
|
||||||
|
</html>
|
3
templates/default/package-updates-body.txt.hbs
Normal file
3
templates/default/package-updates-body.txt.hbs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
The following updates are available:
|
||||||
|
|
||||||
|
{{table updates}}
|
1
templates/default/package-updates-subject.txt.hbs
Normal file
1
templates/default/package-updates-subject.txt.hbs
Normal file
@ -0,0 +1 @@
|
|||||||
|
New software packages available ({{hostname}})
|
18
templates/default/replication-body.html.hbs
Normal file
18
templates/default/replication-body.html.hbs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
Replication job '{{job-id}}' with target '{{job-target}}' and schedule '{{job-schedule}}' failed!<br/><br/>
|
||||||
|
|
||||||
|
Last successful sync: {{timestamp last-sync}}<br/>
|
||||||
|
Next sync try: {{timestamp next-sync}}<br/>
|
||||||
|
Failure count: {{failure-count}}<br/>
|
||||||
|
|
||||||
|
{{#if (eq failure-count 3)}}
|
||||||
|
Note: The system will now reduce the frequency of error reports, as the job appears to be stuck.
|
||||||
|
{{/if}}
|
||||||
|
<br/>
|
||||||
|
Error:<br/>
|
||||||
|
<pre>
|
||||||
|
{{error}}
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
templates/default/replication-body.txt.hbs
Normal file
12
templates/default/replication-body.txt.hbs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Replication job '{{job-id}}' with target '{{job-target}}' and schedule '{{job-schedule}}' failed!
|
||||||
|
|
||||||
|
Last successful sync: {{timestamp last-sync}}
|
||||||
|
Next sync try: {{timestamp next-sync}}
|
||||||
|
Failure count: {{failure-count}}
|
||||||
|
|
||||||
|
{{#if (eq failure-count 3)}}
|
||||||
|
Note: The system will now reduce the frequency of error reports, as the job
|
||||||
|
appears to be stuck.
|
||||||
|
{{/if}}
|
||||||
|
Error:
|
||||||
|
{{ error }}
|
1
templates/default/replication-subject.txt.hbs
Normal file
1
templates/default/replication-subject.txt.hbs
Normal file
@ -0,0 +1 @@
|
|||||||
|
Replication Job: '{{job-id}}' failed
|
1
templates/default/test-body.html.hbs
Normal file
1
templates/default/test-body.html.hbs
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is a test of the notification target '{{ target }}'.
|
1
templates/default/test-body.txt.hbs
Normal file
1
templates/default/test-body.txt.hbs
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is a test of the notification target '{{ target }}'.
|
1
templates/default/test-subject.txt.hbs
Normal file
1
templates/default/test-subject.txt.hbs
Normal file
@ -0,0 +1 @@
|
|||||||
|
Test notification
|
11
templates/default/vzdump-body.html.hbs
Normal file
11
templates/default/vzdump-body.html.hbs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
{{error-message}}
|
||||||
|
<h1 style="font-size: 1.2em">Details</h1>
|
||||||
|
{{table guest-table}}
|
||||||
|
Total running time: {{duration total-time}}<br/>
|
||||||
|
Total size: {{human-bytes total-size}}<br/>
|
||||||
|
<h1 style="font-size: 1.2em">Logs</h1>
|
||||||
|
<pre>{{logs}}</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
templates/default/vzdump-body.txt.hbs
Normal file
10
templates/default/vzdump-body.txt.hbs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{{error-message}}
|
||||||
|
Details
|
||||||
|
=======
|
||||||
|
{{table guest-table}}
|
||||||
|
Total running time: {{duration total-time}}
|
||||||
|
Total size: {{human-bytes total-size}}
|
||||||
|
|
||||||
|
Logs
|
||||||
|
====
|
||||||
|
{{logs}}
|
1
templates/default/vzdump-subject.txt.hbs
Normal file
1
templates/default/vzdump-subject.txt.hbs
Normal file
@ -0,0 +1 @@
|
|||||||
|
vzdump backup status ({{hostname}}): {{status-text}}
|
@ -5,7 +5,7 @@ all:
|
|||||||
export PERLLIB=..
|
export PERLLIB=..
|
||||||
|
|
||||||
.PHONY: check
|
.PHONY: check
|
||||||
check: test-replication test-balloon test-vzdump-notification test-vzdump test-osd
|
check: test-replication test-balloon test-vzdump test-osd
|
||||||
|
|
||||||
.PHONY: test-balloon
|
.PHONY: test-balloon
|
||||||
test-balloon:
|
test-balloon:
|
||||||
@ -17,10 +17,6 @@ test-replication: replication1.t replication2.t replication3.t replication4.t re
|
|||||||
replication%.t: replication_test%.pl
|
replication%.t: replication_test%.pl
|
||||||
./$<
|
./$<
|
||||||
|
|
||||||
.PHONY: test-vzdump-notification
|
|
||||||
test-vzdump-notification:
|
|
||||||
./vzdump_notification_test.pl
|
|
||||||
|
|
||||||
.PHONY: test-vzdump
|
.PHONY: test-vzdump
|
||||||
test-vzdump: test-vzdump-guest-included test-vzdump-new
|
test-vzdump: test-vzdump-guest-included test-vzdump-new
|
||||||
|
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
#!/usr/bin/perl
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
use lib '..';
|
|
||||||
|
|
||||||
use Test::More tests => 3;
|
|
||||||
use Test::MockModule;
|
|
||||||
|
|
||||||
use PVE::VZDump;
|
|
||||||
|
|
||||||
my $STATUS = qr/.*status.*/;
|
|
||||||
my $NO_LOGFILE = qr/.*Could not open log file.*/;
|
|
||||||
my $LOG_TOO_LONG = qr/.*Log output was too long.*/;
|
|
||||||
my $TEST_FILE_PATH = '/tmp/mail_test';
|
|
||||||
my $TEST_FILE_WRONG_PATH = '/tmp/mail_test_wrong';
|
|
||||||
|
|
||||||
sub prepare_mail_with_status {
|
|
||||||
open(TEST_FILE, '>', $TEST_FILE_PATH); # Removes previous content
|
|
||||||
print TEST_FILE "start of log file\n";
|
|
||||||
print TEST_FILE "status: 0\% this should not be in the mail\n";
|
|
||||||
print TEST_FILE "status: 55\% this should not be in the mail\n";
|
|
||||||
print TEST_FILE "status: 100\% this should not be in the mail\n";
|
|
||||||
print TEST_FILE "end of log file\n";
|
|
||||||
close(TEST_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub prepare_long_mail {
|
|
||||||
open(TEST_FILE, '>', $TEST_FILE_PATH); # Removes previous content
|
|
||||||
# 0.5 MB * 2 parts + the overview tables gives more than 1 MB mail
|
|
||||||
print TEST_FILE "a" x (1024*1024);
|
|
||||||
close(TEST_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
my $result_text;
|
|
||||||
my $result_properties;
|
|
||||||
|
|
||||||
my $mock_notification_module = Test::MockModule->new('PVE::Notify');
|
|
||||||
my $mocked_notify = sub {
|
|
||||||
my ($severity, $title, $text, $properties, $metadata) = @_;
|
|
||||||
|
|
||||||
$result_text = $text;
|
|
||||||
$result_properties = $properties;
|
|
||||||
};
|
|
||||||
my $mocked_notify_short = sub {
|
|
||||||
my (@params) = @_;
|
|
||||||
return $mocked_notify->('<some severity>', @params);
|
|
||||||
};
|
|
||||||
|
|
||||||
$mock_notification_module->mock(
|
|
||||||
'notify' => $mocked_notify,
|
|
||||||
'info' => $mocked_notify_short,
|
|
||||||
'notice' => $mocked_notify_short,
|
|
||||||
'warning' => $mocked_notify_short,
|
|
||||||
'error' => $mocked_notify_short,
|
|
||||||
);
|
|
||||||
$mock_notification_module->mock('cfs_read_file', sub {
|
|
||||||
my $path = shift;
|
|
||||||
|
|
||||||
if ($path eq 'datacenter.cfg') {
|
|
||||||
return {};
|
|
||||||
} elsif ($path eq 'notifications.cfg' || $path eq 'priv/notifications.cfg') {
|
|
||||||
return '';
|
|
||||||
} else {
|
|
||||||
die "unexpected cfs_read_file\n";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
my $MAILTO = ['test_address@proxmox.com'];
|
|
||||||
my $SELF = {
|
|
||||||
opts => { mailto => $MAILTO },
|
|
||||||
cmdline => 'test_command_on_cli',
|
|
||||||
};
|
|
||||||
|
|
||||||
my $task = { state => 'ok', vmid => '100', };
|
|
||||||
my $tasklist;
|
|
||||||
sub prepare_test {
|
|
||||||
$result_text = undef;
|
|
||||||
$task->{tmplog} = shift;
|
|
||||||
$tasklist = [ $task ];
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
prepare_test($TEST_FILE_WRONG_PATH);
|
|
||||||
PVE::VZDump::send_notification($SELF, $tasklist, 0, undef, undef, undef);
|
|
||||||
like($result_properties->{logs}, $NO_LOGFILE, "Missing logfile is detected");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
prepare_test($TEST_FILE_PATH);
|
|
||||||
prepare_mail_with_status();
|
|
||||||
PVE::VZDump::send_notification($SELF, $tasklist, 0, undef, undef, undef);
|
|
||||||
unlike($result_properties->{"status-text"}, $STATUS, "Status are not in text part of mails");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
prepare_test($TEST_FILE_PATH);
|
|
||||||
prepare_long_mail();
|
|
||||||
PVE::VZDump::send_notification($SELF, $tasklist, 0, undef, undef, undef);
|
|
||||||
like($result_properties->{logs}, $LOG_TOO_LONG, "Text part of mails gets shortened");
|
|
||||||
}
|
|
||||||
unlink $TEST_FILE_PATH;
|
|
@ -16,6 +16,7 @@ JSSRC= \
|
|||||||
data/PermPathStore.js \
|
data/PermPathStore.js \
|
||||||
data/ResourceStore.js \
|
data/ResourceStore.js \
|
||||||
data/model/RRDModels.js \
|
data/model/RRDModels.js \
|
||||||
|
container/TwoColumnContainer.js \
|
||||||
form/ACMEAPISelector.js \
|
form/ACMEAPISelector.js \
|
||||||
form/ACMEAccountSelector.js \
|
form/ACMEAccountSelector.js \
|
||||||
form/ACMEPluginSelector.js \
|
form/ACMEPluginSelector.js \
|
||||||
@ -97,6 +98,7 @@ JSSRC= \
|
|||||||
grid/Replication.js \
|
grid/Replication.js \
|
||||||
grid/ResourceGrid.js \
|
grid/ResourceGrid.js \
|
||||||
panel/ConfigPanel.js \
|
panel/ConfigPanel.js \
|
||||||
|
panel/BackupAdvancedOptions.js \
|
||||||
panel/BackupJobPrune.js \
|
panel/BackupJobPrune.js \
|
||||||
panel/HealthWidget.js \
|
panel/HealthWidget.js \
|
||||||
panel/IPSet.js \
|
panel/IPSet.js \
|
||||||
@ -131,6 +133,7 @@ JSSRC= \
|
|||||||
window/ScheduleSimulator.js \
|
window/ScheduleSimulator.js \
|
||||||
window/Wizard.js \
|
window/Wizard.js \
|
||||||
window/GuestDiskReassign.js \
|
window/GuestDiskReassign.js \
|
||||||
|
window/GuestStop.js \
|
||||||
window/TreeSettingsEdit.js \
|
window/TreeSettingsEdit.js \
|
||||||
window/PCIMapEdit.js \
|
window/PCIMapEdit.js \
|
||||||
window/USBMapEdit.js \
|
window/USBMapEdit.js \
|
||||||
@ -188,6 +191,7 @@ JSSRC= \
|
|||||||
lxc/CmdMenu.js \
|
lxc/CmdMenu.js \
|
||||||
lxc/Config.js \
|
lxc/Config.js \
|
||||||
lxc/CreateWizard.js \
|
lxc/CreateWizard.js \
|
||||||
|
lxc/DeviceEdit.js \
|
||||||
lxc/DNS.js \
|
lxc/DNS.js \
|
||||||
lxc/FeaturesEdit.js \
|
lxc/FeaturesEdit.js \
|
||||||
lxc/MPEdit.js \
|
lxc/MPEdit.js \
|
||||||
|
@ -51,7 +51,7 @@ Ext.define('PVE.Utils', {
|
|||||||
{ desc: '2.4 Kernel', val: 'l24' },
|
{ desc: '2.4 Kernel', val: 'l24' },
|
||||||
],
|
],
|
||||||
'Microsoft Windows': [
|
'Microsoft Windows': [
|
||||||
{ desc: '11/2022', val: 'win11' },
|
{ desc: '11/2022/2025', val: 'win11' },
|
||||||
{ desc: '10/2016/2019', val: 'win10' },
|
{ desc: '10/2016/2019', val: 'win10' },
|
||||||
{ desc: '8.x/2012/2012r2', val: 'win8' },
|
{ desc: '8.x/2012/2012r2', val: 'win8' },
|
||||||
{ desc: '7/2008r2', val: 'win7' },
|
{ desc: '7/2008r2', val: 'win7' },
|
||||||
@ -1594,14 +1594,14 @@ Ext.define('PVE.Utils', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mp_counts: {
|
lxc_mp_counts: {
|
||||||
mp: 256,
|
mp: 256,
|
||||||
unused: 256,
|
unused: 256,
|
||||||
},
|
},
|
||||||
|
|
||||||
forEachMP: function(func, includeUnused) {
|
forEachLxcMP: function(func, includeUnused) {
|
||||||
for (let i = 0; i < PVE.Utils.mp_counts.mp; i++) {
|
for (let i = 0; i < PVE.Utils.lxc_mp_counts.mp; i++) {
|
||||||
let cont = func('mp', i);
|
let cont = func('mp', i, `mp${i}`);
|
||||||
if (!cont && cont !== undefined) {
|
if (!cont && cont !== undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1611,8 +1611,19 @@ Ext.define('PVE.Utils', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < PVE.Utils.mp_counts.unused; i++) {
|
for (let i = 0; i < PVE.Utils.lxc_mp_counts.unused; i++) {
|
||||||
let cont = func('unused', i);
|
let cont = func('unused', i, `unused${i}`);
|
||||||
|
if (!cont && cont !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
lxc_dev_count: 256,
|
||||||
|
|
||||||
|
forEachLxcDev: function(func) {
|
||||||
|
for (let i = 0; i < PVE.Utils.lxc_dev_count; i++) {
|
||||||
|
let cont = func(i, `dev${i}`);
|
||||||
if (!cont && cont !== undefined) {
|
if (!cont && cont !== undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1875,8 +1886,8 @@ Ext.define('PVE.Utils', {
|
|||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
nextFreeMP: function(type, config) {
|
nextFreeLxcMP: function(type, config) {
|
||||||
for (let i = 0; i < PVE.Utils.mp_counts[type]; i++) {
|
for (let i = 0; i < PVE.Utils.lxc_mp_counts[type]; i++) {
|
||||||
let confid = `${type}${i}`;
|
let confid = `${type}${i}`;
|
||||||
if (!Ext.isDefined(config[confid])) {
|
if (!Ext.isDefined(config[confid])) {
|
||||||
return {
|
return {
|
||||||
|
57
www/manager6/container/TwoColumnContainer.js
Normal file
57
www/manager6/container/TwoColumnContainer.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// This is a container intended to show a field on the first column and one on the second column.
|
||||||
|
// One can set a ratio for the field sizes.
|
||||||
|
//
|
||||||
|
// Works around a limitation of our input panel column1/2 handling that entries are not vertically
|
||||||
|
// aligned when one of them has wrapping text (like it happens sometimes with such longer
|
||||||
|
// descriptions)
|
||||||
|
Ext.define('PVE.container.TwoColumnContainer', {
|
||||||
|
extend: 'Ext.container.Container',
|
||||||
|
alias: 'widget.pveTwoColumnContainer',
|
||||||
|
|
||||||
|
layout: {
|
||||||
|
type: 'hbox',
|
||||||
|
align: 'stretch',
|
||||||
|
},
|
||||||
|
|
||||||
|
// The default ratio of the start widget. It an be an integer or a floating point number
|
||||||
|
startFlex: 1,
|
||||||
|
|
||||||
|
// The default ratio of the end widget. It an be an integer or a floating point number
|
||||||
|
endFlex: 1,
|
||||||
|
|
||||||
|
// the padding between the two columns
|
||||||
|
columnPadding: 20,
|
||||||
|
|
||||||
|
// the config of the first widget
|
||||||
|
startColumn: undefined,
|
||||||
|
|
||||||
|
// the config of the second widget
|
||||||
|
endColumn: undefined,
|
||||||
|
|
||||||
|
// same as fields in a panel
|
||||||
|
padding: '0 0 5 0',
|
||||||
|
|
||||||
|
initComponent: function() {
|
||||||
|
let me = this;
|
||||||
|
|
||||||
|
if (!me.startColumn) {
|
||||||
|
throw "no start widget configured";
|
||||||
|
}
|
||||||
|
if (!me.endColumn) {
|
||||||
|
throw "no end widget configured";
|
||||||
|
}
|
||||||
|
|
||||||
|
Ext.apply(me, {
|
||||||
|
items: [
|
||||||
|
Ext.applyIf({ flex: me.startFlex }, me.startColumn),
|
||||||
|
{
|
||||||
|
xtype: 'box',
|
||||||
|
width: me.columnPadding,
|
||||||
|
},
|
||||||
|
Ext.applyIf({ flex: me.endFlex }, me.endColumn),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
me.callParent();
|
||||||
|
},
|
||||||
|
});
|
@ -7,6 +7,7 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
defaultFocus: undefined,
|
defaultFocus: undefined,
|
||||||
|
|
||||||
subject: gettext("Backup Job"),
|
subject: gettext("Backup Job"),
|
||||||
|
width: 720,
|
||||||
bodyPadding: 0,
|
bodyPadding: 0,
|
||||||
|
|
||||||
url: '/api2/extjs/cluster/backup',
|
url: '/api2/extjs/cluster/backup',
|
||||||
@ -145,58 +146,79 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
compressionChange: function(f, value, oldValue) {
|
||||||
|
this.getView().lookup('backupAdvanced').updateCompression(value, f.isDisabled());
|
||||||
|
},
|
||||||
|
|
||||||
|
compressionDisable: function(f) {
|
||||||
|
this.getView().lookup('backupAdvanced').updateCompression(f.getValue(), true);
|
||||||
|
},
|
||||||
|
|
||||||
|
compressionEnable: function(f) {
|
||||||
|
this.getView().lookup('backupAdvanced').updateCompression(f.getValue(), false);
|
||||||
|
},
|
||||||
|
|
||||||
|
prepareValues: function(data) {
|
||||||
|
let me = this;
|
||||||
|
let viewModel = me.getViewModel();
|
||||||
|
|
||||||
|
// Migrate 'new'-old notification-policy back to old-old mailnotification.
|
||||||
|
// Only should affect users who used pve-manager from pvetest. This was a remnant of
|
||||||
|
// notifications before the overhaul.
|
||||||
|
let policy = data['notification-policy'];
|
||||||
|
if (policy === 'always' || policy === 'failure') {
|
||||||
|
data.mailnotification = policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.exclude) {
|
||||||
|
data.vmid = data.exclude;
|
||||||
|
data.selMode = 'exclude';
|
||||||
|
} else if (data.all) {
|
||||||
|
data.vmid = '';
|
||||||
|
data.selMode = 'all';
|
||||||
|
} else if (data.pool) {
|
||||||
|
data.selMode = 'pool';
|
||||||
|
data.selPool = data.pool;
|
||||||
|
} else {
|
||||||
|
data.selMode = 'include';
|
||||||
|
}
|
||||||
|
viewModel.set('selMode', data.selMode);
|
||||||
|
|
||||||
|
if (data['prune-backups']) {
|
||||||
|
Object.assign(data, data['prune-backups']);
|
||||||
|
delete data['prune-backups'];
|
||||||
|
} else if (data.maxfiles !== undefined) {
|
||||||
|
if (data.maxfiles > 0) {
|
||||||
|
data['keep-last'] = data.maxfiles;
|
||||||
|
} else {
|
||||||
|
data['keep-all'] = 1;
|
||||||
|
}
|
||||||
|
delete data.maxfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data['notes-template']) {
|
||||||
|
data['notes-template'] =
|
||||||
|
PVE.Utils.unEscapeNotesTemplate(data['notes-template']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.performance) {
|
||||||
|
Object.assign(data, data.performance);
|
||||||
|
delete data.performance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
init: function(view) {
|
init: function(view) {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
if (view.isCreate) {
|
if (view.isCreate) {
|
||||||
me.lookup('modeSelector').setValue('include');
|
me.lookup('modeSelector').setValue('include');
|
||||||
} else {
|
} else {
|
||||||
view.load({
|
view.load({
|
||||||
success: function(response, _options) {
|
success: function(response, _options) {
|
||||||
let data = response.result.data;
|
let values = me.prepareValues(response.result.data);
|
||||||
|
view.setValues(values);
|
||||||
// Migrate 'new'-old notification-policy back to
|
|
||||||
// old-old mailnotification. Only should affect
|
|
||||||
// users who used pve-manager from pvetest.
|
|
||||||
// This was a remnant of notifications before the
|
|
||||||
// overhaul.
|
|
||||||
let policy = data['notification-policy'];
|
|
||||||
if (policy === 'always' || policy === 'failure') {
|
|
||||||
data.mailnotification = policy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.exclude) {
|
|
||||||
data.vmid = data.exclude;
|
|
||||||
data.selMode = 'exclude';
|
|
||||||
} else if (data.all) {
|
|
||||||
data.vmid = '';
|
|
||||||
data.selMode = 'all';
|
|
||||||
} else if (data.pool) {
|
|
||||||
data.selMode = 'pool';
|
|
||||||
data.selPool = data.pool;
|
|
||||||
} else {
|
|
||||||
data.selMode = 'include';
|
|
||||||
}
|
|
||||||
|
|
||||||
me.getViewModel().set('selMode', data.selMode);
|
|
||||||
|
|
||||||
if (data['prune-backups']) {
|
|
||||||
Object.assign(data, data['prune-backups']);
|
|
||||||
delete data['prune-backups'];
|
|
||||||
} else if (data.maxfiles !== undefined) {
|
|
||||||
if (data.maxfiles > 0) {
|
|
||||||
data['keep-last'] = data.maxfiles;
|
|
||||||
} else {
|
|
||||||
data['keep-all'] = 1;
|
|
||||||
}
|
|
||||||
delete data.maxfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data['notes-template']) {
|
|
||||||
data['notes-template'] =
|
|
||||||
PVE.Utils.unEscapeNotesTemplate(data['notes-template']);
|
|
||||||
}
|
|
||||||
|
|
||||||
view.setValues(data);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -207,13 +229,23 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
data: {
|
data: {
|
||||||
selMode: 'include',
|
selMode: 'include',
|
||||||
notificationMode: '__default__',
|
notificationMode: '__default__',
|
||||||
|
mailto: '',
|
||||||
|
mailNotification: 'always',
|
||||||
},
|
},
|
||||||
|
|
||||||
formulas: {
|
formulas: {
|
||||||
poolMode: (get) => get('selMode') === 'pool',
|
poolMode: (get) => get('selMode') === 'pool',
|
||||||
disableVMSelection: (get) => get('selMode') !== 'include' && get('selMode') !== 'exclude',
|
disableVMSelection: (get) => get('selMode') !== 'include' &&
|
||||||
|
get('selMode') !== 'exclude',
|
||||||
showMailtoFields: (get) =>
|
showMailtoFields: (get) =>
|
||||||
['auto', 'legacy-sendmail', '__default__'].includes(get('notificationMode')),
|
['auto', 'legacy-sendmail', '__default__'].includes(get('notificationMode')),
|
||||||
|
enableMailnotificationField: (get) => {
|
||||||
|
let mode = get('notificationMode');
|
||||||
|
let mailto = get('mailto');
|
||||||
|
|
||||||
|
return (['auto', '__default__'].includes(mode) && mailto) ||
|
||||||
|
mode === 'legacy-sendmail';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -318,6 +350,7 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
],
|
],
|
||||||
fieldLabel: gettext('Notification mode'),
|
fieldLabel: gettext('Notification mode'),
|
||||||
name: 'notification-mode',
|
name: 'notification-mode',
|
||||||
|
value: '__default__',
|
||||||
cbind: {
|
cbind: {
|
||||||
deleteEmpty: '{!isCreate}',
|
deleteEmpty: '{!isCreate}',
|
||||||
},
|
},
|
||||||
@ -325,6 +358,15 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
value: '{notificationMode}',
|
value: '{notificationMode}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
xtype: 'textfield',
|
||||||
|
fieldLabel: gettext('Send email to'),
|
||||||
|
name: 'mailto',
|
||||||
|
bind: {
|
||||||
|
hidden: '{!showMailtoFields}',
|
||||||
|
value: '{mailto}',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
xtype: 'pveEmailNotificationSelector',
|
xtype: 'pveEmailNotificationSelector',
|
||||||
fieldLabel: gettext('Send email'),
|
fieldLabel: gettext('Send email'),
|
||||||
@ -334,15 +376,9 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
deleteEmpty: '{!isCreate}',
|
deleteEmpty: '{!isCreate}',
|
||||||
},
|
},
|
||||||
bind: {
|
bind: {
|
||||||
disabled: '{!showMailtoFields}',
|
hidden: '{!showMailtoFields}',
|
||||||
},
|
disabled: '{!enableMailnotificationField}',
|
||||||
},
|
value: '{mailNotification}',
|
||||||
{
|
|
||||||
xtype: 'textfield',
|
|
||||||
fieldLabel: gettext('Send email to'),
|
|
||||||
name: 'mailto',
|
|
||||||
bind: {
|
|
||||||
disabled: '{!showMailtoFields}',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -354,6 +390,11 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
deleteEmpty: '{!isCreate}',
|
deleteEmpty: '{!isCreate}',
|
||||||
},
|
},
|
||||||
value: 'zstd',
|
value: 'zstd',
|
||||||
|
listeners: {
|
||||||
|
change: 'compressionChange',
|
||||||
|
disable: 'compressionDisable',
|
||||||
|
enable: 'compressionEnable',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'pveBackupModeSelector',
|
xtype: 'pveBackupModeSelector',
|
||||||
@ -396,18 +437,6 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
advancedColumn1: [
|
|
||||||
{
|
|
||||||
xtype: 'proxmoxcheckbox',
|
|
||||||
fieldLabel: gettext('Repeat missed'),
|
|
||||||
name: 'repeat-missed',
|
|
||||||
uncheckedValue: 0,
|
|
||||||
defaultValue: 0,
|
|
||||||
cbind: {
|
|
||||||
deleteDefaultValue: '{!isCreate}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
onGetValues: function(values) {
|
onGetValues: function(values) {
|
||||||
return this.up('window').getController().onGetValues(values);
|
return this.up('window').getController().onGetValues(values);
|
||||||
},
|
},
|
||||||
@ -466,6 +495,14 @@ Ext.define('PVE.dc.BackupEdit', {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pveBackupAdvancedOptionsPanel',
|
||||||
|
reference: 'backupAdvanced',
|
||||||
|
title: gettext('Advanced'),
|
||||||
|
cbind: {
|
||||||
|
isCreate: '{isCreate}',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -515,11 +552,13 @@ Ext.define('PVE.dc.BackupView', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let win = Ext.create('PVE.dc.BackupEdit', {
|
Ext.create('PVE.dc.BackupEdit', {
|
||||||
|
autoShow: true,
|
||||||
jobid: rec.data.id,
|
jobid: rec.data.id,
|
||||||
|
listeners: {
|
||||||
|
destroy: () => reload(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
win.on('destroy', reload);
|
|
||||||
win.show();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let run_detail = function() {
|
let run_detail = function() {
|
||||||
@ -578,7 +617,7 @@ Ext.define('PVE.dc.BackupView', {
|
|||||||
delete job['repeat-missed'];
|
delete job['repeat-missed'];
|
||||||
job.all = job.all === true ? 1 : 0;
|
job.all = job.all === true ? 1 : 0;
|
||||||
|
|
||||||
['performance', 'prune-backups'].forEach(key => {
|
['performance', 'prune-backups', 'fleecing'].forEach(key => {
|
||||||
if (job[key]) {
|
if (job[key]) {
|
||||||
job[key] = PVE.Parser.printPropertyString(job[key]);
|
job[key] = PVE.Parser.printPropertyString(job[key]);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ Ext.define('PVE.dc.Tasks', {
|
|||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
let taskstore = Ext.create('Proxmox.data.UpdateStore', {
|
let taskstore = Ext.create('Proxmox.data.UpdateStore', {
|
||||||
storeid: 'pve-cluster-tasks',
|
storeId: 'pve-cluster-tasks',
|
||||||
model: 'proxmox-tasks',
|
model: 'proxmox-tasks',
|
||||||
proxy: {
|
proxy: {
|
||||||
type: 'proxmox',
|
type: 'proxmox',
|
||||||
|
@ -162,7 +162,10 @@ Ext.define('PVE.dc.UserEdit', {
|
|||||||
var data = response.result.data;
|
var data = response.result.data;
|
||||||
me.setValues(data);
|
me.setValues(data);
|
||||||
if (data.keys) {
|
if (data.keys) {
|
||||||
if (data.keys === 'x!oath' || data.keys === 'x!u2f') {
|
if (data.keys === 'x' ||
|
||||||
|
data.keys === 'x!oath' ||
|
||||||
|
data.keys === 'x!u2f' ||
|
||||||
|
data.keys === 'x!yubico') {
|
||||||
me.down('[name="keys"]').setDisabled(1);
|
me.down('[name="keys"]').setDisabled(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ Ext.define('PVE.form.SizeField', {
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
enableKeyEvents: true,
|
enableKeyEvents: true,
|
||||||
setValue: function(v) {
|
setValue: function(v) {
|
||||||
if (!this._transformed) {
|
if (!this._transformed && v !== null) {
|
||||||
let fieldContainer = this.up('fieldcontainer');
|
let fieldContainer = this.up('fieldcontainer');
|
||||||
let vm = fieldContainer.getViewModel();
|
let vm = fieldContainer.getViewModel();
|
||||||
let unit = vm.get('unit');
|
let unit = vm.get('unit');
|
||||||
|
@ -12,6 +12,10 @@ Ext.define('PVE.form.GroupSelector', {
|
|||||||
extend: 'Proxmox.form.ComboGrid',
|
extend: 'Proxmox.form.ComboGrid',
|
||||||
xtype: 'pveGroupSelector',
|
xtype: 'pveGroupSelector',
|
||||||
|
|
||||||
|
editable: true,
|
||||||
|
anyMatch: true,
|
||||||
|
forceSelection: true,
|
||||||
|
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
autoSelect: false,
|
autoSelect: false,
|
||||||
valueField: 'groupid',
|
valueField: 'groupid',
|
||||||
|
@ -37,8 +37,10 @@ Ext.define('PVE.form.IPRefSelector', {
|
|||||||
calculate: function(v) {
|
calculate: function(v) {
|
||||||
if (v.type === 'alias') {
|
if (v.type === 'alias') {
|
||||||
return `${v.scope}/${v.name}`;
|
return `${v.scope}/${v.name}`;
|
||||||
} else {
|
} else if (v.type === 'ipset') {
|
||||||
return `+${v.scope}/${v.name}`;
|
return `+${v.scope}/${v.name}`;
|
||||||
|
} else {
|
||||||
|
return v.ref;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -54,15 +56,6 @@ Ext.define('PVE.form.IPRefSelector', {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var disable_query_for_ips = function(f, value) {
|
|
||||||
if (value === null ||
|
|
||||||
value.match(/^\d/)) { // IP address starts with \d
|
|
||||||
f.queryDelay = 9999999999; // hack: disable with long delay
|
|
||||||
} else {
|
|
||||||
f.queryDelay = 10;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var columns = [];
|
var columns = [];
|
||||||
|
|
||||||
if (!me.ref_type) {
|
if (!me.ref_type) {
|
||||||
@ -107,7 +100,9 @@ Ext.define('PVE.form.IPRefSelector', {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
me.on('change', disable_query_for_ips);
|
me.on('beforequery', function(queryPlan) {
|
||||||
|
return !(queryPlan.query === null || queryPlan.query.match(/^\d/));
|
||||||
|
});
|
||||||
|
|
||||||
me.callParent();
|
me.callParent();
|
||||||
},
|
},
|
||||||
|
@ -83,6 +83,7 @@ Ext.define('PVE.FirewallOptions', {
|
|||||||
add_log_row('log_level_out');
|
add_log_row('log_level_out');
|
||||||
add_log_row('tcp_flags_log_level', 120);
|
add_log_row('tcp_flags_log_level', 120);
|
||||||
add_log_row('smurf_log_level');
|
add_log_row('smurf_log_level');
|
||||||
|
add_boolean_row('nftables', gettext('nftables (tech preview)'), 0);
|
||||||
} else if (me.fwtype === 'vm') {
|
} else if (me.fwtype === 'vm') {
|
||||||
me.rows.enable = {
|
me.rows.enable = {
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -66,7 +66,13 @@ Ext.define('PVE.lxc.CmdMenu', {
|
|||||||
iconCls: 'fa fa-fw fa-stop',
|
iconCls: 'fa fa-fw fa-stop',
|
||||||
disabled: stopped,
|
disabled: stopped,
|
||||||
tooltip: Ext.String.format(gettext('Stop {0} immediately'), 'CT'),
|
tooltip: Ext.String.format(gettext('Stop {0} immediately'), 'CT'),
|
||||||
handler: () => confirmedVMCommand('stop'),
|
handler: () => {
|
||||||
|
Ext.create('PVE.GuestStop', {
|
||||||
|
nodename: info.node,
|
||||||
|
vm: info,
|
||||||
|
autoShow: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: gettext('Reboot'),
|
text: gettext('Reboot'),
|
||||||
|
@ -77,11 +77,13 @@ Ext.define('PVE.lxc.Config', {
|
|||||||
{
|
{
|
||||||
text: gettext('Stop'),
|
text: gettext('Stop'),
|
||||||
disabled: !caps.vms['VM.PowerMgmt'],
|
disabled: !caps.vms['VM.PowerMgmt'],
|
||||||
confirmMsg: Proxmox.Utils.format_task_description('vzstop', vmid),
|
|
||||||
tooltip: Ext.String.format(gettext('Stop {0} immediately'), 'CT'),
|
tooltip: Ext.String.format(gettext('Stop {0} immediately'), 'CT'),
|
||||||
dangerous: true,
|
|
||||||
handler: function() {
|
handler: function() {
|
||||||
vm_command("stop");
|
Ext.create('PVE.GuestStop', {
|
||||||
|
nodename: nodename,
|
||||||
|
vm: vm,
|
||||||
|
autoShow: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
iconCls: 'fa fa-stop',
|
iconCls: 'fa fa-stop',
|
||||||
}],
|
}],
|
||||||
@ -355,6 +357,8 @@ Ext.define('PVE.lxc.Config', {
|
|||||||
itemId: 'firewall-fwlog',
|
itemId: 'firewall-fwlog',
|
||||||
xtype: 'proxmoxLogView',
|
xtype: 'proxmoxLogView',
|
||||||
url: '/api2/extjs' + base_url + '/firewall/log',
|
url: '/api2/extjs' + base_url + '/firewall/log',
|
||||||
|
log_select_timespan: true,
|
||||||
|
submitFormat: 'U',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
154
www/manager6/lxc/DeviceEdit.js
Normal file
154
www/manager6/lxc/DeviceEdit.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
Ext.define('PVE.lxc.DeviceInputPanel', {
|
||||||
|
extend: 'Proxmox.panel.InputPanel',
|
||||||
|
mixins: ['Proxmox.Mixin.CBind'],
|
||||||
|
|
||||||
|
autoComplete: false,
|
||||||
|
|
||||||
|
controller: {
|
||||||
|
xclass: 'Ext.app.ViewController',
|
||||||
|
},
|
||||||
|
|
||||||
|
setVMConfig: function(vmconfig) {
|
||||||
|
let me = this;
|
||||||
|
me.vmconfig = vmconfig;
|
||||||
|
|
||||||
|
if (me.isCreate) {
|
||||||
|
PVE.Utils.forEachLxcDev((i, name) => {
|
||||||
|
if (!Ext.isDefined(vmconfig[name])) {
|
||||||
|
me.confid = name;
|
||||||
|
me.down('field[name=devid]').setValue(i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onGetValues: function(values) {
|
||||||
|
let me = this;
|
||||||
|
let confid = me.isCreate ? "dev" + values.devid : me.confid;
|
||||||
|
delete values.devid;
|
||||||
|
let val = PVE.Parser.printPropertyString(values, 'path');
|
||||||
|
let ret = {};
|
||||||
|
ret[confid] = val;
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxintegerfield',
|
||||||
|
name: 'devid',
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: PVE.Utils.lxc_dev_count - 1,
|
||||||
|
hidden: true,
|
||||||
|
allowBlank: false,
|
||||||
|
disabled: true,
|
||||||
|
cbind: {
|
||||||
|
disabled: '{!isCreate}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'textfield',
|
||||||
|
name: 'path',
|
||||||
|
fieldLabel: gettext('Device Path'),
|
||||||
|
labelWidth: 120,
|
||||||
|
editable: true,
|
||||||
|
allowBlank: false,
|
||||||
|
emptyText: '/dev/xyz',
|
||||||
|
validator: v => v.startsWith('/dev/') ? true : gettext("Path has to start with /dev/"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
advancedColumn1: [
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxintegerfield',
|
||||||
|
name: 'uid',
|
||||||
|
editable: true,
|
||||||
|
fieldLabel: Ext.String.format(gettext('{0} in CT'), 'UID'),
|
||||||
|
labelWidth: 120,
|
||||||
|
emptyText: '0',
|
||||||
|
minValue: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxintegerfield',
|
||||||
|
name: 'gid',
|
||||||
|
editable: true,
|
||||||
|
fieldLabel: Ext.String.format(gettext('{0} in CT'), 'GID'),
|
||||||
|
labelWidth: 120,
|
||||||
|
emptyText: '0',
|
||||||
|
minValue: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
advancedColumn2: [
|
||||||
|
{
|
||||||
|
xtype: 'textfield',
|
||||||
|
name: 'mode',
|
||||||
|
editable: true,
|
||||||
|
fieldLabel: Ext.String.format(gettext('Access Mode in CT')),
|
||||||
|
labelWidth: 120,
|
||||||
|
emptyText: '0660',
|
||||||
|
validator: function(value) {
|
||||||
|
if (/^0[0-7]{3}$|^$/i.test(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return gettext("Access mode has to be an octal number");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
Ext.define('PVE.lxc.DeviceEdit', {
|
||||||
|
extend: 'Proxmox.window.Edit',
|
||||||
|
|
||||||
|
vmconfig: undefined,
|
||||||
|
|
||||||
|
isAdd: true,
|
||||||
|
width: 450,
|
||||||
|
|
||||||
|
initComponent: function() {
|
||||||
|
let me = this;
|
||||||
|
|
||||||
|
me.isCreate = !me.confid;
|
||||||
|
|
||||||
|
let ipanel = Ext.create('PVE.lxc.DeviceInputPanel', {
|
||||||
|
confid: me.confid,
|
||||||
|
isCreate: me.isCreate,
|
||||||
|
pveSelNode: me.pveSelNode,
|
||||||
|
});
|
||||||
|
|
||||||
|
let subject;
|
||||||
|
if (me.isCreate) {
|
||||||
|
subject = gettext('Device');
|
||||||
|
} else {
|
||||||
|
subject = gettext('Device') + ' (' + me.confid + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
Ext.apply(me, {
|
||||||
|
subject: subject,
|
||||||
|
items: [ipanel],
|
||||||
|
});
|
||||||
|
|
||||||
|
me.callParent();
|
||||||
|
|
||||||
|
me.load({
|
||||||
|
success: function(response, options) {
|
||||||
|
ipanel.setVMConfig(response.result.data);
|
||||||
|
if (me.isCreate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = PVE.Parser.parsePropertyString(response.result.data[me.confid], 'path');
|
||||||
|
|
||||||
|
let values = {
|
||||||
|
path: data.path,
|
||||||
|
mode: data.mode,
|
||||||
|
uid: data.uid,
|
||||||
|
gid: data.gid,
|
||||||
|
};
|
||||||
|
|
||||||
|
ipanel.setValues(values);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -87,8 +87,7 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
|
|||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
me.updateVMConfig(vmconfig);
|
me.updateVMConfig(vmconfig);
|
||||||
PVE.Utils.forEachMP((bus, i) => {
|
PVE.Utils.forEachLxcMP((bus, i, name) => {
|
||||||
let name = "mp" + i.toString();
|
|
||||||
if (!Ext.isDefined(vmconfig[name])) {
|
if (!Ext.isDefined(vmconfig[name])) {
|
||||||
me.down('field[name=mpid]').setValue(i);
|
me.down('field[name=mpid]').setValue(i);
|
||||||
return false;
|
return false;
|
||||||
@ -194,7 +193,7 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
|
|||||||
name: 'mpid',
|
name: 'mpid',
|
||||||
fieldLabel: gettext('Mount Point ID'),
|
fieldLabel: gettext('Mount Point ID'),
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
maxValue: PVE.Utils.mp_counts.mp - 1,
|
maxValue: PVE.Utils.lxc_mp_counts.mp - 1,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
|
@ -8,7 +8,7 @@ Ext.define('PVE.lxc.MultiMPPanel', {
|
|||||||
xclass: 'Ext.app.ViewController',
|
xclass: 'Ext.app.ViewController',
|
||||||
|
|
||||||
// count of mps + rootfs
|
// count of mps + rootfs
|
||||||
maxCount: PVE.Utils.mp_counts.mp + 1,
|
maxCount: PVE.Utils.lxc_mp_counts.mp + 1,
|
||||||
|
|
||||||
getNextFreeDisk: function(vmconfig) {
|
getNextFreeDisk: function(vmconfig) {
|
||||||
let nextFreeDisk;
|
let nextFreeDisk;
|
||||||
@ -17,7 +17,7 @@ Ext.define('PVE.lxc.MultiMPPanel', {
|
|||||||
confid: 'rootfs',
|
confid: 'rootfs',
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < PVE.Utils.mp_counts.mp; i++) {
|
for (let i = 0; i < PVE.Utils.lxc_mp_counts.mp; i++) {
|
||||||
let confid = `mp${i}`;
|
let confid = `mp${i}`;
|
||||||
if (!vmconfig[confid]) {
|
if (!vmconfig[confid]) {
|
||||||
nextFreeDisk = {
|
nextFreeDisk = {
|
||||||
|
@ -28,7 +28,6 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
|
|
||||||
initComponent: function() {
|
initComponent: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
let confid;
|
|
||||||
|
|
||||||
var nodename = me.pveSelNode.data.node;
|
var nodename = me.pveSelNode.data.node;
|
||||||
if (!nodename) {
|
if (!nodename) {
|
||||||
@ -116,8 +115,7 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
PVE.Utils.forEachMP(function(bus, i) {
|
PVE.Utils.forEachLxcMP(function(bus, i, confid) {
|
||||||
confid = bus + i;
|
|
||||||
var group = 5;
|
var group = 5;
|
||||||
var header;
|
var header;
|
||||||
if (bus === 'mp') {
|
if (bus === 'mp') {
|
||||||
@ -135,6 +133,18 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
};
|
};
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
let deveditor = Proxmox.UserName === 'root@pam' ? 'PVE.lxc.DeviceEdit' : undefined;
|
||||||
|
|
||||||
|
PVE.Utils.forEachLxcDev(function(i, confid) {
|
||||||
|
rows[confid] = {
|
||||||
|
group: 7,
|
||||||
|
order: i,
|
||||||
|
tdCls: 'pve-itype-icon-pci',
|
||||||
|
editor: deveditor,
|
||||||
|
header: gettext('Device') + ' (' + confid + ')',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
var baseurl = 'nodes/' + nodename + '/lxc/' + vmid + '/config';
|
var baseurl = 'nodes/' + nodename + '/lxc/' + vmid + '/config';
|
||||||
|
|
||||||
me.selModel = Ext.create('Ext.selection.RowModel', {});
|
me.selModel = Ext.create('Ext.selection.RowModel', {});
|
||||||
@ -311,6 +321,7 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
let isDisk = isRootFS || key.match(/^(mp|unused)\d+/);
|
let isDisk = isRootFS || key.match(/^(mp|unused)\d+/);
|
||||||
let isUnusedDisk = key.match(/^unused\d+/);
|
let isUnusedDisk = key.match(/^unused\d+/);
|
||||||
let isUsedDisk = isDisk && !isUnusedDisk;
|
let isUsedDisk = isDisk && !isUnusedDisk;
|
||||||
|
let isDevice = key.match(/^dev\d+/);
|
||||||
|
|
||||||
let noedit = isDelete || !rowdef.editor;
|
let noedit = isDelete || !rowdef.editor;
|
||||||
if (!noedit && Proxmox.UserName !== 'root@pam' && key.match(/^mp\d+$/)) {
|
if (!noedit && Proxmox.UserName !== 'root@pam' && key.match(/^mp\d+$/)) {
|
||||||
@ -326,7 +337,7 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
reassign_menuitem.setDisabled(isRootFS);
|
reassign_menuitem.setDisabled(isRootFS);
|
||||||
resize_menuitem.setDisabled(isUnusedDisk);
|
resize_menuitem.setDisabled(isUnusedDisk);
|
||||||
|
|
||||||
remove_btn.setDisabled(!isDisk || isRootFS || !diskCap || pending);
|
remove_btn.setDisabled(!(isDisk || isDevice) || isRootFS || !diskCap || pending);
|
||||||
revert_btn.setDisabled(!pending);
|
revert_btn.setDisabled(!pending);
|
||||||
|
|
||||||
remove_btn.setText(isUsedDisk ? remove_btn.altText : remove_btn.defaultText);
|
remove_btn.setText(isUsedDisk ? remove_btn.altText : remove_btn.defaultText);
|
||||||
@ -380,6 +391,21 @@ Ext.define('PVE.lxc.RessourceView', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: gettext('Device Passthrough'),
|
||||||
|
iconCls: 'pve-itype-icon-pci',
|
||||||
|
disabled: Proxmox.UserName !== 'root@pam',
|
||||||
|
handler: function() {
|
||||||
|
Ext.create('PVE.lxc.DeviceEdit', {
|
||||||
|
autoShow: true,
|
||||||
|
url: `/api2/extjs/${baseurl}`,
|
||||||
|
pveSelNode: me.pveSelNode,
|
||||||
|
listeners: {
|
||||||
|
destroy: () => me.reload(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -10,6 +10,20 @@ Ext.define('PVE.node.ACMEAccountCreate', {
|
|||||||
url: '/cluster/acme/account',
|
url: '/cluster/acme/account',
|
||||||
showTaskViewer: true,
|
showTaskViewer: true,
|
||||||
defaultExists: false,
|
defaultExists: false,
|
||||||
|
referenceHolder: true,
|
||||||
|
onlineHelp: "sysadmin_certs_acme_account",
|
||||||
|
|
||||||
|
viewModel: {
|
||||||
|
data: {
|
||||||
|
customDirectory: false,
|
||||||
|
eabRequired: false,
|
||||||
|
},
|
||||||
|
formulas: {
|
||||||
|
eabEmptyText: function(get) {
|
||||||
|
return get('eabRequired') ? gettext("required") : gettext("optional");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@ -30,12 +44,18 @@ Ext.define('PVE.node.ACMEAccountCreate', {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxComboGrid',
|
xtype: 'proxmoxComboGrid',
|
||||||
name: 'directory',
|
notFoundIsValid: true,
|
||||||
|
isFormField: false,
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
valueField: 'url',
|
valueField: 'url',
|
||||||
displayField: 'name',
|
displayField: 'name',
|
||||||
fieldLabel: gettext('ACME Directory'),
|
fieldLabel: gettext('ACME Directory'),
|
||||||
store: {
|
store: {
|
||||||
|
listeners: {
|
||||||
|
'load': function() {
|
||||||
|
this.add({ name: gettext("Custom"), url: '' });
|
||||||
|
},
|
||||||
|
},
|
||||||
autoLoad: true,
|
autoLoad: true,
|
||||||
fields: ['name', 'url'],
|
fields: ['name', 'url'],
|
||||||
idProperty: ['name'],
|
idProperty: ['name'],
|
||||||
@ -43,10 +63,6 @@ Ext.define('PVE.node.ACMEAccountCreate', {
|
|||||||
type: 'proxmox',
|
type: 'proxmox',
|
||||||
url: '/api2/json/cluster/acme/directories',
|
url: '/api2/json/cluster/acme/directories',
|
||||||
},
|
},
|
||||||
sorters: {
|
|
||||||
property: 'name',
|
|
||||||
direction: 'ASC',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
listConfig: {
|
listConfig: {
|
||||||
columns: [
|
columns: [
|
||||||
@ -64,42 +80,99 @@ Ext.define('PVE.node.ACMEAccountCreate', {
|
|||||||
},
|
},
|
||||||
listeners: {
|
listeners: {
|
||||||
change: function(combogrid, value) {
|
change: function(combogrid, value) {
|
||||||
var me = this;
|
let me = this;
|
||||||
if (!value) {
|
|
||||||
return;
|
let vm = me.up('window').getViewModel();
|
||||||
|
let dirField = me.up('window').lookupReference('directoryInput');
|
||||||
|
let tosButton = me.up('window').lookupReference('queryTos');
|
||||||
|
|
||||||
|
let isCustom = combogrid.getSelection().get('name') === gettext("Custom");
|
||||||
|
vm.set('customDirectory', isCustom);
|
||||||
|
|
||||||
|
dirField.setValue(value);
|
||||||
|
|
||||||
|
if (!isCustom) {
|
||||||
|
tosButton.click();
|
||||||
|
} else {
|
||||||
|
me.up('window').clearToSFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
var disp = me.up('window').down('#tos_url_display');
|
|
||||||
var field = me.up('window').down('#tos_url');
|
|
||||||
var checkbox = me.up('window').down('#tos_checkbox');
|
|
||||||
|
|
||||||
disp.setValue(gettext('Loading'));
|
|
||||||
field.setValue(undefined);
|
|
||||||
checkbox.setValue(undefined);
|
|
||||||
checkbox.setHidden(true);
|
|
||||||
|
|
||||||
Proxmox.Utils.API2Request({
|
|
||||||
url: '/cluster/acme/meta',
|
|
||||||
method: 'GET',
|
|
||||||
params: {
|
|
||||||
directory: value,
|
|
||||||
},
|
|
||||||
success: function(response, opt) {
|
|
||||||
if (response.result.data.termsOfService) {
|
|
||||||
field.setValue(response.result.data.termsOfService);
|
|
||||||
disp.setValue(response.result.data.termsOfService);
|
|
||||||
checkbox.setHidden(false);
|
|
||||||
} else {
|
|
||||||
disp.setValue(undefined);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
failure: function(response, opt) {
|
|
||||||
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
xtype: 'fieldcontainer',
|
||||||
|
layout: 'hbox',
|
||||||
|
fieldLabel: gettext('URL'),
|
||||||
|
bind: {
|
||||||
|
hidden: '{!customDirectory}',
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxtextfield',
|
||||||
|
name: 'directory',
|
||||||
|
reference: 'directoryInput',
|
||||||
|
flex: 1,
|
||||||
|
allowBlank: false,
|
||||||
|
listeners: {
|
||||||
|
change: function(textbox, value) {
|
||||||
|
let me = this;
|
||||||
|
me.up('window').clearToSFields();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxButton',
|
||||||
|
margin: '0 0 0 5',
|
||||||
|
reference: 'queryTos',
|
||||||
|
text: gettext('Query URL'),
|
||||||
|
listeners: {
|
||||||
|
click: function(button) {
|
||||||
|
let me = this;
|
||||||
|
|
||||||
|
let w = me.up('window');
|
||||||
|
let vm = w.getViewModel();
|
||||||
|
let disp = w.down('#tos_url_display');
|
||||||
|
let field = w.down('#tos_url');
|
||||||
|
let checkbox = w.down('#tos_checkbox');
|
||||||
|
let value = w.lookupReference('directoryInput').getValue();
|
||||||
|
w.clearToSFields();
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
disp.setValue(gettext("Loading"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Proxmox.Utils.API2Request({
|
||||||
|
url: '/cluster/acme/meta',
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
directory: value,
|
||||||
|
},
|
||||||
|
success: function(response, opt) {
|
||||||
|
if (response.result.data && response.result.data.termsOfService) {
|
||||||
|
field.setValue(response.result.data.termsOfService);
|
||||||
|
disp.setValue(response.result.data.termsOfService);
|
||||||
|
checkbox.setHidden(false);
|
||||||
|
} else {
|
||||||
|
// Needed to pass input verification and enable register button
|
||||||
|
// has no influence on the submitted form
|
||||||
|
checkbox.setValue(true);
|
||||||
|
disp.setValue("No terms of service agreement required");
|
||||||
|
}
|
||||||
|
vm.set('eabRequired', !!(response.result.data &&
|
||||||
|
response.result.data.externalAccountRequired));
|
||||||
|
},
|
||||||
|
failure: function(response, opt) {
|
||||||
|
disp.setValue(undefined);
|
||||||
|
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
xtype: 'displayfield',
|
xtype: 'displayfield',
|
||||||
itemId: 'tos_url_display',
|
itemId: 'tos_url_display',
|
||||||
@ -123,8 +196,41 @@ Ext.define('PVE.node.ACMEAccountCreate', {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxtextfield',
|
||||||
|
name: 'eab-kid',
|
||||||
|
fieldLabel: gettext('EAB Key ID'),
|
||||||
|
bind: {
|
||||||
|
hidden: '{!customDirectory}',
|
||||||
|
allowBlank: '{!eabRequired}',
|
||||||
|
emptyText: '{eabEmptyText}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxtextfield',
|
||||||
|
name: 'eab-hmac-key',
|
||||||
|
fieldLabel: gettext('EAB Key'),
|
||||||
|
bind: {
|
||||||
|
hidden: '{!customDirectory}',
|
||||||
|
allowBlank: '{!eabRequired}',
|
||||||
|
emptyText: '{eabEmptyText}',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
clearToSFields: function() {
|
||||||
|
let me = this;
|
||||||
|
|
||||||
|
let disp = me.down('#tos_url_display');
|
||||||
|
let field = me.down('#tos_url');
|
||||||
|
let checkbox = me.down('#tos_checkbox');
|
||||||
|
|
||||||
|
disp.setValue("Terms of service not fetched yet");
|
||||||
|
field.setValue(undefined);
|
||||||
|
checkbox.setValue(undefined);
|
||||||
|
checkbox.setHidden(true);
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ext.define('PVE.node.ACMEAccountView', {
|
Ext.define('PVE.node.ACMEAccountView', {
|
||||||
|
269
www/manager6/panel/BackupAdvancedOptions.js
Normal file
269
www/manager6/panel/BackupAdvancedOptions.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
* Input panel for advanced backup options intended to be used as part of an edit/create window.
|
||||||
|
*/
|
||||||
|
Ext.define('PVE.panel.BackupAdvancedOptions', {
|
||||||
|
extend: 'Proxmox.panel.InputPanel',
|
||||||
|
xtype: 'pveBackupAdvancedOptionsPanel',
|
||||||
|
mixins: ['Proxmox.Mixin.CBind'],
|
||||||
|
|
||||||
|
cbindData: function() {
|
||||||
|
let me = this;
|
||||||
|
me.isCreate = !!me.isCreate;
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
viewModel: {
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
controller: {
|
||||||
|
xclass: 'Ext.app.ViewController',
|
||||||
|
|
||||||
|
toggleFleecing: function(cb, value) {
|
||||||
|
let me = this;
|
||||||
|
me.lookup('fleecingStorage').setDisabled(!value);
|
||||||
|
},
|
||||||
|
|
||||||
|
control: {
|
||||||
|
'proxmoxcheckbox[reference=fleecingEnabled]': {
|
||||||
|
change: 'toggleFleecing',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
onGetValues: function(formValues) {
|
||||||
|
let me = this;
|
||||||
|
if (me.needMask) { // isMasked() may not yet be true if not rendered once
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = {};
|
||||||
|
|
||||||
|
if (!me.isCreate) {
|
||||||
|
options.delete = []; // to avoid having to check this all the time
|
||||||
|
}
|
||||||
|
const deletePropertyOnEdit = me.isCreate
|
||||||
|
? () => { /* no-op on create */ }
|
||||||
|
: key => options.delete.push(key);
|
||||||
|
|
||||||
|
let fleecing = {}, fleecingOptions = ['fleecing-enabled', 'fleecing-storage'];
|
||||||
|
let performance = {}, performanceOptions = ['max-workers', 'pbs-entries-max'];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(formValues)) {
|
||||||
|
if (performanceOptions.includes(key)) {
|
||||||
|
performance[key] = value;
|
||||||
|
// deleteEmpty is not currently implemented for pveBandwidthField
|
||||||
|
} else if (key === 'bwlimit' && value === '') {
|
||||||
|
deletePropertyOnEdit('bwlimit');
|
||||||
|
} else if (key === 'delete') {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.filter(opt => !performanceOptions.includes(opt)).forEach(
|
||||||
|
opt => deletePropertyOnEdit(opt),
|
||||||
|
);
|
||||||
|
} else if (!performanceOptions.includes(formValues.delete)) {
|
||||||
|
deletePropertyOnEdit(value);
|
||||||
|
}
|
||||||
|
} else if (fleecingOptions.includes(key)) {
|
||||||
|
let fleecingKey = key.slice('fleecing-'.length);
|
||||||
|
fleecing[fleecingKey] = value;
|
||||||
|
} else {
|
||||||
|
options[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(performance).length > 0) {
|
||||||
|
options.performance = PVE.Parser.printPropertyString(performance);
|
||||||
|
} else {
|
||||||
|
deletePropertyOnEdit('performance');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(fleecing).length > 0) {
|
||||||
|
options.fleecing = PVE.Parser.printPropertyString(fleecing);
|
||||||
|
} else {
|
||||||
|
deletePropertyOnEdit('fleecing');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (me.isCreate) {
|
||||||
|
delete options.delete;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
|
||||||
|
onSetValues: function(values) {
|
||||||
|
if (values.fleecing) {
|
||||||
|
for (const [key, value] of Object.entries(values.fleecing)) {
|
||||||
|
values[`fleecing-${key}`] = value;
|
||||||
|
}
|
||||||
|
delete values.fleecing;
|
||||||
|
}
|
||||||
|
if (values["pbs-change-detection-mode"] === '__default__') {
|
||||||
|
delete values["pbs-change-detection-mode"];
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCompression: function(value, disabled) {
|
||||||
|
this.lookup('zstdThreadCount').setDisabled(disabled || value !== 'zstd');
|
||||||
|
},
|
||||||
|
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
xtype: 'pveTwoColumnContainer',
|
||||||
|
startColumn: {
|
||||||
|
xtype: 'pveBandwidthField',
|
||||||
|
name: 'bwlimit',
|
||||||
|
fieldLabel: gettext('Bandwidth Limit'),
|
||||||
|
emptyText: gettext('Fallback'),
|
||||||
|
backendUnit: 'KiB',
|
||||||
|
allowZero: true,
|
||||||
|
emptyValue: '',
|
||||||
|
autoEl: {
|
||||||
|
tag: 'div',
|
||||||
|
'data-qtip': Ext.String.format(gettext('Use {0} for unlimited'), 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endFlex: 2,
|
||||||
|
endColumn: {
|
||||||
|
xtype: 'displayfield',
|
||||||
|
value: `${gettext('Limit I/O bandwidth.')} ${Ext.String.format(gettext("Schema default: {0}"), 0)}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pveTwoColumnContainer',
|
||||||
|
startColumn: {
|
||||||
|
xtype: 'proxmoxintegerfield',
|
||||||
|
name: 'zstd',
|
||||||
|
reference: 'zstdThreadCount',
|
||||||
|
fieldLabel: Ext.String.format(gettext('{0} Threads'), 'Zstd'),
|
||||||
|
fieldStyle: 'text-align: right',
|
||||||
|
emptyText: gettext('Fallback'),
|
||||||
|
minValue: 0,
|
||||||
|
cbind: {
|
||||||
|
deleteEmpty: '{!isCreate}',
|
||||||
|
},
|
||||||
|
autoEl: {
|
||||||
|
tag: 'div',
|
||||||
|
'data-qtip': gettext('With 0, half of the available cores are used'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endFlex: 2,
|
||||||
|
endColumn: {
|
||||||
|
xtype: 'displayfield',
|
||||||
|
value: `${gettext('Threads used for zstd compression (non-PBS).')} ${Ext.String.format(gettext("Schema default: {0}"), 1)}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pveTwoColumnContainer',
|
||||||
|
startColumn: {
|
||||||
|
xtype: 'proxmoxintegerfield',
|
||||||
|
name: 'max-workers',
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 256,
|
||||||
|
fieldLabel: gettext('IO-Workers'),
|
||||||
|
fieldStyle: 'text-align: right',
|
||||||
|
emptyText: gettext('Fallback'),
|
||||||
|
cbind: {
|
||||||
|
deleteEmpty: '{!isCreate}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endFlex: 2,
|
||||||
|
endColumn: {
|
||||||
|
xtype: 'displayfield',
|
||||||
|
value: `${gettext('I/O workers in the QEMU process (VMs only).')} ${Ext.String.format(gettext("Schema default: {0}"), 16)}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pveTwoColumnContainer',
|
||||||
|
startColumn: {
|
||||||
|
xtype: 'proxmoxcheckbox',
|
||||||
|
name: 'fleecing-enabled',
|
||||||
|
reference: 'fleecingEnabled',
|
||||||
|
fieldLabel: gettext('Fleecing'),
|
||||||
|
uncheckedValue: 0,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
endFlex: 2,
|
||||||
|
endColumn: {
|
||||||
|
xtype: 'displayfield',
|
||||||
|
value: gettext('Backup write cache that can reduce IO pressure inside guests (VMs only).'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pveTwoColumnContainer',
|
||||||
|
startColumn: {
|
||||||
|
xtype: 'pveStorageSelector',
|
||||||
|
name: 'fleecing-storage',
|
||||||
|
fieldLabel: gettext('Fleecing Storage'),
|
||||||
|
reference: 'fleecingStorage',
|
||||||
|
clusterView: true,
|
||||||
|
storageContent: 'images',
|
||||||
|
allowBlank: false,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
endFlex: 2,
|
||||||
|
endColumn: {
|
||||||
|
xtype: 'displayfield',
|
||||||
|
value: gettext('Prefer a fast and local storage, ideally with support for discard and thin-provisioning or sparse files.'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// It's part of the 'performance' property string, so have a field to preserve the
|
||||||
|
// value, but don't expose it. It's a rather niche setting and difficult to
|
||||||
|
// convey/understand what it does.
|
||||||
|
xtype: 'proxmoxintegerfield',
|
||||||
|
name: 'pbs-entries-max',
|
||||||
|
hidden: true,
|
||||||
|
fieldLabel: 'TODO',
|
||||||
|
fieldStyle: 'text-align: right',
|
||||||
|
emptyText: 'TODO',
|
||||||
|
cbind: {
|
||||||
|
deleteEmpty: '{!isCreate}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pveTwoColumnContainer',
|
||||||
|
startColumn: {
|
||||||
|
xtype: 'proxmoxcheckbox',
|
||||||
|
fieldLabel: gettext('Repeat missed'),
|
||||||
|
name: 'repeat-missed',
|
||||||
|
uncheckedValue: 0,
|
||||||
|
defaultValue: 0,
|
||||||
|
cbind: {
|
||||||
|
deleteDefaultValue: '{!isCreate}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endFlex: 2,
|
||||||
|
endColumn: {
|
||||||
|
xtype: 'displayfield',
|
||||||
|
value: gettext("Run jobs as soon as possible if they couldn't start on schedule, for example, due to the node being offline."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'pveTwoColumnContainer',
|
||||||
|
startColumn: {
|
||||||
|
xtype: 'proxmoxKVComboBox',
|
||||||
|
fieldLabel: gettext('PBS change detection mode'),
|
||||||
|
name: 'pbs-change-detection-mode',
|
||||||
|
deleteEmpty: true,
|
||||||
|
value: '__default__',
|
||||||
|
comboItems: [
|
||||||
|
['__default__', "Default"],
|
||||||
|
['data', "Data"],
|
||||||
|
['metadata', "Metadata"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
endFlex: 2,
|
||||||
|
endColumn: {
|
||||||
|
xtype: 'displayfield',
|
||||||
|
value: gettext("EXPERIMENTAL: Mode to detect file changes and archive encoding format for container backups."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'component',
|
||||||
|
padding: '5 1',
|
||||||
|
html: `<span class="pmx-hint">${gettext('Note')}</span>: ${
|
||||||
|
gettext("The node-specific 'vzdump.conf' or, if this is not set, the default from the config schema is used to determine fallback values.")}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -93,7 +93,13 @@ Ext.define('PVE.qemu.CmdMenu', {
|
|||||||
iconCls: 'fa fa-fw fa-stop',
|
iconCls: 'fa fa-fw fa-stop',
|
||||||
disabled: stopped,
|
disabled: stopped,
|
||||||
tooltip: Ext.String.format(gettext('Stop {0} immediately'), 'VM'),
|
tooltip: Ext.String.format(gettext('Stop {0} immediately'), 'VM'),
|
||||||
handler: () => confirmedVMCommand('stop'),
|
handler: () => {
|
||||||
|
Ext.create('PVE.GuestStop', {
|
||||||
|
nodename: info.node,
|
||||||
|
vm: info,
|
||||||
|
autoShow: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: gettext('Reboot'),
|
text: gettext('Reboot'),
|
||||||
|
@ -176,11 +176,13 @@ Ext.define('PVE.qemu.Config', {
|
|||||||
}, {
|
}, {
|
||||||
text: gettext('Stop'),
|
text: gettext('Stop'),
|
||||||
disabled: !caps.vms['VM.PowerMgmt'],
|
disabled: !caps.vms['VM.PowerMgmt'],
|
||||||
dangerous: true,
|
|
||||||
tooltip: Ext.String.format(gettext('Stop {0} immediately'), 'VM'),
|
tooltip: Ext.String.format(gettext('Stop {0} immediately'), 'VM'),
|
||||||
confirmMsg: Proxmox.Utils.format_task_description('qmstop', vmid),
|
|
||||||
handler: function() {
|
handler: function() {
|
||||||
vm_command("stop", { timeout: 30 });
|
Ext.create('PVE.GuestStop', {
|
||||||
|
nodename: nodename,
|
||||||
|
vm: vm,
|
||||||
|
autoShow: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
iconCls: 'fa fa-stop',
|
iconCls: 'fa fa-stop',
|
||||||
}, {
|
}, {
|
||||||
|
@ -11,6 +11,36 @@ Ext.define('PVE.qemu.DisplayInputPanel', {
|
|||||||
return { vga: ret };
|
return { vga: ret };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
viewModel: {
|
||||||
|
data: {
|
||||||
|
type: '__default__',
|
||||||
|
clipboard: '__default__',
|
||||||
|
},
|
||||||
|
formulas: {
|
||||||
|
matchNonGUIOption: function(get) {
|
||||||
|
return get('type').match(/^(serial\d|none)$/);
|
||||||
|
},
|
||||||
|
memoryEmptyText: function(get) {
|
||||||
|
let val = get('type');
|
||||||
|
if (val === "cirrus") {
|
||||||
|
return "4";
|
||||||
|
} else if (val === "std" || val.match(/^qxl\d?$/) || val === "vmware") {
|
||||||
|
return "16";
|
||||||
|
} else if (val.match(/^virtio/)) {
|
||||||
|
return "256";
|
||||||
|
} else if (get('matchNonGUIOption')) {
|
||||||
|
return "N/A";
|
||||||
|
} else {
|
||||||
|
console.debug("unexpected display type", val);
|
||||||
|
return Proxmox.Utils.defaultText;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isVNC: get => get('clipboard') === 'vnc',
|
||||||
|
hideDefaultHint: get => get('isVNC') || get('matchNonGUIOption'),
|
||||||
|
hideVNCHint: get => !get('isVNC') || get('matchNonGUIOption'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
items: [{
|
items: [{
|
||||||
name: 'type',
|
name: 'type',
|
||||||
xtype: 'proxmoxKVComboBox',
|
xtype: 'proxmoxKVComboBox',
|
||||||
@ -27,29 +57,8 @@ Ext.define('PVE.qemu.DisplayInputPanel', {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
listeners: {
|
bind: {
|
||||||
change: function(cb, val) {
|
value: '{type}',
|
||||||
if (!val) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let memoryfield = this.up('panel').down('field[name=memory]');
|
|
||||||
let disableMemoryField = false;
|
|
||||||
|
|
||||||
if (val === "cirrus") {
|
|
||||||
memoryfield.setEmptyText("4");
|
|
||||||
} else if (val === "std" || val.match(/^qxl\d?$/) || val === "vmware") {
|
|
||||||
memoryfield.setEmptyText("16");
|
|
||||||
} else if (val.match(/^virtio/)) {
|
|
||||||
memoryfield.setEmptyText("256");
|
|
||||||
} else if (val.match(/^(serial\d|none)$/)) {
|
|
||||||
memoryfield.setEmptyText("N/A");
|
|
||||||
disableMemoryField = true;
|
|
||||||
} else {
|
|
||||||
console.debug("unexpected display type", val);
|
|
||||||
memoryfield.setEmptyText(Proxmox.Utils.defaultText);
|
|
||||||
}
|
|
||||||
memoryfield.setDisabled(disableMemoryField);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -60,7 +69,49 @@ Ext.define('PVE.qemu.DisplayInputPanel', {
|
|||||||
maxValue: 512,
|
maxValue: 512,
|
||||||
step: 4,
|
step: 4,
|
||||||
name: 'memory',
|
name: 'memory',
|
||||||
|
bind: {
|
||||||
|
emptyText: '{memoryEmptyText}',
|
||||||
|
disabled: '{matchNonGUIOption}',
|
||||||
|
},
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
advancedItems: [
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxKVComboBox',
|
||||||
|
name: 'clipboard',
|
||||||
|
deleteEmpty: false,
|
||||||
|
value: '__default__',
|
||||||
|
fieldLabel: gettext('Clipboard'),
|
||||||
|
comboItems: [
|
||||||
|
['__default__', Proxmox.Utils.defaultText],
|
||||||
|
['vnc', 'VNC'],
|
||||||
|
],
|
||||||
|
bind: {
|
||||||
|
value: '{clipboard}',
|
||||||
|
disabled: '{matchNonGUIOption}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'displayfield',
|
||||||
|
name: 'vncHint',
|
||||||
|
userCls: 'pmx-hint',
|
||||||
|
value: gettext('You cannot use the default SPICE clipboard if the VNC Clipboard is selected.') + ' ' +
|
||||||
|
gettext('VNC Clipboard requires spice-tools installed in the Guest-VM.'),
|
||||||
|
bind: {
|
||||||
|
hidden: '{hideVNCHint}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'displayfield',
|
||||||
|
name: 'defaultHint',
|
||||||
|
userCls: 'pmx-hint',
|
||||||
|
value: gettext('This option depends on your display type.') + ' ' +
|
||||||
|
gettext('If the display type uses SPICE you are able to use the default SPICE Clipboard.'),
|
||||||
|
bind: {
|
||||||
|
hidden: '{hideDefaultHint}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
Ext.define('PVE.qemu.DisplayEdit', {
|
Ext.define('PVE.qemu.DisplayEdit', {
|
||||||
|
@ -634,7 +634,6 @@ Ext.define('PVE.qemu.HardwareView', {
|
|||||||
isCloudInit ||
|
isCloudInit ||
|
||||||
!(isDisk || isEfi || tpmMoveable),
|
!(isDisk || isEfi || tpmMoveable),
|
||||||
);
|
);
|
||||||
move_menuitem.setDisabled(isUnusedDisk);
|
|
||||||
reassign_menuitem.setDisabled(pending || (isEfi || tpmMoveable));
|
reassign_menuitem.setDisabled(pending || (isEfi || tpmMoveable));
|
||||||
resize_menuitem.setDisabled(pending || !isUsedDisk);
|
resize_menuitem.setDisabled(pending || !isUsedDisk);
|
||||||
|
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
Ext.define('PVE.qemu.MachineInputPanel', {
|
Ext.define('PVE.qemu.MachineInputPanel', {
|
||||||
extend: 'Proxmox.panel.InputPanel',
|
extend: 'Proxmox.panel.InputPanel',
|
||||||
xtype: 'pveMachineInputPanel',
|
xtype: 'pveMachineInputPanel',
|
||||||
|
onlineHelp: 'qm_machine_type',
|
||||||
|
|
||||||
|
viewModel: {
|
||||||
|
data: {
|
||||||
|
type: '__default__',
|
||||||
|
},
|
||||||
|
formulas: {
|
||||||
|
q35: get => get('type') === 'q35',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
controller: {
|
controller: {
|
||||||
xclass: 'Ext.app.ViewController',
|
xclass: 'Ext.app.ViewController',
|
||||||
@ -35,17 +45,29 @@ Ext.define('PVE.qemu.MachineInputPanel', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onGetValues: function(values) {
|
onGetValues: function(values) {
|
||||||
|
if (values.delete === 'machine' && values.viommu) {
|
||||||
|
delete values.delete;
|
||||||
|
values.machine = 'pc';
|
||||||
|
}
|
||||||
if (values.version && values.version !== 'latest') {
|
if (values.version && values.version !== 'latest') {
|
||||||
values.machine = values.version;
|
values.machine = values.version;
|
||||||
delete values.delete;
|
delete values.delete;
|
||||||
}
|
}
|
||||||
delete values.version;
|
delete values.version;
|
||||||
return values;
|
if (values.delete === 'machine' && !values.viommu) {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
let ret = {};
|
||||||
|
ret.machine = PVE.Parser.printPropertyString(values, 'machine');
|
||||||
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
setValues: function(values) {
|
setValues: function(values) {
|
||||||
let me = this;
|
let me = this;
|
||||||
|
|
||||||
|
let machineConf = PVE.Parser.parsePropertyString(values.machine, 'type');
|
||||||
|
values.machine = machineConf.type;
|
||||||
|
|
||||||
me.isWindows = values.isWindows;
|
me.isWindows = values.isWindows;
|
||||||
if (values.machine === 'pc') {
|
if (values.machine === 'pc') {
|
||||||
values.machine = '__default__';
|
values.machine = '__default__';
|
||||||
@ -58,6 +80,9 @@ Ext.define('PVE.qemu.MachineInputPanel', {
|
|||||||
values.version = 'pc-q35-5.1';
|
values.version = 'pc-q35-5.1';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
values.viommu = machineConf.viommu || '__default__';
|
||||||
|
|
||||||
if (values.machine !== '__default__' && values.machine !== 'q35') {
|
if (values.machine !== '__default__' && values.machine !== 'q35') {
|
||||||
values.version = values.machine;
|
values.version = values.machine;
|
||||||
values.machine = values.version.match(/q35/) ? 'q35' : '__default__';
|
values.machine = values.version.match(/q35/) ? 'q35' : '__default__';
|
||||||
@ -78,6 +103,9 @@ Ext.define('PVE.qemu.MachineInputPanel', {
|
|||||||
['__default__', PVE.Utils.render_qemu_machine('')],
|
['__default__', PVE.Utils.render_qemu_machine('')],
|
||||||
['q35', 'q35'],
|
['q35', 'q35'],
|
||||||
],
|
],
|
||||||
|
bind: {
|
||||||
|
value: '{type}',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
advancedItems: [
|
advancedItems: [
|
||||||
@ -113,6 +141,39 @@ Ext.define('PVE.qemu.MachineInputPanel', {
|
|||||||
fieldLabel: gettext('Note'),
|
fieldLabel: gettext('Note'),
|
||||||
value: gettext('Machine version change may affect hardware layout and settings in the guest OS.'),
|
value: gettext('Machine version change may affect hardware layout and settings in the guest OS.'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxKVComboBox',
|
||||||
|
name: 'viommu',
|
||||||
|
fieldLabel: gettext('vIOMMU'),
|
||||||
|
reference: 'viommu-q35',
|
||||||
|
deleteEmpty: false,
|
||||||
|
value: '__default__',
|
||||||
|
comboItems: [
|
||||||
|
['__default__', Proxmox.Utils.defaultText + ' (None)'],
|
||||||
|
['intel', gettext('Intel (AMD Compatible)')],
|
||||||
|
['virtio', 'VirtIO'],
|
||||||
|
],
|
||||||
|
bind: {
|
||||||
|
hidden: '{!q35}',
|
||||||
|
disabled: '{!q35}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'proxmoxKVComboBox',
|
||||||
|
name: 'viommu',
|
||||||
|
fieldLabel: gettext('vIOMMU'),
|
||||||
|
reference: 'viommu-i440fx',
|
||||||
|
deleteEmpty: false,
|
||||||
|
value: '__default__',
|
||||||
|
comboItems: [
|
||||||
|
['__default__', Proxmox.Utils.defaultText + ' (None)'],
|
||||||
|
['virtio', 'VirtIO'],
|
||||||
|
],
|
||||||
|
bind: {
|
||||||
|
hidden: '{q35}',
|
||||||
|
disabled: '{q35}',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -54,6 +54,8 @@ Ext.define('PVE.sdn.zones.EvpnInputPanel', {
|
|||||||
fieldLabel: gettext('Primary Exit Node'),
|
fieldLabel: gettext('Primary Exit Node'),
|
||||||
multiSelect: false,
|
multiSelect: false,
|
||||||
autoSelect: false,
|
autoSelect: false,
|
||||||
|
skipEmptyText: true,
|
||||||
|
deleteEmpty: !me.isCreate,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxcheckbox',
|
xtype: 'proxmoxcheckbox',
|
||||||
|
@ -24,6 +24,9 @@ Ext.define('PVE.sdn.zones.QinQInputPanel', {
|
|||||||
name: 'bridge',
|
name: 'bridge',
|
||||||
fieldLabel: 'Bridge',
|
fieldLabel: 'Bridge',
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
|
vtype: 'BridgeName',
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xtype: 'proxmoxintegerfield',
|
xtype: 'proxmoxintegerfield',
|
||||||
|
@ -20,10 +20,13 @@ Ext.define('PVE.sdn.zones.VlanInputPanel', {
|
|||||||
|
|
||||||
me.items = [
|
me.items = [
|
||||||
{
|
{
|
||||||
xtype: 'textfield',
|
xtype: 'textfield',
|
||||||
name: 'bridge',
|
name: 'bridge',
|
||||||
fieldLabel: 'Bridge',
|
fieldLabel: 'Bridge',
|
||||||
allowBlank: false,
|
allowBlank: false,
|
||||||
|
vtype: 'BridgeName',
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: 10,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ Ext.define('PVE.window.GuestDiskReassign', {
|
|||||||
},
|
},
|
||||||
formulas: {
|
formulas: {
|
||||||
mpMaxCount: get => get('mpType') === 'mp'
|
mpMaxCount: get => get('mpType') === 'mp'
|
||||||
? PVE.Utils.mp_counts.mps - 1
|
? PVE.Utils.lxc_mp_counts.mps - 1
|
||||||
: PVE.Utils.mp_counts.unused - 1,
|
: PVE.Utils.lxc_mp_counts.unused - 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ Ext.define('PVE.window.GuestDiskReassign', {
|
|||||||
view.VMConfig = result.data;
|
view.VMConfig = result.data;
|
||||||
|
|
||||||
mpIdSelector.setValue(
|
mpIdSelector.setValue(
|
||||||
PVE.Utils.nextFreeMP(
|
PVE.Utils.nextFreeLxcMP(
|
||||||
view.getViewModel().get('mpType'),
|
view.getViewModel().get('mpType'),
|
||||||
view.VMConfig,
|
view.VMConfig,
|
||||||
).id,
|
).id,
|
||||||
|
@ -561,10 +561,17 @@ Ext.define('PVE.window.GuestImport', {
|
|||||||
fieldLabel: gettext('Live Import'),
|
fieldLabel: gettext('Live Import'),
|
||||||
reference: 'liveimport',
|
reference: 'liveimport',
|
||||||
isFormField: false,
|
isFormField: false,
|
||||||
boxLabelCls: 'pmx-hint black x-form-cb-label',
|
boxLabel: gettext('Starts a previously stopped VM on Proxmox VE and imports the disks in the background.'),
|
||||||
bind: {
|
bind: {
|
||||||
value: '{liveImport}',
|
value: '{liveImport}',
|
||||||
boxLabel: '{liveImportNote}',
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'displayfield',
|
||||||
|
userCls: 'pmx-hint black',
|
||||||
|
value: gettext('Note: If anything goes wrong during the live-import, new data written by the VM may be lost.'),
|
||||||
|
bind: {
|
||||||
|
hidden: '{!liveImport}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
77
www/manager6/window/GuestStop.js
Normal file
77
www/manager6/window/GuestStop.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
Ext.define('PVE.GuestStop', {
|
||||||
|
extend: 'Ext.window.MessageBox',
|
||||||
|
|
||||||
|
closeAction: 'destroy',
|
||||||
|
|
||||||
|
initComponent: function() {
|
||||||
|
let me = this;
|
||||||
|
|
||||||
|
if (!me.nodename) {
|
||||||
|
throw "no node name specified";
|
||||||
|
}
|
||||||
|
if (!me.vm) {
|
||||||
|
throw "no vm specified";
|
||||||
|
}
|
||||||
|
|
||||||
|
let isQemuVM = me.vm.type === 'qemu';
|
||||||
|
let overruleTaskType = isQemuVM ? 'qmshutdown' : 'vzshutdown';
|
||||||
|
|
||||||
|
me.taskType = isQemuVM ? 'qmstop' : 'vzstop';
|
||||||
|
me.url = `/nodes/${me.nodename}/${me.vm.type}/${me.vm.vmid}/status/stop`;
|
||||||
|
|
||||||
|
let caps = Ext.state.Manager.get('GuiCap');
|
||||||
|
let hasSysModify = !!caps.nodes['Sys.Modify'];
|
||||||
|
|
||||||
|
// offer to overrule if there is at least one matching shutdown task and the guest is not
|
||||||
|
// HA-enabled. Also allow users to abort tasks started by one of their API tokens.
|
||||||
|
let activeShutdownTask = Ext.getStore('pve-cluster-tasks')?.findBy(task =>
|
||||||
|
(hasSysModify || task.data.user === Proxmox.UserName) &&
|
||||||
|
task.data.id === me.vm.vmid.toString() &&
|
||||||
|
task.data.status === undefined &&
|
||||||
|
task.data.type === overruleTaskType,
|
||||||
|
) !== -1;
|
||||||
|
let haEnabled = me.vm.hastate && me.vm.hastate !== 'unmanaged';
|
||||||
|
|
||||||
|
me.callParent();
|
||||||
|
|
||||||
|
// message box has its actual content in a sub-container, the top one is just for layouting
|
||||||
|
me.promptContainer.add({
|
||||||
|
xtype: 'proxmoxcheckbox',
|
||||||
|
name: 'overrule-shutdown',
|
||||||
|
checked: !haEnabled && activeShutdownTask,
|
||||||
|
boxLabel: gettext('Overrule active shutdown tasks'),
|
||||||
|
hidden: !(hasSysModify || activeShutdownTask),
|
||||||
|
disabled: !(hasSysModify || activeShutdownTask) || haEnabled,
|
||||||
|
padding: '3 0 0 0',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handler: function(btn) {
|
||||||
|
let me = this;
|
||||||
|
if (btn === 'yes') {
|
||||||
|
let overruleField = me.promptContainer.down('proxmoxcheckbox[name=overrule-shutdown]');
|
||||||
|
let params = !overruleField.isDisabled() && overruleField.getSubmitValue()
|
||||||
|
? { 'overrule-shutdown': 1 }
|
||||||
|
: undefined;
|
||||||
|
Proxmox.Utils.API2Request({
|
||||||
|
url: me.url,
|
||||||
|
waitMsgTarget: me,
|
||||||
|
method: 'POST',
|
||||||
|
params: params,
|
||||||
|
failure: response => Ext.Msg.alert('Error', response.htmlStatus),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
let me = this;
|
||||||
|
let cfg = {
|
||||||
|
title: gettext('Confirm'),
|
||||||
|
icon: Ext.Msg.WARNING,
|
||||||
|
msg: Proxmox.Utils.format_task_description(me.taskType, me.vm.vmid),
|
||||||
|
buttons: Ext.Msg.YESNO,
|
||||||
|
callback: btn => me.handler(btn),
|
||||||
|
};
|
||||||
|
me.callParent([cfg]);
|
||||||
|
},
|
||||||
|
});
|
@ -344,6 +344,7 @@ Ext.define('PVE.window.LoginWindow', {
|
|||||||
itemId: 'usernameField',
|
itemId: 'usernameField',
|
||||||
reference: 'usernameField',
|
reference: 'usernameField',
|
||||||
stateId: 'login-username',
|
stateId: 'login-username',
|
||||||
|
inputAttrTpl: 'autocomplete=username',
|
||||||
bind: {
|
bind: {
|
||||||
visible: "{!openid}",
|
visible: "{!openid}",
|
||||||
disabled: "{openid}",
|
disabled: "{openid}",
|
||||||
@ -355,6 +356,7 @@ Ext.define('PVE.window.LoginWindow', {
|
|||||||
fieldLabel: gettext('Password'),
|
fieldLabel: gettext('Password'),
|
||||||
name: 'password',
|
name: 'password',
|
||||||
reference: 'passwordField',
|
reference: 'passwordField',
|
||||||
|
inputAttrTpl: 'autocomplete=current-password',
|
||||||
bind: {
|
bind: {
|
||||||
visible: "{!openid}",
|
visible: "{!openid}",
|
||||||
disabled: "{openid}",
|
disabled: "{openid}",
|
||||||
|
@ -41,6 +41,7 @@ Ext.define('PVE.window.Settings', {
|
|||||||
me.lookup('summarycolumns').setValue(summarycolumns);
|
me.lookup('summarycolumns').setValue(summarycolumns);
|
||||||
|
|
||||||
me.lookup('guestNotesCollapse').setValue(sp.get('guest-notes-collapse', 'never'));
|
me.lookup('guestNotesCollapse').setValue(sp.get('guest-notes-collapse', 'never'));
|
||||||
|
me.lookup('editNotesOnDoubleClick').setValue(sp.get('edit-notes-on-double-click', false));
|
||||||
|
|
||||||
var settings = ['fontSize', 'fontFamily', 'letterSpacing', 'lineHeight'];
|
var settings = ['fontSize', 'fontFamily', 'letterSpacing', 'lineHeight'];
|
||||||
settings.forEach(function(setting) {
|
settings.forEach(function(setting) {
|
||||||
@ -146,6 +147,9 @@ Ext.define('PVE.window.Settings', {
|
|||||||
'field[reference=guestNotesCollapse]': {
|
'field[reference=guestNotesCollapse]': {
|
||||||
change: (e, v) => Ext.state.Manager.getProvider().set('guest-notes-collapse', v),
|
change: (e, v) => Ext.state.Manager.getProvider().set('guest-notes-collapse', v),
|
||||||
},
|
},
|
||||||
|
'field[reference=editNotesOnDoubleClick]': {
|
||||||
|
change: (e, v) => Ext.state.Manager.getProvider().set('edit-notes-on-double-click', v),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -250,7 +254,7 @@ Ext.define('PVE.window.Settings', {
|
|||||||
{
|
{
|
||||||
xtype: 'proxmoxKVComboBox',
|
xtype: 'proxmoxKVComboBox',
|
||||||
fieldLabel: gettext('Summary columns') + ':',
|
fieldLabel: gettext('Summary columns') + ':',
|
||||||
labelWidth: 150,
|
labelWidth: 125,
|
||||||
stateId: 'summarycolumns',
|
stateId: 'summarycolumns',
|
||||||
reference: 'summarycolumns',
|
reference: 'summarycolumns',
|
||||||
comboItems: [
|
comboItems: [
|
||||||
@ -263,7 +267,7 @@ Ext.define('PVE.window.Settings', {
|
|||||||
{
|
{
|
||||||
xtype: 'proxmoxKVComboBox',
|
xtype: 'proxmoxKVComboBox',
|
||||||
fieldLabel: gettext('Guest Notes') + ':',
|
fieldLabel: gettext('Guest Notes') + ':',
|
||||||
labelWidth: 150,
|
labelWidth: 125,
|
||||||
stateId: 'guest-notes-collapse',
|
stateId: 'guest-notes-collapse',
|
||||||
reference: 'guestNotesCollapse',
|
reference: 'guestNotesCollapse',
|
||||||
comboItems: [
|
comboItems: [
|
||||||
@ -272,6 +276,15 @@ Ext.define('PVE.window.Settings', {
|
|||||||
['auto', 'auto (Collapse if empty)'],
|
['auto', 'auto (Collapse if empty)'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
xtype: 'checkbox',
|
||||||
|
fieldLabel: gettext('Notes'),
|
||||||
|
labelWidth: 125,
|
||||||
|
boxLabel: gettext('Open editor on double-click'),
|
||||||
|
reference: 'editNotesOnDoubleClick',
|
||||||
|
inputValue: true,
|
||||||
|
uncheckedValue: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -35,8 +35,12 @@ Ext.define('PVE.Login', {
|
|||||||
message: 'Loading...',
|
message: 'Loading...',
|
||||||
});
|
});
|
||||||
Proxmox.Utils.API2Request({
|
Proxmox.Utils.API2Request({
|
||||||
url: '/api2/extjs/access/tfa',
|
url: '/api2/extjs/access/ticket',
|
||||||
params: { response: code },
|
params: {
|
||||||
|
username: ticketResponse.username,
|
||||||
|
'tfa-challenge': ticketResponse.ticket,
|
||||||
|
password: `totp:${code}`
|
||||||
|
},
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
timeout: 5000, // it'll delay both success & failure
|
timeout: 5000, // it'll delay both success & failure
|
||||||
success: function(resp, opts) {
|
success: function(resp, opts) {
|
||||||
|
@ -7,9 +7,4 @@ Ext.Ajax.setDisableCaching(false);
|
|||||||
// do not send '_dc' parameter
|
// do not send '_dc' parameter
|
||||||
Ext.Ajax.disableCaching = false;
|
Ext.Ajax.disableCaching = false;
|
||||||
|
|
||||||
Ext.MessageBox = Ext.Msg = {
|
|
||||||
alert: (title, message) => console.warn(title, message),
|
|
||||||
show: ({ title, message }) => console.warn(title, message),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ext.Loader.injectScriptElement = (url) => console.warn(`surpressed loading ${url}`);
|
Ext.Loader.injectScriptElement = (url) => console.warn(`surpressed loading ${url}`);
|
||||||
|
@ -111,6 +111,8 @@ Ext.define('PVE.Workspace', {
|
|||||||
// also sets the cookie
|
// also sets the cookie
|
||||||
Proxmox.Utils.setAuthData(loginData);
|
Proxmox.Utils.setAuthData(loginData);
|
||||||
|
|
||||||
|
Proxmox.Utils.checked_command(Ext.emptyFn); // display subscription status
|
||||||
|
|
||||||
PVE.Workspace.gotoPage('');
|
PVE.Workspace.gotoPage('');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user