data/UpdateStore.js \
data/DiffStore.js \
data/ObjectStore.js \
- data/TimezoneStore.js
+ data/TimezoneStore.js \
+ grid/ObjectGrid.js \
+ window/Edit.js \
+ window/TaskViewer.js \
+ node/TimeEdit.js \
+ node/TimeView.js
all:
Ext.Ajax.request(newopts);
},
+ assemble_field_data: function(values, data) {
+ if (Ext.isObject(data)) {
+ Ext.Object.each(data, function(name, val) {
+ if (values.hasOwnProperty(name)) {
+ var bucket = values[name];
+ if (!Ext.isArray(bucket)) {
+ bucket = values[name] = [bucket];
+ }
+ if (Ext.isArray(val)) {
+ values[name] = bucket.concat(val);
+ } else {
+ bucket.push(val);
+ }
+ } else {
+ values[name] = val;
+ }
+ });
+ }
+ },
+
+ dialog_title: function(subject, create, isAdd) {
+ if (create) {
+ if (isAdd) {
+ return gettext('Add') + ': ' + subject;
+ } else {
+ return gettext('Create') + ': ' + subject;
+ }
+ } else {
+ return gettext('Edit') + ': ' + subject;
+ }
+ },
+
+ parse_task_upid: function(upid) {
+ var task = {};
+
+ var res = upid.match(/^UPID:(\S+):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8,9}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/);
+ if (!res) {
+ throw "unable to parse upid '" + upid + "'";
+ }
+ task.node = res[1];
+ task.pid = parseInt(res[2], 16);
+ task.pstart = parseInt(res[3], 16);
+ task.starttime = parseInt(res[4], 16);
+ task.type = res[5];
+ task.id = res[6];
+ task.user = res[7];
+
+ return task;
+ },
+
+ render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
+ var servertime = new Date(value * 1000);
+ return Ext.Date.format(servertime, 'Y-m-d H:i:s');
+ },
+
},
singleton: true,
this.callParent([config]);
}
+}, function() {
+
+ Ext.define('KeyValue', {
+ extend: "Ext.data.Model",
+ fields: [ 'key', 'value' ],
+ idProperty: 'key'
+ });
+
});
--- /dev/null
+/* Renders a list of key values objets
+
+mandatory config parameters:
+rows: an object container where each propery is a key-value object we want to render
+ var rows = {
+ keyboard: {
+ header: gettext('Keyboard Layout'),
+ editor: 'Your.KeyboardEdit',
+ required: true
+ },
+
+optional:
+disabled: setting this parameter to true will disable selection and focus on the
+proxmoxObjectGrid as well as greying out input elements.
+Useful for a readonly tabular display
+
+*/
+
+Ext.define('Proxmox.grid.ObjectGrid', {
+ extend: 'Ext.grid.GridPanel',
+ alias: ['widget.proxmoxObjectGrid'],
+ disabled: false,
+ hideHeaders: true,
+
+ getObjectValue: function(key, defaultValue) {
+ var me = this;
+ var rec = me.store.getById(key);
+ if (rec) {
+ return rec.data.value;
+ }
+ return defaultValue;
+ },
+
+ renderKey: function(key, metaData, record, rowIndex, colIndex, store) {
+ var me = this;
+ var rows = me.rows;
+ var rowdef = (rows && rows[key]) ? rows[key] : {};
+ return rowdef.header || key;
+ },
+
+ renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
+ var me = this;
+ var rows = me.rows;
+ var key = record.data.key;
+ var rowdef = (rows && rows[key]) ? rows[key] : {};
+
+ var renderer = rowdef.renderer;
+ if (renderer) {
+ return renderer(value, metaData, record, rowIndex, colIndex, store);
+ }
+
+ return value;
+ },
+
+ initComponent : function() {
+ var me = this;
+
+ var rows = me.rows;
+
+ if (!me.rstore) {
+ if (!me.url) {
+ throw "no url specified";
+ }
+
+ me.rstore = Ext.create('Proxmox.data.ObjectStore', {
+ url: me.url,
+ interval: me.interval,
+ extraParams: me.extraParams,
+ rows: me.rows
+ });
+ }
+
+ var rstore = me.rstore;
+
+ var store = Ext.create('Proxmox.data.DiffStore', { rstore: rstore,
+ sorters: [],
+ filters: []
+ });
+
+ if (rows) {
+ Ext.Object.each(rows, function(key, rowdef) {
+ if (Ext.isDefined(rowdef.defaultValue)) {
+ store.add({ key: key, value: rowdef.defaultValue });
+ } else if (rowdef.required) {
+ store.add({ key: key, value: undefined });
+ }
+ });
+ }
+
+ if (me.sorterFn) {
+ store.sorters.add(Ext.create('Ext.util.Sorter', {
+ sorterFn: me.sorterFn
+ }));
+ }
+
+ store.filters.add(Ext.create('Ext.util.Filter', {
+ filterFn: function(item) {
+ if (rows) {
+ var rowdef = rows[item.data.key];
+ if (!rowdef || (rowdef.visible === false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }));
+
+ Proxmox.Utils.monStoreErrors(me, rstore);
+
+ Ext.applyIf(me, {
+ store: store,
+ stateful: false,
+ columns: [
+ {
+ header: gettext('Name'),
+ width: me.cwidth1 || 200,
+ dataIndex: 'key',
+ renderer: me.renderKey
+ },
+ {
+ flex: 1,
+ header: gettext('Value'),
+ dataIndex: 'value',
+ renderer: me.renderValue
+ }
+ ]
+ });
+
+ me.callParent();
+ }
+});
--- /dev/null
+Ext.define('Proxmox.node.TimeEdit', {
+ extend: 'Proxmox.window.Edit',
+ alias: ['widget.proxmoxNodeTimeEdit'],
+
+ initComponent : function() {
+ var me = this;
+
+ if (!me.nodename) {
+ throw "no node name specified";
+ }
+
+ Ext.applyIf(me, {
+ subject: gettext('Time zone'),
+ url: "/api2/extjs/nodes/" + me.nodename + "/time",
+ fieldDefaults: {
+ labelWidth: 70
+ },
+ width: 400,
+ items: {
+ xtype: 'combo',
+ fieldLabel: gettext('Time zone'),
+ name: 'timezone',
+ queryMode: 'local',
+ store: Ext.create('Proxmox.data.TimezoneStore'),
+ valueField: 'zone',
+ displayField: 'zone',
+ triggerAction: 'all',
+ forceSelection: true,
+ editable: false,
+ allowBlank: false
+ }
+ });
+
+ me.callParent();
+
+ me.load();
+ }
+});
--- /dev/null
+Ext.define('Proxmox.node.TimeView', {
+ extend: 'Proxmox.grid.ObjectGrid',
+ alias: ['widget.proxmoxNodeTimeView'],
+
+ initComponent : function() {
+ var me = this;
+
+ if (!me.nodename) {
+ throw "no node name specified";
+ }
+
+ var tzoffset = (new Date()).getTimezoneOffset()*60000;
+ var renderlocaltime = function(value) {
+ var servertime = new Date((value * 1000) + tzoffset);
+ return Ext.Date.format(servertime, 'Y-m-d H:i:s');
+ };
+
+ var run_editor = function() {
+ var win = Ext.create('Proxmox.node.TimeEdit', {
+ nodename: me.nodename
+ });
+ win.show();
+ };
+
+ Ext.apply(me, {
+ url: "/api2/json/nodes/" + me.nodename + "/time",
+ cwidth1: 150,
+ interval: 1000,
+ rows: {
+ timezone: {
+ header: gettext('Time zone'),
+ required: true
+ },
+ localtime: {
+ header: gettext('Server time'),
+ required: true,
+ renderer: renderlocaltime
+ }
+ },
+ tbar: [
+ {
+ text: gettext("Edit"),
+ handler: run_editor
+ }
+ ],
+ listeners: {
+ itemdblclick: run_editor
+ }
+ });
+
+ me.callParent();
+
+ me.on('activate', me.rstore.startUpdate);
+ me.on('destroy', me.rstore.stopUpdate);
+ }
+});
--- /dev/null
+// fixme: how can we avoid those lint errors?
+/*jslint confusion: true */
+Ext.define('Proxmox.window.Edit', {
+ extend: 'Ext.window.Window',
+ alias: 'widget.proxmoxWindowEdit',
+
+ resizable: false,
+
+ // use this tio atimatically generate a title like
+ // Create: <subject>
+ subject: undefined,
+
+ // set create to true if you want a Create button (instead
+ // OK and RESET)
+ create: false,
+
+ // set to true if you want an Add button (instead of Create)
+ isAdd: false,
+
+ // set to true if you want an Remove button (instead of Create)
+ isRemove: false,
+
+ backgroundDelay: 0,
+
+ showProgress: false,
+
+ isValid: function() {
+ var me = this;
+
+ var form = me.formPanel.getForm();
+ return form.isValid();
+ },
+
+ getValues: function(dirtyOnly) {
+ var me = this;
+
+ var values = {};
+
+ var form = me.formPanel.getForm();
+
+ form.getFields().each(function(field) {
+ if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
+ Proxmox.Utils.assemble_field_data(values, field.getSubmitData());
+ }
+ });
+
+ Ext.Array.each(me.query('inputpanel'), function(panel) {
+ Proxmox.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
+ });
+
+ return values;
+ },
+
+ setValues: function(values) {
+ var me = this;
+
+ var form = me.formPanel.getForm();
+
+ Ext.iterate(values, function(fieldId, val) {
+ var field = form.findField(fieldId);
+ if (field && !field.up('inputpanel')) {
+ field.setValue(val);
+ if (form.trackResetOnLoad) {
+ field.resetOriginalValue();
+ }
+ }
+ });
+
+ Ext.Array.each(me.query('inputpanel'), function(panel) {
+ panel.setValues(values);
+ });
+ },
+
+ submit: function() {
+ var me = this;
+
+ var form = me.formPanel.getForm();
+
+ var values = me.getValues();
+ Ext.Object.each(values, function(name, val) {
+ if (values.hasOwnProperty(name)) {
+ if (Ext.isArray(val) && !val.length) {
+ values[name] = '';
+ }
+ }
+ });
+
+ if (me.digest) {
+ values.digest = me.digest;
+ }
+
+ if (me.backgroundDelay) {
+ values.background_delay = me.backgroundDelay;
+ }
+
+ var url = me.url;
+ if (me.method === 'DELETE') {
+ url = url + "?" + Ext.Object.toQueryString(values);
+ values = undefined;
+ }
+
+ Proxmox.Utils.API2Request({
+ url: url,
+ waitMsgTarget: me,
+ method: me.method || (me.backgroundDelay ? 'POST' : 'PUT'),
+ params: values,
+ failure: function(response, options) {
+ if (response.result && response.result.errors) {
+ form.markInvalid(response.result.errors);
+ }
+ Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+ },
+ success: function(response, options) {
+ var hasProgressBar = (me.backgroundDelay || me.showProgress) &&
+ response.result.data ? true : false;
+
+ if (hasProgressBar) {
+ // stay around so we can trigger our close events
+ // when background action is completed
+ me.hide();
+
+ var upid = response.result.data;
+ var win = Ext.create('PVE.window.TaskProgress', {
+ upid: upid,
+ listeners: {
+ destroy: function () {
+ me.close();
+ }
+ }
+ });
+ win.show();
+ } else {
+ me.close();
+ }
+ }
+ });
+ },
+
+ load: function(options) {
+ var me = this;
+
+ var form = me.formPanel.getForm();
+
+ options = options || {};
+
+ var newopts = Ext.apply({
+ waitMsgTarget: me
+ }, options);
+
+ var createWrapper = function(successFn) {
+ Ext.apply(newopts, {
+ url: me.url,
+ method: 'GET',
+ success: function(response, opts) {
+ form.clearInvalid();
+ me.digest = response.result.data.digest;
+ if (successFn) {
+ successFn(response, opts);
+ } else {
+ me.setValues(response.result.data);
+ }
+ // hack: fix ExtJS bug
+ Ext.Array.each(me.query('radiofield'), function(f) {
+ f.resetOriginalValue();
+ });
+ },
+ failure: function(response, opts) {
+ Ext.Msg.alert(gettext('Error'), response.htmlStatus, function() {
+ me.close();
+ });
+ }
+ });
+ };
+
+ createWrapper(options.success);
+
+ Proxmox.Utils.API2Request(newopts);
+ },
+
+ initComponent : function() {
+ var me = this;
+
+ if (!me.url) {
+ throw "no url specified";
+ }
+
+ var items = Ext.isArray(me.items) ? me.items : [ me.items ];
+
+ me.items = undefined;
+
+ me.formPanel = Ext.create('Ext.form.Panel', {
+ url: me.url,
+ method: me.method || 'PUT',
+ trackResetOnLoad: true,
+ bodyPadding: 10,
+ border: false,
+ defaults: {
+ border: false
+ },
+ fieldDefaults: Ext.apply({}, me.fieldDefaults, {
+ labelWidth: 100,
+ anchor: '100%'
+ }),
+ items: items
+ });
+
+ var form = me.formPanel.getForm();
+
+ var submitText;
+ if (me.create) {
+ if (me.isAdd) {
+ submitText = gettext('Add');
+ } else if (me.isRemove) {
+ submitText = gettext('Remove');
+ } else {
+ submitText = gettext('Create');
+ }
+ } else {
+ submitText = gettext('OK');
+ }
+
+ var submitBtn = Ext.create('Ext.Button', {
+ text: submitText,
+ disabled: !me.create,
+ handler: function() {
+ me.submit();
+ }
+ });
+
+ var resetBtn = Ext.create('Ext.Button', {
+ text: 'Reset',
+ disabled: true,
+ handler: function(){
+ form.reset();
+ }
+ });
+
+ var set_button_status = function() {
+ var valid = form.isValid();
+ var dirty = form.isDirty();
+ submitBtn.setDisabled(!valid || !(dirty || me.create));
+ resetBtn.setDisabled(!dirty);
+ };
+
+ form.on('dirtychange', set_button_status);
+ form.on('validitychange', set_button_status);
+
+ var colwidth = 300;
+ if (me.fieldDefaults && me.fieldDefaults.labelWidth) {
+ colwidth += me.fieldDefaults.labelWidth - 100;
+ }
+
+
+ var twoColumn = items[0].column1 || items[0].column2;
+
+ if (me.subject && !me.title) {
+ me.title = Proxmox.Utils.dialog_title(me.subject, me.create, me.isAdd);
+ }
+
+ if (me.create) {
+ me.buttons = [ submitBtn ] ;
+ } else {
+ me.buttons = [ submitBtn, resetBtn ];
+ }
+
+ if (items[0].onlineHelp) {
+ var helpButton = Ext.create('PVE.button.Help');
+ me.buttons.unshift(helpButton, '->');
+ Ext.GlobalEvents.fireEvent('pveShowHelp', items[0].onlineHelp);
+ }
+
+ Ext.applyIf(me, {
+ modal: true,
+ width: twoColumn ? colwidth*2 : colwidth,
+ border: false,
+ items: [ me.formPanel ]
+ });
+
+ me.callParent();
+
+ // always mark invalid fields
+ me.on('afterlayout', function() {
+ // on touch devices, the isValid function
+ // triggers a layout, which triggers an isValid
+ // and so on
+ // to prevent this we disable the layouting here
+ // and enable it afterwards
+ me.suspendLayout = true;
+ me.isValid();
+ me.suspendLayout = false;
+ });
+ }
+});
--- /dev/null
+Ext.define('Proxmox.window.TaskProgress', {
+ extend: 'Ext.window.Window',
+ alias: 'widget.proxmoxTaskProgress',
+
+ initComponent: function() {
+ var me = this;
+
+ if (!me.upid) {
+ throw "no task specified";
+ }
+
+ var task = Proxmox.Utils.parse_task_upid(me.upid);
+
+ var statstore = Ext.create('Proxmox.data.ObjectStore', {
+ url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
+ interval: 1000,
+ rows: {
+ status: { defaultValue: 'unknown' },
+ exitstatus: { defaultValue: 'unknown' }
+ }
+ });
+
+ me.on('destroy', statstore.stopUpdate);
+
+ var getObjectValue = function(key, defaultValue) {
+ var rec = statstore.getById(key);
+ if (rec) {
+ return rec.data.value;
+ }
+ return defaultValue;
+ };
+
+ var pbar = Ext.create('Ext.ProgressBar', { text: 'running...' });
+
+ me.mon(statstore, 'load', function() {
+ var status = getObjectValue('status');
+ if (status === 'stopped') {
+ var exitstatus = getObjectValue('exitstatus');
+ if (exitstatus == 'OK') {
+ pbar.reset();
+ pbar.updateText("Done!");
+ Ext.Function.defer(me.close, 1000, me);
+ } else {
+ me.close();
+ Ext.Msg.alert('Task failed', exitstatus);
+ }
+ }
+ });
+
+ // fixme: ??
+ //var descr = Proxmox.Utils.format_task_description(task.type, task.id);
+
+ Ext.apply(me, {
+ title: "Task: " + me.upid,
+ width: 300,
+ layout: 'auto',
+ modal: true,
+ bodyPadding: 5,
+ items: pbar,
+ buttons: [
+ {
+ text: gettext('Details'),
+ handler: function() {
+ var win = Ext.create('Proxmox.window.TaskViewer', {
+ upid: me.upid
+ });
+ win.show();
+ me.close();
+ }
+ }
+ ]
+ });
+
+ me.callParent();
+
+ statstore.startUpdate();
+
+ pbar.wait();
+ }
+});
+
+// fixme: how can we avoid those lint errors?
+/*jslint confusion: true */
+
+Ext.define('Proxmox.window.TaskViewer', {
+ extend: 'Ext.window.Window',
+ alias: 'widget.proxmoxTaskViewer',
+
+ initComponent: function() {
+ var me = this;
+
+ if (!me.upid) {
+ throw "no task specified";
+ }
+
+ var task = Proxmox.Utils.parse_task_upid(me.upid);
+
+ var statgrid;
+
+ var rows = {
+ status: {
+ header: gettext('Status'),
+ defaultValue: 'unknown',
+ renderer: function(value) {
+ if (value != 'stopped') {
+ return value;
+ }
+ var es = statgrid.getObjectValue('exitstatus');
+ if (es) {
+ return value + ': ' + es;
+ }
+ }
+ },
+ exitstatus: {
+ visible: false
+ },
+ type: {
+ header: gettext('Task type'),
+ required: true
+ },
+ user: {
+ header: gettext('User name'),
+ required: true
+ },
+ node: {
+ header: gettext('Node'),
+ required: true
+ },
+ pid: {
+ header: gettext('Process ID'),
+ required: true
+ },
+ starttime: {
+ header: gettext('Start Time'),
+ required: true,
+ renderer: Proxmox.Utils.render_timestamp
+ },
+ upid: {
+ header: gettext('Unique task ID')
+ }
+ };
+
+ var statstore = Ext.create('Proxmox.data.ObjectStore', {
+ url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
+ interval: 1000,
+ rows: rows
+ });
+
+ me.on('destroy', statstore.stopUpdate);
+
+ var stop_task = function() {
+ Proxmox.Utils.API2Request({
+ url: "/nodes/" + task.node + "/tasks/" + me.upid,
+ waitMsgTarget: me,
+ method: 'DELETE',
+ failure: function(response, opts) {
+ Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+ }
+ });
+ };
+
+ var stop_btn1 = new Ext.Button({
+ text: gettext('Stop'),
+ disabled: true,
+ handler: stop_task
+ });
+
+ var stop_btn2 = new Ext.Button({
+ text: gettext('Stop'),
+ disabled: true,
+ handler: stop_task
+ });
+
+ statgrid = Ext.create('Proxmox.grid.ObjectGrid', {
+ title: gettext('Status'),
+ layout: 'fit',
+ tbar: [ stop_btn1 ],
+ rstore: statstore,
+ rows: rows,
+ border: false
+ });
+
+ var logView = Ext.create('Proxmox.panel.LogView', {
+ title: gettext('Output'),
+ tbar: [ stop_btn2 ],
+ border: false,
+ url: "/api2/extjs/nodes/" + task.node + "/tasks/" + me.upid + "/log"
+ });
+
+ me.mon(statstore, 'load', function() {
+ var status = statgrid.getObjectValue('status');
+
+ if (status === 'stopped') {
+ logView.requestUpdate(undefined, true);
+ logView.scrollToEnd = false;
+ statstore.stopUpdate();
+ }
+
+ stop_btn1.setDisabled(status !== 'running');
+ stop_btn2.setDisabled(status !== 'running');
+ });
+
+ statstore.startUpdate();
+
+ Ext.apply(me, {
+ // fixme: better title
+ title: "Task viewer: " + me.upid,
+ width: 800,
+ height: 400,
+ layout: 'fit',
+ modal: true,
+ items: [{
+ xtype: 'tabpanel',
+ region: 'center',
+ items: [ logView, statgrid ]
+ }]
+ });
+
+ me.callParent();
+
+ logView.fireEvent('show', logView);
+ }
+});
+