]>
Commit | Line | Data |
---|---|---|
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: "nautilus", version: "14.2"}, | |
53 | {release: "octopus", version: "15.2"}, | |
54 | //{release: "pacific", version: "16.1"}, | |
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 | }; | |
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 | viewModel: { | |
143 | data: { | |
144 | nodename: '', | |
145 | cephRelease: 'octopus', | |
146 | configuration: true, | |
147 | isInstalled: false, | |
148 | } | |
149 | }, | |
150 | cbindData: { | |
151 | nodename: undefined | |
152 | }, | |
153 | ||
154 | title: gettext('Setup'), | |
155 | navigateNext: function() { | |
156 | var tp = this.down('#wizcontent'); | |
157 | var atab = tp.getActiveTab(); | |
158 | ||
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 | }, | |
166 | setInitialTab: function (index) { | |
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); | |
178 | } | |
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: { | |
193 | border: false, | |
194 | bodyBorder: false, | |
195 | }, | |
196 | items: [ | |
197 | { | |
198 | xtype: 'pveCephInstallWizardInfo', | |
199 | }, | |
200 | { | |
201 | flex: 1, | |
202 | }, | |
203 | { | |
204 | xtype: 'pveCephHighestVersionDisplay', | |
205 | labelWidth: 180, | |
206 | cbind:{ | |
207 | nodename: '{nodename}' | |
208 | }, | |
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); | |
215 | }, | |
216 | }, | |
217 | { | |
218 | xtype: 'pveCephVersionSelector', | |
219 | labelWidth: 180, | |
220 | submitValue: false, | |
221 | bind: { | |
222 | value: '{cephRelease}', | |
223 | }, | |
224 | listeners: { | |
225 | change: function(field, release) { | |
226 | let wizard = this.up('pveCephInstallWizard'); | |
227 | wizard.down('#next').setText( | |
228 | Ext.String.format(gettext('Start {0} installation'), release), | |
229 | ); | |
230 | }, | |
231 | }, | |
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')); | |
252 | } | |
253 | } | |
254 | }, | |
255 | { | |
256 | title: gettext('Installation'), | |
257 | xtype: 'panel', | |
258 | layout: 'fit', | |
259 | cbind:{ | |
260 | nodename: '{nodename}' | |
261 | }, | |
262 | viewModel: {}, // needed to inherit parent viewModel data | |
263 | listeners: { | |
264 | afterrender: function() { | |
265 | var me = this; | |
266 | if (this.getViewModel().get('isInstalled')) { | |
267 | this.mask("Ceph is already installed, click next to create your configuration.",['pve-static-mask']); | |
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', | |
280 | url: '/api2/json/nodes/' + nodename + '/ceph/status' | |
281 | }, | |
282 | listeners: { | |
283 | load: function(rec, response, success, operation) { | |
284 | ||
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 | cbind: { | |
343 | nodename: '{nodename}' | |
344 | }, | |
345 | viewModel: { | |
346 | data: { | |
347 | replicas: undefined, | |
348 | minreplicas: undefined | |
349 | } | |
350 | }, | |
351 | listeners: { | |
352 | activate: function() { | |
353 | this.up('pveCephInstallWizard').down('#submit').setText(gettext('Next')); | |
354 | }, | |
355 | beforeshow: function() { | |
356 | if (this.up('pveCephInstallWizard').getViewModel().get('configuration')) { | |
357 | this.mask("Configuration already initialized",['pve-static-mask']); | |
358 | } else { | |
359 | this.unmask(); | |
360 | } | |
361 | }, | |
362 | deactivate: function() { | |
363 | this.up('pveCephInstallWizard').down('#submit').setText(gettext('Finish')); | |
364 | } | |
365 | }, | |
366 | column1: [ | |
367 | { | |
368 | xtype: 'displayfield', | |
369 | value: gettext('Ceph cluster configuration') + ':' | |
370 | }, | |
371 | { | |
372 | xtype: 'proxmoxNetworkSelector', | |
373 | name: 'network', | |
374 | value: '', | |
375 | fieldLabel: 'Public Network IP/CIDR', | |
376 | bind: { | |
377 | allowBlank: '{configuration}' | |
378 | }, | |
379 | cbind: { | |
380 | nodename: '{nodename}' | |
381 | } | |
382 | }, | |
383 | { | |
384 | xtype: 'proxmoxNetworkSelector', | |
385 | name: 'cluster-network', | |
386 | fieldLabel: 'Cluster Network IP/CIDR', | |
387 | allowBlank: true, | |
388 | autoSelect: false, | |
389 | emptyText: gettext('Same as Public Network'), | |
390 | cbind: { | |
391 | nodename: '{nodename}' | |
392 | } | |
393 | } | |
394 | // FIXME: add hint about cluster network and/or reference user to docs?? | |
395 | ], | |
396 | column2: [ | |
397 | { | |
398 | xtype: 'displayfield', | |
399 | value: gettext('First Ceph monitor') + ':' | |
400 | }, | |
401 | { | |
402 | xtype: 'pveNodeSelector', | |
403 | fieldLabel: gettext('Monitor node'), | |
404 | name: 'mon-node', | |
405 | selectCurNode: true, | |
406 | allowBlank: false | |
407 | }, | |
408 | { | |
409 | xtype: 'displayfield', | |
410 | value: gettext('Additional monitors are recommended. They can be created at any time in the Monitor tab.'), | |
411 | userCls: 'pmx-hint' | |
412 | } | |
413 | ], | |
414 | advancedColumn1: [ | |
415 | { | |
416 | xtype: 'numberfield', | |
417 | name: 'size', | |
418 | fieldLabel: 'Number of replicas', | |
419 | bind: { | |
420 | value: '{replicas}' | |
421 | }, | |
422 | maxValue: 7, | |
423 | minValue: 2, | |
424 | emptyText: '3' | |
425 | }, | |
426 | { | |
427 | xtype: 'numberfield', | |
428 | name: 'min_size', | |
429 | fieldLabel: 'Minimum replicas', | |
430 | bind: { | |
431 | maxValue: '{replicas}', | |
432 | value: '{minreplicas}' | |
433 | }, | |
434 | minValue: 2, | |
435 | maxValue: 3, | |
436 | setMaxValue: function(value) { | |
437 | this.maxValue = Ext.Number.from(value, 2); | |
438 | // allow enough to avoid split brains with max 'size', but more makes simply no sense | |
439 | if (this.maxValue > 4) { | |
440 | this.maxValue = 4; | |
441 | } | |
442 | this.toggleSpinners(); | |
443 | this.validate(); | |
444 | }, | |
445 | emptyText: '2' | |
446 | } | |
447 | ], | |
448 | onGetValues: function(values) { | |
449 | ['cluster-network', 'size', 'min_size'].forEach(function(field) { | |
450 | if (!values[field]) { | |
451 | delete values[field]; | |
452 | } | |
453 | }); | |
454 | return values; | |
455 | }, | |
456 | onSubmit: function() { | |
457 | var me = this; | |
458 | if (!this.up('pveCephInstallWizard').getViewModel().get('configuration')) { | |
459 | var wizard = me.up('window'); | |
460 | var kv = wizard.getValues(); | |
461 | delete kv['delete']; | |
462 | var monNode = kv['mon-node']; | |
463 | delete kv['mon-node']; | |
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/' + monNode + '/ceph/mon/' + monNode, | |
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 | ||
489 | } else { | |
490 | me.up('pveCephInstallWizard').navigateNext(); | |
491 | } | |
492 | } | |
493 | }, | |
494 | { | |
495 | title: gettext('Success'), | |
496 | xtype: 'panel', | |
497 | border: false, | |
498 | bodyBorder: false, | |
499 | onlineHelp: 'pve_ceph_install', | |
500 | html: '<h3>Installation successful!</h3>'+ | |
501 | '<p>The basic installation and configuration is complete. Depending on your setup, some of the following steps are required to start using Ceph:</p>'+ | |
502 | '<ol><li>Install Ceph on other nodes</li>'+ | |
503 | '<li>Create additional Ceph Monitors</li>'+ | |
504 | '<li>Create Ceph OSDs</li>'+ | |
505 | '<li>Create Ceph Pools</li></ol>'+ | |
506 | '<p>To learn more, click on the help button below.</p>', | |
507 | listeners: { | |
508 | activate: function() { | |
509 | // notify owning container that it should display a help button | |
510 | if (this.onlineHelp) { | |
511 | Ext.GlobalEvents.fireEvent('proxmoxShowHelp', this.onlineHelp); | |
512 | } | |
513 | ||
514 | var tp = this.up('#wizcontent'); | |
515 | var idx = tp.items.indexOf(this)-1; | |
516 | for(;idx >= 0;idx--) { | |
517 | var nc = tp.items.getAt(idx); | |
518 | if (nc) { | |
519 | nc.disable(); | |
520 | } | |
521 | } | |
522 | }, | |
523 | deactivate: function() { | |
524 | if (this.onlineHelp) { | |
525 | Ext.GlobalEvents.fireEvent('proxmoxHideHelp', this.onlineHelp); | |
526 | } | |
527 | } | |
528 | }, | |
529 | onSubmit: function() { | |
530 | var wizard = this.up('pveCephInstallWizard'); | |
531 | wizard.close(); | |
532 | } | |
533 | } | |
534 | ] | |
535 | }); |