Commit 9940ea01 authored by Dietmar Maurer's avatar Dietmar Maurer

add sencha touch based GUI for mobile devices

parent 364b1b58
......@@ -7,6 +7,7 @@ PERLSOURCE = \
API2Tools.pm \
API2Client.pm \
ExtJSIndex.pm \
TouchIndex.pm \
NoVncIndex.pm \
HTTPServer.pm \
APIDaemon.pm \
......
......@@ -25,6 +25,7 @@ use PVE::API2::Formatter::HTML;
use PVE::ExtJSIndex;
use PVE::NoVncIndex;
use PVE::TouchIndex;
my $pidfile = "/var/run/pveproxy/pveproxy.pid";
my $lockfile = "/var/lock/pveproxy.lck";
......@@ -177,6 +178,21 @@ if ($opt_debug || !($cpid = fork ())) {
exit (0);
sub is_phone {
my ($ua) = @_;
return 0 if !$ua;
return 1 if $ua =~ m/(iPhone|iPod|Windows Phone)/;
if ($ua =~ m/Mobile(\/|\s)/) {
return 1 if $ua =~ m/(BlackBerry|BB)/;
return 1 if ($ua =~ m/(Android)/) && ($ua !~ m/(Silk)/);
}
return 0;
}
# NOTE: Requests to those pages are not authenticated
# so we must be very careful here
......@@ -201,12 +217,22 @@ sub get_index {
$username = '' if !$username;
my $mobile = is_phone($r->header('User-Agent')) ? 1 : 0;
if (defined($args->{mobile})) {
$mobile = $args->{mobile} ? 1 : 0;
}
my $page;
if (defined($args->{console}) && $args->{novnc}) {
$page = PVE::NoVncIndex::get_index($lang, $username, $token, $args->{console});
} else {
$page = PVE::ExtJSIndex::get_index($lang, $username, $token, $args->{console});
if ($mobile) {
$page = PVE::TouchIndex::get_index($lang, $username, $token, $args->{console});
} else {
$page = PVE::ExtJSIndex::get_index($lang, $username, $token, $args->{console});
}
}
my $headers = HTTP::Headers->new(Content_Type => "text/html; charset=utf-8");
......
......@@ -17,5 +17,6 @@ WWWROOTDIR=${WWWBASEDIR}/root
WWWLOCALEDIR=${WWWBASEDIR}/locale
WWWIMAGEDIR=${WWWBASEDIR}/images
WWWEXT4DIR=${WWWBASEDIR}/ext4
WWWTOUCHDIR=${WWWBASEDIR}/touch
WWWCSSDIR=${WWWBASEDIR}/css
WWWJSDIR=${WWWBASEDIR}/js
SUBDIRS = images ext4 css manager bootstrap touch
SUBDIRS = images ext4 css manager bootstrap touch mobile
all: ${SUBDIRS}
......
......@@ -2,6 +2,7 @@ include ../../defines.mk
JSSRC= \
Utils.js \
Toolkit.js \
Parser.js \
StateProvider.js \
button/Button.js \
......
// ExtJS related things
PVE.Utils.toolkit = 'extjs',
// do not send '_dc' parameter
Ext.Ajax.disableCaching = false;
// custom Vtypes
Ext.apply(Ext.form.field.VTypes, {
IPAddress: function(v) {
return IP4_match.test(v);
},
IPAddressText: gettext('Example') + ': 192.168.1.1',
IPAddressMask: /[\d\.]/i,
IP64Address: function(v) {
return IP64_match.test(v);
},
IP64AddressText: gettext('Example') + ': 192.168.1.1 2001:DB8::42',
IP64AddressMask: /[A-Fa-f0-9\.:]/,
MacAddress: function(v) {
return (/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/).test(v);
},
MacAddressMask: /[a-fA-F0-9:]/,
MacAddressText: gettext('Example') + ': 01:23:45:67:89:ab',
BridgeName: function(v) {
return (/^vmbr\d{1,4}$/).test(v);
},
BridgeNameText: gettext('Format') + ': vmbr<b>N</b>, where 0 <= <b>N</b> <= 9999',
BondName: function(v) {
return (/^bond\d{1,4}$/).test(v);
},
BondNameText: gettext('Format') + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999',
InterfaceName: function(v) {
return (/^[a-z][a-z0-9_]{1,20}$/).test(v);
},
InterfaceNameText: gettext('Format') + ': [a-z][a-z0-9_]{1,20}',
QemuStartDate: function(v) {
return (/^(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)$/).test(v);
},
QemuStartDateText: gettext('Format') + ': "now" or "2006-06-17T16:01:21" or "2006-06-17"',
StorageId: function(v) {
return (/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i).test(v);
},
StorageIdText: gettext("Allowed characters") + ": 'a-z', '0-9', '-', '_', '.'",
HttpProxy: function(v) {
return (/^http:\/\/.*$/).test(v);
},
HttpProxyText: gettext('Example') + ": http://username:password&#64;host:port/",
DnsName: function(v) {
return (/^(([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)\.)*([A-Za-z0-9]([A-Za-z0-9\-]*[A-Za-z0-9])?)$/).test(v);
},
DnsNameText: gettext('This is not a valid DNS name')
});
// we dont want that a displayfield set the form dirty flag!
Ext.override(Ext.form.field.Display, {
isDirty: function() { return false; }
});
// hack: ExtJS does not display the correct value if we
// call setValue while the store is loading, so we need
// to call it again after loading
Ext.override(Ext.form.field.ComboBox, {
onLoad: function() {
this.setValue(this.value, false);
this.callOverridden(arguments);
}
});
Ext.define('Ext.ux.IFrame', {
extend: 'Ext.Component',
alias: 'widget.uxiframe',
loadMask: 'Loading...',
src: 'about:blank',
renderTpl: [
'<iframe src="{src}" name="{frameName}" width="100%" height="100%" frameborder="0"></iframe>'
],
initComponent: function () {
this.callParent();
this.frameName = this.frameName || this.id + '-frame';
this.addEvents(
'beforeload',
'load'
);
Ext.apply(this.renderSelectors, {
iframeEl: 'iframe'
});
},
initEvents : function() {
var me = this;
me.callParent();
me.iframeEl.on('load', me.onLoad, me);
},
initRenderData: function() {
return Ext.apply(this.callParent(), {
src: this.src,
frameName: this.frameName
});
},
getBody: function() {
var doc = this.getDoc();
return doc.body || doc.documentElement;
},
getDoc: function() {
try {
return this.getWin().document;
} catch (ex) {
return null;
}
},
getWin: function() {
var me = this,
name = me.frameName,
win = Ext.isIE
? me.iframeEl.dom.contentWindow
: window.frames[name];
return win;
},
getFrame: function() {
var me = this;
return me.iframeEl.dom;
},
beforeDestroy: function () {
this.cleanupListeners(true);
this.callParent();
},
cleanupListeners: function(destroying){
var doc, prop;
if (this.rendered) {
try {
doc = this.getDoc();
if (doc) {
Ext.EventManager.removeAll(doc);
if (destroying) {
for (prop in doc) {
if (doc.hasOwnProperty && doc.hasOwnProperty(prop)) {
delete doc[prop];
}
}
}
}
} catch(e) { }
}
},
onLoad: function() {
var me = this,
doc = me.getDoc(),
fn = me.onRelayedEvent;
if (doc) {
try {
Ext.EventManager.removeAll(doc);
// These events need to be relayed from the inner document (where they stop
// bubbling) up to the outer document. This has to be done at the DOM level so
// the event reaches listeners on elements like the document body. The effected
// mechanisms that depend on this bubbling behavior are listed to the right
// of the event.
Ext.EventManager.on(doc, {
mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
mousemove: fn, // window resize drag detection
mouseup: fn, // window resize termination
click: fn, // not sure, but just to be safe
dblclick: fn, // not sure again
scope: me
});
} catch(e) {
// cannot do this xss
}
// We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
Ext.EventManager.on(this.getWin(), 'beforeunload', me.cleanupListeners, me);
this.el.unmask();
this.fireEvent('load', this);
} else if(me.src && me.src != '') {
this.el.unmask();
this.fireEvent('error', this);
}
},
load: function (src) {
var me = this,
text = me.loadMask,
frame = me.getFrame();
if (me.fireEvent('beforeload', me, src) !== false) {
if (text && me.el) {
me.el.mask(text);
}
frame.src = me.src = (src || me.src);
}
}
});
......@@ -13,9 +13,6 @@ Ext.Ajax.defaultHeaders = {
'Accept': 'application/json'
};
// do not send '_dc' parameter
Ext.Ajax.disableCaching = false;
Ext.Ajax.on('beforerequest', function(conn, options) {
if (PVE.CSRFPreventionToken) {
if (!options.headers) {
......@@ -47,231 +44,12 @@ var IPV6_REGEXP = "(?:" +
var IP64_match = new RegExp("^(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + ")$");
// custom Vtypes
Ext.apply(Ext.form.field.VTypes, {
IPAddress: function(v) {
return IP4_match.test(v);
},
IPAddressText: gettext('Example') + ': 192.168.1.1',
IPAddressMask: /[\d\.]/i,
IP64Address: function(v) {
return IP64_match.test(v);
},
IP64AddressText: gettext('Example') + ': 192.168.1.1 2001:DB8::42',
IP64AddressMask: /[A-Fa-f0-9\.:]/,
MacAddress: function(v) {
return (/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/).test(v);
},
MacAddressMask: /[a-fA-F0-9:]/,
MacAddressText: gettext('Example') + ': 01:23:45:67:89:ab',
BridgeName: function(v) {
return (/^vmbr\d{1,4}$/).test(v);
},
BridgeNameText: gettext('Format') + ': vmbr<b>N</b>, where 0 <= <b>N</b> <= 9999',
BondName: function(v) {
return (/^bond\d{1,4}$/).test(v);
},
BondNameText: gettext('Format') + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999',
InterfaceName: function(v) {
return (/^[a-z][a-z0-9_]{1,20}$/).test(v);
},
InterfaceNameText: gettext('Format') + ': [a-z][a-z0-9_]{1,20}',
QemuStartDate: function(v) {
return (/^(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)$/).test(v);
},
QemuStartDateText: gettext('Format') + ': "now" or "2006-06-17T16:01:21" or "2006-06-17"',
StorageId: function(v) {
return (/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i).test(v);
},
StorageIdText: gettext("Allowed characters") + ": 'a-z', '0-9', '-', '_', '.'",
HttpProxy: function(v) {
return (/^http:\/\/.*$/).test(v);
},
HttpProxyText: gettext('Example') + ": http://username:password&#64;host:port/",
DnsName: function(v) {
return (/^(([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)\.)*([A-Za-z0-9]([A-Za-z0-9\-]*[A-Za-z0-9])?)$/).test(v);
},
DnsNameText: gettext('This is not a valid DNS name')
});
// we dont want that a displayfield set the form dirty flag!
Ext.override(Ext.form.field.Display, {
isDirty: function() { return false; }
});
// hack: ExtJS does not display the correct value if we
// call setValue while the store is loading, so we need
// to call it again after loading
Ext.override(Ext.form.field.ComboBox, {
onLoad: function() {
this.setValue(this.value, false);
this.callOverridden(arguments);
}
});
Ext.define('Ext.ux.IFrame', {
extend: 'Ext.Component',
alias: 'widget.uxiframe',
loadMask: 'Loading...',
src: 'about:blank',
renderTpl: [
'<iframe src="{src}" name="{frameName}" width="100%" height="100%" frameborder="0"></iframe>'
],
initComponent: function () {
this.callParent();
this.frameName = this.frameName || this.id + '-frame';
this.addEvents(
'beforeload',
'load'
);
Ext.apply(this.renderSelectors, {
iframeEl: 'iframe'
});
},
initEvents : function() {
var me = this;
me.callParent();
me.iframeEl.on('load', me.onLoad, me);
},
initRenderData: function() {
return Ext.apply(this.callParent(), {
src: this.src,
frameName: this.frameName
});
},
getBody: function() {
var doc = this.getDoc();
return doc.body || doc.documentElement;
},
getDoc: function() {
try {
return this.getWin().document;
} catch (ex) {
return null;
}
},
getWin: function() {
var me = this,
name = me.frameName,
win = Ext.isIE
? me.iframeEl.dom.contentWindow
: window.frames[name];
return win;
},
getFrame: function() {
var me = this;
return me.iframeEl.dom;
},
beforeDestroy: function () {
this.cleanupListeners(true);
this.callParent();
},
cleanupListeners: function(destroying){
var doc, prop;
if (this.rendered) {
try {
doc = this.getDoc();
if (doc) {
Ext.EventManager.removeAll(doc);
if (destroying) {
for (prop in doc) {
if (doc.hasOwnProperty && doc.hasOwnProperty(prop)) {
delete doc[prop];
}
}
}
}
} catch(e) { }
}
},
onLoad: function() {
var me = this,
doc = me.getDoc(),
fn = me.onRelayedEvent;
if (doc) {
try {
Ext.EventManager.removeAll(doc);
// These events need to be relayed from the inner document (where they stop
// bubbling) up to the outer document. This has to be done at the DOM level so
// the event reaches listeners on elements like the document body. The effected
// mechanisms that depend on this bubbling behavior are listed to the right
// of the event.
Ext.EventManager.on(doc, {
mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
mousemove: fn, // window resize drag detection
mouseup: fn, // window resize termination
click: fn, // not sure, but just to be safe
dblclick: fn, // not sure again
scope: me
});
} catch(e) {
// cannot do this xss
}
// We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
Ext.EventManager.on(this.getWin(), 'beforeunload', me.cleanupListeners, me);
this.el.unmask();
this.fireEvent('load', this);
} else if(me.src && me.src != '') {
this.el.unmask();
this.fireEvent('error', this);
}
},
load: function (src) {
var me = this,
text = me.loadMask,
frame = me.getFrame();
if (me.fireEvent('beforeload', me, src) !== false) {
if (text && me.el) {
me.el.mask(text);
}
frame.src = me.src = (src || me.src);
}
}
});
Ext.define('PVE.Utils', { statics: {
// this class only contains static functions
toolkit: undefined, // (extjs|touch), set inside Toolkit.js
log_severity_hash: {
0: "panic",
1: "alert",
......@@ -592,7 +370,11 @@ Ext.define('PVE.Utils', { statics: {
Ext.apply(newopts, {
success: function(response, options) {
if (options.waitMsgTarget) {
options.waitMsgTarget.setLoading(false);
if (PVE.Utils.toolkit === 'touch') {
options.waitMsgTarget.setMasked(false);
} else {
options.waitMsgTarget.setLoading(false);
}
}
var result = Ext.decode(response.responseText);
response.result = result;
......@@ -607,7 +389,11 @@ Ext.define('PVE.Utils', { statics: {
},
failure: function(response, options) {
if (options.waitMsgTarget) {
options.waitMsgTarget.setLoading(false);
if (PVE.Utils.toolkit === 'touch') {
options.waitMsgTarget.setMasked(false);
} else {
options.waitMsgTarget.setLoading(false);
}
}
response.result = {};
try {
......@@ -632,8 +418,12 @@ Ext.define('PVE.Utils', { statics: {
var target = newopts.waitMsgTarget;
if (target) {
// Note: ExtJS bug - this does not work when component is not rendered
target.setLoading(newopts.waitMsg);
if (PVE.Utils.toolkit === 'touch') {
target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg} );
} else {
// Note: ExtJS bug - this does not work when component is not rendered
target.setLoading(newopts.waitMsg);
}
}
Ext.Ajax.request(newopts);
},
......
Ext.define('PVE.ClusterInfo', {
extend: 'Ext.Component',
alias: 'widget.pveClusterInfo',
config: {
style: 'background-color: white;',
styleHtmlContent: true,
tpl: [
'<table style="margin-bottom:0px;">',
'<tr><td>Node:</td><td><b>{local_node}</large></b></tr>',
'<tpl if="cluster_name">',
'<tr><td>Cluster:</td><td>{cluster_name}</td></tr>',
'<tr><td>Members:</td><td>{nodes}</td></tr>',
'<tr><td>Quorate:</td><td>{quorate}</td></tr>',
'</tpl>',
'<tr><td>Version:</td><td>{version}</td></tr>',
'</table>',
]
},
});
Ext.define('PVE.Datacenter', {
extend: 'PVE.Page',
alias: 'widget.pveDatacenter',
statics: {
pathMatch: function(loc) {
if (loc === '') {
return [''];
}
}
},
config: {
appUrl: '',
items: [
{
xtype: 'titlebar',
docked: 'top',
title: gettext('Datacenter'),
items: [
{
xtype: 'button',
align: 'right',
iconCls: 'refresh',
handler: function() {
this.up('pvePage').reload();
}
},
{
xtype: 'pveMenuButton',
align: 'right',
pveStdMenu: true,
menuItems: [
{
text: gettext('Tasks'),
handler: function() {
PVE.Workspace.gotoPage('tasks');
}
}
]
}
]
},
{
xtype: 'pveClusterInfo'
},
{
xtype: 'component',
cls: 'dark',
padding: 5,
html: gettext('Nodes')
},
{
xtype: 'list',
flex: 1,
disableSelection: true,
listeners: {
itemsingletap: function(list, index, target, record) {
PVE.Workspace.gotoPage('nodes/' + record.get('name'));
}
},
itemTpl: '{name}' +
'<br><small>Online: {[PVE.Utils.format_boolean(values.state)]}</small>' +
'<br><small>Support: {[PVE.Utils.render_support_level(values.level)]}</small>'
}
]
},
reload: function() {
var me = this;
var ci = me.down('pveClusterInfo');
me.summary = {};
PVE.Utils.API2Request({
url: '/version',
method: 'GET',
success: function(response) {
var d = response.result.data;
me.summary.version = d.version + '-' + d.release + '/' + d.repoid;
ci.setData(me.summary);
}
});
var list = me.down('list');
PVE.Utils.API2Request({
url: '/cluster/status',
method: 'GET',
success: function(response) {
var d = response.result.data;
list.setData(d.filter(function(el) { return (el.type === "node"); }));
var node_count = 0;
d.forEach(function(el) {
if (el.type === "node") {
node_count++;
if (el.local) {
me.summary.local_node = el.name;
}
} else if (el.type === "cluster") {
me.summary.cluster_name = el.name;
} else if (el.type === "quorum") {
me.summary.quorate = el.quorate;
}
});
me.summary.nodes = node_count;
ci.setData(me.summary);
}
});
},
initialize: function() {
var me = this;
me.reload();
}
});
Ext.define('PVE.Login', {
extend: 'Ext.form.Panel',
alias: "widget.pveLogin",
config: {
title: 'Login',
padding: 10,
appUrl: 'login',
items: [
{
xtype: 'image',
src: '/pve2/images/proxmox_logo.png',
height: 30,
width: 209
},
{
xtype: 'fieldset',
title: 'Proxmox VE Login',
items: [
{
xtype: 'textfield',
placeHolder: gettext('User name'),
itemId: 'userNameTextField',
name: 'username',
required: true
},
{
xtype: 'passwordfield',
placeHolder: gettext('Password'),
itemId: 'passwordTextField',
name: 'password',
required: true
},
{
xtype: 'pveRealmSelector',
itemId: 'realmSelectorField',
name: 'realm'
}
]
},
{
xtype: 'label',
html: 'Login failed. Please enter the correct credentials.',
itemId: 'signInFailedLabel',
hidden: true,
hideAnimation: 'fadeOut',
showAnimation: 'fadeIn',
style: 'color:#990000;margin:5px 0px;'
},
{
xtype: 'button',
itemId: 'logInButton',
ui: 'action',
text: 'Log In',
handler: function() {
var form = this.up('formpanel');
var usernameField = form.down('#userNameTextField'),
passwordField = form.down('#passwordTextField'),
realmField = form.down('#realmSelectorField'),
label = form.down('#signInFailedLabel');
label.hide();
var username = usernameField.getValue();
var password = passwordField.getValue();
var realm = realmField.getValue();
PVE.Utils.API2Request({
url: '/access/ticket',
method: 'POST',
waitMsgTarget: form,
params: { username: username, password: password, realm: realm },
failure: function(response, options) {
label.show();
},
success: function(response, options) {
usernameField.setValue('');
passwordField.setValue('');
PVE.Workspace.updateLoginData(response.result.data);
}
});
}
}
]
}
});
include ../../defines.mk
JSSRC= \
../ext4/extjs/src/util/Cookies.js \
../manager/Utils.js \
../manager/Parser.js \
Toolkit.js \
PVEProxy.js \
MenuButton.js \
Workspace.js \
NodeSelector.js \
RealmSelector.js \
Login.js \
TaskList.js \
TaskViewer.js \
Datacenter.js \
NodeSummary.js \
Migrate.js \
QemuSummary.js \
OpenVzSummary.js \
app.js
all: pvemanager-mobile.js
pvemanager-mobile.js: ${JSSRC}
cat ${JSSRC} >$@.tmp
mv $@.tmp $@
.PHONY: install
install: pvemanager-mobile.js
install -d ${WWWTOUCHDIR}
install -m 0644 pvemanager-mobile.js ${WWWTOUCHDIR}
chown -R www-data:www-data ${WWWTOUCHDIR}
.PHONY: distclean
distclean: clean
.PHONY: clean
clean:
rm -rf *~ pvemanager-mobile.js
Ext.define('PVE.MenuButton', {
extend: 'Ext.Button',
alias: 'widget.pveMenuButton',
menuPanel: undefined,
createMenuPanel: function() {
var me = this;
var data = me.getMenuItems() || [];
var addHide = function (fn) {
return function () {
if (me.menuPanel) {
me.menuPanel.hide();
Ext.Viewport.remove(me.menuPanel);
me.menuPanel.destroy();
me.menuPanel = undefined;
}
return fn.apply(this, arguments);
};
};
var items = [];
data.forEach(function(el) {
items.push(Ext.apply(el, {
xtype: 'button',
ui: 'plain',
handler: addHide(el.handler)
}));
});
if (me.getPveStdMenu()) {
items.push({
xtype: 'button',
ui: 'plain',
text: gettext('Logout'),
handler: addHide(function() {
PVE.Workspace.showLogin();
})
});
}
me.menuPanel = Ext.create('Ext.Panel', {
modal: true,
hideOnMaskTap: true,
visible: false,
minWidth: 200,
layout: {
type:'vbox',
align: 'stretch'
},
items: items
});
PVE.Workspace.history.on('change', function() {
if (me.menuPanel) {
Ext.Viewport.remove(me.menuPanel);
me.menuPanel.destroy();
me.menuPanel = undefined;
}
});
},
config: {
menuItems: undefined,
pveStdMenu: false, // add LOGOUT
handler: function() {
var me = this;
if (!me.menuPanel) {
me.createMenuPanel();
}
me.menuPanel.showBy(me, 'tr-bc?');
}
},
initialize: function() {
var me = this;
this.callParent();
if (me.getPveStdMenu()) {
me.setIconCls('more');
}
}
});
\ No newline at end of file
Ext.define('PVE.MigrateBase', {
extend: 'PVE.Page',
nodename: undefined,
vmid: undefined,
vmtype: undefined, // qemu or openvz
config: {
items: [
{
xtype: 'titlebar',
docked: 'top',
items: [
{
xtype: 'pveMenuButton',
align: 'right',
pveStdMenu: true
}
]
},
{
xtype: 'formpanel',
flex: 1,
padding: 10,
items: [
{
xtype: 'fieldset',
items: [
{
xtype: 'pveNodeSelector',
placeHolder: gettext('Target node'),
name: 'target',
required: true,
},
{
xtype: 'checkboxfield',
name : 'online',
checked: true,
label: gettext('Online')
}
]
},
{
xtype: 'button',
itemId: 'migrate',
ui: 'action',
text: gettext('Migrate')
}
]
}
]
},
initialize: function() {
var me = this;
var btn = me.down('#migrate');
btn.setHandler(function() {
var form = this.up('formpanel');
var values = form.getValues();
if (!values.target) {
Ext.Msg.alert('Error', 'Please select a target node');
return;
}
PVE.Utils.API2Request({
params: { target: values.target, online: values.online ? 1 : 0 },
url: '/nodes/' + me.nodename + '/' + me.vmtype + '/' + me.vmid + "/migrate",
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var page = 'nodes/' + me.nodename + '/tasks/' + upid;
PVE.Workspace.gotoPage(page);
}
});
});
}
});
Ext.define('PVE.QemuMigrate', {
extend: 'PVE.MigrateBase',
vmtype: 'qemu',
statics: {
pathMatch: function(loc) {
return loc.match(/^nodes\/([^\s\/]+)\/qemu\/(\d+)\/migrate$/);
}
},
initialize: function() {
var me = this;
var match = me.self.pathMatch(me.getAppUrl());
if (!match) {
throw "pathMatch failed";
}
me.nodename = match[1];
me.vmid = match[2];
me.down('titlebar').setTitle(gettext('Migrate') + ': VM ' + me.vmid);
this.callParent();
}
});
Ext.define('PVE.OpenVzMigrate', {
extend: 'PVE.MigrateBase',
vmtype: 'openvz',
statics: {
pathMatch: function(loc) {
return loc.match(/^nodes\/([^\s\/]+)\/openvz\/(\d+)\/migrate$/);
}
},
initialize: function() {
var me = this;
var match = me.self.pathMatch(me.getAppUrl());
if (!match) {
throw "pathMatch failed";
}
me.nodename = match[1];
me.vmid = match[2];
me.down('titlebar').setTitle(gettext('Migrate') + ': CT ' + me.vmid);
this.callParent();
}
});
Ext.define('PVE.form.NodeSelector', {
extend: 'Ext.field.Select',
alias: ['widget.pveNodeSelector'],
config: {
autoSelect: false,
valueField: 'node',
displayField: 'node',
store: {
fields: [ 'node', 'cpu', 'maxcpu', 'mem', 'maxmem', 'uptime' ],
autoLoad: true,
proxy: {
type: 'pve',
url: '/api2/json/nodes'
},
sorters: [
{
property : 'node',
direction: 'ASC'
}
]
},
value: ''
}
});
Ext.define('PVE.NodeInfo', {
extend: 'Ext.Component',
alias: 'widget.pveNodeInfo',
config: {
style: 'background-color: white;',
styleHtmlContent: true,
data: [],
tpl: [
'<table style="margin-bottom:0px;">',
'<tr><td>Version:</td><td>{pveversion}</td></tr>',
'<tr><td>Memory:</td><td>{[this.meminfo(values)]}</td></tr>',
'<tr><td>CPU:</td><td>{[this.cpuinfo(values)]}</td></tr>',
'<tr><td>Uptime:</td><td>{[PVE.Utils.format_duration_long(values.uptime)]}</td></tr>',
'</table>',
{
meminfo: function(values) {
var d = values.memory;
if (!d) {
return '-';
}
return PVE.Utils.format_size(d.used || 0) + " of " + PVE.Utils.format_size(d.total);
},
cpuinfo: function(values) {
if (!values.cpuinfo) {
return '-';
}
var per = values.cpu * 100;
return per.toFixed(2) + "% (" + values.cpuinfo.cpus + " CPUs)";
}
}
]
},
});
Ext.define('PVE.NodeSummary', {
extend: 'PVE.Page',
alias: 'widget.pveNodeSummary',
statics: {
pathMatch: function(loc) {
return loc.match(/^nodes\/([^\s\/]+)$/);
}
},
nodename: undefined,
config: {
items: [
{
xtype: 'titlebar',
docked: 'top',
items: [
{
xtype: 'button',
align: 'right',
iconCls: 'refresh',
handler: function() {
this.up('pvePage').reload();
}
},
{
xtype: 'pveMenuButton',
align: 'right',
pveStdMenu: true
}
]
},
{
xtype: 'pveNodeInfo'
},
{
xtype: 'component',
cls: 'dark',
padding: 5,
html: gettext('Virtual machines')
},
{
xtype: 'list',
flex: 1,
disableSelection: true,
listeners: {
itemsingletap: function(list, index, target, record) {
PVE.Workspace.gotoPage('nodes/' + record.get('nodename') + '/' +
record.get('type') + '/' + record.get('vmid'));
}
},
grouped: true,
itemTpl: [
'{name}<br>',
'<small>',
'id: {vmid} ',
'<tpl if="uptime">',
'cpu: {[this.cpuinfo(values)]} ',
'mem: {[this.meminfo(values)]} ',
'</tpl>',
'</small>',
{
meminfo: function(values) {
if (!values.uptime) {
return '-';
}
return PVE.Utils.format_size(values.mem)
},
cpuinfo: function(values) {
if (!values.uptime) {
return '-';
}
return (values.cpu*100).toFixed(1);
}
}
]
}
]
},
reload: function() {
var me = this;
var ni = me.down('pveNodeInfo');
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/status',
method: 'GET',
success: function(response) {
var d = response.result.data;
if (d.pveversion) {
d.pveversion = d.pveversion.replace(/pve\-manager\//, '');
}
ni.setData(d);
}
});
var list = me.down('list');
list.setMasked(false);
var error_handler = function(response) {
list.setMasked({ xtype: 'loadmask', message: response.htmlStatus} );
};
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/openvz',
method: 'GET',
success: function(response) {
var d = response.result.data;
d.nodename = me.nodename;
d.forEach(function(el) { el.type = 'openvz'; el.nodename = me.nodename });
me.store.each(function(rec) {
if (rec.get('type') === 'openvz') {
rec.destroy();
}
});
me.store.add(d);
},
failure: error_handler
});
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/qemu',
method: 'GET',
success: function(response) {
var d = response.result.data;
d.forEach(function(el) { el.type = 'qemu'; el.nodename = me.nodename });
me.store.each(function(rec) {
if (rec.get('type') === 'qemu') {
rec.destroy();
}
});
me.store.add(d);
},
failure: error_handler
});
},
initialize: function() {
var me = this;
var match = me.self.pathMatch(me.getAppUrl());
if (!match) {
throw "pathMatch failed";
}
me.nodename = match[1];
me.down('titlebar').setTitle(gettext('Node') + ': ' + me.nodename);
me.down('pveMenuButton').setMenuItems([
{
text: gettext('Tasks'),
handler: function() {
PVE.Workspace.gotoPage('nodes/' + me.nodename + '/tasks');
}
},
]);
me.store = Ext.create('Ext.data.Store', {
fields: [ 'name', 'vmid', 'nodename', 'type', 'memory', 'uptime', 'mem', 'maxmem', 'cpu', 'cpus'],
sorters: ['vmid'],
grouper: {
groupFn: function(record) {
return record.get('type');
}
},
});
var list = me.down('list');
list.setStore(me.store);
me.reload();
this.callParent();
}
});
Ext.define('PVE.OpenVzSummary', {
extend: 'PVE.Page',
alias: 'widget.pveOpenVzSummary',
statics: {
pathMatch: function(loc) {
return loc.match(/^nodes\/([^\s\/]+)\/openvz\/(\d+)$/);
}
},
nodename: undefined,
vmid: undefined,
vm_command: function(cmd, params) {
var me = this;
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/openvz/' + me.vmid + '/status/' + cmd,
method: 'POST',
success: function(response, opts) {
var upid = response.result.data;
var page = 'nodes/' + me.nodename + '/tasks/' + upid;
PVE.Workspace.gotoPage(page);
},
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
},
config: {
items: [
{
xtype: 'titlebar',
docked: 'top',
items: [
{
xtype: 'button',
align: 'right',
iconCls: 'refresh',
handler: function() {
this.up('pvePage').reload();
}
},
{
xtype: 'pveMenuButton',
align: 'right',
pveStdMenu: true,
}
]
},
{
xtype: 'component',
itemId: 'ctstatus',
styleHtmlContent: true,
style: 'background-color:white;',
tpl: [
'<table style="margin-bottom:0px;">',
'<tr><td>Status:</td><td>{status}</td></tr>',
'<tr><td>Memory:</td><td>{[this.meminfo(values)]}</td></tr>',
'<tr><td>CPU:</td><td>{[this.cpuinfo(values)]}</td></tr>',
'<tr><td>Uptime:</td><td>{[PVE.Utils.format_duration_long(values.uptime)]}</td></tr>',
'</table>',
{
meminfo: function(values) {
if (!Ext.isDefined(values.mem)) {
return '-';
}
return PVE.Utils.format_size(values.mem || 0) + " of " +
PVE.Utils.format_size(values.maxmem);
},
cpuinfo: function(values) {
if (!Ext.isDefined(values.cpu)) {
return '-';
}
var per = values.cpu * 100;
return per.toFixed(2) + "% (" + values.cpus + " CPUs)";
}
}
]
},
{
xtype: 'component',
padding: 5,
html: gettext('Configuration')
},
{
xtype: 'container',
scrollable: 'both',
flex: 1,
styleHtmlContent: true,
itemId: 'ctconfig',
style: 'background-color:white;white-space:pre;',
tpl: [
'<table style="margin-bottom:0px;">',
'<tpl for=".">',
'<tr><td>{key}</td><td>{value}</td></tr>',
'</tpl>',
'</table>'
]
}
]
},
reload: function() {
var me = this;
var cti = me.down('#ctstatus');
var error_handler = function(response) {
me.setMasked({ xtype: 'loadmask', message: response.htmlStatus} );
};
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/openvz/' + me.vmid + '/status/current',
method: 'GET',
success: function(response) {
var d = response.result.data;
cti.setData(d);
},
failure: error_handler
});
var ctc = me.down('#ctconfig');
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/openvz/' + me.vmid + '/config',
method: 'GET',
success: function(response) {
var d = response.result.data;
var names = ['hostname', 'memory', 'swap', 'cpus', 'ostemplate',
'ip_address', 'nameserver', 'searchdomain', 'netif'];
var kv = PVE.Workspace.obj_to_kv(d, names);
ctc.setData(kv);
},
failure: error_handler
});
},
initialize: function() {
var me = this;
var match = me.self.pathMatch(me.getAppUrl());
if (!match) {
throw "pathMatch failed";
}
me.nodename = match[1];
me.vmid = match[2];
me.down('titlebar').setTitle('CT: ' + me.vmid);
me.down('pveMenuButton').setMenuItems([
{
text: gettext('Start'),
handler: function() {
me.vm_command("start", {});
}
},
{
text: gettext('Shutdown'),
handler: function() {
me.vm_command("shutdown", {});
}
},
{
text: gettext('Stop'),
handler: function() {
me.vm_command("stop", {});
}
},
{
text: gettext('Migrate'),
handler: function() {
PVE.Workspace.gotoPage('nodes/' + me.nodename + '/openvz/' + me.vmid + '/migrate');
}
},
{
text: gettext('Console'),
handler: function() {
PVE.Utils.openConsoleWindow('html5', 'openvz', me.vmid, me.nodename);
}
},
{
text: gettext('Spice'),
handler: function() {
PVE.Utils.openConsoleWindow('vv', 'openvz', me.vmid, me.nodename);
}
}
]);
me.reload();
this.callParent();
}
});
Ext.define('PVE.RestProxy', {
extend: 'Ext.data.RestProxy',
alias : 'proxy.pve',
constructor: function(config) {
var me = this;
config = config || {};
Ext.applyIf(config, {
pageParam : null,
startParam: null,
limitParam: null,
groupParam: null,
sortParam: null,
filterParam: null,
noCache : false,
reader: {
type: 'json',
rootProperty: config.root || 'data'
},
afterRequest: function(request, success) {
me.fireEvent('afterload', me, request, success);
return;
}
});
me.callParent([config]);
}
});
Ext.define('pve-domains', {
extend: "Ext.data.Model",
config: {
fields: [ 'realm', 'type', 'comment', 'default', 'tfa',
{
name: 'descr',
// Note: We use this in the RealmComboBox.js
// (see Bug #125)
convert: function(value, record) {
var info = record.data;
var text;
if (value) {
return value;
}
// return realm if there is no comment
text = info.comment || info.realm;
if (info.tfa) {
text += " (+ " + info.tfa + ")";
}
return text;
}
}
],
proxy: {
type: 'pve',
url: "/api2/json/access/domains"
}
}
});
Ext.define('pve-tasks', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'starttime', type : 'date', dateFormat: 'timestamp' },
{ name: 'endtime', type : 'date', dateFormat: 'timestamp' },
{ name: 'pid', type: 'int' },
'node', 'upid', 'user', 'status', 'type', 'id'
],
idProperty: 'upid'
}
});
Ext.define('PVE.QemuSummary', {
extend: 'PVE.Page',
alias: 'widget.pveQemuSummary',
statics: {
pathMatch: function(loc) {
return loc.match(/^nodes\/([^\s\/]+)\/qemu\/(\d+)$/);
}
},
nodename: undefined,
vmid: undefined,
vm_command: function(cmd, params) {
var me = this;
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/status/' + cmd,
method: 'POST',
success: function(response, opts) {
var upid = response.result.data;
var page = 'nodes/' + me.nodename + '/tasks/' + upid;
PVE.Workspace.gotoPage(page);
},
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
},
config: {
items: [
{
xtype: 'titlebar',
docked: 'top',
items: [
{
xtype: 'button',
align: 'right',
iconCls: 'refresh',
handler: function() {
this.up('pvePage').reload();
}
},
{
xtype: 'pveMenuButton',
align: 'right',
pveStdMenu: true
}
]
},
{
xtype: 'component',
itemId: 'vmstatus',
styleHtmlContent: true,
style: 'background-color:white;',
tpl: [
'<table style="margin-bottom:0px;">',
'<tr><td>Status:</td><td>{qmpstatus}</td></tr>',
'<tr><td>Memory:</td><td>{[this.meminfo(values)]}</td></tr>',
'<tr><td>CPU:</td><td>{[this.cpuinfo(values)]}</td></tr>',
'<tr><td>Uptime:</td><td>{[PVE.Utils.format_duration_long(values.uptime)]}</td></tr>',
'</table>',
{
meminfo: function(values) {
if (!Ext.isDefined(values.mem)) {
return '-';
}
return PVE.Utils.format_size(values.mem || 0) + " of " +
PVE.Utils.format_size(values.maxmem);
},
cpuinfo: function(values) {
if (!Ext.isDefined(values.cpu)) {
return '-';
}
var per = values.cpu * 100;
return per.toFixed(2) + "% (" + values.cpus + " CPUs)";
}
}
]
},
{
xtype: 'component',
cls: 'dark',
padding: 5,
html: gettext('Configuration')
},
{
xtype: 'container',
scrollable: 'both',
flex: 1,
styleHtmlContent: true,
itemId: 'vmconfig',
style: 'background-color:white;white-space:pre',
tpl: [
'<table style="margin-bottom:0px;">',
'<tpl for=".">',
'<tr><td>{key}</td><td>{value}</td></tr>',
'</tpl>',
'</table>'
]
}
]
},
reload: function() {
var me = this;
var vmi = me.down('#vmstatus');
var error_handler = function(response) {
me.setMasked({ xtype: 'loadmask', message: response.htmlStatus} );
};
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/status/current',
method: 'GET',
success: function(response) {
var d = response.result.data;
vmi.setData(d);
},
failure: error_handler
});
var vmc = me.down('#vmconfig');
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/config',
method: 'GET',
success: function(response) {
var d = response.result.data;
var names = ['name', 'memory', 'sockets', 'cores', 'ostype',
'bootdisk', /^net\d+/,
/^ide\d+/, /^virtio\d+/, /^sata\d+/,
/^scsi\d+/, /^unused\d+/ ];
var kv = PVE.Workspace.obj_to_kv(d, names);
vmc.setData(kv);
},
failure: error_handler
});
},
initialize: function() {
var me = this;
var match = me.self.pathMatch(me.getAppUrl());
if (!match) {
throw "pathMatch failed";
}
me.nodename = match[1];
me.vmid = match[2];
me.down('titlebar').setTitle('VM: ' + me.vmid);
me.down('pveMenuButton').setMenuItems([
{
text: gettext('Start'),
handler: function() {
me.vm_command("start", {});
}
},
{
text: gettext('Shutdown'),
handler: function() {
me.vm_command("shutdown", {});
}
},
{
text: gettext('Stop'),
handler: function() {
me.vm_command("stop", {});
}
},
{
text: gettext('Migrate'),
handler: function() {
PVE.Workspace.gotoPage('nodes/' + me.nodename + '/qemu/' + me.vmid + '/migrate');
}
},
{
text: gettext('Console'),
handler: function() {
PVE.Utils.openConsoleWindow('html5', 'kvm', me.vmid, me.nodename);
}
},
{
text: gettext('Spice'),
handler: function() {
PVE.Utils.openConsoleWindow('vv', 'kvm', me.vmid, me.nodename);
}
}
]);
me.reload();
this.callParent();
}
});
Ext.define('PVE.form.RealmSelector', {
extend: 'Ext.field.Select',
alias: ['widget.pveRealmSelector'],
config: {
autoSelect: false,
valueField: 'realm',
displayField: 'descr',
store: { model: 'pve-domains' },
value: 'pam'
},
needOTP: function(realm) {
var me = this;
var rec = me.store.findRecord('realm', realm);
return rec && rec.data && rec.data.tfa ? rec.data.tfa : undefined;
},
initialize: function() {
var me = this;
me.callParent();
var realmstore = me.getStore();
realmstore.load({
callback: function(r, o, success) {
if (success) {
var def = me.getValue();
if (!def || !realmstore.findRecord('realm', def)) {
def = 'pam';
Ext.each(r, function(record) {
if (record.get('default')) {
def = record.get('realm');
}
});
}
if (def) {
me.setValue(def);
}
}
}
});
}
});
Ext.define('PVE.TaskListBase', {
extend: 'PVE.Page',
config: {
baseUrl: undefined,
items: [
{
xtype: 'titlebar',
docked: 'top',
items: [
{
xtype: 'button',
align: 'right',
iconCls: 'refresh',
handler: function() {
this.up('pvePage').reload();
}
},
{
xtype: 'pveMenuButton',
align: 'right',
pveStdMenu: true
}
]
},
{
xtype: 'list',
//flex: 1,
height: 800,
disableSelection: true,
listeners: {
itemsingletap: function(list, index, target, record) {
PVE.Workspace.gotoPage('nodes/' + record.get('node') + '/tasks/' +
record.get('upid'));
}
},
itemTpl: [
'<small>{starttime:date("M d H:i:s")} - {endtime:date("M d H:i:s")}</small><br>',
'{[this.desc(values)]}<br>',
'<small>node: {node} Status: {status}</small>',
{
desc: function(values) {
return PVE.Utils.format_task_description(values.type, values.id);
}
}
]
}
]
},
reload: function() {
var me = this;
me.store.load();
},
initialize: function() {
var me = this;
me.store = Ext.create('Ext.data.Store', {
model: 'pve-tasks',
proxy: {
type: 'pve',
url: '/api2/json' + me.getBaseUrl()
},
sorters: [
{
property : 'starttime',
direction: 'DESC'
}
]
});
var list = me.down('list');
list.setStore(me.store);
me.reload();
this.callParent();
}
});
Ext.define('PVE.ClusterTaskList', {
extend: 'PVE.TaskListBase',
statics: {
pathMatch: function(loc) {
return loc.match(/^tasks$/);
}
},
config: {
baseUrl: '/cluster/tasks',
},
initialize: function() {
var me = this;
me.down('titlebar').setTitle(gettext('Tasks') + ': ' + gettext('Cluster'));
var match = me.self.pathMatch(me.getAppUrl());
if (!match) {
throw "pathMatch failed";
}
this.callParent();
}
});
Ext.define('PVE.NodeTaskList', {
extend: 'PVE.TaskListBase',
statics: {
pathMatch: function(loc) {
return loc.match(/^nodes\/([^\s\/]+)\/tasks$/);
}
},
nodename: undefined,
initialize: function() {
var me = this;
var match = me.self.pathMatch(me.getAppUrl());
if (!match) {
throw "pathMatch failed";
}
me.nodename = match[1];
me.setBaseUrl('/nodes/' + me.nodename + '/tasks');
me.down('titlebar').setTitle(gettext('Tasks') + ': ' + me.nodename);
this.callParent();
}
});
Ext.define('PVE.TaskViewer', {
extend: 'PVE.Page',
alias: 'widget.pveTaskViewer',
statics: {
pathMatch: function(loc) {
return loc.match(/^nodes\/([^\s\/]+)\/tasks\/([^\s\/]+)$/);
}
},
nodename: undefined,
upid: undefined,
taskInfo: undefined,
taskStatus: 'running', // assume running
config: {
items: [
{
xtype: 'titlebar',
title: gettext("Task Viewer"),
docked: 'top',
items: [
{
xtype: 'pveMenuButton',
align: 'right',
pveStdMenu: true
}
]
},
{
itemId: 'taskStatus',
xtype: 'component',
styleHtmlContent: true,
style: 'background-color:white;',
data: [],
tpl: [
'<table style="margin-bottom:0px;">',
'<tpl for=".">',
'<tr><td>{key}</td><td>{value}</td></tr>',
'</tpl>',
'</table>'
]
},
{
xtype: 'component',
cls: 'dark',
padding: 5,
html: gettext('Log')
},
{
itemId: 'taskLog',
xtype: 'container',
flex: 1,
scrollable: 'both',
styleHtmlContent: true,
style: 'background-color:white;white-space: pre;font-family: Monospace;',
data: {},
tpl: '{text}'
}
]
},
reloadLog: function() {
var me = this;
var logCmp = me.down('#taskLog');
PVE.Utils.API2Request({
url: "/nodes/" + me.nodename + "/tasks/" + me.upid + "/log",
method: 'GET',
success: function(response) {
var d = response.result.data;
var text = '';
Ext.Array.each(d, function(el) {
text += Ext.htmlEncode(el.t) + "\n";
});
logCmp.setData({ text: text });
},
failure: function(response) {
logCmp.setData({ text: response.htmlStatus } );
}
});
},
reload: function() {
var me = this;
var statusCmp = me.down('#taskStatus');
var logCmp = me.down('#taskLog');
PVE.Utils.API2Request({
url: "/nodes/" + me.nodename + "/tasks/" + me.upid + "/status",
method: 'GET',
success: function(response) {
me.reloadLog();
var d = response.result.data;
var kv = [];
kv.push({ key: gettext('Status'), value: d.exitstatus || d.status });
kv.push({ key: gettext('Node'), value: d.node });
kv.push({ key: gettext('User'), value: d.user });
kv.push({ key: gettext('Starttime'), value: PVE.Utils.render_timestamp(d.starttime) });
me.setMasked(false);
statusCmp.setData(kv);
if (d.status !== 'stopped') {
Ext.defer(me.reload, 2000, me);
}
},
failure: function(response) {
me.setMasked({ xtype: 'loadmask', message: response.htmlStatus} );
}
});
},
initialize: function() {
var me = this;
var match = me.self.pathMatch(me.getAppUrl());
if (!match) {
throw "pathMatch failed";
}
me.nodename = match[1];
me.upid = match[2];
me.taskInfo = PVE.Utils.parse_task_upid(me.upid);
me.down('titlebar').setTitle(me.taskInfo.desc);
me.reload();
this.callParent();
}
});
// Sencha Touch related things
PVE.Utils.toolkit = 'touch',
Ext.Ajax.setDisableCaching(false);
// do not send '_dc' parameter
Ext.Ajax.disableCaching = false;
Ext.define('PVE.Page', {
extend: 'Ext.Container',
alias: 'widget.pvePage',
statics: {
pathMatch: function(loc) {
throw "implement this in subclass";
}
},
config: {
layout: 'vbox',
appUrl: undefined
}
});
Ext.define('PVE.ErrorPage', {
extend: 'Ext.Panel',
config: {
html: "no such page",
padding: 10,
layout: {
type: 'vbox',
pack: 'center',
align: 'stretch'
},
items: [
{
xtype: 'titlebar',
docked: 'top',
title: gettext('Error'),
items: [
{
xtype: 'pveMenuButton',
align: 'right',
pveStdMenu: true
}
]
}
]
}
});
Ext.define('PVE.Workspace', { statics: {
// this class only contains static functions
loginData: null, // Data from last login call
appWindow: null,
history: null,
pages: [
'PVE.OpenVzSummary',
'PVE.QemuMigrate',
'PVE.QemuSummary',
'PVE.NodeSummary',
'PVE.ClusterTaskList',
'PVE.NodeTaskList',
'PVE.TaskViewer',
'PVE.Datacenter'
],
setHistory: function(h) {
PVE.Workspace.history = h;
PVE.Workspace.history.setUpdateUrl(true);
PVE.Workspace.loadPage(PVE.Workspace.history.getToken());
PVE.Workspace.history.on('change', function(loc) {
PVE.Workspace.loadPage(loc);
});
},
__setAppWindow: function(comp, dir) {
var old = PVE.Workspace.appWindow;
PVE.Workspace.appWindow = comp;
if (old) {
if (dir === 'noanim') {
Ext.Viewport.setActiveItem(PVE.Workspace.appWindow);
} else {
var anim = { type: 'slide', direction: dir || 'left' };
Ext.Viewport.animateActiveItem(PVE.Workspace.appWindow, anim);
}
// remove old after anim (hack, because anim.after does not work in 2.3.1a)
Ext.Function.defer(function(){
if (comp !== old) {
Ext.Viewport.remove(old);
}
}, 500);
} else {
Ext.Viewport.setActiveItem(PVE.Workspace.appWindow, anim);
}
},
updateLoginData: function(loginData) {
PVE.Workspace.loginData = loginData;
PVE.CSRFPreventionToken = loginData.CSRFPreventionToken;
PVE.UserName = loginData.username;
// creates a session cookie (expire = null)
// that way the cookie gets deleted after browser window close
Ext.util.Cookies.set('PVEAuthCookie', loginData.ticket, null, '/', null, true);
PVE.Workspace.gotoPage('');
},
showLogin: function() {
PVE.Utils.authClear();
PVE.UserName = null;
PVE.Workspace.loginData = null;
PVE.Workspace.gotoPage('');
},
gotoPage: function(loc) {
var match;
var old = PVE.Workspace.appWindow;
if (old.getAppUrl) {
var old_loc = old.getAppUrl();
if (old_loc !== loc) {
PVE.Workspace.history.add(Ext.create('Ext.app.Action', { url: loc }));
} else {
PVE.Workspace.loadPage(loc);
}
} else {
PVE.Workspace.history.add(Ext.create('Ext.app.Action', { url: loc }));
}
},
loadPage: function(loc) {
loc = loc || '';
var comp;
if (!PVE.Utils.authOK()) {
comp = Ext.create('PVE.Login', {});
} else {
Ext.Array.each(PVE.Workspace.pages, function(p, index) {
var c = Ext.ClassManager.get(p);
var match = c.pathMatch(loc);
if (match) {
comp = Ext.create(p, { appUrl: loc });
return false; // stop iteration
}
});
if (!comp) {
comp = Ext.create('PVE.ErrorPage', {});
}
}
PVE.Workspace.__setAppWindow(comp, 'noanim');
},
obj_to_kv: function(d, names) {
var kv = [];
var done = { digest: 1 };
var pushItem = function(item) {
if (done[item.key]) return;
done[item.key] = 1;
if (item.value) kv.push(item);
}
var keys = Ext.Array.sort(Ext.Object.getKeys(d));
Ext.Array.each(names, function(k) {
if (typeof(k) === 'object') {
Ext.Array.each(keys, function(n) {
if (k.test(n)) {
pushItem({ key: n, value: d[n] });
}
});
} else {
pushItem({ key: k, value: d[k] });
}
});
Ext.Array.each(keys, function(k) {
pushItem({ key: k, value: d[k] });
});
return kv;
}
}});
Ext.application({
launch: function() {
var me = this;
PVE.Workspace.setHistory(me.getHistory());
Ext.Ajax.on('requestexception', function(conn, response) {
if (response.status === 401) {
PVE.Workspace.showLogin();
}
});
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment