]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/Workspace.js
52c66108ca247b39adae21c281239ab3480e2a02
[pve-manager.git] / www / manager6 / Workspace.js
1 /*
2 * Workspace base class
3 *
4 * popup login window when auth fails (call onLogin handler)
5 * update (re-login) ticket every 15 minutes
6 *
7 */
8
9 Ext.define('PVE.Workspace', {
10 extend: 'Ext.container.Viewport',
11
12 title: 'Proxmox Virtual Environment',
13
14 loginData: null, // Data from last login call
15
16 onLogin: function(loginData) {
17 // override me
18 },
19
20 // private
21 updateLoginData: function(loginData) {
22 let me = this;
23 me.loginData = loginData;
24 Proxmox.Utils.setAuthData(loginData);
25
26 let rt = me.down('pveResourceTree');
27 rt.setDatacenterText(loginData.clustername);
28 PVE.ClusterName = loginData.clustername;
29
30 if (loginData.cap) {
31 Ext.state.Manager.set('GuiCap', loginData.cap);
32 }
33 me.response401count = 0;
34
35 me.onLogin(loginData);
36 },
37
38 // private
39 showLogin: function() {
40 let me = this;
41
42 Proxmox.Utils.authClear();
43 Ext.state.Manager.clear('GuiCap');
44 Proxmox.UserName = null;
45 me.loginData = null;
46
47 if (!me.login) {
48 me.login = Ext.create('PVE.window.LoginWindow', {
49 handler: function(data) {
50 me.login = null;
51 me.updateLoginData(data);
52 Proxmox.Utils.checked_command(Ext.emptyFn); // display subscription status
53 },
54 });
55 }
56 me.onLogin(null);
57 me.login.show();
58 },
59
60 initComponent: function() {
61 let me = this;
62
63 Ext.tip.QuickTipManager.init();
64
65 // fixme: what about other errors
66 Ext.Ajax.on('requestexception', function(conn, response, options) {
67 if ((response.status === 401 || response.status === '401') && !PVE.Utils.silenceAuthFailures) { // auth failure
68 // don't immediately show as logged out to cope better with some big
69 // upgrades, which may temporarily produce a false positive 401 err
70 me.response401count++;
71 if (me.response401count > 5) {
72 me.showLogin();
73 }
74 }
75 });
76
77 me.callParent();
78
79 if (!Proxmox.Utils.authOK()) {
80 me.showLogin();
81 } else if (me.loginData) {
82 me.onLogin(me.loginData);
83 }
84
85 Ext.TaskManager.start({
86 run: function() {
87 let ticket = Proxmox.Utils.authOK();
88 if (!ticket || !Proxmox.UserName) {
89 return;
90 }
91
92 Ext.Ajax.request({
93 params: {
94 username: Proxmox.UserName,
95 password: ticket,
96 },
97 url: '/api2/json/access/ticket',
98 method: 'POST',
99 success: function(response, opts) {
100 let obj = Ext.decode(response.responseText);
101 me.updateLoginData(obj.data);
102 },
103 });
104 },
105 interval: 15 * 60 * 1000,
106 });
107 },
108 });
109
110 Ext.define('PVE.StdWorkspace', {
111 extend: 'PVE.Workspace',
112
113 alias: ['widget.pveStdWorkspace'],
114
115 // private
116 setContent: function(comp) {
117 let me = this;
118
119 let view = me.child('#content');
120 let layout = view.getLayout();
121 let current = layout.getActiveItem();
122
123 if (comp) {
124 Proxmox.Utils.setErrorMask(view, false);
125 comp.border = false;
126 view.add(comp);
127 if (current !== null && layout.getNext()) {
128 layout.next();
129 let task = Ext.create('Ext.util.DelayedTask', function() {
130 view.remove(current);
131 });
132 task.delay(10);
133 }
134 } else {
135 view.removeAll(); // helper for cleaning the content when logging out
136 }
137 },
138
139 selectById: function(nodeid) {
140 let me = this;
141 me.down('pveResourceTree').selectById(nodeid);
142 },
143
144 onLogin: function(loginData) {
145 let me = this;
146
147 me.updateUserInfo();
148
149 if (loginData) {
150 PVE.data.ResourceStore.startUpdate();
151
152 Proxmox.Utils.API2Request({
153 url: '/version',
154 method: 'GET',
155 success: function(response) {
156 PVE.VersionInfo = response.result.data;
157 me.updateVersionInfo();
158 },
159 });
160
161 PVE.UIOptions.update();
162
163 Proxmox.Utils.API2Request({
164 url: '/cluster/sdn',
165 method: 'GET',
166 success: function(response) {
167 PVE.SDNInfo = response.result.data;
168 },
169 failure: function(response) {
170 PVE.SDNInfo = null;
171 let ui = Ext.ComponentQuery.query('treelistitem[text="SDN"]')[0];
172 if (ui) {
173 ui.addCls('x-hidden-display');
174 }
175 },
176 });
177
178 Proxmox.Utils.API2Request({
179 url: '/access/domains',
180 method: 'GET',
181 success: function(response) {
182 let [_username, realm] = Proxmox.Utils.parse_userid(Proxmox.UserName);
183 response.result.data.forEach((domain) => {
184 if (domain.realm === realm) {
185 let schema = PVE.Utils.authSchema[domain.type];
186 if (schema) {
187 me.query('#tfaitem')[0].setHidden(!schema.tfa);
188 me.query('#passworditem')[0].setHidden(!schema.pwchange);
189 }
190 }
191 });
192 },
193 });
194 }
195 },
196
197 updateUserInfo: function() {
198 let me = this;
199 let ui = me.query('#userinfo')[0];
200 ui.setText(Ext.String.htmlEncode(Proxmox.UserName || ''));
201 ui.updateLayout();
202 },
203
204 updateVersionInfo: function() {
205 let me = this;
206
207 let ui = me.query('#versioninfo')[0];
208
209 if (PVE.VersionInfo) {
210 let version = PVE.VersionInfo.version;
211 ui.update('Virtual Environment ' + version);
212 } else {
213 ui.update('Virtual Environment');
214 }
215 ui.updateLayout();
216 },
217
218 initComponent: function() {
219 let me = this;
220
221 Ext.History.init();
222
223 let appState = Ext.create('PVE.StateProvider');
224 Ext.state.Manager.setProvider(appState);
225
226 let selview = Ext.create('PVE.form.ViewSelector', {
227 flex: 1,
228 padding: '0 5 0 0',
229 });
230
231 let rtree = Ext.createWidget('pveResourceTree', {
232 viewFilter: selview.getViewFilter(),
233 flex: 1,
234 selModel: {
235 selType: 'treemodel',
236 listeners: {
237 selectionchange: function(sm, selected) {
238 if (selected.length <= 0) {
239 return;
240 }
241 let treeNode = selected[0];
242 let treeTypeToClass = {
243 root: 'PVE.dc.Config',
244 node: 'PVE.node.Config',
245 qemu: 'PVE.qemu.Config',
246 lxc: 'pveLXCConfig',
247 storage: 'PVE.storage.Browser',
248 sdn: 'PVE.sdn.Browser',
249 pool: 'pvePoolConfig',
250 };
251 PVE.curSelectedNode = treeNode;
252 me.setContent({
253 xtype: treeTypeToClass[treeNode.data.type || 'root'] || 'pvePanelConfig',
254 showSearch: treeNode.data.id === 'root' || Ext.isDefined(treeNode.data.groupbyid),
255 pveSelNode: treeNode,
256 workspace: me,
257 viewFilter: selview.getViewFilter(),
258 });
259 },
260 },
261 },
262 });
263
264 selview.on('select', function(combo, records) {
265 if (records) {
266 let view = combo.getViewFilter();
267 rtree.setViewFilter(view);
268 }
269 });
270
271 let caps = appState.get('GuiCap');
272
273 let createVM = Ext.createWidget('button', {
274 pack: 'end',
275 margin: '3 5 0 0',
276 baseCls: 'x-btn',
277 iconCls: 'fa fa-desktop',
278 text: gettext("Create VM"),
279 disabled: !caps.vms['VM.Allocate'],
280 handler: function() {
281 let wiz = Ext.create('PVE.qemu.CreateWizard', {});
282 wiz.show();
283 },
284 });
285
286 let createCT = Ext.createWidget('button', {
287 pack: 'end',
288 margin: '3 5 0 0',
289 baseCls: 'x-btn',
290 iconCls: 'fa fa-cube',
291 text: gettext("Create CT"),
292 disabled: !caps.vms['VM.Allocate'],
293 handler: function() {
294 let wiz = Ext.create('PVE.lxc.CreateWizard', {});
295 wiz.show();
296 },
297 });
298
299 appState.on('statechange', function(sp, key, value) {
300 if (key === 'GuiCap' && value) {
301 caps = value;
302 createVM.setDisabled(!caps.vms['VM.Allocate']);
303 createCT.setDisabled(!caps.vms['VM.Allocate']);
304 }
305 });
306
307 Ext.apply(me, {
308 layout: { type: 'border' },
309 border: false,
310 items: [
311 {
312 region: 'north',
313 title: gettext('Header'), // for ARIA
314 header: false, // avoid rendering the title
315 layout: {
316 type: 'hbox',
317 align: 'middle',
318 },
319 baseCls: 'x-plain',
320 defaults: {
321 baseCls: 'x-plain',
322 },
323 border: false,
324 margin: '2 0 2 5',
325 items: [
326 {
327 xtype: 'proxmoxlogo',
328 },
329 {
330 minWidth: 150,
331 id: 'versioninfo',
332 html: 'Virtual Environment',
333 style: {
334 'font-size': '14px',
335 'line-height': '18px',
336 },
337 },
338 {
339 xtype: 'pveGlobalSearchField',
340 tree: rtree,
341 },
342 {
343 flex: 1,
344 },
345 {
346 xtype: 'proxmoxHelpButton',
347 hidden: false,
348 baseCls: 'x-btn',
349 iconCls: 'fa fa-book x-btn-icon-el-default-toolbar-small ',
350 listenToGlobalEvent: false,
351 onlineHelp: 'pve_documentation_index',
352 text: gettext('Documentation'),
353 margin: '0 5 0 0',
354 },
355 createVM,
356 createCT,
357 {
358 pack: 'end',
359 margin: '0 5 0 0',
360 id: 'userinfo',
361 xtype: 'button',
362 baseCls: 'x-btn',
363 style: {
364 // proxmox dark grey p light grey as border
365 backgroundColor: '#464d4d',
366 borderColor: '#ABBABA',
367 },
368 iconCls: 'fa fa-user',
369 menu: [
370 {
371 iconCls: 'fa fa-gear',
372 text: gettext('My Settings'),
373 handler: function() {
374 var win = Ext.create('PVE.window.Settings');
375 win.show();
376 },
377 },
378 {
379 text: gettext('Password'),
380 itemId: 'passworditem',
381 iconCls: 'fa fa-fw fa-key',
382 handler: function() {
383 var win = Ext.create('Proxmox.window.PasswordEdit', {
384 userid: Proxmox.UserName,
385 confirmCurrentPassword: Proxmox.UserName !== 'root@pam',
386 });
387 win.show();
388 },
389 },
390 {
391 text: 'TFA',
392 itemId: 'tfaitem',
393 iconCls: 'fa fa-fw fa-lock',
394 handler: function(btn, event, rec) {
395 Ext.state.Manager.getProvider().set('dctab', { value: 'tfa' }, true);
396 me.selectById('root');
397 },
398 },
399 {
400 iconCls: 'fa fa-paint-brush',
401 text: gettext('Color Theme'),
402 handler: function() {
403 Ext.create('Proxmox.window.ThemeEditWindow')
404 .show();
405 },
406 },
407 {
408 iconCls: 'fa fa-language',
409 text: gettext('Language'),
410 handler: function() {
411 Ext.create('Proxmox.window.LanguageEditWindow')
412 .show();
413 },
414 },
415 '-',
416 {
417 iconCls: 'fa fa-fw fa-sign-out',
418 text: gettext("Logout"),
419 handler: function() {
420 PVE.data.ResourceStore.loadData([], false);
421 me.showLogin();
422 me.setContent(null);
423 var rt = me.down('pveResourceTree');
424 rt.setDatacenterText(undefined);
425 rt.clearTree();
426
427 // empty the stores of the StatusPanel child items
428 var statusPanels = Ext.ComponentQuery.query('pveStatusPanel grid');
429 Ext.Array.forEach(statusPanels, function(comp) {
430 if (comp.getStore()) {
431 comp.getStore().loadData([], false);
432 }
433 });
434 },
435 },
436 ],
437 },
438 ],
439 },
440 {
441 region: 'center',
442 stateful: true,
443 stateId: 'pvecenter',
444 minWidth: 100,
445 minHeight: 100,
446 id: 'content',
447 xtype: 'container',
448 layout: { type: 'card' },
449 border: false,
450 margin: '0 5 0 0',
451 items: [],
452 },
453 {
454 region: 'west',
455 stateful: true,
456 stateId: 'pvewest',
457 itemId: 'west',
458 xtype: 'container',
459 border: false,
460 layout: { type: 'vbox', align: 'stretch' },
461 margin: '0 0 0 5',
462 split: true,
463 width: 300,
464 items: [
465 {
466 xtype: 'container',
467 layout: 'hbox',
468 padding: '0 0 5 0',
469 items: [
470 selview,
471 {
472 xtype: 'button',
473 cls: 'x-btn-default-toolbar-small',
474 iconCls: 'fa fa-fw fa-gear x-btn-icon-el-default-toolbar-small',
475 handler: () => {
476 Ext.create('PVE.window.TreeSettingsEdit', {
477 autoShow: true,
478 apiCallDone: () => PVE.UIOptions.fireUIConfigChanged(),
479 });
480 },
481 },
482 ],
483 },
484 rtree,
485 ],
486 listeners: {
487 resize: function(panel, width, height) {
488 var viewWidth = me.getSize().width;
489 if (width > viewWidth - 100 && viewWidth > 150) {
490 panel.setWidth(viewWidth - 100);
491 }
492 },
493 },
494 },
495 {
496 xtype: 'pveStatusPanel',
497 stateful: true,
498 stateId: 'pvesouth',
499 itemId: 'south',
500 region: 'south',
501 margin: '0 5 5 5',
502 title: gettext('Logs'),
503 collapsible: true,
504 header: false,
505 height: 200,
506 split: true,
507 listeners: {
508 resize: function(panel, width, height) {
509 var viewHeight = me.getSize().height;
510 if (height > viewHeight - 150 && viewHeight > 200) {
511 panel.setHeight(viewHeight - 150);
512 }
513 },
514 },
515 },
516 ],
517 });
518
519 me.callParent();
520
521 me.updateUserInfo();
522
523 // on resize, center all modal windows
524 Ext.on('resize', function() {
525 let modalWindows = Ext.ComponentQuery.query('window[modal]');
526 if (modalWindows.length > 0) {
527 modalWindows.forEach(win => win.alignTo(me, 'c-c'));
528 }
529 });
530
531 let tagSelectors = [];
532 ['circle', 'dense'].forEach((style) => {
533 ['dark', 'light'].forEach((variant) => {
534 tagSelectors.push(`.proxmox-tags-${style} .proxmox-tag-${variant}`);
535 });
536 });
537
538 Ext.create('Ext.tip.ToolTip', {
539 target: me.el,
540 delegate: tagSelectors.join(', '),
541 trackMouse: true,
542 renderTo: Ext.getBody(),
543 border: 0,
544 minWidth: 0,
545 padding: 0,
546 bodyBorder: 0,
547 bodyPadding: 0,
548 dismissDelay: 0,
549 userCls: 'pmx-tag-tooltip',
550 shadow: false,
551 listeners: {
552 beforeshow: function(tip) {
553 let tag = Ext.htmlEncode(tip.triggerElement.innerHTML);
554 let tagEl = Proxmox.Utils.getTagElement(tag, PVE.UIOptions.tagOverrides);
555 tip.update(`<span class="proxmox-tags-full">${tagEl}</span>`);
556 },
557 },
558 });
559 },
560 });
561