]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/ceph/CephInstallWizard.js
update shipped appliance info index
[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: "octopus", version: "15.2" },
53 { release: "pacific", version: "16.2" },
54 { release: "quincy", version: "17.2" },
55 ],
56 },
57 });
58
59 Ext.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 = "";
98 for (const [_nodename, data] of Object.entries(nodes)) {
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',
111 17: 'quincy',
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);
130 },
131 });
132 },
133 });
134
135 Ext.define('PVE.ceph.CephInstallWizard', {
136 extend: 'PVE.window.Wizard',
137 alias: 'widget.pveCephInstallWizard',
138 mixins: ['Proxmox.Mixin.CBind'],
139
140 resizable: false,
141 nodename: undefined,
142
143 viewModel: {
144 data: {
145 nodename: '',
146 cephRelease: 'quincy',
147 configuration: true,
148 isInstalled: false,
149 },
150 },
151 cbindData: {
152 nodename: undefined,
153 },
154
155 title: gettext('Setup'),
156 navigateNext: function() {
157 var tp = this.down('#wizcontent');
158 var atab = tp.getActiveTab();
159
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 },
167 setInitialTab: function(index) {
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);
179 }
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: {
194 border: false,
195 bodyBorder: false,
196 },
197 items: [
198 {
199 xtype: 'pveCephInstallWizardInfo',
200 },
201 {
202 flex: 1,
203 },
204 {
205 xtype: 'pveCephHighestVersionDisplay',
206 labelWidth: 180,
207 cbind: {
208 nodename: '{nodename}',
209 },
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);
216 },
217 },
218 {
219 xtype: 'pveCephVersionSelector',
220 labelWidth: 180,
221 submitValue: false,
222 bind: {
223 value: '{cephRelease}',
224 },
225 listeners: {
226 change: function(field, release) {
227 let wizard = this.up('pveCephInstallWizard');
228 wizard.down('#next').setText(
229 Ext.String.format(gettext('Start {0} installation'), release),
230 );
231 },
232 },
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'));
253 },
254 },
255 },
256 {
257 title: gettext('Installation'),
258 xtype: 'panel',
259 layout: 'fit',
260 cbind: {
261 nodename: '{nodename}',
262 },
263 viewModel: {}, // needed to inherit parent viewModel data
264 listeners: {
265 afterrender: function() {
266 var me = this;
267 if (this.getViewModel().get('isInstalled')) {
268 this.mask("Ceph is already installed, click next to create your configuration.", ['pve-static-mask']);
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',
281 url: '/api2/json/nodes/' + nodename + '/ceph/status',
282 },
283 listeners: {
284 load: function(rec, response, success, operation) {
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();
290 me.up('pveCephInstallWizard').getViewModel().set('configuration', false);
291 me.down('textfield').setValue('success');
292 } else if (operation.error.statusText.match("rados_connect failed", "i")) {
293 me.updateStore.stopUpdate();
294 me.up('pveCephInstallWizard').getViewModel().set('configuration', true);
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 }
299 },
300 },
301 });
302 me.updateStore.startUpdate();
303 },
304 destroy: function() {
305 var me = this;
306 if (me.updateStore) {
307 me.updateStore.stopUpdate();
308 }
309 },
310 },
311 items: [
312 {
313 xtype: 'pveNoVncConsole',
314 itemId: 'jsconsole',
315 consoleType: 'cmd',
316 xtermjs: true,
317 cbind: {
318 nodename: '{nodename}',
319 },
320 beforeLoad: function() {
321 let me = this;
322 let wizard = me.up('pveCephInstallWizard');
323 let release = wizard.getViewModel().get('cephRelease');
324 me.cmdOpts = `--version\0${release}`;
325 },
326 cmd: 'ceph_install',
327 },
328 {
329 xtype: 'textfield',
330 name: 'installSuccess',
331 value: '',
332 allowBlank: false,
333 submitValue: false,
334 hidden: true,
335 },
336 ],
337 },
338 {
339 xtype: 'inputpanel',
340 title: gettext('Configuration'),
341 onlineHelp: 'chapter_pveceph',
342 height: 300,
343 cbind: {
344 nodename: '{nodename}',
345 },
346 viewModel: {
347 data: {
348 replicas: undefined,
349 minreplicas: undefined,
350 },
351 },
352 listeners: {
353 activate: function() {
354 this.up('pveCephInstallWizard').down('#submit').setText(gettext('Next'));
355 },
356 afterrender: function() {
357 if (this.up('pveCephInstallWizard').getViewModel().get('configuration')) {
358 this.mask("Configuration already initialized", ['pve-static-mask']);
359 } else {
360 this.unmask();
361 }
362 },
363 deactivate: function() {
364 this.up('pveCephInstallWizard').down('#submit').setText(gettext('Finish'));
365 },
366 },
367 column1: [
368 {
369 xtype: 'displayfield',
370 value: gettext('Ceph cluster configuration') + ':',
371 },
372 {
373 xtype: 'proxmoxNetworkSelector',
374 name: 'network',
375 value: '',
376 fieldLabel: 'Public Network IP/CIDR',
377 autoSelect: false,
378 bind: {
379 allowBlank: '{configuration}',
380 },
381 cbind: {
382 nodename: '{nodename}',
383 },
384 },
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: {
393 nodename: '{nodename}',
394 },
395 },
396 // FIXME: add hint about cluster network and/or reference user to docs??
397 ],
398 column2: [
399 {
400 xtype: 'displayfield',
401 value: gettext('First Ceph monitor') + ':',
402 },
403 {
404 xtype: 'displayfield',
405 fieldLabel: gettext('Monitor node'),
406 cbind: {
407 value: '{nodename}',
408 },
409 },
410 {
411 xtype: 'displayfield',
412 value: gettext('Additional monitors are recommended. They can be created at any time in the Monitor tab.'),
413 userCls: 'pmx-hint',
414 },
415 ],
416 advancedColumn1: [
417 {
418 xtype: 'numberfield',
419 name: 'size',
420 fieldLabel: 'Number of replicas',
421 bind: {
422 value: '{replicas}',
423 },
424 maxValue: 7,
425 minValue: 2,
426 emptyText: '3',
427 },
428 {
429 xtype: 'numberfield',
430 name: 'min_size',
431 fieldLabel: 'Minimum replicas',
432 bind: {
433 maxValue: '{replicas}',
434 value: '{minreplicas}',
435 },
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;
443 }
444 this.toggleSpinners();
445 this.validate();
446 },
447 emptyText: '2',
448 },
449 ],
450 onGetValues: function(values) {
451 ['cluster-network', 'size', 'min_size'].forEach(function(field) {
452 if (!values[field]) {
453 delete values[field];
454 }
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();
463 delete kv.delete;
464 var nodename = me.nodename;
465 delete kv.nodename;
466 Proxmox.Utils.API2Request({
467 url: `/nodes/${nodename}/ceph/init`,
468 waitMsgTarget: wizard,
469 method: 'POST',
470 params: kv,
471 success: function() {
472 Proxmox.Utils.API2Request({
473 url: `/nodes/${nodename}/ceph/mon/${nodename}`,
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);
481 },
482 });
483 },
484 failure: function(response, opts) {
485 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
486 },
487 });
488 } else {
489 me.up('pveCephInstallWizard').navigateNext();
490 }
491 },
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>'+
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>'+
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>'+
505 '<p>To learn more, click on the help button below.</p>',
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 }
512
513 var tp = this.up('#wizcontent');
514 var idx = tp.items.indexOf(this)-1;
515 for (;idx >= 0; idx--) {
516 var nc = tp.items.getAt(idx);
517 if (nc) {
518 nc.disable();
519 }
520 }
521 },
522 deactivate: function() {
523 if (this.onlineHelp) {
524 Ext.GlobalEvents.fireEvent('proxmoxHideHelp', this.onlineHelp);
525 }
526 },
527 },
528 onSubmit: function() {
529 var wizard = this.up('pveCephInstallWizard');
530 wizard.close();
531 },
532 },
533 ],
534 });