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