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