]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/ceph/CephInstallWizard.js
ui: ceph install wizard: allow selecting repository
[pve-manager.git] / www / manager6 / ceph / CephInstallWizard.js
1 Ext.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,
7 distributed storage system, designed for excellent performance, reliability,
8 and scalability."</p></blockquote>
9 <p>
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
14 needed once per cluster and will be skipped if a config is already present.
15 </p>
16 <p>
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>.
20 </p>`,
21 });
22
23 Ext.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: [
52 { release: "quincy", version: "17.2" },
53 ],
54 },
55 });
56
57 Ext.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 = "";
96 for (const [_nodename, data] of Object.entries(nodes)) {
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',
109 17: 'quincy',
110 18: 'reef',
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);
129 },
130 });
131 },
132 });
133
134 Ext.define('PVE.ceph.CephInstallWizard', {
135 extend: 'PVE.window.Wizard',
136 alias: 'widget.pveCephInstallWizard',
137 mixins: ['Proxmox.Mixin.CBind'],
138
139 resizable: false,
140 nodename: undefined,
141
142 width: 760, // 4:3
143 height: 570,
144
145 viewModel: {
146 data: {
147 nodename: '',
148 cephRelease: 'quincy',
149 cephRepo: 'enterprise',
150 configuration: true,
151 isInstalled: false,
152 },
153 },
154 cbindData: {
155 nodename: undefined,
156 },
157
158 title: gettext('Setup'),
159 navigateNext: function() {
160 var tp = this.down('#wizcontent');
161 var atab = tp.getActiveTab();
162
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 },
170 setInitialTab: function(index) {
171 var tp = this.down('#wizcontent');
172 var initialTab = tp.items.getAt(index);
173 initialTab.enable();
174 tp.setActiveTab(initialTab);
175 },
176 onShow: function() {
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 }
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: {
197 border: false,
198 bodyBorder: false,
199 },
200 items: [
201 {
202 xtype: 'pveCephInstallWizardInfo',
203 },
204 {
205 flex: 1,
206 },
207 {
208 xtype: 'pveCephHighestVersionDisplay',
209 labelWidth: 150,
210 cbind: {
211 nodename: '{nodename}',
212 },
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);
219 },
220 },
221 {
222 xtype: 'container',
223 layout: 'hbox',
224 defaults: {
225 border: false,
226 layout: 'anchor',
227 flex: 1,
228 },
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 },
244 },
245 },
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 }],
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'));
282 },
283 },
284 },
285 {
286 title: gettext('Installation'),
287 xtype: 'panel',
288 layout: 'fit',
289 cbind: {
290 nodename: '{nodename}',
291 },
292 viewModel: {}, // needed to inherit parent viewModel data
293 listeners: {
294 afterrender: function() {
295 var me = this;
296 if (this.getViewModel().get('isInstalled')) {
297 this.mask("Ceph is already installed, click next to create your configuration.", ['pve-static-mask']);
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',
310 url: '/api2/json/nodes/' + nodename + '/ceph/status',
311 },
312 listeners: {
313 load: function(rec, response, success, operation) {
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();
319 me.up('pveCephInstallWizard').getViewModel().set('configuration', false);
320 me.down('textfield').setValue('success');
321 } else if (operation.error.statusText.match("rados_connect failed", "i")) {
322 me.updateStore.stopUpdate();
323 me.up('pveCephInstallWizard').getViewModel().set('configuration', true);
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 }
328 },
329 },
330 });
331 me.updateStore.startUpdate();
332 },
333 destroy: function() {
334 var me = this;
335 if (me.updateStore) {
336 me.updateStore.stopUpdate();
337 }
338 },
339 },
340 items: [
341 {
342 xtype: 'pveNoVncConsole',
343 itemId: 'jsconsole',
344 consoleType: 'cmd',
345 xtermjs: true,
346 cbind: {
347 nodename: '{nodename}',
348 },
349 beforeLoad: function() {
350 let me = this;
351 let wizard = me.up('pveCephInstallWizard');
352 let release = wizard.getViewModel().get('cephRelease');
353 let repo = wizard.getViewModel().get('cephRepo');
354 me.cmdOpts = `--version\0${release}\0--repository\0${repo}`;
355 },
356 cmd: 'ceph_install',
357 },
358 {
359 xtype: 'textfield',
360 name: 'installSuccess',
361 value: '',
362 allowBlank: false,
363 submitValue: false,
364 hidden: true,
365 },
366 ],
367 },
368 {
369 xtype: 'inputpanel',
370 title: gettext('Configuration'),
371 onlineHelp: 'chapter_pveceph',
372 height: 300,
373 cbind: {
374 nodename: '{nodename}',
375 },
376 viewModel: {
377 data: {
378 replicas: undefined,
379 minreplicas: undefined,
380 },
381 },
382 listeners: {
383 activate: function() {
384 this.up('pveCephInstallWizard').down('#submit').setText(gettext('Next'));
385 },
386 afterrender: function() {
387 if (this.up('pveCephInstallWizard').getViewModel().get('configuration')) {
388 this.mask("Configuration already initialized", ['pve-static-mask']);
389 } else {
390 this.unmask();
391 }
392 },
393 deactivate: function() {
394 this.up('pveCephInstallWizard').down('#submit').setText(gettext('Finish'));
395 },
396 },
397 column1: [
398 {
399 xtype: 'displayfield',
400 value: gettext('Ceph cluster configuration') + ':',
401 },
402 {
403 xtype: 'proxmoxNetworkSelector',
404 name: 'network',
405 value: '',
406 fieldLabel: 'Public Network IP/CIDR',
407 autoSelect: false,
408 bind: {
409 allowBlank: '{configuration}',
410 },
411 cbind: {
412 nodename: '{nodename}',
413 },
414 },
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: {
423 nodename: '{nodename}',
424 },
425 },
426 // FIXME: add hint about cluster network and/or reference user to docs??
427 ],
428 column2: [
429 {
430 xtype: 'displayfield',
431 value: gettext('First Ceph monitor') + ':',
432 },
433 {
434 xtype: 'displayfield',
435 fieldLabel: gettext('Monitor node'),
436 cbind: {
437 value: '{nodename}',
438 },
439 },
440 {
441 xtype: 'displayfield',
442 value: gettext('Additional monitors are recommended. They can be created at any time in the Monitor tab.'),
443 userCls: 'pmx-hint',
444 },
445 ],
446 advancedColumn1: [
447 {
448 xtype: 'numberfield',
449 name: 'size',
450 fieldLabel: 'Number of replicas',
451 bind: {
452 value: '{replicas}',
453 },
454 maxValue: 7,
455 minValue: 2,
456 emptyText: '3',
457 },
458 {
459 xtype: 'numberfield',
460 name: 'min_size',
461 fieldLabel: 'Minimum replicas',
462 bind: {
463 maxValue: '{replicas}',
464 value: '{minreplicas}',
465 },
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;
473 }
474 this.toggleSpinners();
475 this.validate();
476 },
477 emptyText: '2',
478 },
479 ],
480 onGetValues: function(values) {
481 ['cluster-network', 'size', 'min_size'].forEach(function(field) {
482 if (!values[field]) {
483 delete values[field];
484 }
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();
493 delete kv.delete;
494 var nodename = me.nodename;
495 delete kv.nodename;
496 Proxmox.Utils.API2Request({
497 url: `/nodes/${nodename}/ceph/init`,
498 waitMsgTarget: wizard,
499 method: 'POST',
500 params: kv,
501 success: function() {
502 Proxmox.Utils.API2Request({
503 url: `/nodes/${nodename}/ceph/mon/${nodename}`,
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);
511 },
512 });
513 },
514 failure: function(response, opts) {
515 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
516 },
517 });
518 } else {
519 me.up('pveCephInstallWizard').navigateNext();
520 }
521 },
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>'+
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>'+
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>'+
535 '<p>To learn more, click on the help button below.</p>',
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 }
542
543 var tp = this.up('#wizcontent');
544 var idx = tp.items.indexOf(this)-1;
545 for (;idx >= 0; idx--) {
546 var nc = tp.items.getAt(idx);
547 if (nc) {
548 nc.disable();
549 }
550 }
551 },
552 deactivate: function() {
553 if (this.onlineHelp) {
554 Ext.GlobalEvents.fireEvent('proxmoxHideHelp', this.onlineHelp);
555 }
556 },
557 },
558 onSubmit: function() {
559 var wizard = this.up('pveCephInstallWizard');
560 wizard.close();
561 },
562 },
563 ],
564 });