]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/ceph/CephInstallWizard.js
ui: ceph install wizard: allow selecting repository
[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
35ff09a4
TL
142 width: 760, // 4:3
143 height: 570,
144
80fdf8d2
TL
145 viewModel: {
146 data: {
147 nodename: '',
87df8bde 148 cephRelease: 'quincy',
b686fd37 149 cephRepo: 'enterprise',
80fdf8d2
TL
150 configuration: true,
151 isInstalled: false,
f6710aac 152 },
80fdf8d2
TL
153 },
154 cbindData: {
f6710aac 155 nodename: undefined,
80fdf8d2 156 },
37e75d86 157
80fdf8d2
TL
158 title: gettext('Setup'),
159 navigateNext: function() {
160 var tp = this.down('#wizcontent');
161 var atab = tp.getActiveTab();
8ba2d669 162
80fdf8d2
TL
163 var next = tp.items.indexOf(atab) + 1;
164 var ntab = tp.items.getAt(next);
165 if (ntab) {
166 ntab.enable();
167 tp.setActiveTab(ntab);
168 }
169 },
8058410f 170 setInitialTab: function(index) {
80fdf8d2
TL
171 var tp = this.down('#wizcontent');
172 var initialTab = tp.items.getAt(index);
173 initialTab.enable();
174 tp.setActiveTab(initialTab);
175 },
176 onShow: function() {
cd56fd55
TL
177 this.callParent(arguments);
178 var isInstalled = this.getViewModel().get('isInstalled');
179 if (isInstalled) {
180 this.getViewModel().set('configuration', false);
181 this.setInitialTab(2);
182 }
80fdf8d2
TL
183 },
184 items: [
185 {
186 xtype: 'panel',
187 title: gettext('Info'),
188 viewModel: {}, // needed to inherit parent viewModel data
189 border: false,
190 bodyBorder: false,
191 onlineHelp: 'chapter_pveceph',
192 layout: {
193 type: 'vbox',
194 align: 'stretch',
195 },
196 defaults: {
4616a55b
TM
197 border: false,
198 bodyBorder: false,
80fdf8d2
TL
199 },
200 items: [
201 {
202 xtype: 'pveCephInstallWizardInfo',
37e75d86 203 },
80fdf8d2
TL
204 {
205 flex: 1,
37e75d86 206 },
80fdf8d2
TL
207 {
208 xtype: 'pveCephHighestVersionDisplay',
b686fd37 209 labelWidth: 150,
8058410f 210 cbind: {
f6710aac 211 nodename: '{nodename}',
37e75d86 212 },
80fdf8d2
TL
213 gotNewestVersion: function(release, maxversiontext, maxversion) {
214 if (release === 'unknown') {
215 return;
216 }
217 let wizard = this.up('pveCephInstallWizard');
218 wizard.getViewModel().set('cephRelease', release);
c3035c01 219 },
80fdf8d2
TL
220 },
221 {
b686fd37
TL
222 xtype: 'container',
223 layout: 'hbox',
224 defaults: {
225 border: false,
226 layout: 'anchor',
227 flex: 1,
80fdf8d2 228 },
b686fd37
TL
229 items: [{
230 xtype: 'pveCephVersionSelector',
231 labelWidth: 150,
232 padding: '0 10 0 0',
233 submitValue: false,
234 bind: {
235 value: '{cephRelease}',
236 },
237 listeners: {
238 change: function(field, release) {
239 let wizard = this.up('pveCephInstallWizard');
240 wizard.down('#next').setText(
241 Ext.String.format(gettext('Start {0} installation'), release),
242 );
243 },
c3035c01
TL
244 },
245 },
b686fd37
TL
246 {
247 xtype: 'proxmoxKVComboBox',
248 fieldLabel: gettext('Repository'),
249 padding: '0 0 0 10',
250 comboItems: [
251 ['enterprise', gettext('Enterprise (recommended)')],
252 ['no-subscription', gettext('No-Subscription')],
253 ['test', gettext('Test')],
254 ],
255 labelWidth: 150,
256 submitValue: false,
257 value: 'enterprise',
258 bind: {
259 value: '{cephRepo}',
260 },
261 }],
80fdf8d2
TL
262 },
263 ],
264 listeners: {
265 activate: function() {
266 // notify owning container that it should display a help button
267 if (this.onlineHelp) {
268 Ext.GlobalEvents.fireEvent('proxmoxShowHelp', this.onlineHelp);
269 }
270 let wizard = this.up('pveCephInstallWizard');
271 let release = wizard.getViewModel().get('cephRelease');
272 wizard.down('#back').hide(true);
273 wizard.down('#next').setText(
274 Ext.String.format(gettext('Start {0} installation'), release),
275 );
276 },
277 deactivate: function() {
278 if (this.onlineHelp) {
279 Ext.GlobalEvents.fireEvent('proxmoxHideHelp', this.onlineHelp);
280 }
281 this.up('pveCephInstallWizard').down('#next').setText(gettext('Next'));
f6710aac
TL
282 },
283 },
80fdf8d2
TL
284 },
285 {
286 title: gettext('Installation'),
287 xtype: 'panel',
288 layout: 'fit',
8058410f 289 cbind: {
f6710aac 290 nodename: '{nodename}',
80fdf8d2
TL
291 },
292 viewModel: {}, // needed to inherit parent viewModel data
293 listeners: {
294 afterrender: function() {
295 var me = this;
296 if (this.getViewModel().get('isInstalled')) {
f6710aac 297 this.mask("Ceph is already installed, click next to create your configuration.", ['pve-static-mask']);
80fdf8d2
TL
298 } else {
299 me.down('pveNoVncConsole').fireEvent('activate');
300 }
301 },
302 activate: function() {
303 let me = this;
304 const nodename = me.nodename;
305 me.updateStore = Ext.create('Proxmox.data.UpdateStore', {
306 storeid: 'ceph-status-' + nodename,
307 interval: 1000,
308 proxy: {
309 type: 'proxmox',
f6710aac 310 url: '/api2/json/nodes/' + nodename + '/ceph/status',
c3035c01
TL
311 },
312 listeners: {
80fdf8d2 313 load: function(rec, response, success, operation) {
80fdf8d2
TL
314 if (success) {
315 me.updateStore.stopUpdate();
316 me.down('textfield').setValue('success');
317 } else if (operation.error.statusText.match("not initialized", "i")) {
318 me.updateStore.stopUpdate();
f6710aac 319 me.up('pveCephInstallWizard').getViewModel().set('configuration', false);
80fdf8d2
TL
320 me.down('textfield').setValue('success');
321 } else if (operation.error.statusText.match("rados_connect failed", "i")) {
322 me.updateStore.stopUpdate();
f6710aac 323 me.up('pveCephInstallWizard').getViewModel().set('configuration', true);
80fdf8d2
TL
324 me.down('textfield').setValue('success');
325 } else if (!operation.error.statusText.match("not installed", "i")) {
326 Proxmox.Utils.setErrorMask(me, operation.error.statusText);
327 }
f6710aac
TL
328 },
329 },
80fdf8d2
TL
330 });
331 me.updateStore.startUpdate();
332 },
333 destroy: function() {
334 var me = this;
335 if (me.updateStore) {
336 me.updateStore.stopUpdate();
4616a55b 337 }
f6710aac 338 },
4616a55b 339 },
80fdf8d2
TL
340 items: [
341 {
342 xtype: 'pveNoVncConsole',
343 itemId: 'jsconsole',
344 consoleType: 'cmd',
345 xtermjs: true,
346 cbind: {
347 nodename: '{nodename}',
4616a55b 348 },
80fdf8d2 349 beforeLoad: function() {
c3035c01 350 let me = this;
80fdf8d2
TL
351 let wizard = me.up('pveCephInstallWizard');
352 let release = wizard.getViewModel().get('cephRelease');
b686fd37
TL
353 let repo = wizard.getViewModel().get('cephRepo');
354 me.cmdOpts = `--version\0${release}\0--repository\0${repo}`;
4616a55b 355 },
80fdf8d2 356 cmd: 'ceph_install',
4616a55b 357 },
80fdf8d2
TL
358 {
359 xtype: 'textfield',
360 name: 'installSuccess',
361 value: '',
362 allowBlank: false,
363 submitValue: false,
f6710aac
TL
364 hidden: true,
365 },
366 ],
80fdf8d2
TL
367 },
368 {
369 xtype: 'inputpanel',
370 title: gettext('Configuration'),
371 onlineHelp: 'chapter_pveceph',
5839e002 372 height: 300,
80fdf8d2 373 cbind: {
f6710aac 374 nodename: '{nodename}',
4616a55b 375 },
80fdf8d2
TL
376 viewModel: {
377 data: {
378 replicas: undefined,
f6710aac
TL
379 minreplicas: undefined,
380 },
80fdf8d2
TL
381 },
382 listeners: {
383 activate: function() {
384 this.up('pveCephInstallWizard').down('#submit').setText(gettext('Next'));
4616a55b 385 },
5839e002 386 afterrender: function() {
80fdf8d2 387 if (this.up('pveCephInstallWizard').getViewModel().get('configuration')) {
f6710aac 388 this.mask("Configuration already initialized", ['pve-static-mask']);
80fdf8d2
TL
389 } else {
390 this.unmask();
836d66f7
TM
391 }
392 },
80fdf8d2
TL
393 deactivate: function() {
394 this.up('pveCephInstallWizard').down('#submit').setText(gettext('Finish'));
f6710aac 395 },
80fdf8d2
TL
396 },
397 column1: [
398 {
399 xtype: 'displayfield',
f6710aac 400 value: gettext('Ceph cluster configuration') + ':',
80fdf8d2
TL
401 },
402 {
403 xtype: 'proxmoxNetworkSelector',
404 name: 'network',
405 value: '',
406 fieldLabel: 'Public Network IP/CIDR',
5f7f0095 407 autoSelect: false,
80fdf8d2 408 bind: {
f6710aac 409 allowBlank: '{configuration}',
8ba2d669 410 },
80fdf8d2 411 cbind: {
f6710aac
TL
412 nodename: '{nodename}',
413 },
4616a55b 414 },
80fdf8d2
TL
415 {
416 xtype: 'proxmoxNetworkSelector',
417 name: 'cluster-network',
418 fieldLabel: 'Cluster Network IP/CIDR',
419 allowBlank: true,
420 autoSelect: false,
421 emptyText: gettext('Same as Public Network'),
422 cbind: {
f6710aac
TL
423 nodename: '{nodename}',
424 },
425 },
80fdf8d2
TL
426 // FIXME: add hint about cluster network and/or reference user to docs??
427 ],
428 column2: [
429 {
430 xtype: 'displayfield',
f6710aac 431 value: gettext('First Ceph monitor') + ':',
80fdf8d2
TL
432 },
433 {
f25c3855 434 xtype: 'displayfield',
80fdf8d2 435 fieldLabel: gettext('Monitor node'),
f25c3855
AL
436 cbind: {
437 value: '{nodename}',
438 },
80fdf8d2
TL
439 },
440 {
441 xtype: 'displayfield',
442 value: gettext('Additional monitors are recommended. They can be created at any time in the Monitor tab.'),
f6710aac
TL
443 userCls: 'pmx-hint',
444 },
80fdf8d2
TL
445 ],
446 advancedColumn1: [
447 {
448 xtype: 'numberfield',
449 name: 'size',
450 fieldLabel: 'Number of replicas',
451 bind: {
f6710aac 452 value: '{replicas}',
4616a55b 453 },
80fdf8d2
TL
454 maxValue: 7,
455 minValue: 2,
f6710aac 456 emptyText: '3',
80fdf8d2
TL
457 },
458 {
459 xtype: 'numberfield',
460 name: 'min_size',
461 fieldLabel: 'Minimum replicas',
462 bind: {
463 maxValue: '{replicas}',
f6710aac 464 value: '{minreplicas}',
4616a55b 465 },
80fdf8d2
TL
466 minValue: 2,
467 maxValue: 3,
468 setMaxValue: function(value) {
469 this.maxValue = Ext.Number.from(value, 2);
470 // allow enough to avoid split brains with max 'size', but more makes simply no sense
471 if (this.maxValue > 4) {
472 this.maxValue = 4;
a19aa238 473 }
80fdf8d2
TL
474 this.toggleSpinners();
475 this.validate();
71798b4b 476 },
f6710aac
TL
477 emptyText: '2',
478 },
80fdf8d2
TL
479 ],
480 onGetValues: function(values) {
481 ['cluster-network', 'size', 'min_size'].forEach(function(field) {
482 if (!values[field]) {
483 delete values[field];
8ba2d669 484 }
80fdf8d2
TL
485 });
486 return values;
487 },
488 onSubmit: function() {
489 var me = this;
490 if (!this.up('pveCephInstallWizard').getViewModel().get('configuration')) {
491 var wizard = me.up('window');
492 var kv = wizard.getValues();
399ffa76 493 delete kv.delete;
80fdf8d2
TL
494 var nodename = me.nodename;
495 delete kv.nodename;
496 Proxmox.Utils.API2Request({
f8b7b9e1 497 url: `/nodes/${nodename}/ceph/init`,
80fdf8d2
TL
498 waitMsgTarget: wizard,
499 method: 'POST',
500 params: kv,
501 success: function() {
502 Proxmox.Utils.API2Request({
f25c3855 503 url: `/nodes/${nodename}/ceph/mon/${nodename}`,
80fdf8d2
TL
504 waitMsgTarget: wizard,
505 method: 'POST',
506 success: function() {
507 me.up('pveCephInstallWizard').navigateNext();
508 },
509 failure: function(response, opts) {
510 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
f6710aac 511 },
80fdf8d2 512 });
8134b6b7 513 },
80fdf8d2
TL
514 failure: function(response, opts) {
515 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
f6710aac 516 },
4616a55b 517 });
80fdf8d2
TL
518 } else {
519 me.up('pveCephInstallWizard').navigateNext();
4616a55b 520 }
f6710aac 521 },
80fdf8d2
TL
522 },
523 {
524 title: gettext('Success'),
525 xtype: 'panel',
526 border: false,
527 bodyBorder: false,
528 onlineHelp: 'pve_ceph_install',
529 html: '<h3>Installation successful!</h3>'+
f00da0a5 530 '<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
531 '<ol><li>Install Ceph on other nodes</li>'+
532 '<li>Create additional Ceph Monitors</li>'+
533 '<li>Create Ceph OSDs</li>'+
534 '<li>Create Ceph Pools</li></ol>'+
f00da0a5 535 '<p>To learn more, click on the help button below.</p>',
80fdf8d2
TL
536 listeners: {
537 activate: function() {
538 // notify owning container that it should display a help button
539 if (this.onlineHelp) {
540 Ext.GlobalEvents.fireEvent('proxmoxShowHelp', this.onlineHelp);
541 }
4616a55b 542
80fdf8d2
TL
543 var tp = this.up('#wizcontent');
544 var idx = tp.items.indexOf(this)-1;
8058410f 545 for (;idx >= 0; idx--) {
80fdf8d2
TL
546 var nc = tp.items.getAt(idx);
547 if (nc) {
548 nc.disable();
4616a55b
TM
549 }
550 }
551 },
80fdf8d2
TL
552 deactivate: function() {
553 if (this.onlineHelp) {
554 Ext.GlobalEvents.fireEvent('proxmoxHideHelp', this.onlineHelp);
555 }
f6710aac 556 },
80fdf8d2
TL
557 },
558 onSubmit: function() {
559 var wizard = this.up('pveCephInstallWizard');
560 wizard.close();
f6710aac
TL
561 },
562 },
563 ],
c3035c01 564});