mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-07-27 03:55:33 +00:00

By switching from 'ceph osd tree' to the 'ceph osd df tree' mon API equivalent , we get the same data structure with more information per OSD. One of them is the number of PGs stored on that OSD. The number of PGs per OSD is an important number, for example when trying to figure out why the performance is not as good as expected. Therefore, adding it to the OSD overview visible by default should reduce the number of times, one needs to access the CLI. Comparing runtime cost on a 3 node ceph cluster with 4 OSDs each doing 50k iterations gives: Rate osd-df-tree osd-tree osd-df-tree 9141/s -- -25% osd-tree 12136/s 33% -- So, while definitively a bit slower, but it's still in the µs range, and as such below HTTP in TLS in TCP connection setup for most users, so worth the extra useful information. Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com> [ TL: slight rewording of subject and add benchmark data ] Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
965 lines
21 KiB
JavaScript
965 lines
21 KiB
JavaScript
Ext.define('PVE.CephCreateOsd', {
|
|
extend: 'Proxmox.window.Edit',
|
|
xtype: 'pveCephCreateOsd',
|
|
|
|
subject: 'Ceph OSD',
|
|
|
|
showProgress: true,
|
|
|
|
onlineHelp: 'pve_ceph_osds',
|
|
|
|
initComponent: function() {
|
|
let me = this;
|
|
|
|
if (!me.nodename) {
|
|
throw "no node name specified";
|
|
}
|
|
|
|
me.isCreate = true;
|
|
|
|
Proxmox.Utils.API2Request({
|
|
url: `/nodes/${me.nodename}/ceph/crush`,
|
|
method: 'GET',
|
|
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
|
|
success: function({ result: { data } }) {
|
|
let classes = [...new Set(
|
|
Array.from(
|
|
data.matchAll(/^device\s[0-9]*\sosd\.[0-9]*\sclass\s(.*)$/gim),
|
|
m => m[1],
|
|
).filter(v => !['hdd', 'ssd', 'nvme'].includes(v)),
|
|
)].map(v => [v, v]);
|
|
|
|
if (classes.length) {
|
|
let kvField = me.down('field[name=crush-device-class]');
|
|
kvField.setComboItems([...kvField.comboItems, ...classes]);
|
|
}
|
|
},
|
|
});
|
|
|
|
Ext.applyIf(me, {
|
|
url: "/nodes/" + me.nodename + "/ceph/osd",
|
|
method: 'POST',
|
|
items: [
|
|
{
|
|
xtype: 'inputpanel',
|
|
onGetValues: function(values) {
|
|
Object.keys(values || {}).forEach(function(name) {
|
|
if (values[name] === '') {
|
|
delete values[name];
|
|
}
|
|
});
|
|
|
|
return values;
|
|
},
|
|
column1: [
|
|
{
|
|
xtype: 'pmxDiskSelector',
|
|
name: 'dev',
|
|
nodename: me.nodename,
|
|
diskType: 'unused',
|
|
includePartitions: true,
|
|
fieldLabel: gettext('Disk'),
|
|
allowBlank: false,
|
|
},
|
|
],
|
|
column2: [
|
|
{
|
|
xtype: 'pmxDiskSelector',
|
|
name: 'db_dev',
|
|
nodename: me.nodename,
|
|
diskType: 'journal_disks',
|
|
includePartitions: true,
|
|
fieldLabel: gettext('DB Disk'),
|
|
value: '',
|
|
autoSelect: false,
|
|
allowBlank: true,
|
|
emptyText: 'use OSD disk',
|
|
listeners: {
|
|
change: function(field, val) {
|
|
me.down('field[name=db_dev_size]').setDisabled(!val);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'numberfield',
|
|
name: 'db_dev_size',
|
|
fieldLabel: gettext('DB size') + ' (GiB)',
|
|
minValue: 1,
|
|
maxValue: 128*1024,
|
|
decimalPrecision: 2,
|
|
allowBlank: true,
|
|
disabled: true,
|
|
emptyText: gettext('Automatic'),
|
|
},
|
|
],
|
|
advancedColumn1: [
|
|
{
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'encrypted',
|
|
fieldLabel: gettext('Encrypt OSD'),
|
|
},
|
|
{
|
|
xtype: 'proxmoxKVComboBox',
|
|
comboItems: [
|
|
['hdd', 'HDD'],
|
|
['ssd', 'SSD'],
|
|
['nvme', 'NVMe'],
|
|
],
|
|
name: 'crush-device-class',
|
|
nodename: me.nodename,
|
|
fieldLabel: gettext('Device Class'),
|
|
value: '',
|
|
autoSelect: false,
|
|
allowBlank: true,
|
|
editable: true,
|
|
emptyText: 'auto detect',
|
|
deleteEmpty: !me.isCreate,
|
|
},
|
|
],
|
|
advancedColumn2: [
|
|
{
|
|
xtype: 'pmxDiskSelector',
|
|
name: 'wal_dev',
|
|
nodename: me.nodename,
|
|
diskType: 'journal_disks',
|
|
includePartitions: true,
|
|
fieldLabel: gettext('WAL Disk'),
|
|
value: '',
|
|
autoSelect: false,
|
|
allowBlank: true,
|
|
emptyText: 'use OSD/DB disk',
|
|
listeners: {
|
|
change: function(field, val) {
|
|
me.down('field[name=wal_dev_size]').setDisabled(!val);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'numberfield',
|
|
name: 'wal_dev_size',
|
|
fieldLabel: gettext('WAL size') + ' (GiB)',
|
|
minValue: 0.5,
|
|
maxValue: 128*1024,
|
|
decimalPrecision: 2,
|
|
allowBlank: true,
|
|
disabled: true,
|
|
emptyText: gettext('Automatic'),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
xtype: 'displayfield',
|
|
padding: '5 0 0 0',
|
|
userCls: 'pmx-hint',
|
|
value: 'Note: Ceph is not compatible with disks backed by a hardware ' +
|
|
'RAID controller. For details see ' +
|
|
'<a target="_blank" href="' + Proxmox.Utils.get_help_link('chapter_pveceph') + '">the reference documentation</a>.',
|
|
},
|
|
],
|
|
});
|
|
|
|
me.callParent();
|
|
},
|
|
});
|
|
|
|
Ext.define('PVE.CephRemoveOsd', {
|
|
extend: 'Proxmox.window.Edit',
|
|
alias: ['widget.pveCephRemoveOsd'],
|
|
|
|
isRemove: true,
|
|
|
|
showProgress: true,
|
|
method: 'DELETE',
|
|
items: [
|
|
{
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'cleanup',
|
|
checked: true,
|
|
labelWidth: 130,
|
|
fieldLabel: gettext('Cleanup Disks'),
|
|
},
|
|
{
|
|
xtype: 'displayfield',
|
|
name: 'osd-flag-hint',
|
|
userCls: 'pmx-hint',
|
|
value: gettext('Global flags limiting the self healing of Ceph are enabled.'),
|
|
hidden: true,
|
|
},
|
|
{
|
|
xtype: 'displayfield',
|
|
name: 'degraded-objects-hint',
|
|
userCls: 'pmx-hint',
|
|
value: gettext('Objects are degraded. Consider waiting until the cluster is healthy.'),
|
|
hidden: true,
|
|
},
|
|
],
|
|
initComponent: function() {
|
|
let me = this;
|
|
|
|
if (!me.nodename) {
|
|
throw "no node name specified";
|
|
}
|
|
if (me.osdid === undefined || me.osdid < 0) {
|
|
throw "no osdid specified";
|
|
}
|
|
|
|
me.isCreate = true;
|
|
|
|
me.title = gettext('Destroy') + ': Ceph OSD osd.' + me.osdid.toString();
|
|
|
|
Ext.applyIf(me, {
|
|
url: "/nodes/" + me.nodename + "/ceph/osd/" + me.osdid.toString(),
|
|
});
|
|
|
|
me.callParent();
|
|
|
|
if (me.warnings.flags) {
|
|
me.down('field[name=osd-flag-hint]').setHidden(false);
|
|
}
|
|
if (me.warnings.degraded) {
|
|
me.down('field[name=degraded-objects-hint]').setHidden(false);
|
|
}
|
|
},
|
|
});
|
|
|
|
Ext.define('PVE.CephSetFlags', {
|
|
extend: 'Proxmox.window.Edit',
|
|
xtype: 'pveCephSetFlags',
|
|
|
|
showProgress: true,
|
|
|
|
width: 720,
|
|
layout: 'fit',
|
|
|
|
onlineHelp: 'pve_ceph_osds',
|
|
isCreate: true,
|
|
title: Ext.String.format(gettext('Manage {0}'), 'Global OSD Flags'),
|
|
submitText: gettext('Apply'),
|
|
|
|
items: [
|
|
{
|
|
xtype: 'inputpanel',
|
|
onGetValues: function(values) {
|
|
let me = this;
|
|
let val = {};
|
|
me.down('#flaggrid').getStore().each((rec) => {
|
|
val[rec.data.name] = rec.data.value ? 1 : 0;
|
|
});
|
|
|
|
return val;
|
|
},
|
|
items: [
|
|
{
|
|
xtype: 'grid',
|
|
itemId: 'flaggrid',
|
|
store: {
|
|
listeners: {
|
|
update: function() {
|
|
this.commitChanges();
|
|
},
|
|
},
|
|
},
|
|
|
|
columns: [
|
|
{
|
|
text: gettext('Enable'),
|
|
xtype: 'checkcolumn',
|
|
width: 75,
|
|
dataIndex: 'value',
|
|
},
|
|
{
|
|
text: 'Name',
|
|
dataIndex: 'name',
|
|
},
|
|
{
|
|
text: 'Description',
|
|
flex: 1,
|
|
dataIndex: 'description',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
|
|
initComponent: function() {
|
|
let me = this;
|
|
|
|
if (!me.nodename) {
|
|
throw "no node name specified";
|
|
}
|
|
|
|
Ext.applyIf(me, {
|
|
url: "/cluster/ceph/flags",
|
|
method: 'PUT',
|
|
});
|
|
|
|
me.callParent();
|
|
|
|
let grid = me.down('#flaggrid');
|
|
me.load({
|
|
success: function(response, options) {
|
|
let data = response.result.data;
|
|
grid.getStore().setData(data);
|
|
// re-align after store load, else the window is not centered
|
|
me.alignTo(Ext.getBody(), 'c-c');
|
|
},
|
|
});
|
|
},
|
|
});
|
|
|
|
Ext.define('PVE.node.CephOsdTree', {
|
|
extend: 'Ext.tree.Panel',
|
|
alias: ['widget.pveNodeCephOsdTree'],
|
|
onlineHelp: 'chapter_pveceph',
|
|
|
|
viewModel: {
|
|
data: {
|
|
nodename: '',
|
|
flags: [],
|
|
maxversion: '0',
|
|
mixedversions: false,
|
|
versions: {},
|
|
isOsd: false,
|
|
downOsd: false,
|
|
upOsd: false,
|
|
inOsd: false,
|
|
outOsd: false,
|
|
osdid: '',
|
|
osdhost: '',
|
|
},
|
|
},
|
|
|
|
controller: {
|
|
xclass: 'Ext.app.ViewController',
|
|
|
|
reload: function() {
|
|
let me = this;
|
|
let view = me.getView();
|
|
let vm = me.getViewModel();
|
|
let nodename = vm.get('nodename');
|
|
let sm = view.getSelectionModel();
|
|
Proxmox.Utils.API2Request({
|
|
url: "/nodes/" + nodename + "/ceph/osd",
|
|
waitMsgTarget: view,
|
|
method: 'GET',
|
|
failure: function(response, opts) {
|
|
let msg = response.htmlStatus;
|
|
PVE.Utils.showCephInstallOrMask(view, msg, nodename, win =>
|
|
view.mon(win, 'cephInstallWindowClosed', me.reload),
|
|
);
|
|
},
|
|
success: function(response, opts) {
|
|
let data = response.result.data;
|
|
let selected = view.getSelection();
|
|
let name;
|
|
if (selected.length) {
|
|
name = selected[0].data.name;
|
|
}
|
|
data.versions = data.versions || {};
|
|
vm.set('versions', data.versions);
|
|
// extract max version
|
|
let maxversion = "0";
|
|
let mixedversions = false;
|
|
let traverse;
|
|
traverse = function(node, fn) {
|
|
fn(node);
|
|
if (Array.isArray(node.children)) {
|
|
node.children.forEach(c => { traverse(c, fn); });
|
|
}
|
|
};
|
|
traverse(data.root, node => {
|
|
// compatibility for old api call
|
|
if (node.type === 'host' && !node.version) {
|
|
node.version = data.versions[node.name];
|
|
}
|
|
|
|
if (node.version === undefined) {
|
|
return;
|
|
}
|
|
|
|
if (PVE.Utils.compare_ceph_versions(node.version, maxversion) !== 0 && maxversion !== "0") {
|
|
mixedversions = true;
|
|
}
|
|
|
|
if (PVE.Utils.compare_ceph_versions(node.version, maxversion) > 0) {
|
|
maxversion = node.version;
|
|
}
|
|
});
|
|
vm.set('maxversion', maxversion);
|
|
vm.set('mixedversions', mixedversions);
|
|
sm.deselectAll();
|
|
view.setRootNode(data.root);
|
|
view.expandAll();
|
|
if (name) {
|
|
let node = view.getRootNode().findChild('name', name, true);
|
|
if (node) {
|
|
view.setSelection([node]);
|
|
}
|
|
}
|
|
|
|
let flags = data.flags.split(',');
|
|
vm.set('flags', flags);
|
|
},
|
|
});
|
|
},
|
|
|
|
osd_cmd: function(comp) {
|
|
let me = this;
|
|
let vm = this.getViewModel();
|
|
let cmd = comp.cmd;
|
|
let params = comp.params || {};
|
|
let osdid = vm.get('osdid');
|
|
|
|
let doRequest = function() {
|
|
let targetnode = vm.get('osdhost');
|
|
// cmds not node specific and need to work if the OSD node is down
|
|
if (['in', 'out'].includes(cmd)) {
|
|
targetnode = vm.get('nodename');
|
|
}
|
|
Proxmox.Utils.API2Request({
|
|
url: `/nodes/${targetnode}/ceph/osd/${osdid}/${cmd}`,
|
|
waitMsgTarget: me.getView(),
|
|
method: 'POST',
|
|
params: params,
|
|
success: () => { me.reload(); },
|
|
failure: function(response, opts) {
|
|
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
|
},
|
|
});
|
|
};
|
|
|
|
if (cmd === 'scrub') {
|
|
Ext.MessageBox.defaultButton = params.deep === 1 ? 2 : 1;
|
|
Ext.Msg.show({
|
|
title: gettext('Confirm'),
|
|
icon: params.deep === 1 ? Ext.Msg.WARNING : Ext.Msg.QUESTION,
|
|
msg: params.deep !== 1
|
|
? Ext.String.format(gettext("Scrub OSD.{0}"), osdid)
|
|
: Ext.String.format(gettext("Deep Scrub OSD.{0}"), osdid) +
|
|
"<br>Caution: This can reduce performance while it is running.",
|
|
buttons: Ext.Msg.YESNO,
|
|
callback: function(btn) {
|
|
if (btn !== 'yes') {
|
|
return;
|
|
}
|
|
doRequest();
|
|
},
|
|
});
|
|
} else {
|
|
doRequest();
|
|
}
|
|
},
|
|
|
|
create_osd: function() {
|
|
let me = this;
|
|
let vm = this.getViewModel();
|
|
Ext.create('PVE.CephCreateOsd', {
|
|
nodename: vm.get('nodename'),
|
|
taskDone: () => { me.reload(); },
|
|
}).show();
|
|
},
|
|
|
|
destroy_osd: async function() {
|
|
let me = this;
|
|
let vm = this.getViewModel();
|
|
|
|
let warnings = {
|
|
flags: false,
|
|
degraded: false,
|
|
};
|
|
|
|
let flagsPromise = Proxmox.Async.api2({
|
|
url: `/cluster/ceph/flags`,
|
|
method: 'GET',
|
|
});
|
|
|
|
let statusPromise = Proxmox.Async.api2({
|
|
url: `/cluster/ceph/status`,
|
|
method: 'GET',
|
|
});
|
|
|
|
me.getView().mask(gettext('Loading...'));
|
|
|
|
try {
|
|
let result = await Promise.all([flagsPromise, statusPromise]);
|
|
|
|
let flagsData = result[0].result.data;
|
|
let statusData = result[1].result.data;
|
|
|
|
let flags = Array.from(
|
|
flagsData.filter(v => v.value),
|
|
v => v.name,
|
|
).filter(v => ['norebalance', 'norecover', 'noout'].includes(v));
|
|
|
|
if (flags.length) {
|
|
warnings.flags = true;
|
|
}
|
|
if (Object.keys(statusData.pgmap).includes('degraded_objects')) {
|
|
warnings.degraded = true;
|
|
}
|
|
} catch (error) {
|
|
Ext.Msg.alert(gettext('Error'), error.htmlStatus);
|
|
me.getView().unmask();
|
|
return;
|
|
}
|
|
|
|
me.getView().unmask();
|
|
Ext.create('PVE.CephRemoveOsd', {
|
|
nodename: vm.get('osdhost'),
|
|
osdid: vm.get('osdid'),
|
|
warnings: warnings,
|
|
taskDone: () => { me.reload(); },
|
|
autoShow: true,
|
|
});
|
|
},
|
|
|
|
set_flags: function() {
|
|
let me = this;
|
|
let vm = this.getViewModel();
|
|
Ext.create('PVE.CephSetFlags', {
|
|
nodename: vm.get('nodename'),
|
|
taskDone: () => { me.reload(); },
|
|
}).show();
|
|
},
|
|
|
|
service_cmd: function(comp) {
|
|
let me = this;
|
|
let vm = this.getViewModel();
|
|
let cmd = comp.cmd || comp;
|
|
|
|
let doRequest = function() {
|
|
Proxmox.Utils.API2Request({
|
|
url: `/nodes/${vm.get('osdhost')}/ceph/${cmd}`,
|
|
params: { service: "osd." + vm.get('osdid') },
|
|
waitMsgTarget: me.getView(),
|
|
method: 'POST',
|
|
success: function(response, options) {
|
|
let upid = response.result.data;
|
|
let win = Ext.create('Proxmox.window.TaskProgress', {
|
|
upid: upid,
|
|
taskDone: () => { me.reload(); },
|
|
});
|
|
win.show();
|
|
},
|
|
failure: function(response, opts) {
|
|
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
|
},
|
|
});
|
|
};
|
|
|
|
if (cmd === "stop") {
|
|
Proxmox.Utils.API2Request({
|
|
url: `/nodes/${vm.get('osdhost')}/ceph/cmd-safety`,
|
|
params: {
|
|
service: 'osd',
|
|
id: vm.get('osdid'),
|
|
action: 'stop',
|
|
},
|
|
waitMsgTarget: me.getView(),
|
|
method: 'GET',
|
|
success: function({ result: { data } }) {
|
|
if (!data.safe) {
|
|
Ext.Msg.show({
|
|
title: gettext('Warning'),
|
|
message: data.status,
|
|
icon: Ext.Msg.WARNING,
|
|
buttons: Ext.Msg.OKCANCEL,
|
|
buttonText: { ok: gettext('Stop OSD') },
|
|
fn: function(selection) {
|
|
if (selection === 'ok') {
|
|
doRequest();
|
|
}
|
|
},
|
|
});
|
|
} else {
|
|
doRequest();
|
|
}
|
|
},
|
|
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
|
|
});
|
|
} else {
|
|
doRequest();
|
|
}
|
|
},
|
|
|
|
set_selection_status: function(tp, selection) {
|
|
if (selection.length < 1) {
|
|
return;
|
|
}
|
|
let rec = selection[0];
|
|
let vm = this.getViewModel();
|
|
|
|
let isOsd = rec.data.host && rec.data.type === 'osd' && rec.data.id >= 0;
|
|
|
|
vm.set('isOsd', isOsd);
|
|
vm.set('downOsd', isOsd && rec.data.status === 'down');
|
|
vm.set('upOsd', isOsd && rec.data.status !== 'down');
|
|
vm.set('inOsd', isOsd && rec.data.in);
|
|
vm.set('outOsd', isOsd && !rec.data.in);
|
|
vm.set('osdid', isOsd ? rec.data.id : undefined);
|
|
vm.set('osdhost', isOsd ? rec.data.host : undefined);
|
|
},
|
|
|
|
render_status: function(value, metaData, rec) {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
let inout = rec.data.in ? 'in' : 'out';
|
|
let updownicon = value === 'up' ? 'good fa-arrow-circle-up'
|
|
: 'critical fa-arrow-circle-down';
|
|
|
|
let inouticon = rec.data.in ? 'good fa-circle'
|
|
: 'warning fa-circle-o';
|
|
|
|
let text = value + ' <i class="fa ' + updownicon + '"></i> / ' +
|
|
inout + ' <i class="fa ' + inouticon + '"></i>';
|
|
|
|
return text;
|
|
},
|
|
|
|
render_wal: function(value, metaData, rec) {
|
|
if (!value &&
|
|
rec.data.osdtype === 'bluestore' &&
|
|
rec.data.type === 'osd') {
|
|
return 'N/A';
|
|
}
|
|
return value;
|
|
},
|
|
|
|
render_version: function(value, metadata, rec) {
|
|
let vm = this.getViewModel();
|
|
let versions = vm.get('versions');
|
|
let icon = "";
|
|
let version = value || "";
|
|
let maxversion = vm.get('maxversion');
|
|
if (value && PVE.Utils.compare_ceph_versions(value, maxversion) !== 0) {
|
|
let host_version = rec.parentNode?.data?.version || versions[rec.data.host] || "";
|
|
if (rec.data.type === 'host' || PVE.Utils.compare_ceph_versions(host_version, maxversion) !== 0) {
|
|
icon = PVE.Utils.get_ceph_icon_html('HEALTH_UPGRADE');
|
|
} else {
|
|
icon = PVE.Utils.get_ceph_icon_html('HEALTH_OLD');
|
|
}
|
|
} else if (value && vm.get('mixedversions')) {
|
|
icon = PVE.Utils.get_ceph_icon_html('HEALTH_OK');
|
|
}
|
|
|
|
return icon + version;
|
|
},
|
|
|
|
render_osd_val: function(value, metaData, rec) {
|
|
return rec.data.type === 'osd' ? value : '';
|
|
},
|
|
render_osd_weight: function(value, metaData, rec) {
|
|
if (rec.data.type !== 'osd') {
|
|
return '';
|
|
}
|
|
return Ext.util.Format.number(value, '0.00###');
|
|
},
|
|
|
|
render_osd_latency: function(value, metaData, rec) {
|
|
if (rec.data.type !== 'osd') {
|
|
return '';
|
|
}
|
|
let commit_ms = rec.data.commit_latency_ms,
|
|
apply_ms = rec.data.apply_latency_ms;
|
|
return apply_ms + ' / ' + commit_ms;
|
|
},
|
|
|
|
render_osd_size: function(value, metaData, rec) {
|
|
return this.render_osd_val(Proxmox.Utils.render_size(value), metaData, rec);
|
|
},
|
|
|
|
control: {
|
|
'#': {
|
|
selectionchange: 'set_selection_status',
|
|
},
|
|
},
|
|
|
|
init: function(view) {
|
|
let me = this;
|
|
let vm = this.getViewModel();
|
|
|
|
if (!view.pveSelNode.data.node) {
|
|
throw "no node name specified";
|
|
}
|
|
|
|
vm.set('nodename', view.pveSelNode.data.node);
|
|
|
|
me.callParent();
|
|
me.reload();
|
|
},
|
|
},
|
|
|
|
stateful: true,
|
|
stateId: 'grid-ceph-osd',
|
|
rootVisible: false,
|
|
useArrows: true,
|
|
|
|
columns: [
|
|
{
|
|
xtype: 'treecolumn',
|
|
text: 'Name',
|
|
dataIndex: 'name',
|
|
width: 150,
|
|
},
|
|
{
|
|
text: 'Type',
|
|
dataIndex: 'type',
|
|
hidden: true,
|
|
align: 'right',
|
|
width: 75,
|
|
},
|
|
{
|
|
text: gettext("Class"),
|
|
dataIndex: 'device_class',
|
|
align: 'right',
|
|
width: 75,
|
|
},
|
|
{
|
|
text: "OSD Type",
|
|
dataIndex: 'osdtype',
|
|
align: 'right',
|
|
width: 100,
|
|
},
|
|
{
|
|
text: "Bluestore Device",
|
|
dataIndex: 'blfsdev',
|
|
align: 'right',
|
|
width: 75,
|
|
hidden: true,
|
|
},
|
|
{
|
|
text: "DB Device",
|
|
dataIndex: 'dbdev',
|
|
align: 'right',
|
|
width: 75,
|
|
hidden: true,
|
|
},
|
|
{
|
|
text: "WAL Device",
|
|
dataIndex: 'waldev',
|
|
align: 'right',
|
|
renderer: 'render_wal',
|
|
width: 75,
|
|
hidden: true,
|
|
},
|
|
{
|
|
text: 'Status',
|
|
dataIndex: 'status',
|
|
align: 'right',
|
|
renderer: 'render_status',
|
|
width: 120,
|
|
},
|
|
{
|
|
text: gettext('Version'),
|
|
dataIndex: 'version',
|
|
align: 'right',
|
|
renderer: 'render_version',
|
|
},
|
|
{
|
|
text: 'weight',
|
|
dataIndex: 'crush_weight',
|
|
align: 'right',
|
|
renderer: 'render_osd_weight',
|
|
width: 90,
|
|
},
|
|
{
|
|
text: 'reweight',
|
|
dataIndex: 'reweight',
|
|
align: 'right',
|
|
renderer: 'render_osd_weight',
|
|
width: 90,
|
|
},
|
|
{
|
|
text: gettext('Used') + ' (%)',
|
|
dataIndex: 'percent_used',
|
|
align: 'right',
|
|
renderer: function(value, metaData, rec) {
|
|
if (rec.data.type !== 'osd') {
|
|
return '';
|
|
}
|
|
return Ext.util.Format.number(value, '0.00');
|
|
},
|
|
width: 100,
|
|
},
|
|
{
|
|
text: gettext('Total'),
|
|
dataIndex: 'total_space',
|
|
align: 'right',
|
|
renderer: 'render_osd_size',
|
|
width: 100,
|
|
},
|
|
{
|
|
text: 'Apply/Commit<br>Latency (ms)',
|
|
dataIndex: 'apply_latency_ms',
|
|
align: 'right',
|
|
renderer: 'render_osd_latency',
|
|
width: 120,
|
|
},
|
|
{
|
|
text: 'PGs',
|
|
dataIndex: 'pgs',
|
|
align: 'right',
|
|
renderer: 'render_osd_val',
|
|
width: 90,
|
|
},
|
|
],
|
|
|
|
|
|
tbar: {
|
|
items: [
|
|
{
|
|
text: gettext('Reload'),
|
|
iconCls: 'fa fa-refresh',
|
|
handler: 'reload',
|
|
},
|
|
'-',
|
|
{
|
|
text: gettext('Create') + ': OSD',
|
|
handler: 'create_osd',
|
|
},
|
|
{
|
|
text: Ext.String.format(gettext('Manage {0}'), 'Global Flags'),
|
|
handler: 'set_flags',
|
|
},
|
|
'->',
|
|
{
|
|
xtype: 'tbtext',
|
|
data: {
|
|
osd: undefined,
|
|
},
|
|
bind: {
|
|
data: {
|
|
osd: "{osdid}",
|
|
},
|
|
},
|
|
tpl: [
|
|
'<tpl if="osd">',
|
|
'osd.{osd}:',
|
|
'<tpl else>',
|
|
gettext('No OSD selected'),
|
|
'</tpl>',
|
|
],
|
|
},
|
|
{
|
|
text: gettext('Start'),
|
|
iconCls: 'fa fa-play',
|
|
disabled: true,
|
|
bind: {
|
|
disabled: '{!downOsd}',
|
|
},
|
|
cmd: 'start',
|
|
handler: 'service_cmd',
|
|
},
|
|
{
|
|
text: gettext('Stop'),
|
|
iconCls: 'fa fa-stop',
|
|
disabled: true,
|
|
bind: {
|
|
disabled: '{!upOsd}',
|
|
},
|
|
cmd: 'stop',
|
|
handler: 'service_cmd',
|
|
},
|
|
{
|
|
text: gettext('Restart'),
|
|
iconCls: 'fa fa-refresh',
|
|
disabled: true,
|
|
bind: {
|
|
disabled: '{!upOsd}',
|
|
},
|
|
cmd: 'restart',
|
|
handler: 'service_cmd',
|
|
},
|
|
'-',
|
|
{
|
|
text: 'Out',
|
|
iconCls: 'fa fa-circle-o',
|
|
disabled: true,
|
|
bind: {
|
|
disabled: '{!inOsd}',
|
|
},
|
|
cmd: 'out',
|
|
handler: 'osd_cmd',
|
|
},
|
|
{
|
|
text: 'In',
|
|
iconCls: 'fa fa-circle',
|
|
disabled: true,
|
|
bind: {
|
|
disabled: '{!outOsd}',
|
|
},
|
|
cmd: 'in',
|
|
handler: 'osd_cmd',
|
|
},
|
|
'-',
|
|
{
|
|
text: gettext('More'),
|
|
iconCls: 'fa fa-bars',
|
|
disabled: true,
|
|
bind: {
|
|
disabled: '{!isOsd}',
|
|
},
|
|
menu: [
|
|
{
|
|
text: gettext('Scrub'),
|
|
iconCls: 'fa fa-shower',
|
|
cmd: 'scrub',
|
|
handler: 'osd_cmd',
|
|
},
|
|
{
|
|
text: gettext('Deep Scrub'),
|
|
iconCls: 'fa fa-bath',
|
|
cmd: 'scrub',
|
|
params: {
|
|
deep: 1,
|
|
},
|
|
handler: 'osd_cmd',
|
|
},
|
|
{
|
|
text: gettext('Destroy'),
|
|
itemId: 'remove',
|
|
iconCls: 'fa fa-fw fa-trash-o',
|
|
bind: {
|
|
disabled: '{!downOsd}',
|
|
},
|
|
handler: 'destroy_osd',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
|
|
fields: [
|
|
'name', 'type', 'status', 'host', 'in', 'id',
|
|
{ type: 'number', name: 'reweight' },
|
|
{ type: 'number', name: 'percent_used' },
|
|
{ type: 'integer', name: 'bytes_used' },
|
|
{ type: 'integer', name: 'total_space' },
|
|
{ type: 'integer', name: 'apply_latency_ms' },
|
|
{ type: 'integer', name: 'commit_latency_ms' },
|
|
{ type: 'string', name: 'device_class' },
|
|
{ type: 'string', name: 'osdtype' },
|
|
{ type: 'string', name: 'blfsdev' },
|
|
{ type: 'string', name: 'dbdev' },
|
|
{ type: 'string', name: 'waldev' },
|
|
{
|
|
type: 'string', name: 'version', calculate: function(data) {
|
|
return PVE.Utils.parse_ceph_version(data);
|
|
},
|
|
},
|
|
{
|
|
type: 'string', name: 'iconCls', calculate: function(data) {
|
|
let iconMap = {
|
|
host: 'fa-building',
|
|
osd: 'fa-hdd-o',
|
|
root: 'fa-server',
|
|
};
|
|
return 'fa x-fa-tree ' + iconMap[data.type];
|
|
},
|
|
},
|
|
{ type: 'number', name: 'crush_weight' },
|
|
],
|
|
});
|