mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-06-11 12:31:10 +00:00

the "proxmox-inline-button" class is redundant in the crisp theme as it only sets the buttons text to black. we mainly use that class for "help" buttons. this is useful in the dark theme, because we want help buttons to stand out a bit so (possibly confused) users are drawn to them more easily. removing the class here doesn't change anything for "crisp", but makes the dark theme appear more consistent. also fixes up an unnecessary space. Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
532 lines
12 KiB
JavaScript
532 lines
12 KiB
JavaScript
/*
|
|
* Workspace base class
|
|
*
|
|
* popup login window when auth fails (call onLogin handler)
|
|
* update (re-login) ticket every 15 minutes
|
|
*
|
|
*/
|
|
|
|
Ext.define('PVE.Workspace', {
|
|
extend: 'Ext.container.Viewport',
|
|
|
|
title: 'Proxmox Virtual Environment',
|
|
|
|
loginData: null, // Data from last login call
|
|
|
|
onLogin: function(loginData) {
|
|
// override me
|
|
},
|
|
|
|
// private
|
|
updateLoginData: function(loginData) {
|
|
let me = this;
|
|
me.loginData = loginData;
|
|
Proxmox.Utils.setAuthData(loginData);
|
|
|
|
let rt = me.down('pveResourceTree');
|
|
rt.setDatacenterText(loginData.clustername);
|
|
PVE.ClusterName = loginData.clustername;
|
|
|
|
if (loginData.cap) {
|
|
Ext.state.Manager.set('GuiCap', loginData.cap);
|
|
}
|
|
me.response401count = 0;
|
|
|
|
me.onLogin(loginData);
|
|
},
|
|
|
|
// private
|
|
showLogin: function() {
|
|
let me = this;
|
|
|
|
Proxmox.Utils.authClear();
|
|
Ext.state.Manager.clear('GuiCap');
|
|
Proxmox.UserName = null;
|
|
me.loginData = null;
|
|
|
|
if (!me.login) {
|
|
me.login = Ext.create('PVE.window.LoginWindow', {
|
|
handler: function(data) {
|
|
me.login = null;
|
|
me.updateLoginData(data);
|
|
Proxmox.Utils.checked_command(Ext.emptyFn); // display subscription status
|
|
},
|
|
});
|
|
}
|
|
me.onLogin(null);
|
|
me.login.show();
|
|
},
|
|
|
|
initComponent: function() {
|
|
let me = this;
|
|
|
|
Ext.tip.QuickTipManager.init();
|
|
|
|
// fixme: what about other errors
|
|
Ext.Ajax.on('requestexception', function(conn, response, options) {
|
|
if ((response.status === 401 || response.status === '401') && !PVE.Utils.silenceAuthFailures) { // auth failure
|
|
// don't immediately show as logged out to cope better with some big
|
|
// upgrades, which may temporarily produce a false positive 401 err
|
|
me.response401count++;
|
|
if (me.response401count > 5) {
|
|
me.showLogin();
|
|
}
|
|
}
|
|
});
|
|
|
|
me.callParent();
|
|
|
|
if (!Proxmox.Utils.authOK()) {
|
|
me.showLogin();
|
|
} else if (me.loginData) {
|
|
me.onLogin(me.loginData);
|
|
}
|
|
|
|
Ext.TaskManager.start({
|
|
run: function() {
|
|
let ticket = Proxmox.Utils.authOK();
|
|
if (!ticket || !Proxmox.UserName) {
|
|
return;
|
|
}
|
|
|
|
Ext.Ajax.request({
|
|
params: {
|
|
username: Proxmox.UserName,
|
|
password: ticket,
|
|
},
|
|
url: '/api2/json/access/ticket',
|
|
method: 'POST',
|
|
success: function(response, opts) {
|
|
let obj = Ext.decode(response.responseText);
|
|
me.updateLoginData(obj.data);
|
|
},
|
|
});
|
|
},
|
|
interval: 15 * 60 * 1000,
|
|
});
|
|
},
|
|
});
|
|
|
|
Ext.define('PVE.StdWorkspace', {
|
|
extend: 'PVE.Workspace',
|
|
|
|
alias: ['widget.pveStdWorkspace'],
|
|
|
|
// private
|
|
setContent: function(comp) {
|
|
let me = this;
|
|
|
|
let view = me.child('#content');
|
|
let layout = view.getLayout();
|
|
let current = layout.getActiveItem();
|
|
|
|
if (comp) {
|
|
Proxmox.Utils.setErrorMask(view, false);
|
|
comp.border = false;
|
|
view.add(comp);
|
|
if (current !== null && layout.getNext()) {
|
|
layout.next();
|
|
let task = Ext.create('Ext.util.DelayedTask', function() {
|
|
view.remove(current);
|
|
});
|
|
task.delay(10);
|
|
}
|
|
} else {
|
|
view.removeAll(); // helper for cleaning the content when logging out
|
|
}
|
|
},
|
|
|
|
selectById: function(nodeid) {
|
|
let me = this;
|
|
me.down('pveResourceTree').selectById(nodeid);
|
|
},
|
|
|
|
onLogin: function(loginData) {
|
|
let me = this;
|
|
|
|
me.updateUserInfo();
|
|
|
|
if (loginData) {
|
|
PVE.data.ResourceStore.startUpdate();
|
|
|
|
Proxmox.Utils.API2Request({
|
|
url: '/version',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
PVE.VersionInfo = response.result.data;
|
|
me.updateVersionInfo();
|
|
},
|
|
});
|
|
|
|
PVE.UIOptions.update();
|
|
|
|
Proxmox.Utils.API2Request({
|
|
url: '/cluster/sdn',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
PVE.SDNInfo = response.result.data;
|
|
},
|
|
failure: function(response) {
|
|
PVE.SDNInfo = null;
|
|
let ui = Ext.ComponentQuery.query('treelistitem[text="SDN"]')[0];
|
|
if (ui) {
|
|
ui.addCls('x-hidden-display');
|
|
}
|
|
},
|
|
});
|
|
|
|
Proxmox.Utils.API2Request({
|
|
url: '/access/domains',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
let [_username, realm] = Proxmox.Utils.parse_userid(Proxmox.UserName);
|
|
response.result.data.forEach((domain) => {
|
|
if (domain.realm === realm) {
|
|
let schema = PVE.Utils.authSchema[domain.type];
|
|
if (schema) {
|
|
me.query('#tfaitem')[0].setHidden(!schema.tfa);
|
|
me.query('#passworditem')[0].setHidden(!schema.pwchange);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
});
|
|
}
|
|
},
|
|
|
|
updateUserInfo: function() {
|
|
let me = this;
|
|
let ui = me.query('#userinfo')[0];
|
|
ui.setText(Ext.String.htmlEncode(Proxmox.UserName || ''));
|
|
ui.updateLayout();
|
|
},
|
|
|
|
updateVersionInfo: function() {
|
|
let me = this;
|
|
|
|
let ui = me.query('#versioninfo')[0];
|
|
|
|
if (PVE.VersionInfo) {
|
|
let version = PVE.VersionInfo.version;
|
|
ui.update('Virtual Environment ' + version);
|
|
} else {
|
|
ui.update('Virtual Environment');
|
|
}
|
|
ui.updateLayout();
|
|
},
|
|
|
|
initComponent: function() {
|
|
let me = this;
|
|
|
|
Ext.History.init();
|
|
|
|
let appState = Ext.create('PVE.StateProvider');
|
|
Ext.state.Manager.setProvider(appState);
|
|
|
|
let selview = Ext.create('PVE.form.ViewSelector', {
|
|
flex: 1,
|
|
padding: '0 5 0 0',
|
|
});
|
|
|
|
let rtree = Ext.createWidget('pveResourceTree', {
|
|
viewFilter: selview.getViewFilter(),
|
|
flex: 1,
|
|
selModel: {
|
|
selType: 'treemodel',
|
|
listeners: {
|
|
selectionchange: function(sm, selected) {
|
|
if (selected.length <= 0) {
|
|
return;
|
|
}
|
|
let treeNode = selected[0];
|
|
let treeTypeToClass = {
|
|
root: 'PVE.dc.Config',
|
|
node: 'PVE.node.Config',
|
|
qemu: 'PVE.qemu.Config',
|
|
lxc: 'pveLXCConfig',
|
|
storage: 'PVE.storage.Browser',
|
|
sdn: 'PVE.sdn.Browser',
|
|
pool: 'pvePoolConfig',
|
|
};
|
|
PVE.curSelectedNode = treeNode;
|
|
me.setContent({
|
|
xtype: treeTypeToClass[treeNode.data.type || 'root'] || 'pvePanelConfig',
|
|
showSearch: treeNode.data.id === 'root' || Ext.isDefined(treeNode.data.groupbyid),
|
|
pveSelNode: treeNode,
|
|
workspace: me,
|
|
viewFilter: selview.getViewFilter(),
|
|
});
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
selview.on('select', function(combo, records) {
|
|
if (records) {
|
|
let view = combo.getViewFilter();
|
|
rtree.setViewFilter(view);
|
|
}
|
|
});
|
|
|
|
let caps = appState.get('GuiCap');
|
|
|
|
let createVM = Ext.createWidget('button', {
|
|
pack: 'end',
|
|
margin: '3 5 0 0',
|
|
baseCls: 'x-btn',
|
|
iconCls: 'fa fa-desktop',
|
|
text: gettext("Create VM"),
|
|
disabled: !caps.vms['VM.Allocate'],
|
|
handler: function() {
|
|
let wiz = Ext.create('PVE.qemu.CreateWizard', {});
|
|
wiz.show();
|
|
},
|
|
});
|
|
|
|
let createCT = Ext.createWidget('button', {
|
|
pack: 'end',
|
|
margin: '3 5 0 0',
|
|
baseCls: 'x-btn',
|
|
iconCls: 'fa fa-cube',
|
|
text: gettext("Create CT"),
|
|
disabled: !caps.vms['VM.Allocate'],
|
|
handler: function() {
|
|
let wiz = Ext.create('PVE.lxc.CreateWizard', {});
|
|
wiz.show();
|
|
},
|
|
});
|
|
|
|
appState.on('statechange', function(sp, key, value) {
|
|
if (key === 'GuiCap' && value) {
|
|
caps = value;
|
|
createVM.setDisabled(!caps.vms['VM.Allocate']);
|
|
createCT.setDisabled(!caps.vms['VM.Allocate']);
|
|
}
|
|
});
|
|
|
|
Ext.apply(me, {
|
|
layout: { type: 'border' },
|
|
border: false,
|
|
items: [
|
|
{
|
|
region: 'north',
|
|
title: gettext('Header'), // for ARIA
|
|
header: false, // avoid rendering the title
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'middle',
|
|
},
|
|
baseCls: 'x-plain',
|
|
defaults: {
|
|
baseCls: 'x-plain',
|
|
},
|
|
border: false,
|
|
margin: '2 0 2 5',
|
|
items: [
|
|
{
|
|
xtype: 'proxmoxlogo',
|
|
},
|
|
{
|
|
minWidth: 150,
|
|
id: 'versioninfo',
|
|
html: 'Virtual Environment',
|
|
style: {
|
|
'font-size': '14px',
|
|
'line-height': '18px',
|
|
},
|
|
},
|
|
{
|
|
xtype: 'pveGlobalSearchField',
|
|
tree: rtree,
|
|
},
|
|
{
|
|
flex: 1,
|
|
},
|
|
{
|
|
xtype: 'proxmoxHelpButton',
|
|
hidden: false,
|
|
baseCls: 'x-btn',
|
|
iconCls: 'fa fa-book x-btn-icon-el-default-toolbar-small ',
|
|
listenToGlobalEvent: false,
|
|
onlineHelp: 'pve_documentation_index',
|
|
text: gettext('Documentation'),
|
|
margin: '0 5 0 0',
|
|
},
|
|
createVM,
|
|
createCT,
|
|
{
|
|
pack: 'end',
|
|
margin: '0 5 0 0',
|
|
id: 'userinfo',
|
|
xtype: 'button',
|
|
baseCls: 'x-btn',
|
|
style: {
|
|
// proxmox dark grey p light grey as border
|
|
backgroundColor: '#464d4d',
|
|
borderColor: '#ABBABA',
|
|
},
|
|
iconCls: 'fa fa-user',
|
|
menu: [
|
|
{
|
|
iconCls: 'fa fa-gear',
|
|
text: gettext('My Settings'),
|
|
handler: function() {
|
|
var win = Ext.create('PVE.window.Settings');
|
|
win.show();
|
|
},
|
|
},
|
|
{
|
|
text: gettext('Password'),
|
|
itemId: 'passworditem',
|
|
iconCls: 'fa fa-fw fa-key',
|
|
handler: function() {
|
|
var win = Ext.create('Proxmox.window.PasswordEdit', {
|
|
userid: Proxmox.UserName,
|
|
});
|
|
win.show();
|
|
},
|
|
},
|
|
{
|
|
text: 'TFA',
|
|
itemId: 'tfaitem',
|
|
iconCls: 'fa fa-fw fa-lock',
|
|
handler: function(btn, event, rec) {
|
|
Ext.state.Manager.getProvider().set('dctab', { value: 'tfa' }, true);
|
|
me.selectById('root');
|
|
},
|
|
},
|
|
{
|
|
iconCls: 'fa fa-paint-brush',
|
|
text: gettext('Theme'),
|
|
handler: function() {
|
|
Ext.create('Proxmox.window.ThemeEditWindow')
|
|
.show();
|
|
},
|
|
},
|
|
{
|
|
iconCls: 'fa fa-language',
|
|
text: gettext('Language'),
|
|
handler: function() {
|
|
Ext.create('Proxmox.window.LanguageEditWindow')
|
|
.show();
|
|
},
|
|
},
|
|
'-',
|
|
{
|
|
iconCls: 'fa fa-fw fa-sign-out',
|
|
text: gettext("Logout"),
|
|
handler: function() {
|
|
PVE.data.ResourceStore.loadData([], false);
|
|
me.showLogin();
|
|
me.setContent(null);
|
|
var rt = me.down('pveResourceTree');
|
|
rt.setDatacenterText(undefined);
|
|
rt.clearTree();
|
|
|
|
// empty the stores of the StatusPanel child items
|
|
var statusPanels = Ext.ComponentQuery.query('pveStatusPanel grid');
|
|
Ext.Array.forEach(statusPanels, function(comp) {
|
|
if (comp.getStore()) {
|
|
comp.getStore().loadData([], false);
|
|
}
|
|
});
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
region: 'center',
|
|
stateful: true,
|
|
stateId: 'pvecenter',
|
|
minWidth: 100,
|
|
minHeight: 100,
|
|
id: 'content',
|
|
xtype: 'container',
|
|
layout: { type: 'card' },
|
|
border: false,
|
|
margin: '0 5 0 0',
|
|
items: [],
|
|
},
|
|
{
|
|
region: 'west',
|
|
stateful: true,
|
|
stateId: 'pvewest',
|
|
itemId: 'west',
|
|
xtype: 'container',
|
|
border: false,
|
|
layout: { type: 'vbox', align: 'stretch' },
|
|
margin: '0 0 0 5',
|
|
split: true,
|
|
width: 300,
|
|
items: [
|
|
{
|
|
xtype: 'container',
|
|
layout: 'hbox',
|
|
padding: '0 0 5 0',
|
|
items: [
|
|
selview,
|
|
{
|
|
xtype: 'button',
|
|
cls: 'x-btn-default-toolbar-small',
|
|
iconCls: 'fa fa-fw fa-gear x-btn-icon-el-default-toolbar-small',
|
|
handler: () => {
|
|
Ext.create('PVE.window.TreeSettingsEdit', {
|
|
autoShow: true,
|
|
apiCallDone: () => PVE.UIOptions.fireUIConfigChanged(),
|
|
});
|
|
},
|
|
},
|
|
],
|
|
},
|
|
rtree,
|
|
],
|
|
listeners: {
|
|
resize: function(panel, width, height) {
|
|
var viewWidth = me.getSize().width;
|
|
if (width > viewWidth - 100) {
|
|
panel.setWidth(viewWidth - 100);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'pveStatusPanel',
|
|
stateful: true,
|
|
stateId: 'pvesouth',
|
|
itemId: 'south',
|
|
region: 'south',
|
|
margin: '0 5 5 5',
|
|
title: gettext('Logs'),
|
|
collapsible: true,
|
|
header: false,
|
|
height: 200,
|
|
split: true,
|
|
listeners: {
|
|
resize: function(panel, width, height) {
|
|
var viewHeight = me.getSize().height;
|
|
if (height > viewHeight - 150) {
|
|
panel.setHeight(viewHeight - 150);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
me.callParent();
|
|
|
|
me.updateUserInfo();
|
|
|
|
// on resize, center all modal windows
|
|
Ext.on('resize', function() {
|
|
let modalWindows = Ext.ComponentQuery.query('window[modal]');
|
|
if (modalWindows.length > 0) {
|
|
modalWindows.forEach(win => win.alignTo(me, 'c-c'));
|
|
}
|
|
});
|
|
},
|
|
});
|
|
|