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