From a0af013278498884ce8dd3eadc5c10f23f6a41e0 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 23 Dec 2011 10:41:50 +0100 Subject: [PATCH] add Datacenter summary --- PVE/API2/Cluster.pm | 133 +++++++++++++++++++ debian/changelog.Debian | 6 + debian/control.in | 2 +- defines.mk | 2 +- www/manager/Makefile | 1 + www/manager/dc/Config.js | 5 + www/manager/dc/Summary.js | 261 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 www/manager/dc/Summary.js diff --git a/PVE/API2/Cluster.pm b/PVE/API2/Cluster.pm index af4a1ab8..e8af7aa2 100644 --- a/PVE/API2/Cluster.pm +++ b/PVE/API2/Cluster.pm @@ -3,6 +3,8 @@ package PVE::API2::Cluster; use strict; use warnings; +use XML::Parser; + use PVE::SafeSyslog; use PVE::Tools qw(extract_param); use PVE::INotify; @@ -66,6 +68,7 @@ __PACKAGE__->register_method ({ { name => 'tasks' }, { name => 'backup' }, { name => 'ha' }, + { name => 'status' }, ]; return $result; @@ -347,4 +350,134 @@ __PACKAGE__->register_method({ return undef; }}); +my $parse_clustat = sub { + my ($clinfo, $members, $nodename, $raw) = @_; + + my $createNode = sub { + my ($expat, $tag, %attrib) = @_; + my $node = { type => $tag, %attrib }; + + if ($tag eq 'node') { + my $name = $node->{name}; + return if !$name; # just to be sure + + foreach my $key (qw(estranged local qdisk rgmanager rgmanager_master state)) { + $node->{$key} = int($node->{$key}) if defined($node->{$key}); + } + $node->{nodeid} = hex($node->{nodeid}) if defined($node->{nodeid}); + + # unique ID for GUI + $node->{id} = "node/$node->{name}"; + + my $pmxcfs = 0; + if (!$members) { # no cluster + if ($name eq $nodename) { + $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ? + } + } elsif ($members->{$name}) { + $pmxcfs = $members->{$name}->{online} ? 1 : 0 + } + $node->{pmxcfs} = $pmxcfs; + + if ($members && $members->{$name}) { + if (my $ip = $members->{$name}->{ip}) { + $node->{ip} = $ip; + } + } + } elsif ($tag eq 'group') { + my $name = $node->{name}; + return if !$name; # just to be sure + # unique ID for GUI + $node->{id} = "group/$node->{name}"; + } else { + $node->{id} = $tag; + } + + return $node; + }; + + my $extract_tags = { + cluster => 1, + quorum => 1, + node => 1, + group => 1, + }; + + my $handlers = { + Init => sub { + my $expat = shift; + $expat->{NodeList} = []; + }, + Final => sub { + my $expat = shift; + $expat->{NodeList}; + }, + Start => sub { + my $expat = shift; + my $tag = shift; + if ($extract_tags->{$tag}) { + my $node = &$createNode($expat, $tag, @_); + push @{$expat->{NodeList}}, $node; + } + }, + }; + + my $data = []; + if ($raw) { + my $parser = new XML::Parser(Handlers => $handlers); + $data = $parser->parse($raw); + } + return $data; +}; + +__PACKAGE__->register_method({ + name => 'get_status', + path => 'status', + method => 'GET', + description => "Get cluster status informations.", + permissions => { user => 'all' }, + protected => 1, + parameters => { + additionalProperties => 0, + properties => {}, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + type => { + type => 'string' + }, + }, + }, + }, + code => sub { + my ($param) = @_; + + # we also add info from pmxcfs + my $clinfo = PVE::Cluster::get_clinfo(); + my $members = PVE::Cluster::get_members(); + my $nodename = PVE::INotify::nodename(); + + if ($members) { + my $cmd = ['clustat', '-x']; + my $out = ''; + PVE::Tools::run_command($cmd, outfunc => sub { $out .= shift; }); + return &$parse_clustat($clinfo, $members, $nodename, $out); + } else { + # fake entry for local node if no cluster defined + my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ? + return [{ + type => 'node', + id => "node/$nodename", + name => $nodename, + 'local' => 1, + nodeid => 0, + pmxcfs => $pmxcfs, + state => 1 + }]; + } + }}); + 1; diff --git a/debian/changelog.Debian b/debian/changelog.Debian index d9cdf6e9..6441259d 100644 --- a/debian/changelog.Debian +++ b/debian/changelog.Debian @@ -1,3 +1,9 @@ +pve-manager (2.0-18) unstable; urgency=low + + * add Datacenter summary + + -- Proxmox Support Team Fri, 23 Dec 2011 10:39:34 +0100 + pve-manager (2.0-17) unstable; urgency=low * new HA configuration panel diff --git a/debian/control.in b/debian/control.in index a624805c..a109b5ff 100644 --- a/debian/control.in +++ b/debian/control.in @@ -3,7 +3,7 @@ Version: @VERSION@-@PACKAGERELEASE@ Section: admin Priority: optional Architecture: all -Depends: perl5, libtimedate-perl, apache2-mpm-prefork, libauthen-pam-perl, libintl-perl, rsync, libapache2-request-perl, libjson-perl, libdigest-sha1-perl, liblockfile-simple-perl, vncterm, qemu-server (>= 1.1-1), libwww-perl, wget, libnet-dns-perl, vlan, ifenslave-2.6 (>= 1.1.0-10), liblinux-inotify2-perl, debconf (>= 0.5) | debconf-2.0, netcat-traditional, pve-cluster, libpve-common-perl, libpve-storage-perl, libterm-readline-gnu-perl, libpve-access-control, libio-socket-ssl-perl, libfilesys-df-perl, libfile-readbackwards-perl, libfile-sync-perl, redhat-cluster-pve, cstream, mail-transport-agent +Depends: perl5, libtimedate-perl, apache2-mpm-prefork, libauthen-pam-perl, libintl-perl, rsync, libapache2-request-perl, libjson-perl, libdigest-sha1-perl, liblockfile-simple-perl, vncterm, qemu-server (>= 1.1-1), libwww-perl, wget, libnet-dns-perl, vlan, ifenslave-2.6 (>= 1.1.0-10), liblinux-inotify2-perl, debconf (>= 0.5) | debconf-2.0, netcat-traditional, pve-cluster, libpve-common-perl, libpve-storage-perl, libterm-readline-gnu-perl, libpve-access-control, libio-socket-ssl-perl, libfilesys-df-perl, libfile-readbackwards-perl, libfile-sync-perl, redhat-cluster-pve, cstream, mail-transport-agent, libxml-parser-perl Conflicts: netcat-openbsd, vzdump Replaces: vzdump Provides: vzdump diff --git a/defines.mk b/defines.mk index a86e081a..b94eecaf 100644 --- a/defines.mk +++ b/defines.mk @@ -2,7 +2,7 @@ RELEASE=2.0 VERSION=2.0 PACKAGE=pve-manager -PACKAGERELEASE=17 +PACKAGERELEASE=18 BINDIR=${DESTDIR}/usr/bin PERLLIBDIR=${DESTDIR}/usr/share/perl5 diff --git a/www/manager/Makefile b/www/manager/Makefile index a06d2bd8..1035267e 100644 --- a/www/manager/Makefile +++ b/www/manager/Makefile @@ -110,6 +110,7 @@ JSSRC= \ storage/NFSEdit.js \ storage/IScsiEdit.js \ storage/LVMEdit.js \ + dc/Summary.js \ dc/OptionView.js \ dc/StorageView.js \ dc/UserEdit.js \ diff --git a/www/manager/dc/Config.js b/www/manager/dc/Config.js index c26bc9e9..337ccf08 100644 --- a/www/manager/dc/Config.js +++ b/www/manager/dc/Config.js @@ -9,6 +9,11 @@ Ext.define('PVE.dc.Config', { title: gettext("Datacenter"), hstateid: 'dctab', items: [ + { + title: gettext('Summary'), + xtype: 'pveDcSummary', + itemId: 'summary' + }, { xtype: 'pveDcOptionView', title: gettext('Options'), diff --git a/www/manager/dc/Summary.js b/www/manager/dc/Summary.js new file mode 100644 index 00000000..30524354 --- /dev/null +++ b/www/manager/dc/Summary.js @@ -0,0 +1,261 @@ +Ext.define('PVE.dc.NodeView', { + extend: 'Ext.grid.GridPanel', + + alias: ['widget.pveDcNodeView'], + + initComponent : function() { + var me = this; + + var rstore = Ext.create('PVE.data.UpdateStore', { + interval: 3000, + storeid: 'pve-dc-nodes', + model: 'pve-dc-nodes', + proxy: { + type: 'pve', + url: "/api2/json/cluster/status" + }, + filters: { + property: 'type', + value : 'node' + } + }); + + var store = Ext.create('PVE.data.DiffStore', { rstore: rstore }); + + Ext.apply(me, { + store: store, + stateful: false, + columns: [ + { + header: gettext('Name'), + width: 200, + sortable: true, + dataIndex: 'name' + }, + { + header: 'ID', + width: 50, + sortable: true, + dataIndex: 'nodeid' + }, + { + header: gettext('Online'), + width: 100, + sortable: true, + dataIndex: 'state', + renderer: PVE.Utils.format_boolean + }, + { + header: gettext('Estranged'), + width: 100, + sortable: true, + dataIndex: 'estranged', + renderer: PVE.Utils.format_boolean + }, + { + header: gettext('Server Address'), + width: 100, + sortable: true, + dataIndex: 'ip' + }, + { + header: gettext('Services'), + flex: 1, + width: 80, + sortable: true, + dataIndex: 'pmxcfs', + renderer: function(value, metaData, record) { + var list = []; + var data = record.data; + if (data) { + if (data.pmxcfs) { + list.push('PVECluster'); + } + if (data.rgmanager) { + list.push('RGManager'); + } + + } + return list.join(', '); + } + } + ], + listeners: { + show: rstore.startUpdate, + hide: rstore.stopUpdate, + destroy: rstore.stopUpdate + } + }); + + me.callParent(); + } +}, function() { + + Ext.define('pve-dc-nodes', { + extend: 'Ext.data.Model', + fields: [ 'id', 'type', 'name', 'state', 'nodeid', 'ip', + 'pmxcfs', 'rgmanager', 'estranged' ], + idProperty: 'id' + }); + +}); + +Ext.define('PVE.dc.HAServiceView', { + extend: 'Ext.grid.GridPanel', + + alias: ['widget.pveHaServiceView'], + + initComponent : function() { + var me = this; + + var rstore = Ext.create('PVE.data.UpdateStore', { + interval: 3000, + storeid: 'pve-ha-services', + model: 'pve-ha-services', + proxy: { + type: 'pve', + url: "/api2/json/cluster/status" + }, + filters: { + property: 'type', + value : 'group' + } + }); + + var store = Ext.create('PVE.data.DiffStore', { rstore: rstore }); + + var noClusterText = gettext("Standalone node - no cluster defined"); + var status = Ext.create('Ext.Component', { + padding: 2, + html: ' ', + dock: 'bottom' + }); + + Ext.apply(me, { + store: store, + stateful: false, + //tbar: [ 'start', 'stop' ], + bbar: [ status ], + columns: [ + { + header: gettext('Name'), + flex: 1, + sortable: true, + dataIndex: 'name' + }, + { + header: gettext('Owner'), + flex: 1, + sortable: true, + dataIndex: 'owner' + }, + { + header: gettext('State'), + width: 80, + sortable: true, + dataIndex: 'state_str' + }, + { + header: gettext('Restarts'), + width: 80, + sortable: true, + dataIndex: 'restarts' + }, + { + header: gettext('Last transition'), + width: 200, + sortable: true, + dataIndex: 'last_transition' + }, + { + header: gettext('Last owner'), + flex: 1, + sortable: true, + dataIndex: 'last_owner' + }, + ], + listeners: { + show: rstore.startUpdate, + hide: rstore.stopUpdate, + destroy: rstore.stopUpdate + } + }); + + me.callParent(); + + rstore.on('load', function(s, records, success) { + if (!success) { + return; + } + + var cluster_rec = rstore.getById('cluster'); + var quorum_rec = rstore.getById('quorum'); + + if (!(cluster_rec && quorum_rec)) { + status.update(noClusterText); + return; + } + + var cluster_raw = cluster_rec.raw; + var quorum_raw = quorum_rec.raw; + if (!(cluster_raw && quorum_raw)) { + status.update(noClusterText); + return; + } + + status.update("Quorate: " + PVE.Utils.format_boolean(quorum_raw.quorate)); + }); + + } +}, function() { + + Ext.define('pve-ha-services', { + extend: 'Ext.data.Model', + fields: [ 'id', 'type', 'name', 'owner', 'last_owner', 'state_str', 'restarts', + { name: 'last_transition', type: 'date', dateFormat: 'timestamp'} + ], + idProperty: 'id' + }); + +}); + + +Ext.define('PVE.dc.Summary', { + extend: 'Ext.panel.Panel', + alias: 'widget.pveDcSummary', + + initComponent: function() { + var me = this; + + var hagrid = Ext.create('PVE.dc.HAServiceView', { + title: gettext('HA Service Status'), + region: 'south', + border: false, + split: true, + flex: 1 + }); + + var nodegrid = Ext.create('PVE.dc.NodeView', { + title: gettext('Nodes'), + border: false, + region: 'center', + flex: 3 + }); + + Ext.apply(me, { + layout: 'border', + items: [ nodegrid, hagrid ], + listeners: { + show: function() { + hagrid.fireEvent('show', hagrid); + nodegrid.fireEvent('show', hagrid); + }, + hide: function() { + hagrid.fireEvent('hide', hagrid); + nodegrid.fireEvent('hide', hagrid); + } + } + }); + me.callParent(); + } +});