pve-manager/www/manager6/grid/Replication.js
Dominik Csapak 01740cb2f7 ui: factor out standalone node check
into Utils and use it where we manually checked that

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-11-13 11:19:46 +01:00

483 lines
10 KiB
JavaScript

Ext.define('PVE.window.ReplicaEdit', {
extend: 'Proxmox.window.Edit',
xtype: 'pveReplicaEdit',
subject: gettext('Replication Job'),
url: '/cluster/replication',
method: 'POST',
initComponent: function() {
var me = this;
var vmid = me.pveSelNode.data.vmid;
var nodename = me.pveSelNode.data.node;
var items = [];
items.push({
xtype: me.isCreate && !vmid?'pveGuestIDSelector':'displayfield',
name: 'guest',
fieldLabel: 'CT/VM ID',
value: vmid || '',
});
items.push(
{
xtype: me.isCreate ? 'pveNodeSelector':'displayfield',
name: 'target',
disallowedNodes: [nodename],
allowBlank: false,
onlineValidator: true,
fieldLabel: gettext("Target"),
},
{
xtype: 'pveCalendarEvent',
fieldLabel: gettext('Schedule'),
emptyText: '*/15 - ' + Ext.String.format(gettext('Every {0} minutes'), 15),
name: 'schedule',
},
{
xtype: 'numberfield',
fieldLabel: gettext('Rate limit') + ' (MB/s)',
step: 1,
minValue: 1,
emptyText: gettext('unlimited'),
name: 'rate',
},
{
xtype: 'textfield',
fieldLabel: gettext('Comment'),
name: 'comment',
},
{
xtype: 'proxmoxcheckbox',
name: 'enabled',
defaultValue: 'on',
checked: true,
fieldLabel: gettext('Enabled'),
},
);
me.items = [
{
xtype: 'inputpanel',
itemId: 'ipanel',
onlineHelp: 'pvesr_schedule_time_format',
onGetValues: function(values) {
let win = this.up('window');
values.disable = values.enabled ? 0 : 1;
delete values.enabled;
PVE.Utils.delete_if_default(values, 'rate', '', win.isCreate);
PVE.Utils.delete_if_default(values, 'disable', 0, win.isCreate);
PVE.Utils.delete_if_default(values, 'schedule', '*/15', win.isCreate);
PVE.Utils.delete_if_default(values, 'comment', '', win.isCreate);
if (win.isCreate) {
values.type = 'local';
let vm = vmid || values.guest;
let id = -1;
if (win.highestids[vm] !== undefined) {
id = win.highestids[vm];
}
id++;
values.id = vm + '-' + id.toString();
delete values.guest;
}
return values;
},
items: items,
},
];
me.callParent();
if (me.isCreate) {
me.load({
success: function(response) {
var jobs = response.result.data;
var highestids = {};
Ext.Array.forEach(jobs, function(job) {
var match = /^([0-9]+)-([0-9]+)$/.exec(job.id);
if (match) {
let jobVMID = parseInt(match[1], 10);
let id = parseInt(match[2], 10);
if (highestids[jobVMID] === undefined || highestids[jobVMID] < id) {
highestids[jobVMID] = id;
}
}
});
me.highestids = highestids;
},
});
} else {
me.load({
success: function(response, options) {
response.result.data.enabled = !response.result.data.disable;
me.setValues(response.result.data);
me.digest = response.result.data.digest;
},
});
}
},
});
/* callback is a function and string */
Ext.define('PVE.grid.ReplicaView', {
extend: 'Ext.grid.Panel',
xtype: 'pveReplicaView',
onlineHelp: 'chapter_pvesr',
stateful: true,
stateId: 'grid-pve-replication-status',
controller: {
xclass: 'Ext.app.ViewController',
addJob: function(button, event, rec) {
let me = this;
let view = me.getView();
Ext.create('PVE.window.ReplicaEdit', {
isCreate: true,
method: 'POST',
pveSelNode: view.pveSelNode,
listeners: {
destroy: () => me.reload(),
},
autoShow: true,
});
},
editJob: function(button, event, { data }) {
let me = this;
let view = me.getView();
Ext.create('PVE.window.ReplicaEdit', {
url: `/cluster/replication/${data.id}`,
method: 'PUT',
pveSelNode: view.pveSelNode,
listeners: {
destroy: () => me.reload(),
},
autoShow: true,
});
},
scheduleJobNow: function(button, event, rec) {
let me = this;
let view = me.getView();
Proxmox.Utils.API2Request({
url: `/api2/extjs/nodes/${view.nodename}/replication/${rec.data.id}/schedule_now`,
method: 'POST',
waitMsgTarget: view,
callback: () => me.reload(),
failure: (response, opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
});
},
showLog: function(button, event, rec) {
let me = this;
let view = this.getView();
let logView = Ext.create('Proxmox.panel.LogView', {
border: false,
url: `/api2/extjs/nodes/${view.nodename}/replication/${rec.data.id}/log`,
});
let task = Ext.TaskManager.newTask({
run: () => logView.requestUpdate(),
interval: 1000,
});
let win = Ext.create('Ext.window.Window', {
items: [logView],
layout: 'fit',
width: 800,
height: 400,
modal: true,
title: gettext("Replication Log"),
listeners: {
destroy: function() {
task.stop();
me.reload();
},
},
});
task.start();
win.show();
},
reload: function() {
this.getView().rstore.load();
},
dblClick: function(grid, record, item) {
this.editJob(undefined, undefined, record);
},
// currently replication is for cluster only, so disable the whole component for non-cluster
checkPrerequisites: function() {
let view = this.getView();
if (PVE.Utils.isStandaloneNode()) {
view.mask(gettext("Replication needs at least two nodes"), ['pve-static-mask']);
}
},
control: {
'#': {
itemdblclick: 'dblClick',
afterlayout: 'checkPrerequisites',
},
},
},
tbar: [
{
text: gettext('Add'),
itemId: 'addButton',
handler: 'addJob',
},
{
xtype: 'proxmoxButton',
text: gettext('Edit'),
itemId: 'editButton',
handler: 'editJob',
disabled: true,
},
{
xtype: 'proxmoxStdRemoveButton',
itemId: 'removeButton',
baseurl: '/api2/extjs/cluster/replication/',
dangerous: true,
callback: 'reload',
},
{
xtype: 'proxmoxButton',
text: gettext('Log'),
itemId: 'logButton',
handler: 'showLog',
disabled: true,
},
{
xtype: 'proxmoxButton',
text: gettext('Schedule now'),
itemId: 'scheduleNowButton',
handler: 'scheduleJobNow',
disabled: true,
},
],
initComponent: function() {
var me = this;
var mode = '';
var url = '/cluster/replication';
me.nodename = me.pveSelNode.data.node;
me.vmid = me.pveSelNode.data.vmid;
me.columns = [
{
header: gettext('Enabled'),
width: 80,
dataIndex: 'enabled',
align: 'center',
renderer: Proxmox.Utils.renderEnabledIcon,
sortable: true,
},
{
text: 'ID',
dataIndex: 'id',
width: 60,
hidden: true,
},
{
text: gettext('Guest'),
dataIndex: 'guest',
width: 75,
},
{
text: gettext('Job'),
dataIndex: 'jobnum',
width: 60,
},
{
text: gettext('Target'),
dataIndex: 'target',
},
];
if (!me.nodename) {
mode = 'dc';
me.stateId = 'grid-pve-replication-dc';
} else if (!me.vmid) {
mode = 'node';
url = `/nodes/${me.nodename}/replication`;
} else {
mode = 'vm';
url = `/nodes/${me.nodename}/replication?guest=${me.vmid}`;
}
if (mode !== 'dc') {
me.columns.push(
{
text: gettext('Status'),
dataIndex: 'state',
minWidth: 160,
flex: 1,
renderer: function(value, metadata, record) {
if (record.data.pid) {
metadata.tdCls = 'x-grid-row-loading';
return '';
}
let icons = [], states = [];
if (record.data.remove_job) {
icons.push('<i class="fa fa-ban warning" title="'
+ gettext("Removal Scheduled") + '"></i>');
states.push(gettext("Removal Scheduled"));
}
if (record.data.error) {
icons.push('<i class="fa fa-times critical" title="'
+ gettext("Error") + '"></i>');
states.push(record.data.error);
}
if (icons.length === 0) {
icons.push('<i class="fa fa-check good"></i>');
states.push(gettext('OK'));
}
return icons.join(',') + ' ' + states.join(',');
},
},
{
text: gettext('Last Sync'),
dataIndex: 'last_sync',
width: 150,
renderer: function(value, metadata, record) {
if (!value) {
return '-';
}
if (record.data.pid) {
return gettext('syncing');
}
return Proxmox.Utils.render_timestamp(value);
},
},
{
text: gettext('Duration'),
dataIndex: 'duration',
width: 60,
renderer: Proxmox.Utils.render_duration,
},
{
text: gettext('Next Sync'),
dataIndex: 'next_sync',
width: 150,
renderer: function(value) {
if (!value) {
return '-';
}
let now = new Date(), next = new Date(value * 1000);
if (next < now) {
return gettext('pending');
}
return Proxmox.Utils.render_timestamp(value);
},
},
);
}
me.columns.push(
{
text: gettext('Schedule'),
width: 75,
dataIndex: 'schedule',
},
{
text: gettext('Rate limit'),
dataIndex: 'rate',
renderer: function(value) {
if (!value) {
return gettext('unlimited');
}
return value.toString() + ' MB/s';
},
hidden: true,
},
{
text: gettext('Comment'),
dataIndex: 'comment',
renderer: Ext.htmlEncode,
},
);
me.rstore = Ext.create('Proxmox.data.UpdateStore', {
storeid: 'pve-replica-' + me.nodename + me.vmid,
model: mode === 'dc'? 'pve-replication' : 'pve-replication-state',
interval: 3000,
proxy: {
type: 'proxmox',
url: "/api2/json" + url,
},
});
me.store = Ext.create('Proxmox.data.DiffStore', {
rstore: me.rstore,
sorters: [
{
property: 'guest',
},
{
property: 'jobnum',
},
],
});
me.callParent();
// we cannot access the log and scheduleNow button
// in the datacenter, because
// we do not know where/if the jobs runs
if (mode === 'dc') {
me.down('#logButton').setHidden(true);
me.down('#scheduleNowButton').setHidden(true);
}
// if we set the warning mask, we do not want to load
// or set the mask on store errors
if (PVE.Utils.isStandaloneNode()) {
return;
}
Proxmox.Utils.monStoreErrors(me, me.rstore);
me.on('destroy', me.rstore.stopUpdate);
me.rstore.startUpdate();
},
}, function() {
Ext.define('pve-replication', {
extend: 'Ext.data.Model',
fields: [
'id', 'target', 'comment', 'rate', 'type',
{ name: 'guest', type: 'integer' },
{ name: 'jobnum', type: 'integer' },
{ name: 'schedule', defaultValue: '*/15' },
{ name: 'disable', defaultValue: '' },
{ name: 'enabled', calculate: function(data) { return !data.disable; } },
],
});
Ext.define('pve-replication-state', {
extend: 'pve-replication',
fields: [
'last_sync', 'next_sync', 'error', 'duration', 'state',
'fail_count', 'remove_job', 'pid',
],
});
});