392e3cf11d
Ext.util.Sorter does not have an 'order' property, so 'order: DESC' didn't have an effect. The default is 'ASC' and it is arguably the preferred direction for all affected sorters anyways. Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
351 lines
7.7 KiB
JavaScript
351 lines
7.7 KiB
JavaScript
Ext.define('pve-cluster-nodes', {
|
|
extend: 'Ext.data.Model',
|
|
fields: [
|
|
'node', { type: 'integer', name: 'nodeid' }, 'ring0_addr', 'ring1_addr',
|
|
{ type: 'integer', name: 'quorum_votes' },
|
|
],
|
|
proxy: {
|
|
type: 'proxmox',
|
|
url: "/api2/json/cluster/config/nodes",
|
|
},
|
|
idProperty: 'nodeid',
|
|
});
|
|
|
|
Ext.define('pve-cluster-info', {
|
|
extend: 'Ext.data.Model',
|
|
proxy: {
|
|
type: 'proxmox',
|
|
url: "/api2/json/cluster/config/join",
|
|
},
|
|
});
|
|
|
|
Ext.define('PVE.ClusterAdministration', {
|
|
extend: 'Ext.panel.Panel',
|
|
xtype: 'pveClusterAdministration',
|
|
|
|
title: gettext('Cluster Administration'),
|
|
onlineHelp: 'chapter_pvecm',
|
|
|
|
border: false,
|
|
defaults: { border: false },
|
|
|
|
viewModel: {
|
|
parent: null,
|
|
data: {
|
|
totem: {},
|
|
nodelist: [],
|
|
preferred_node: {
|
|
name: '',
|
|
fp: '',
|
|
addr: '',
|
|
},
|
|
isInCluster: false,
|
|
nodecount: 0,
|
|
},
|
|
},
|
|
|
|
items: [
|
|
{
|
|
xtype: 'panel',
|
|
title: gettext('Cluster Information'),
|
|
controller: {
|
|
xclass: 'Ext.app.ViewController',
|
|
|
|
init: function(view) {
|
|
view.store = Ext.create('Proxmox.data.UpdateStore', {
|
|
autoStart: true,
|
|
interval: 15 * 1000,
|
|
storeid: 'pve-cluster-info',
|
|
model: 'pve-cluster-info',
|
|
});
|
|
view.store.on('load', this.onLoad, this);
|
|
view.on('destroy', view.store.stopUpdate);
|
|
},
|
|
|
|
onLoad: function(store, records, success, operation) {
|
|
let vm = this.getViewModel();
|
|
|
|
let data = records?.[0]?.data;
|
|
if (!success || !data || !data.nodelist?.length) {
|
|
let error = operation.getError();
|
|
if (error) {
|
|
let msg = Proxmox.Utils.getResponseErrorMessage(error);
|
|
if (error.status !== 424 && !msg.match(/node is not in a cluster/i)) {
|
|
// an actual error, not just the "not in a cluster one", so show it!
|
|
Proxmox.Utils.setErrorMask(this.getView(), error);
|
|
}
|
|
}
|
|
vm.set('totem', {});
|
|
vm.set('isInCluster', false);
|
|
vm.set('nodelist', []);
|
|
vm.set('preferred_node', {
|
|
name: '',
|
|
addr: '',
|
|
fp: '',
|
|
});
|
|
return;
|
|
}
|
|
vm.set('totem', data.totem);
|
|
vm.set('isInCluster', !!data.totem.cluster_name);
|
|
vm.set('nodelist', data.nodelist);
|
|
|
|
let nodeinfo = data.nodelist.find(el => el.name === data.preferred_node);
|
|
|
|
let links = {};
|
|
let ring_addr = [];
|
|
PVE.Utils.forEachCorosyncLink(nodeinfo, (num, link) => {
|
|
links[num] = link;
|
|
ring_addr.push(link);
|
|
});
|
|
|
|
vm.set('preferred_node', {
|
|
name: data.preferred_node,
|
|
addr: nodeinfo.pve_addr,
|
|
peerLinks: links,
|
|
ring_addr: ring_addr,
|
|
fp: nodeinfo.pve_fp,
|
|
});
|
|
},
|
|
|
|
onCreate: function() {
|
|
let view = this.getView();
|
|
view.store.stopUpdate();
|
|
Ext.create('PVE.ClusterCreateWindow', {
|
|
autoShow: true,
|
|
listeners: {
|
|
destroy: function() {
|
|
view.store.startUpdate();
|
|
},
|
|
},
|
|
});
|
|
},
|
|
|
|
onClusterInfo: function() {
|
|
let vm = this.getViewModel();
|
|
Ext.create('PVE.ClusterInfoWindow', {
|
|
autoShow: true,
|
|
joinInfo: {
|
|
ipAddress: vm.get('preferred_node.addr'),
|
|
fingerprint: vm.get('preferred_node.fp'),
|
|
peerLinks: vm.get('preferred_node.peerLinks'),
|
|
ring_addr: vm.get('preferred_node.ring_addr'),
|
|
totem: vm.get('totem'),
|
|
},
|
|
});
|
|
},
|
|
|
|
onJoin: function() {
|
|
let view = this.getView();
|
|
view.store.stopUpdate();
|
|
Ext.create('PVE.ClusterJoinNodeWindow', {
|
|
autoShow: true,
|
|
listeners: {
|
|
destroy: function() {
|
|
view.store.startUpdate();
|
|
},
|
|
},
|
|
});
|
|
},
|
|
},
|
|
tbar: [
|
|
{
|
|
text: gettext('Create Cluster'),
|
|
reference: 'createButton',
|
|
handler: 'onCreate',
|
|
bind: {
|
|
disabled: '{isInCluster}',
|
|
},
|
|
},
|
|
{
|
|
text: gettext('Join Information'),
|
|
reference: 'addButton',
|
|
handler: 'onClusterInfo',
|
|
bind: {
|
|
disabled: '{!isInCluster}',
|
|
},
|
|
},
|
|
{
|
|
text: gettext('Join Cluster'),
|
|
reference: 'joinButton',
|
|
handler: 'onJoin',
|
|
bind: {
|
|
disabled: '{isInCluster}',
|
|
},
|
|
},
|
|
],
|
|
layout: 'hbox',
|
|
bodyPadding: 5,
|
|
items: [
|
|
{
|
|
xtype: 'displayfield',
|
|
fieldLabel: gettext('Cluster Name'),
|
|
bind: {
|
|
value: '{totem.cluster_name}',
|
|
hidden: '{!isInCluster}',
|
|
},
|
|
flex: 1,
|
|
},
|
|
{
|
|
xtype: 'displayfield',
|
|
fieldLabel: gettext('Config Version'),
|
|
bind: {
|
|
value: '{totem.config_version}',
|
|
hidden: '{!isInCluster}',
|
|
},
|
|
flex: 1,
|
|
},
|
|
{
|
|
xtype: 'displayfield',
|
|
fieldLabel: gettext('Number of Nodes'),
|
|
labelWidth: 120,
|
|
bind: {
|
|
value: '{nodecount}',
|
|
hidden: '{!isInCluster}',
|
|
},
|
|
flex: 1,
|
|
},
|
|
{
|
|
xtype: 'displayfield',
|
|
value: gettext('Standalone node - no cluster defined'),
|
|
bind: {
|
|
hidden: '{isInCluster}',
|
|
},
|
|
flex: 1,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
xtype: 'grid',
|
|
title: gettext('Cluster Nodes'),
|
|
autoScroll: true,
|
|
enableColumnHide: false,
|
|
controller: {
|
|
xclass: 'Ext.app.ViewController',
|
|
|
|
init: function(view) {
|
|
view.rstore = Ext.create('Proxmox.data.UpdateStore', {
|
|
autoLoad: true,
|
|
xtype: 'update',
|
|
interval: 5 * 1000,
|
|
autoStart: true,
|
|
storeid: 'pve-cluster-nodes',
|
|
model: 'pve-cluster-nodes',
|
|
});
|
|
view.setStore(Ext.create('Proxmox.data.DiffStore', {
|
|
rstore: view.rstore,
|
|
sorters: {
|
|
property: 'nodeid',
|
|
direction: 'ASC',
|
|
},
|
|
}));
|
|
Proxmox.Utils.monStoreErrors(view, view.rstore);
|
|
view.rstore.on('load', this.onLoad, this);
|
|
view.on('destroy', view.rstore.stopUpdate);
|
|
},
|
|
|
|
onLoad: function(store, records, success) {
|
|
let view = this.getView();
|
|
let vm = this.getViewModel();
|
|
|
|
if (!success || !records || !records.length) {
|
|
vm.set('nodecount', 0);
|
|
return;
|
|
}
|
|
vm.set('nodecount', records.length);
|
|
|
|
// show/hide columns according to used links
|
|
let linkIndex = view.columns.length;
|
|
Ext.each(view.columns, (col, i) => {
|
|
if (col.linkNumber !== undefined) {
|
|
col.setHidden(true);
|
|
// save offset at which link columns start, so we can address them directly below
|
|
if (i < linkIndex) {
|
|
linkIndex = i;
|
|
}
|
|
}
|
|
});
|
|
|
|
PVE.Utils.forEachCorosyncLink(records[0].data,
|
|
(linknum, val) => {
|
|
if (linknum > 7) {
|
|
return;
|
|
}
|
|
view.columns[linkIndex + linknum].setHidden(false);
|
|
},
|
|
);
|
|
},
|
|
},
|
|
columns: {
|
|
items: [
|
|
{
|
|
header: gettext('Nodename'),
|
|
hidden: false,
|
|
dataIndex: 'name',
|
|
},
|
|
{
|
|
header: gettext('ID'),
|
|
minWidth: 100,
|
|
width: 100,
|
|
flex: 0,
|
|
hidden: false,
|
|
dataIndex: 'nodeid',
|
|
},
|
|
{
|
|
header: gettext('Votes'),
|
|
minWidth: 100,
|
|
width: 100,
|
|
flex: 0,
|
|
hidden: false,
|
|
dataIndex: 'quorum_votes',
|
|
},
|
|
{
|
|
header: Ext.String.format(gettext('Link {0}'), 0),
|
|
dataIndex: 'ring0_addr',
|
|
linkNumber: 0,
|
|
},
|
|
{
|
|
header: Ext.String.format(gettext('Link {0}'), 1),
|
|
dataIndex: 'ring1_addr',
|
|
linkNumber: 1,
|
|
},
|
|
{
|
|
header: Ext.String.format(gettext('Link {0}'), 2),
|
|
dataIndex: 'ring2_addr',
|
|
linkNumber: 2,
|
|
},
|
|
{
|
|
header: Ext.String.format(gettext('Link {0}'), 3),
|
|
dataIndex: 'ring3_addr',
|
|
linkNumber: 3,
|
|
},
|
|
{
|
|
header: Ext.String.format(gettext('Link {0}'), 4),
|
|
dataIndex: 'ring4_addr',
|
|
linkNumber: 4,
|
|
},
|
|
{
|
|
header: Ext.String.format(gettext('Link {0}'), 5),
|
|
dataIndex: 'ring5_addr',
|
|
linkNumber: 5,
|
|
},
|
|
{
|
|
header: Ext.String.format(gettext('Link {0}'), 6),
|
|
dataIndex: 'ring6_addr',
|
|
linkNumber: 6,
|
|
},
|
|
{
|
|
header: Ext.String.format(gettext('Link {0}'), 7),
|
|
dataIndex: 'ring7_addr',
|
|
linkNumber: 7,
|
|
},
|
|
],
|
|
defaults: {
|
|
flex: 1,
|
|
hidden: true,
|
|
minWidth: 150,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
});
|