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) { let vm = this.getViewModel(); if (!success || !records || !records[0].data) { vm.set('totem', {}); vm.set('isInCluster', false); vm.set('nodelist', []); vm.set('preferred_node', { name: '', addr: '', fp: '', }); return; } let data = records[0].data; vm.set('totem', data.totem); vm.set('isInCluster', !!data.totem.cluster_name); vm.set('nodelist', data.nodelist); var nodeinfo = Ext.Array.findBy(data.nodelist, function(el) { return 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', order: 'DESC', }, })); 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, }, }, }, ], });