]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/ceph/CephInstallWizard.js
ui: ceph status details: refactoring and clenaups
[pve-manager.git] / www / manager6 / ceph / CephInstallWizard.js
CommitLineData
37e75d86
TL
1Ext.define('PVE.ceph.CephInstallWizardInfo', {
2 extend: 'Ext.panel.Panel',
3 xtype: 'pveCephInstallWizardInfo',
4
5 html: `<h3>Ceph?</h3>
6 <blockquote cite="https://ceph.com/"><p>"<b>Ceph</b> is a unified,
f00da0a5 7 distributed storage system, designed for excellent performance, reliability,
37e75d86
TL
8 and scalability."</p></blockquote>
9 <p>
f00da0a5
DW
10 <b>Ceph</b> is currently <b>not installed</b> on this node. This wizard
11 will guide you through the installation. Click on the next button below
12 to begin. After the initial installation, the wizard will offer to create
13 an initial configuration. This configuration step is only
37e75d86
TL
14 needed once per cluster and will be skipped if a config is already present.
15 </p>
16 <p>
f00da0a5
DW
17 Before starting the installation, please take a look at our documentation,
18 by clicking the help button below. If you want to gain deeper knowledge about
19 Ceph, visit <a target="_blank" href="https://docs.ceph.com/en/latest/">ceph.com</a>.
37e75d86
TL
20 </p>`,
21});
22
c3035c01
TL
23Ext.define('PVE.ceph.CephVersionSelector', {
24 extend: 'Ext.form.field.ComboBox',
25 xtype: 'pveCephVersionSelector',
26
27 fieldLabel: gettext('Ceph version to install'),
28
29 displayField: 'display',
30 valueField: 'release',
31
32 queryMode: 'local',
33 editable: false,
34 forceSelection: true,
35
36 store: {
37 fields: [
38 'release',
39 'version',
40 {
41 name: 'display',
42 calculate: d => `${d.release} (${d.version})`,
43 },
44 ],
45 proxy: {
46 type: 'memory',
47 reader: {
48 type: 'json',
49 },
50 },
51 data: [
8058410f 52 { release: "octopus", version: "15.2" },
00fa70bf 53 { release: "pacific", version: "16.2" },
c3035c01
TL
54 ],
55 },
56});
57
58Ext.define('PVE.ceph.CephHighestVersionDisplay', {
59 extend: 'Ext.form.field.Display',
60 xtype: 'pveCephHighestVersionDisplay',
61
62 fieldLabel: gettext('Ceph in the cluster'),
63
64 value: 'unknown',
65
66 // called on success with (release, versionTxt, versionParts)
67 gotNewestVersion: Ext.emptyFn,
68
69 initComponent: function() {
70 let me = this;
71
72 me.callParent(arguments);
73
74 Proxmox.Utils.API2Request({
75 method: 'GET',
76 url: '/cluster/ceph/metadata',
77 params: {
78 scope: 'versions',
79 },
80 waitMsgTarget: me,
81 success: (response) => {
82 let res = response.result;
83 if (!res || !res.data || !res.data.node) {
84 me.setValue(
85 gettext('Could not detect a ceph installation in the cluster'),
86 );
87 return;
88 }
89 let nodes = res.data.node;
90 if (me.nodename) {
91 // can happen on ceph purge, we do not yet cleanup old version data
92 delete nodes[me.nodename];
93 }
94
95 let maxversion = [];
96 let maxversiontext = "";
436f638d 97 for (const [_nodename, data] of Object.entries(nodes)) {
c3035c01
TL
98 let version = data.version.parts;
99 if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) {
100 maxversion = version;
101 maxversiontext = data.version.str;
102 }
103 }
104 // FIXME: get from version selector store
105 const major2release = {
106 13: 'luminous',
107 14: 'nautilus',
108 15: 'octopus',
109 16: 'pacific',
110 };
111 let release = major2release[maxversion[0]] || 'unknown';
112 let newestVersionTxt = `${Ext.String.capitalize(release)} (${maxversiontext})`;
113
114 if (release === 'unknown') {
115 me.setValue(
116 gettext('Could not detect a ceph installation in the cluster'),
117 );
118 } else {
119 me.setValue(Ext.String.format(
120 gettext('Newest ceph version in cluster is {0}'),
121 newestVersionTxt,
122 ));
123 }
124 me.gotNewestVersion(release, maxversiontext, maxversion);
125 },
126 failure: function(response, opts) {
127 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
f6710aac 128 },
c3035c01
TL
129 });
130 },
131});
132
4616a55b 133Ext.define('PVE.ceph.CephInstallWizard', {
80fdf8d2
TL
134 extend: 'PVE.window.Wizard',
135 alias: 'widget.pveCephInstallWizard',
136 mixins: ['Proxmox.Mixin.CBind'],
37e75d86 137
80fdf8d2
TL
138 resizable: false,
139 nodename: undefined,
37e75d86 140
80fdf8d2
TL
141 viewModel: {
142 data: {
143 nodename: '',
2e061d81 144 cephRelease: 'pacific',
80fdf8d2
TL
145 configuration: true,
146 isInstalled: false,
f6710aac 147 },
80fdf8d2
TL
148 },
149 cbindData: {
f6710aac 150 nodename: undefined,
80fdf8d2 151 },
37e75d86 152
80fdf8d2
TL
153 title: gettext('Setup'),
154 navigateNext: function() {
155 var tp = this.down('#wizcontent');
156 var atab = tp.getActiveTab();
8ba2d669 157
80fdf8d2
TL
158 var next = tp.items.indexOf(atab) + 1;
159 var ntab = tp.items.getAt(next);
160 if (ntab) {
161 ntab.enable();
162 tp.setActiveTab(ntab);
163 }
164 },
8058410f 165 setInitialTab: function(index) {
80fdf8d2
TL
166 var tp = this.down('#wizcontent');
167 var initialTab = tp.items.getAt(index);
168 initialTab.enable();
169 tp.setActiveTab(initialTab);
170 },
171 onShow: function() {
172 this.callParent(arguments);
173 var isInstalled = this.getViewModel().get('isInstalled');
174 if (isInstalled) {
175 this.getViewModel().set('configuration', false);
176 this.setInitialTab(2);
8ba2d669 177 }
80fdf8d2
TL
178 },
179 items: [
180 {
181 xtype: 'panel',
182 title: gettext('Info'),
183 viewModel: {}, // needed to inherit parent viewModel data
184 border: false,
185 bodyBorder: false,
186 onlineHelp: 'chapter_pveceph',
187 layout: {
188 type: 'vbox',
189 align: 'stretch',
190 },
191 defaults: {
4616a55b
TM
192 border: false,
193 bodyBorder: false,
80fdf8d2
TL
194 },
195 items: [
196 {
197 xtype: 'pveCephInstallWizardInfo',
37e75d86 198 },
80fdf8d2
TL
199 {
200 flex: 1,
37e75d86 201 },
80fdf8d2
TL
202 {
203 xtype: 'pveCephHighestVersionDisplay',
204 labelWidth: 180,
8058410f 205 cbind: {
f6710aac 206 nodename: '{nodename}',
37e75d86 207 },
80fdf8d2
TL
208 gotNewestVersion: function(release, maxversiontext, maxversion) {
209 if (release === 'unknown') {
210 return;
211 }
212 let wizard = this.up('pveCephInstallWizard');
213 wizard.getViewModel().set('cephRelease', release);
c3035c01 214 },
80fdf8d2
TL
215 },
216 {
217 xtype: 'pveCephVersionSelector',
218 labelWidth: 180,
219 submitValue: false,
220 bind: {
221 value: '{cephRelease}',
222 },
223 listeners: {
224 change: function(field, release) {
c3035c01 225 let wizard = this.up('pveCephInstallWizard');
80fdf8d2
TL
226 wizard.down('#next').setText(
227 Ext.String.format(gettext('Start {0} installation'), release),
228 );
c3035c01
TL
229 },
230 },
80fdf8d2
TL
231 },
232 ],
233 listeners: {
234 activate: function() {
235 // notify owning container that it should display a help button
236 if (this.onlineHelp) {
237 Ext.GlobalEvents.fireEvent('proxmoxShowHelp', this.onlineHelp);
238 }
239 let wizard = this.up('pveCephInstallWizard');
240 let release = wizard.getViewModel().get('cephRelease');
241 wizard.down('#back').hide(true);
242 wizard.down('#next').setText(
243 Ext.String.format(gettext('Start {0} installation'), release),
244 );
245 },
246 deactivate: function() {
247 if (this.onlineHelp) {
248 Ext.GlobalEvents.fireEvent('proxmoxHideHelp', this.onlineHelp);
249 }
250 this.up('pveCephInstallWizard').down('#next').setText(gettext('Next'));
f6710aac
TL
251 },
252 },
80fdf8d2
TL
253 },
254 {
255 title: gettext('Installation'),
256 xtype: 'panel',
257 layout: 'fit',
8058410f 258 cbind: {
f6710aac 259 nodename: '{nodename}',
80fdf8d2
TL
260 },
261 viewModel: {}, // needed to inherit parent viewModel data
262 listeners: {
263 afterrender: function() {
264 var me = this;
265 if (this.getViewModel().get('isInstalled')) {
f6710aac 266 this.mask("Ceph is already installed, click next to create your configuration.", ['pve-static-mask']);
80fdf8d2
TL
267 } else {
268 me.down('pveNoVncConsole').fireEvent('activate');
269 }
270 },
271 activate: function() {
272 let me = this;
273 const nodename = me.nodename;
274 me.updateStore = Ext.create('Proxmox.data.UpdateStore', {
275 storeid: 'ceph-status-' + nodename,
276 interval: 1000,
277 proxy: {
278 type: 'proxmox',
f6710aac 279 url: '/api2/json/nodes/' + nodename + '/ceph/status',
c3035c01
TL
280 },
281 listeners: {
80fdf8d2 282 load: function(rec, response, success, operation) {
80fdf8d2
TL
283 if (success) {
284 me.updateStore.stopUpdate();
285 me.down('textfield').setValue('success');
286 } else if (operation.error.statusText.match("not initialized", "i")) {
287 me.updateStore.stopUpdate();
f6710aac 288 me.up('pveCephInstallWizard').getViewModel().set('configuration', false);
80fdf8d2
TL
289 me.down('textfield').setValue('success');
290 } else if (operation.error.statusText.match("rados_connect failed", "i")) {
291 me.updateStore.stopUpdate();
f6710aac 292 me.up('pveCephInstallWizard').getViewModel().set('configuration', true);
80fdf8d2
TL
293 me.down('textfield').setValue('success');
294 } else if (!operation.error.statusText.match("not installed", "i")) {
295 Proxmox.Utils.setErrorMask(me, operation.error.statusText);
296 }
f6710aac
TL
297 },
298 },
80fdf8d2
TL
299 });
300 me.updateStore.startUpdate();
301 },
302 destroy: function() {
303 var me = this;
304 if (me.updateStore) {
305 me.updateStore.stopUpdate();
4616a55b 306 }
f6710aac 307 },
4616a55b 308 },
80fdf8d2
TL
309 items: [
310 {
311 xtype: 'pveNoVncConsole',
312 itemId: 'jsconsole',
313 consoleType: 'cmd',
314 xtermjs: true,
315 cbind: {
316 nodename: '{nodename}',
4616a55b 317 },
80fdf8d2 318 beforeLoad: function() {
c3035c01 319 let me = this;
80fdf8d2
TL
320 let wizard = me.up('pveCephInstallWizard');
321 let release = wizard.getViewModel().get('cephRelease');
322 me.cmdOpts = `--version\0${release}`;
4616a55b 323 },
80fdf8d2 324 cmd: 'ceph_install',
4616a55b 325 },
80fdf8d2
TL
326 {
327 xtype: 'textfield',
328 name: 'installSuccess',
329 value: '',
330 allowBlank: false,
331 submitValue: false,
f6710aac
TL
332 hidden: true,
333 },
334 ],
80fdf8d2
TL
335 },
336 {
337 xtype: 'inputpanel',
338 title: gettext('Configuration'),
339 onlineHelp: 'chapter_pveceph',
5839e002 340 height: 300,
80fdf8d2 341 cbind: {
f6710aac 342 nodename: '{nodename}',
4616a55b 343 },
80fdf8d2
TL
344 viewModel: {
345 data: {
346 replicas: undefined,
f6710aac
TL
347 minreplicas: undefined,
348 },
80fdf8d2
TL
349 },
350 listeners: {
351 activate: function() {
352 this.up('pveCephInstallWizard').down('#submit').setText(gettext('Next'));
4616a55b 353 },
5839e002 354 afterrender: function() {
80fdf8d2 355 if (this.up('pveCephInstallWizard').getViewModel().get('configuration')) {
f6710aac 356 this.mask("Configuration already initialized", ['pve-static-mask']);
80fdf8d2
TL
357 } else {
358 this.unmask();
836d66f7
TM
359 }
360 },
80fdf8d2
TL
361 deactivate: function() {
362 this.up('pveCephInstallWizard').down('#submit').setText(gettext('Finish'));
f6710aac 363 },
80fdf8d2
TL
364 },
365 column1: [
366 {
367 xtype: 'displayfield',
f6710aac 368 value: gettext('Ceph cluster configuration') + ':',
80fdf8d2
TL
369 },
370 {
371 xtype: 'proxmoxNetworkSelector',
372 name: 'network',
373 value: '',
374 fieldLabel: 'Public Network IP/CIDR',
375 bind: {
f6710aac 376 allowBlank: '{configuration}',
8ba2d669 377 },
80fdf8d2 378 cbind: {
f6710aac
TL
379 nodename: '{nodename}',
380 },
4616a55b 381 },
80fdf8d2
TL
382 {
383 xtype: 'proxmoxNetworkSelector',
384 name: 'cluster-network',
385 fieldLabel: 'Cluster Network IP/CIDR',
386 allowBlank: true,
387 autoSelect: false,
388 emptyText: gettext('Same as Public Network'),
389 cbind: {
f6710aac
TL
390 nodename: '{nodename}',
391 },
392 },
80fdf8d2
TL
393 // FIXME: add hint about cluster network and/or reference user to docs??
394 ],
395 column2: [
396 {
397 xtype: 'displayfield',
f6710aac 398 value: gettext('First Ceph monitor') + ':',
80fdf8d2
TL
399 },
400 {
401 xtype: 'pveNodeSelector',
402 fieldLabel: gettext('Monitor node'),
403 name: 'mon-node',
404 selectCurNode: true,
f6710aac 405 allowBlank: false,
80fdf8d2
TL
406 },
407 {
408 xtype: 'displayfield',
409 value: gettext('Additional monitors are recommended. They can be created at any time in the Monitor tab.'),
f6710aac
TL
410 userCls: 'pmx-hint',
411 },
80fdf8d2
TL
412 ],
413 advancedColumn1: [
414 {
415 xtype: 'numberfield',
416 name: 'size',
417 fieldLabel: 'Number of replicas',
418 bind: {
f6710aac 419 value: '{replicas}',
4616a55b 420 },
80fdf8d2
TL
421 maxValue: 7,
422 minValue: 2,
f6710aac 423 emptyText: '3',
80fdf8d2
TL
424 },
425 {
426 xtype: 'numberfield',
427 name: 'min_size',
428 fieldLabel: 'Minimum replicas',
429 bind: {
430 maxValue: '{replicas}',
f6710aac 431 value: '{minreplicas}',
4616a55b 432 },
80fdf8d2
TL
433 minValue: 2,
434 maxValue: 3,
435 setMaxValue: function(value) {
436 this.maxValue = Ext.Number.from(value, 2);
437 // allow enough to avoid split brains with max 'size', but more makes simply no sense
438 if (this.maxValue > 4) {
439 this.maxValue = 4;
a19aa238 440 }
80fdf8d2
TL
441 this.toggleSpinners();
442 this.validate();
71798b4b 443 },
f6710aac
TL
444 emptyText: '2',
445 },
80fdf8d2
TL
446 ],
447 onGetValues: function(values) {
448 ['cluster-network', 'size', 'min_size'].forEach(function(field) {
449 if (!values[field]) {
450 delete values[field];
8ba2d669 451 }
80fdf8d2
TL
452 });
453 return values;
454 },
455 onSubmit: function() {
456 var me = this;
457 if (!this.up('pveCephInstallWizard').getViewModel().get('configuration')) {
458 var wizard = me.up('window');
459 var kv = wizard.getValues();
399ffa76 460 delete kv.delete;
80fdf8d2
TL
461 var monNode = kv['mon-node'];
462 delete kv['mon-node'];
463 var nodename = me.nodename;
464 delete kv.nodename;
465 Proxmox.Utils.API2Request({
f8b7b9e1 466 url: `/nodes/${nodename}/ceph/init`,
80fdf8d2
TL
467 waitMsgTarget: wizard,
468 method: 'POST',
469 params: kv,
470 success: function() {
471 Proxmox.Utils.API2Request({
f8b7b9e1 472 url: `/nodes/${monNode}/ceph/mon/${monNode}`,
80fdf8d2
TL
473 waitMsgTarget: wizard,
474 method: 'POST',
475 success: function() {
476 me.up('pveCephInstallWizard').navigateNext();
477 },
478 failure: function(response, opts) {
479 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
f6710aac 480 },
80fdf8d2 481 });
8134b6b7 482 },
80fdf8d2
TL
483 failure: function(response, opts) {
484 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
f6710aac 485 },
4616a55b 486 });
80fdf8d2
TL
487 } else {
488 me.up('pveCephInstallWizard').navigateNext();
4616a55b 489 }
f6710aac 490 },
80fdf8d2
TL
491 },
492 {
493 title: gettext('Success'),
494 xtype: 'panel',
495 border: false,
496 bodyBorder: false,
497 onlineHelp: 'pve_ceph_install',
498 html: '<h3>Installation successful!</h3>'+
f00da0a5 499 '<p>The basic installation and configuration is complete. Depending on your setup, some of the following steps are required to start using Ceph:</p>'+
80fdf8d2
TL
500 '<ol><li>Install Ceph on other nodes</li>'+
501 '<li>Create additional Ceph Monitors</li>'+
502 '<li>Create Ceph OSDs</li>'+
503 '<li>Create Ceph Pools</li></ol>'+
f00da0a5 504 '<p>To learn more, click on the help button below.</p>',
80fdf8d2
TL
505 listeners: {
506 activate: function() {
507 // notify owning container that it should display a help button
508 if (this.onlineHelp) {
509 Ext.GlobalEvents.fireEvent('proxmoxShowHelp', this.onlineHelp);
510 }
4616a55b 511
80fdf8d2
TL
512 var tp = this.up('#wizcontent');
513 var idx = tp.items.indexOf(this)-1;
8058410f 514 for (;idx >= 0; idx--) {
80fdf8d2
TL
515 var nc = tp.items.getAt(idx);
516 if (nc) {
517 nc.disable();
4616a55b
TM
518 }
519 }
520 },
80fdf8d2
TL
521 deactivate: function() {
522 if (this.onlineHelp) {
523 Ext.GlobalEvents.fireEvent('proxmoxHideHelp', this.onlineHelp);
524 }
f6710aac 525 },
80fdf8d2
TL
526 },
527 onSubmit: function() {
528 var wizard = this.up('pveCephInstallWizard');
529 wizard.close();
f6710aac
TL
530 },
531 },
532 ],
c3035c01 533});