gui: ceph: add ServiceList component and use it

this is an abstraction for listing Ceph Services with a few improvements:
* start/stop/restart buttons for all services (incl. icons)
* a syslog button to view the syslog of that service
* correct reloading behaviour when creating/destroying

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
Dominik Csapak 2019-06-04 14:47:53 +02:00 committed by Thomas Lamprecht
parent c081e24cff
commit c1814e6174
4 changed files with 358 additions and 221 deletions

View File

@ -96,6 +96,7 @@ JSSRC= \
panel/IPSet.js \
panel/ConfigPanel.js \
grid/BackupView.js \
ceph/ServiceList.js \
ceph/FS.js \
ceph/Pool.js \
ceph/OSD.js \

View File

@ -1,234 +1,46 @@
Ext.define('PVE.CephCreateMon', {
extend: 'Proxmox.window.Edit',
alias: ['widget.pveCephCreateMon'],
Ext.define('PVE.node.CephMonMgrList', {
extend: 'Ext.container.Container',
xtype: 'pveNodeCephMonMgr',
subject: 'Ceph Monitor/Manager',
onlineHelp: 'pve_ceph_monitors',
showProgress: true,
setNode: function(nodename) {
var me = this;
me.nodename = nodename;
me.url = "/nodes/" + nodename + "/ceph/mon";
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
me.setNode(me.nodename);
me.isCreate = true;
Ext.applyIf(me, {
method: 'POST',
items: [
{
xtype: 'pveNodeSelector',
submitValue: false,
fieldLabel: gettext('Host'),
selectCurNode: true,
allowBlank: false,
listeners: {
change: function(f, value) {
me.setNode(value);
}
}
}
]
});
me.callParent();
}
});
Ext.define('PVE.node.CephMonList', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveNodeCephMonList'],
mixins: ['Proxmox.Mixin.CBind' ],
onlineHelp: 'chapter_pveceph',
stateful: true,
stateId: 'grid-ceph-monitor',
defaults: {
border: false,
onlineHelp: 'chapter_pveceph',
flex: 1
},
initComponent: function() {
var me = this;
layout: {
type: 'vbox',
align: 'stretch'
},
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var sm = Ext.create('Ext.selection.RowModel', {});
var rstore = Ext.create('Proxmox.data.UpdateStore', {
interval: 3000,
storeid: 'ceph-mon-list' + nodename,
model: 'ceph-mon-list',
proxy: {
type: 'proxmox',
url: "/api2/json/nodes/" + nodename + "/ceph/mon"
}
});
var store = Ext.create('Proxmox.data.DiffStore', {
rstore: rstore,
sorters: [{ property: 'name'}]
});
var service_cmd = function(cmd) {
var rec = sm.getSelection()[0];
if (!rec.data.host) {
Ext.Msg.alert(gettext('Error'), "entry has no host");
return;
}
Proxmox.Utils.API2Request({
url: "/nodes/" + rec.data.host + "/ceph/" + cmd,
method: 'POST',
params: { service: "mon." + rec.data.name },
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid });
win.show();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
};
var start_btn = new Proxmox.button.Button({
text: gettext('Start'),
selModel: sm,
disabled: true,
handler: function(){
service_cmd("start");
}
});
var stop_btn = new Proxmox.button.Button({
text: gettext('Stop'),
selModel: sm,
disabled: true,
handler: function(){
service_cmd("stop");
}
});
var restart_btn = new Proxmox.button.Button({
text: gettext('Restart'),
selModel: sm,
disabled: true,
handler: function(){
service_cmd("restart");
}
});
var create_btn = new Ext.Button({
text: gettext('Create'),
handler: function(){
var win = Ext.create('PVE.CephCreateMon', {
nodename: nodename
});
win.show();
}
});
var remove_btn = new Proxmox.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
handler: function() {
var rec = sm.getSelection()[0];
if (!rec.data.host) {
Ext.Msg.alert(gettext('Error'), "entry has no host");
return;
}
Proxmox.Utils.API2Request({
url: "/nodes/" + rec.data.host + "/ceph/mon/" +
rec.data.name,
method: 'DELETE',
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid });
win.show();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
Ext.apply(me, {
store: store,
selModel: sm,
tbar: [ start_btn, stop_btn, restart_btn, '-', create_btn, remove_btn ],
columns: [
{
header: gettext('Name'),
width: 100,
sortable: true,
renderer: function(v) { return "mon." + v; },
dataIndex: 'name'
},
{
header: gettext('Host'),
width: 100,
sortable: true,
renderer: function(v) {
return v || 'unknown';
},
dataIndex: 'host'
},
items: [
{
xtype: 'pveNodeCephServiceList',
cbind: { pveSelNode: '{pveSelNode}' },
type: 'mon',
additionalColumns: [
{
header: gettext('Quorum'),
width: 70,
sortable: false,
sortable: true,
renderer: Proxmox.Utils.format_boolean,
dataIndex: 'quorum'
},
{
header: gettext('Address'),
flex: 1,
sortable: true,
dataIndex: 'addr'
}
],
listeners: {
activate: rstore.startUpdate,
destroy: rstore.stopUpdate
}
});
var regex = new RegExp("not (installed|initialized)", "i");
PVE.Utils.handleStoreErrorOrMask(me, rstore, regex, function(me, error){
me.store.rstore.stopUpdate();
PVE.Utils.showCephInstallOrMask(me, error.statusText, nodename,
function(win){
me.mon(win, 'cephInstallWindowClosed', function(){
me.store.rstore.startUpdate();
});
}
);
});
me.callParent();
}
}, function() {
Ext.define('ceph-mon-list', {
extend: 'Ext.data.Model',
fields: [ 'addr', 'name', 'rank', 'host', 'quorum' ],
idProperty: 'name'
});
stateId: 'grid-ceph-monitor',
showCephInstallMask: true,
title: gettext('Monitor')
},
{
xtype: 'pveNodeCephServiceList',
type: 'mgr',
stateId: 'grid-ceph-manager',
cbind: { pveSelNode: '{pveSelNode}' },
title: gettext('Manager')
}
]
});

View File

@ -0,0 +1,324 @@
Ext.define('PVE.CephCreateService', {
extend: 'Proxmox.window.Edit',
xtype: 'pveCephCreateService',
showProgress: true,
setNode: function(nodename) {
var me = this;
me.nodename = nodename;
me.url = "/nodes/" + nodename + "/ceph/" + me.type;
},
method: 'POST',
isCreate: true,
items: [
{
xtype: 'pveNodeSelector',
submitValue: false,
fieldLabel: gettext('Host'),
selectCurNode: true,
allowBlank: false,
listeners: {
change: function(f, value) {
var me = this.up('pveCephCreateService');
me.setNode(value);
}
}
}
],
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.type) {
throw "no type specified";
}
me.setNode(me.nodename);
me.callParent();
}
});
Ext.define('PVE.node.CephServiceList', {
extend: 'Ext.grid.GridPanel',
xtype: 'pveNodeCephServiceList',
onlineHelp: 'chapter_pveceph',
emptyText: Ext.String.format(gettext('No {0} configured.'), 'MDS'),
stateful: true,
// will be called when the store loads
storeLoadCallback: Ext.emptyFn,
// if set to true, does shows the ceph install mask if needed
showCephInstallMask: false,
controller: {
xclass: 'Ext.app.ViewController',
init: function(view) {
if (view.pveSelNode) {
view.nodename = view.pveSelNode.data.node;
}
if (!view.nodename) {
throw "no node name specified";
}
if (!view.type) {
throw "no type specified";
}
view.rstore = Ext.create('Proxmox.data.UpdateStore', {
autoLoad: true,
autoStart: true,
interval: 3000,
storeid: 'ceph-' + view.type + '-list' + view.nodename,
model: 'ceph-service-list',
proxy: {
type: 'proxmox',
url: "/api2/json/nodes/" + view.nodename + "/ceph/" + view.type
}
});
view.setStore(Ext.create('Proxmox.data.DiffStore', {
rstore: view.rstore,
sorters: [{ property: 'name' }]
}));
if (view.storeLoadCallback) {
view.rstore.on('load', view.storeLoadCallback, this);
}
view.on('destroy', view.rstore.stopUpdate);
if (view.showCephInstallMask) {
var regex = new RegExp("not (installed|initialized)", "i");
PVE.Utils.handleStoreErrorOrMask(view, view.rstore, regex, function(me, error) {
view.rstore.stopUpdate();
PVE.Utils.showCephInstallOrMask(view.ownerCt, error.statusText, view.nodename,
function(win){
me.mon(win, 'cephInstallWindowClosed', function(){
view.rstore.startUpdate();
});
}
);
});
}
},
service_cmd: function(rec, cmd) {
var view = this.getView();
if (!rec.data.host) {
Ext.Msg.alert(gettext('Error'), "entry has no host");
return;
}
Proxmox.Utils.API2Request({
url: "/nodes/" + rec.data.host + "/ceph/" + cmd,
method: 'POST',
params: { service: view.type + '.' + rec.data.name },
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('Proxmox.window.TaskProgress', {
upid: upid,
taskDone: function() {
view.rstore.load();
}
});
win.show();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
},
onChangeService: function(btn) {
var me = this;
var view = this.getView();
var cmd = btn.action;
var rec = view.getSelection()[0];
me.service_cmd(rec, cmd);
},
showSyslog: function() {
var view = this.getView();
var rec = view.getSelection()[0];
var servicename = 'ceph-' + view.type + '@' + rec.data.name;
var url = "/api2/extjs/nodes/" + rec.data.host + "/syslog?service=" + encodeURIComponent(servicename);
var win = Ext.create('Ext.window.Window', {
title: gettext('Syslog') + ': ' + servicename,
modal: true,
items: [{
xtype: 'proxmoxLogView',
width: 800,
height: 400,
url: url,
log_select_timespan: 1
}]
});
win.show();
},
onCreate: function() {
var view = this.getView();
var win = Ext.create('PVE.CephCreateService', {
autoShow: true,
nodename: view.nodename,
subject: view.getTitle(),
type: view.type,
taskDone: function() {
view.rstore.load();
}
});
}
},
tbar: [
{
xtype: 'proxmoxButton',
text: gettext('Start'),
iconCls: 'fa fa-play',
action: 'start',
disabled: true,
enableFn: function(rec) {
return rec.data.state === 'stopped' ||
rec.data.state === 'unknown';
},
handler: 'onChangeService'
},
{
xtype: 'proxmoxButton',
text: gettext('Stop'),
iconCls: 'fa fa-stop',
action: 'stop',
enableFn: function(rec) {
return rec.data.state !== 'stopped';
},
disabled: true,
handler: 'onChangeService'
},
{
xtype: 'proxmoxButton',
text: gettext('Restart'),
iconCls: 'fa fa-refresh',
action: 'restart',
disabled: true,
enableFn: function(rec) {
return rec.data.state !== 'stopped';
},
handler: 'onChangeService'
},
'-',
{
text: gettext('Create'),
reference: 'createButton',
handler: 'onCreate'
},
{
text: gettext('Destroy'),
xtype: 'proxmoxStdRemoveButton',
getUrl: function(rec) {
var view = this.up('grid');
if (!rec.data.host) {
Ext.Msg.alert(gettext('Error'), "entry has no host");
return;
}
return "/nodes/" + rec.data.host + "/ceph/" + view.type + "/" + rec.data.name;
},
callback: function(options, success, response) {
var view = this.up('grid');
if (!success) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
return;
}
var upid = response.result.data;
var win = Ext.create('Proxmox.window.TaskProgress', {
upid: upid,
taskDone: function() {
view.rstore.load();
}
});
win.show();
}
},
'-',
{
xtype: 'proxmoxButton',
text: gettext('Syslog'),
disabled: true,
handler: 'showSyslog'
}
],
columns: [
{
header: gettext('Name'),
width: 100,
sortable: true,
renderer: function(v) {
return this.type + '.' + v;
},
dataIndex: 'name'
},
{
header: gettext('Host'),
width: 100,
sortable: true,
renderer: function(v) {
return v || Proxmox.Utils.unknownText;
},
dataIndex: 'host'
},
{
header: gettext('Status'),
width: 70,
sortable: false,
dataIndex: 'state'
},
{
header: gettext('Address'),
flex: 1,
sortable: true,
renderer: function(v) {
return v || Proxmox.Utils.unknownText;
},
dataIndex: 'addr'
},
{
header: gettext('Version'),
flex: 1,
sortable: true,
dataIndex: 'version'
}
],
initComponent: function() {
var me = this;
if (me.additionalColumns) {
me.columns = me.columns.concat(me.additionalColumns);
}
me.callParent();
}
}, function() {
Ext.define('ceph-service-list', {
extend: 'Ext.data.Model',
fields: [ 'addr', 'name', 'rank', 'host', 'quorum', 'state',
'ceph_version', 'ceph_version_short',
{ type: 'string', name: 'version', calculate: function(data) {
return PVE.Utils.parse_ceph_version(data);
} }
],
idProperty: 'name'
});
});

View File

@ -326,7 +326,7 @@ Ext.define('PVE.node.Config', {
itemId: 'ceph-config'
},
{
xtype: 'pveNodeCephMonList',
xtype: 'pveNodeCephMonMgr',
title: gettext('Monitor'),
iconCls: 'fa fa-tv',
groups: ['ceph'],