SHARE
TWEET

Untitled

a guest Apr 5th, 2012 1,228 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. Ext.ns('PVE');
  2.  
  3. // avoid errors when running without development tools
  4. if (!Ext.isDefined(Ext.global.console)) {  
  5.     var console = {
  6.         dir: function() {},
  7.         log: function() {}
  8.     };
  9. }
  10. console.log("Starting PVE Manager");
  11.  
  12. Ext.Ajax.defaultHeaders = {
  13.     'Accept': 'application/json'
  14. };
  15.  
  16. // do not send '_dc' parameter
  17. Ext.Ajax.disableCaching = false;
  18.  
  19. Ext.Ajax.on('beforerequest', function(conn, options) {
  20.     if (PVE.CSRFPreventionToken) {
  21.         if (!options.headers) {
  22.             options.headers = {};
  23.         }
  24.         options.headers.CSRFPreventionToken = PVE.CSRFPreventionToken;
  25.     }
  26. });
  27.  
  28. // custom Vtypes
  29. Ext.apply(Ext.form.field.VTypes, {
  30.     IPAddress:  function(v) {
  31.         return (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/).test(v);
  32.     },
  33.     IPAddressText:  gettext('Example') + ': 192.168.1.1',
  34.     IPAddressMask: /[\d\.]/i,
  35.  
  36.     MacAddress: function(v) {
  37.         return (/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/).test(v);
  38.     },
  39.     MacAddressMask: /[a-fA-F0-9:]/,
  40.     MacAddressText: gettext('Example') + ': 01:23:45:67:89:ab',
  41.  
  42.     BridgeName: function(v) {
  43.         return (/^vmbr\d{1,4}$/).test(v);
  44.     },
  45.     BridgeNameText: gettext('Format') + ': vmbr<b>N</b>, where 0 <= <b>N</b> <= 9999',
  46.  
  47.     BondName: function(v) {
  48.         return (/^bond\d{1,4}$/).test(v);
  49.     },
  50.     BondNameText: gettext('Format') + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999',
  51.  
  52.     QemuStartDate: function(v) {
  53.         return (/^(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)$/).test(v);
  54.     },
  55.     QemuStartDateText: gettext('Format') + ': "now" or "2006-06-17T16:01:21" or "2006-06-17"',
  56.  
  57.     StorageId:  function(v) {
  58.         return (/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i).test(v);
  59.     },
  60.     StorageIdText: gettext("Allowed characters") + ": 'a-z', '0-9', '-', '_', '.'",
  61.  
  62.     HttpProxy:  function(v) {
  63.         return (/^http:\/\/.*$/).test(v);
  64.     },
  65.     HttpProxyText: gettext('Example') + ": http://username:password&#64;host:port/",
  66.  
  67.     DnsName: function(v) {
  68.         return (/^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/).test(v);
  69.     },
  70.     DnsNameText: gettext('This is not a valid DNS name')
  71. });
  72.  
  73. // we dont want that a displayfield set the form dirty flag!
  74. Ext.override(Ext.form.field.Display, {
  75.     isDirty: function() { return false; }
  76. });
  77.  
  78. // hack: ExtJS does not display the correct value if we
  79. // call setValue while the store is loading, so we need
  80. // to call it again after loading
  81. Ext.override(Ext.form.field.ComboBox, {
  82.     onLoad: function() {
  83.         this.setValue(this.value, false);
  84.         this.callOverridden(arguments);
  85.     }
  86. });
  87.  
  88. Ext.define('PVE.Utils', { statics: {
  89.  
  90.     // this class only contains static functions
  91.  
  92.     log_severity_hash: {
  93.         0: "panic",
  94.         1: "alert",
  95.         2: "critical",
  96.         3: "error",
  97.         4: "warning",
  98.         5: "notice",
  99.         6: "info",
  100.         7: "debug"
  101.     },
  102.  
  103.     support_level_hash: {
  104.         'c': gettext('Community'),
  105.         'b': gettext('Basic'),
  106.         's': gettext('Standard'),
  107.         'p': gettext('Premium')
  108.     },
  109.  
  110.     kvm_ostypes: {
  111.         other: gettext('Other OS types'),
  112.         wxp: 'Microsoft Windows XP/2003',
  113.         w2k: 'Microsoft Windows 2000',
  114.         w2k8: 'Microsoft Windows Vista/2008',
  115.         win7: 'Microsoft Windows 7/2008r2',
  116.         l24: 'Linux 2.4 Kernel',
  117.         l26: 'Linux 3.X/2.6 Kernel'
  118.     },
  119.  
  120.     render_kvm_ostype: function (value) {
  121.         if (!value) {
  122.             return gettext('Other OS types');
  123.         }
  124.         var text = PVE.Utils.kvm_ostypes[value];
  125.         if (text) {
  126.             return text + ' (' + value + ')';
  127.         }
  128.         return value;
  129.     },
  130.  
  131.     // fixme: auto-generate this
  132.     // for now, please keep in sync with PVE::Tools::kvmkeymaps
  133.     kvm_keymaps: {
  134.         //ar: 'Arabic',
  135.         da: 'Danish',
  136.         de: 'German',
  137.         'de-ch': 'German (Swiss)',
  138.         'en-gb': 'English (UK)',
  139.         'en-us': 'English (USA',
  140.         es: 'Spanish',
  141.         //et: 'Estonia',
  142.         fi: 'Finnish',
  143.         //fo: 'Faroe Islands',
  144.         fr: 'French',
  145.         'fr-be': 'French (Belgium)',
  146.         'fr-ca': 'French (Canada)',
  147.         'fr-ch': 'French (Swiss)',
  148.         //hr: 'Croatia',
  149.         hu: 'Hungarian',
  150.         is: 'Icelandic',
  151.         it: 'Italian',
  152.         ja: 'Japanese',
  153.         lt: 'Lithuanian',
  154.         //lv: 'Latvian',
  155.         mk: 'Macedonian',
  156.         nl: 'Dutch',
  157.         //'nl-be': 'Dutch (Belgium)',
  158.         no: 'Norwegian',
  159.         pl: 'Polish',
  160.         pt: 'Portuguese',
  161.         'pt-br': 'Portuguese (Brazil)',
  162.         //ru: 'Russian',
  163.         si: 'Slovenian',
  164.         sv: 'Swedish'
  165.         //th: 'Thai',
  166.         //tr: 'Turkish'
  167.     },
  168.  
  169.     kvm_vga_drivers: {
  170.         std: 'Standard VGA',
  171.         vmware: 'VMWare compatible',
  172.         cirrus: 'Cirrus Logic GD5446'
  173.     },
  174.  
  175.     render_kvm_language: function (value) {
  176.         if (!value) {
  177.             return PVE.Utils.defaultText;
  178.         }
  179.         var text = PVE.Utils.kvm_keymaps[value];
  180.         if (text) {
  181.             return text + ' (' + value + ')';
  182.         }
  183.         return value;
  184.     },
  185.  
  186.     kvm_keymap_array: function() {
  187.         var data = [['', PVE.Utils.render_kvm_language('')]];
  188.         Ext.Object.each(PVE.Utils.kvm_keymaps, function(key, value) {
  189.             data.push([key, PVE.Utils.render_kvm_language(value)]);
  190.         });
  191.  
  192.         return data;
  193.     },
  194.  
  195.     language_map: {
  196.         zh_CN: 'Chinese',
  197.         ja: 'Japanese',
  198.         en: 'English',
  199.         de: 'German',
  200.         es: 'Spanish',
  201.         fr: 'French',
  202.         sv: 'Swedish'
  203.     },
  204.  
  205.     render_language: function (value) {
  206.         if (!value) {
  207.             return PVE.Utils.defaultText + ' (English)';
  208.         }
  209.         var text = PVE.Utils.language_map[value];
  210.         if (text) {
  211.             return text + ' (' + value + ')';
  212.         }
  213.         return value;
  214.     },
  215.  
  216.     language_array: function() {
  217.         var data = [['', PVE.Utils.render_language('')]];
  218.         Ext.Object.each(PVE.Utils.language_map, function(key, value) {
  219.             data.push([key, PVE.Utils.render_language(value)]);
  220.         });
  221.  
  222.         return data;
  223.     },
  224.  
  225.     render_kvm_vga_driver: function (value) {
  226.         if (!value) {
  227.             return PVE.Utils.defaultText;
  228.         }
  229.         var text = PVE.Utils.kvm_vga_drivers[value];
  230.         if (text) {
  231.             return text + ' (' + value + ')';
  232.         }
  233.         return value;
  234.     },
  235.  
  236.     kvm_vga_driver_array: function() {
  237.         var data = [['', PVE.Utils.render_kvm_vga_driver('')]];
  238.         Ext.Object.each(PVE.Utils.kvm_vga_drivers, function(key, value) {
  239.             data.push([key, PVE.Utils.render_kvm_vga_driver(value)]);
  240.         });
  241.  
  242.         return data;
  243.     },
  244.  
  245.     authOK: function() {
  246.         return Ext.util.Cookies.get('PVEAuthCookie');
  247.     },
  248.  
  249.     authClear: function() {
  250.         Ext.util.Cookies.clear("PVEAuthCookie");
  251.     },
  252.  
  253.     // fixme: remove - not needed?
  254.     gridLineHeigh: function() {
  255.         return 21;
  256.        
  257.         //if (Ext.isGecko)
  258.         //return 23;
  259.         //return 21;
  260.     },
  261.  
  262.     extractRequestError: function(result, verbose) {
  263.         var msg = gettext('Successful');
  264.  
  265.         if (!result.success) {
  266.             msg = gettext("Unknown error");
  267.             if (result.message) {
  268.                 msg = result.message;
  269.                 if (result.status) {
  270.                     msg += ' (' + result.status + ')';
  271.                 }
  272.             }
  273.             if (verbose && Ext.isObject(result.errors)) {
  274.                 msg += "<br>";
  275.                 Ext.Object.each(result.errors, function(prop, desc) {
  276.                     msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
  277.                         Ext.htmlEncode(desc);
  278.                 });
  279.             }  
  280.         }
  281.  
  282.         return msg;
  283.     },
  284.  
  285.     extractFormActionError: function(action) {
  286.         var msg;
  287.         switch (action.failureType) {
  288.         case Ext.form.action.Action.CLIENT_INVALID:
  289.             msg = gettext('Form fields may not be submitted with invalid values');
  290.             break;
  291.         case Ext.form.action.Action.CONNECT_FAILURE:
  292.             msg = gettext('Connection error');
  293.             var resp = action.response;
  294.             if (resp.status && resp.statusText) {
  295.                 msg += " " + resp.status + ": " + resp.statusText;
  296.             }
  297.             break;
  298.         case Ext.form.action.Action.LOAD_FAILURE:
  299.         case Ext.form.action.Action.SERVER_INVALID:
  300.             msg = PVE.Utils.extractRequestError(action.result, true);
  301.             break;
  302.         }
  303.         return msg;
  304.     },
  305.  
  306.     // Ext.Ajax.request
  307.     API2Request: function(reqOpts) {
  308.  
  309.         var newopts = Ext.apply({
  310.             waitMsg: gettext('Please wait...')
  311.         }, reqOpts);
  312.  
  313.         if (!newopts.url.match(/^\/api2/)) {
  314.             newopts.url = '/api2/extjs' + newopts.url;
  315.         }
  316.         delete newopts.callback;
  317.  
  318.         var createWrapper = function(successFn, callbackFn, failureFn) {
  319.             Ext.apply(newopts, {
  320.                 success: function(response, options) {
  321.                     if (options.waitMsgTarget) {
  322.                         options.waitMsgTarget.setLoading(false);
  323.                     }
  324.                     var result = Ext.decode(response.responseText);
  325.                     response.result = result;
  326.                     if (!result.success) {
  327.                         response.htmlStatus = PVE.Utils.extractRequestError(result, true);
  328.                         Ext.callback(callbackFn, options.scope, [options, false, response]);
  329.                         Ext.callback(failureFn, options.scope, [response, options]);
  330.                         return;
  331.                     }
  332.                     Ext.callback(callbackFn, options.scope, [options, true, response]);
  333.                     Ext.callback(successFn, options.scope, [response, options]);
  334.                 },
  335.                 failure: function(response, options) {
  336.                     if (options.waitMsgTarget) {
  337.                         options.waitMsgTarget.setLoading(false);
  338.                     }
  339.                     response.result = {};
  340.                     try {
  341.                         response.result = Ext.decode(response.responseText);
  342.                     } catch(e) {}
  343.                     var msg = gettext('Connection error') + ' - server offline?';
  344.                     if (response.aborted) {
  345.                         msg = gettext('Connection error') + ' - aborted.';
  346.                     } else if (response.timedout) {
  347.                         msg = gettext('Connection error') + ' - Timeout.';
  348.                     } else if (response.status && response.statusText) {
  349.                         msg = gettext('Connection error') + ' ' + response.status + ': ' + response.statusText;
  350.                     }
  351.                     response.htmlStatus = msg;
  352.                     Ext.callback(callbackFn, options.scope, [options, false, response]);
  353.                     Ext.callback(failureFn, options.scope, [response, options]);
  354.                 }
  355.             });
  356.         };
  357.  
  358.         createWrapper(reqOpts.success, reqOpts.callback, reqOpts.failure);
  359.  
  360.         var target = newopts.waitMsgTarget;
  361.         if (target) {
  362.             // Note: ExtJS bug - this does not work when component is not rendered
  363.             target.setLoading(newopts.waitMsg);
  364.         }
  365.         Ext.Ajax.request(newopts);
  366.     },
  367.  
  368.     assemble_field_data: function(values, data) {
  369.         if (Ext.isObject(data)) {
  370.             Ext.Object.each(data, function(name, val) {
  371.                 if (values.hasOwnProperty(name)) {
  372.                     var bucket = values[name];
  373.                     if (!Ext.isArray(bucket)) {
  374.                         bucket = values[name] = [bucket];
  375.                     }
  376.                     if (Ext.isArray(val)) {
  377.                         values[name] = bucket.concat(val);
  378.                     } else {
  379.                         bucket.push(val);
  380.                     }
  381.                 } else {
  382.                     values[name] = val;
  383.                 }
  384.             });
  385.         }
  386.     },
  387.  
  388.     task_desc_table: {
  389.         vncproxy: [ 'VM/CT', gettext('Console') ],
  390.         vncshell: [ '', gettext('Shell') ],
  391.         qmcreate: [ 'VM', gettext('Create') ],
  392.         qmrestore: [ 'VM', gettext('Restore') ],
  393.         qmdestroy: [ 'VM', gettext('Destroy') ],
  394.         qmigrate: [ 'VM', gettext('Migrate') ],
  395.         qmstart: [ 'VM', gettext('Start') ],
  396.         qmstop: [ 'VM', gettext('Stop') ],
  397.         qmreset: [ 'VM', gettext('Reset') ],
  398.         qmshutdown: [ 'VM', gettext('Shutdown') ],
  399.         qmsuspend: [ 'VM', gettext('Suspend') ],
  400.         qmresume: [ 'VM', gettext('Resume') ],
  401.         vzcreate: ['CT', gettext('Create') ],
  402.         vzrestore: ['CT', gettext('Restore') ],
  403.         vzdestroy: ['CT', gettext('Destroy') ],
  404.         vzmigrate: [ 'CT', gettext('Migrate') ],
  405.         vzstart: ['CT', gettext('Start') ],
  406.         vzstop: ['CT', gettext('Stop') ],
  407.         vzmount: ['CT', gettext('Mount') ],
  408.         vzumount: ['CT', gettext('Unmount') ],
  409.         vzshutdown: ['CT', gettext('Shutdown') ],
  410.         hamigrate: [ 'HA', gettext('Migrate') ],
  411.         hastart: [ 'HA', gettext('Start') ],
  412.         hastop: [ 'HA', gettext('Stop') ],
  413.         srvstart: ['SRV', gettext('Start') ],
  414.         srvstop: ['SRV', gettext('Stop') ],
  415.         srvrestart: ['SRV', gettext('Restart') ],
  416.         srvreload: ['SRV', gettext('Reload') ],
  417.         imgcopy: ['', gettext('Copy data') ],
  418.         imgdel: ['', gettext('Erase data') ],
  419.         download: ['', gettext('Download') ],
  420.         vzdump: ['', gettext('Backup') ]
  421.     },
  422.  
  423.     format_task_description: function(type, id) {      
  424.         var farray = PVE.Utils.task_desc_table[type];
  425.         if (!farray) {
  426.             return type;
  427.         }
  428.         var prefix = farray[0];
  429.         var text = farray[1];
  430.         if (prefix) {
  431.             return prefix + ' ' + id + ' - ' + text;
  432.         }
  433.         return text;
  434.     },
  435.  
  436.     parse_task_upid: function(upid) {
  437.         var task = {};
  438.  
  439.         var res = upid.match(/^UPID:(\S+):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/);
  440.         if (!res) {
  441.             throw "unable to parse upid '" + upid + "'";
  442.         }
  443.         task.node = res[1];
  444.         task.pid = parseInt(res[2], 16);
  445.         task.pstart = parseInt(res[3], 16);
  446.         task.starttime = parseInt(res[4], 16);
  447.         task.type = res[5];
  448.         task.id = res[6];
  449.         task.user = res[7];
  450.  
  451.         task.desc = PVE.Utils.format_task_description(task.type, task.id);
  452.  
  453.         return task;
  454.     },
  455.  
  456.     format_size: function(size) {
  457.         /*jslint confusion: true */
  458.  
  459.         if (size < 1024) {
  460.             return size;
  461.         }
  462.  
  463.         var kb = size / 1024;
  464.  
  465.         if (kb < 1024) {
  466.             return kb.toFixed(0) + "KB";
  467.         }
  468.  
  469.         var mb = size / (1024*1024);
  470.  
  471.         if (mb < 1024) {
  472.             return mb.toFixed(0) + "MB";
  473.         }
  474.  
  475.         var gb = mb / 1024;
  476.  
  477.         if (gb < 1024) {
  478.             return gb.toFixed(2) + "GB";
  479.         }
  480.  
  481.         var tb =  gb / 1024;
  482.  
  483.         return tb.toFixed(2) + "TB";
  484.  
  485.     },
  486.  
  487.     format_html_bar: function(per, text) {
  488.  
  489.         return "<div class='pve-bar-wrap'>" + text + "<div class='pve-bar-border'>" +
  490.             "<div class='pve-bar-inner' style='width:" + per + "%;'></div>" +
  491.             "</div></div>";
  492.        
  493.     },
  494.  
  495.     format_cpu_bar: function(per1, per2, text) {
  496.  
  497.         return "<div class='pve-bar-border'>" +
  498.             "<div class='pve-bar-inner' style='width:" + per1 + "%;'></div>" +
  499.             "<div class='pve-bar-inner2' style='width:" + per2 + "%;'></div>" +
  500.             "<div class='pve-bar-text'>" + text + "</div>" +
  501.             "</div>";
  502.     },
  503.  
  504.     format_large_bar: function(per, text) {
  505.  
  506.         if (!text) {
  507.             text = per.toFixed(1) + "%";
  508.         }
  509.  
  510.         return "<div class='pve-largebar-border'>" +
  511.             "<div class='pve-largebar-inner' style='width:" + per + "%;'></div>" +
  512.             "<div class='pve-largebar-text'>" + text + "</div>" +
  513.             "</div>";
  514.     },
  515.  
  516.     format_duration_long: function(ut) {
  517.  
  518.         var days = Math.floor(ut / 86400);
  519.         ut -= days*86400;
  520.         var hours = Math.floor(ut / 3600);
  521.         ut -= hours*3600;
  522.         var mins = Math.floor(ut / 60);
  523.         ut -= mins*60;
  524.  
  525.         var hours_str = '00' + hours.toString();
  526.         hours_str = hours_str.substr(hours_str.length - 2);
  527.         var mins_str = "00" + mins.toString();
  528.         mins_str = mins_str.substr(mins_str.length - 2);
  529.         var ut_str = "00" + ut.toString();
  530.         ut_str = ut_str.substr(ut_str.length - 2);
  531.  
  532.         if (days) {
  533.             var ds = days > 1 ? PVE.Utils.daysText : PVE.Utils.dayText;
  534.             return days.toString() + ' ' + ds + ' ' +
  535.                 hours_str + ':' + mins_str + ':' + ut_str;
  536.         } else {
  537.             return hours_str + ':' + mins_str + ':' + ut_str;
  538.         }
  539.     },
  540.  
  541.     format_duration_short: function(ut) {
  542.        
  543.         if (ut < 60) {
  544.             return ut.toString() + 's';
  545.         }
  546.  
  547.         if (ut < 3600) {
  548.             var mins = ut / 60;
  549.             return mins.toFixed(0) + 'm';
  550.         }
  551.  
  552.         if (ut < 86400) {
  553.             var hours = ut / 3600;
  554.             return hours.toFixed(0) + 'h';
  555.         }
  556.  
  557.         var days = ut / 86400;
  558.         return days.toFixed(0) + 'd';  
  559.     },
  560.  
  561.     yesText: gettext('Yes'),
  562.     noText: gettext('No'),
  563.     errorText: gettext('Error'),
  564.     unknownText: gettext('Unknown'),
  565.     defaultText: gettext('Default'),
  566.     daysText: gettext('days'),
  567.     dayText: gettext('day'),
  568.     runningText: gettext('running'),
  569.     stoppedText: gettext('stopped'),
  570.     neverText: gettext('never'),
  571.  
  572.     format_expire: function(date) {
  573.         if (!date) {
  574.             return PVE.Utils.neverText;
  575.         }
  576.         return Ext.Date.format(date, "Y-m-d");
  577.     },
  578.  
  579.     format_storage_type: function(value) {
  580.         if (value === 'dir') {
  581.             return 'Directory';
  582.         } else if (value === 'nfs') {
  583.             return 'NFS';
  584.         } else if (value === 'lvm') {
  585.             return 'LVM';
  586.         } else if (value === 'iscsi') {
  587.             return 'iSCSI';
  588.         } else {
  589.             return PVE.Utils.unknownText;
  590.         }
  591.     },
  592.  
  593.     format_boolean_with_default: function(value) {
  594.         if (Ext.isDefined(value) && value !== '') {
  595.             return value ? PVE.Utils.yesText : PVE.Utils.noText;
  596.         }
  597.         return PVE.Utils.defaultText;
  598.     },
  599.  
  600.     format_boolean: function(value) {
  601.         return value ? PVE.Utils.yesText : PVE.Utils.noText;
  602.     },
  603.  
  604.     format_neg_boolean: function(value) {
  605.         return !value ? PVE.Utils.yesText : PVE.Utils.noText;
  606.     },
  607.  
  608.     format_content_types: function(value) {
  609.         var cta = [];
  610.  
  611.         Ext.each(value.split(',').sort(), function(ct) {
  612.             if (ct === 'images') {
  613.                 cta.push('Images');
  614.             } else if (ct === 'backup') {
  615.                 cta.push('Backups');
  616.             } else if (ct === 'vztmpl') {
  617.                 cta.push('Templates');
  618.             } else if (ct === 'iso') {
  619.                 cta.push('ISO');
  620.             } else if (ct === 'rootdir') {
  621.                 cta.push('Containers');
  622.             }
  623.         });
  624.  
  625.         return cta.join(', ');
  626.     },
  627.  
  628.     render_storage_content: function(value, metaData, record) {
  629.         var data = record.data;
  630.         if (Ext.isNumber(data.channel) &&
  631.             Ext.isNumber(data.id) &&
  632.             Ext.isNumber(data.lun)) {
  633.             return "CH " +
  634.                 Ext.String.leftPad(data.channel,2, '0') +
  635.                 " ID " + data.id + " LUN " + data.lun;
  636.         }
  637.         return data.volid.replace(/^.*:(.*\/)?/,'');
  638.     },
  639.  
  640.     render_serverity: function (value) {
  641.         return PVE.Utils.log_severity_hash[value] || value;
  642.     },
  643.  
  644.     render_cpu: function(value, metaData, record, rowIndex, colIndex, store) {
  645.  
  646.         if (!(record.data.uptime && Ext.isNumeric(value))) {
  647.             return '';
  648.         }
  649.  
  650.         var maxcpu = record.data.maxcpu || 1;
  651.  
  652.         if (!Ext.isNumeric(maxcpu) && (maxcpu >= 1)) {
  653.             return '';
  654.         }
  655.        
  656.         var per = value * 100;
  657.  
  658.         return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
  659.     },
  660.  
  661.     render_size: function(value, metaData, record, rowIndex, colIndex, store) {
  662.         /*jslint confusion: true */
  663.  
  664.         if (!Ext.isNumeric(value)) {
  665.             return '';
  666.         }
  667.  
  668.         return PVE.Utils.format_size(value);
  669.     },
  670.  
  671.     render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
  672.         var servertime = new Date(value * 1000);
  673.         return Ext.Date.format(servertime, 'Y-m-d H:i:s');
  674.     },
  675.  
  676.     render_mem_usage: function(value, metaData, record, rowIndex, colIndex, store) {
  677.  
  678.         var mem = value;
  679.         var maxmem = record.data.maxmem;
  680.        
  681.         if (!record.data.uptime) {
  682.             return '';
  683.         }
  684.  
  685.         if (!(Ext.isNumeric(mem) && maxmem)) {
  686.             return '';
  687.         }
  688.  
  689.         var per = (mem * 100) / maxmem;
  690.  
  691.         return per.toFixed(1) + '%';
  692.     },
  693.  
  694.     render_disk_usage: function(value, metaData, record, rowIndex, colIndex, store) {
  695.  
  696.         var disk = value;
  697.         var maxdisk = record.data.maxdisk;
  698.  
  699.         if (!(Ext.isNumeric(disk) && maxdisk)) {
  700.             return '';
  701.         }
  702.  
  703.         var per = (disk * 100) / maxdisk;
  704.  
  705.         return per.toFixed(1) + '%';
  706.     },
  707.  
  708.     render_resource_type: function(value, metaData, record, rowIndex, colIndex, store) {
  709.  
  710.         var cls = 'pve-itype-icon-' + value;
  711.  
  712.         if (record.data.running) {
  713.             metaData.tdCls = cls + "-running";
  714.         } else {
  715.             metaData.tdCls = cls;
  716.         }
  717.  
  718.         return value;
  719.     },
  720.  
  721.     render_uptime: function(value, metaData, record, rowIndex, colIndex, store) {
  722.  
  723.         var uptime = value;
  724.  
  725.         if (uptime === undefined) {
  726.             return '';
  727.         }
  728.        
  729.         if (uptime <= 0) {
  730.             return '-';
  731.         }
  732.  
  733.         return PVE.Utils.format_duration_long(uptime);
  734.     },
  735.  
  736.     render_support_level: function(value, metaData, record) {
  737.         return PVE.Utils.support_level_hash[value] || '-';
  738.     },
  739.  
  740.     render_upid: function(value, metaData, record) {
  741.         var type = record.data.type;
  742.         var id = record.data.id;
  743.  
  744.         return PVE.Utils.format_task_description(type, id);
  745.     },
  746.  
  747.     dialog_title: function(subject, create, isAdd) {
  748.         if (create) {
  749.             if (isAdd) {
  750.                 return gettext('Add') + ': ' + subject;
  751.             } else {
  752.                 return gettext('Create') + ': ' + subject;
  753.             }
  754.         } else {
  755.             return gettext('Edit') + ': ' + subject;
  756.         }
  757.     },
  758.  
  759.     openConoleWindow: function(vmtype, vmid, nodename, vmname) {
  760.         var url = Ext.urlEncode({
  761.             console: vmtype, // kvm, openvz or shell
  762.             vmid: vmid,
  763.             vmname: vmname,
  764.             node: nodename
  765.         });
  766.         var nw = window.open("?" + url, '_blank',
  767.                              "innerWidth=745,innerheight=427");
  768.         nw.focus();
  769.     },
  770.  
  771.     // comp.setLoading() is buggy in ExtJS 4.0.7, so we
  772.     // use el.mask() instead
  773.     setErrorMask: function(comp, msg) {
  774.         var el = comp.el;
  775.         if (!el) {
  776.             return;
  777.         }
  778.         if (!msg) {
  779.             el.unmask();
  780.         } else {
  781.             if (msg === true) {
  782.                 el.mask(gettext("Loading..."));
  783.             } else {
  784.                 el.mask(msg);
  785.             }
  786.         }
  787.     },
  788.  
  789.     monStoreErrors: function(me, store) {
  790.         me.mon(store, 'beforeload', function(s, operation, eOpts) {
  791.             if (!me.loadCount) {
  792.                 me.loadCount = 0; // make sure it is numeric
  793.                 PVE.Utils.setErrorMask(me, true);
  794.             }
  795.         });
  796.  
  797.         // only works with 'pve' proxy
  798.         me.mon(store.proxy, 'afterload', function(proxy, request, success) {
  799.             me.loadCount++;
  800.  
  801.             if (success) {
  802.                 PVE.Utils.setErrorMask(me, false);
  803.                 return;
  804.             }
  805.  
  806.             var msg;
  807.             var operation = request.operation;
  808.             var error = operation.getError();
  809.             if (error.statusText) {
  810.                 msg = error.statusText + ' (' + error.status + ')';
  811.             } else {
  812.                 msg = gettext('Connection error');
  813.             }
  814.             PVE.Utils.setErrorMask(me, msg);
  815.         });
  816.     }
  817.  
  818. }});
  819.  
  820. // Some configuration values are complex strings -
  821. // so we need parsers/generators for them.
  822.  
  823. Ext.define('PVE.Parser', { statics: {
  824.  
  825.     // this class only contains static functions
  826.  
  827.     parseQemuNetwork: function(key, value) {
  828.         if (!(key && value)) {
  829.             return;
  830.         }
  831.  
  832.         var res = {};
  833.  
  834.         var errors = false;
  835.         Ext.Array.each(value.split(','), function(p) {
  836.             if (!p || p.match(/^\s*$/)) {
  837.                 return; // continue
  838.             }
  839.  
  840.             var match_res;
  841.  
  842.             if ((match_res = p.match(/^(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i)) !== null) {
  843.                 res.model = match_res[1].toLowerCase();
  844.                 if (match_res[3]) {
  845.                     res.macaddr = match_res[3];
  846.                 }
  847.             } else if ((match_res = p.match(/^bridge=(\S+)$/)) !== null) {
  848.                 res.bridge = match_res[1];
  849.             } else if ((match_res = p.match(/^rate=(\d+(\.\d+)?)$/)) !== null) {
  850.                 res.rate = match_res[1];
  851.             } else if ((match_res = p.match(/^tag=(\d+(\.\d+)?)$/)) !== null) {
  852.                 res.tag = match_res[1];
  853.             } else {
  854.                 errors = true;
  855.                 return false; // break
  856.             }
  857.         });
  858.  
  859.         if (errors || !res.model) {
  860.             return;
  861.         }
  862.  
  863.         return res;
  864.     },
  865.  
  866.     printQemuNetwork: function(net) {
  867.  
  868.         var netstr = net.model;
  869.         if (net.macaddr) {
  870.             netstr += "=" + net.macaddr;
  871.         }
  872.         if (net.bridge) {
  873.             netstr += ",bridge=" + net.bridge;
  874.             if (net.tag) {
  875.                 netstr += ",tag=" + net.tag;
  876.             }
  877.         }
  878.         if (net.rate) {
  879.             netstr += ",rate=" + net.rate;
  880.         }
  881.         return netstr;
  882.     },
  883.  
  884.     parseQemuDrive: function(key, value) {
  885.         if (!(key && value)) {
  886.             return;
  887.         }
  888.  
  889.         var res = {};
  890.  
  891.         var match_res = key.match(/^([a-z]+)(\d+)$/);
  892.         if (!match_res) {
  893.             return;
  894.         }
  895.         res['interface'] = match_res[1];
  896.         res.index = match_res[2];
  897.  
  898.         var errors = false;
  899.         Ext.Array.each(value.split(','), function(p) {
  900.             if (!p || p.match(/^\s*$/)) {
  901.                 return; // continue
  902.             }
  903.             var match_res = p.match(/^([a-z]+)=(\S+)$/);
  904.             if (!match_res) {
  905.                 if (!p.match(/\=/)) {
  906.                     res.file = p;
  907.                     return; // continue
  908.                 }
  909.                 errors = true;
  910.                 return false; // break
  911.             }
  912.             var k = match_res[1];
  913.             if (k === 'volume') {
  914.                 k = 'file';
  915.             }
  916.  
  917.             if (Ext.isDefined(res[k])) {
  918.                 errors = true;
  919.                 return false; // break
  920.             }
  921.  
  922.             var v = match_res[2];
  923.            
  924.             if (k === 'cache' && v === 'off') {
  925.                 v = 'none';
  926.             }
  927.  
  928.             res[k] = v;
  929.         });
  930.  
  931.         if (errors || !res.file) {
  932.             return;
  933.         }
  934.  
  935.         return res;
  936.     },
  937.  
  938.     printQemuDrive: function(drive) {
  939.  
  940.         var drivestr = drive.file;
  941.  
  942.         Ext.Object.each(drive, function(key, value) {
  943.             if (!Ext.isDefined(value) || key === 'file' ||
  944.                 key === 'index' || key === 'interface') {
  945.                 return; // continue
  946.             }
  947.             drivestr += ',' + key + '=' + value;
  948.         });
  949.  
  950.         return drivestr;
  951.     },
  952.  
  953.     parseOpenVZNetIf: function(value) {
  954.         if (!value) {
  955.             return;
  956.         }
  957.  
  958.         var res = {};
  959.  
  960.         var errors = false;
  961.         Ext.Array.each(value.split(';'), function(item) {
  962.             if (!item || item.match(/^\s*$/)) {
  963.                 return; // continue
  964.             }
  965.  
  966.             var data = {};
  967.             Ext.Array.each(item.split(','), function(p) {
  968.                 if (!p || p.match(/^\s*$/)) {
  969.                     return; // continue
  970.                 }
  971.                 var match_res = p.match(/^(ifname|mac|bridge|host_ifname|host_mac)=(\S+)$/);
  972.                 if (!match_res) {
  973.                     errors = true;
  974.                     return false; // break
  975.                 }
  976.                 data[match_res[1]] = match_res[2];
  977.             });
  978.  
  979.             if (errors || !data.ifname) {
  980.                 errors = true;
  981.                 return false; // break
  982.             }
  983.  
  984.             data.raw = item;
  985.  
  986.             res[data.ifname] = data;
  987.         });
  988.  
  989.         return errors ? undefined: res;
  990.     },
  991.  
  992.     printOpenVZNetIf: function(netif) {
  993.         var netarray = [];
  994.  
  995.         Ext.Object.each(netif, function(iface, data) {
  996.             var tmparray = [];
  997.             Ext.Array.each(['ifname', 'mac', 'bridge', 'host_ifname' , 'host_mac'], function(key) {
  998.                 var value = data[key];
  999.                 if (value) {
  1000.                     tmparray.push(key + '=' + value);
  1001.                 }
  1002.             });
  1003.             netarray.push(tmparray.join(','));
  1004.         });
  1005.  
  1006.         return netarray.join(';');
  1007.     }
  1008. }});
  1009. /* This state provider keeps part of the state inside
  1010.  * the browser history.
  1011.  *
  1012.  * We compress (shorten) url using dictionary based compression
  1013.  * i.e. use column separated list instead of url encoded hash:
  1014.  * #v\d*       version/format
  1015.  * :=          indicates string values
  1016.  * :\d+        lookup value in dictionary hash
  1017.  * #v1:=value1:5:=value2:=value3:...
  1018. */
  1019.  
  1020. Ext.define('PVE.StateProvider', {
  1021.     extend: 'Ext.state.LocalStorageProvider',
  1022.  
  1023.     // private
  1024.     setHV: function(name, newvalue, fireEvents) {
  1025.         var me = this;
  1026.  
  1027.         var changes = false;
  1028.         var oldtext = Ext.encode(me.UIState[name]);
  1029.         var newtext = Ext.encode(newvalue);
  1030.         if (newtext != oldtext) {
  1031.             changes = true;
  1032.             me.UIState[name] = newvalue;
  1033.             //console.log("changed old " + name + " " + oldtext);
  1034.             //console.log("changed new " + name + " " + newtext);
  1035.             if (fireEvents) {
  1036.                 me.fireEvent("statechange", me, name, { value: newvalue });
  1037.             }
  1038.         }
  1039.         return changes;
  1040.     },
  1041.  
  1042.     // private
  1043.     hslist: [
  1044.         // order is important for notifications
  1045.         // [ name, default ]
  1046.         ['view', 'server'],
  1047.         ['rid', 'root'],
  1048.         ['ltab', 'tasks'],
  1049.         ['nodetab', ''],
  1050.         ['storagetab', ''],
  1051.         ['pooltab', ''],
  1052.         ['kvmtab', ''],
  1053.         ['ovztab', ''],
  1054.         ['dctab', '']
  1055.     ],
  1056.  
  1057.     hprefix: 'v1',
  1058.  
  1059.     compDict: {
  1060.         pool: 26,
  1061.         syslog: 25,
  1062.         ubc: 24,
  1063.         initlog: 23,
  1064.         openvz: 22,
  1065.         backup: 21,
  1066.         ressources: 20,
  1067.         content: 19,
  1068.         root: 18,
  1069.         domains: 17,
  1070.         roles: 16,
  1071.         groups: 15,
  1072.         users: 14,
  1073.         time: 13,
  1074.         dns: 12,
  1075.         network: 11,
  1076.         services: 10,
  1077.         options: 9,
  1078.         console: 8,
  1079.         hardware: 7,
  1080.         permissions: 6,
  1081.         summary: 5,
  1082.         tasks: 4,
  1083.         clog: 3,
  1084.         storage: 2,
  1085.         folder: 1,
  1086.         server: 0
  1087.     },
  1088.  
  1089.     decodeHToken: function(token) {
  1090.         var me = this;
  1091.  
  1092.         var state = {};
  1093.         if (!token) {
  1094.             Ext.Array.each(me.hslist, function(rec) {
  1095.                 state[rec[0]] = rec[1];
  1096.             });
  1097.             return state;
  1098.         }
  1099.  
  1100.         // return Ext.urlDecode(token);
  1101.  
  1102.         var items = token.split(':');
  1103.         var prefix = items.shift();
  1104.  
  1105.         if (prefix != me.hprefix) {
  1106.             return me.decodeHToken();
  1107.         }
  1108.  
  1109.         Ext.Array.each(me.hslist, function(rec) {
  1110.             var value = items.shift();
  1111.             if (value) {
  1112.                 if (value[0] === '=') {
  1113.                     value = decodeURIComponent(value.slice(1));
  1114.                 } else {
  1115.                     Ext.Object.each(me.compDict, function(key, cv) {
  1116.                         if (value == cv) {
  1117.                             value = key;
  1118.                             return false;
  1119.                         }
  1120.                     });
  1121.                 }
  1122.             }
  1123.             state[rec[0]] = value;
  1124.         });
  1125.  
  1126.         return state;
  1127.     },
  1128.  
  1129.     encodeHToken: function(state) {
  1130.         var me = this;
  1131.  
  1132.         // return Ext.urlEncode(state);
  1133.  
  1134.         var ctoken = me.hprefix;
  1135.         Ext.Array.each(me.hslist, function(rec) {
  1136.             var value = state[rec[0]];
  1137.             if (!Ext.isDefined(value)) {
  1138.                 value = rec[1];
  1139.             }
  1140.             value = encodeURIComponent(value);
  1141.             if (!value) {
  1142.                 ctoken += ':';
  1143.             } else {
  1144.                 var comp = me.compDict[value];
  1145.                 if (Ext.isDefined(comp)) {
  1146.                     ctoken += ":" + comp;
  1147.                 } else {
  1148.                     ctoken += ":=" + value;
  1149.                 }
  1150.             }
  1151.         });
  1152.  
  1153.         return ctoken;
  1154.     },
  1155.  
  1156.     constructor: function(config){
  1157.         var me = this;
  1158.  
  1159.         me.callParent([config]);
  1160.  
  1161.         me.UIState = me.decodeHToken(); // set default
  1162.  
  1163.         var history_change_cb = function(token) {
  1164.             //console.log("HC " + token);
  1165.             if (!token) {
  1166.                 var res = window.confirm('Are you sure you want to navigate away from this page?');
  1167.                 if (res){
  1168.                     // process text value and close...
  1169.                     Ext.History.back();
  1170.                 } else {
  1171.                     Ext.History.forward();
  1172.                 }
  1173.                 return;
  1174.             }
  1175.  
  1176.             var newstate = me.decodeHToken(token);
  1177.             Ext.Array.each(me.hslist, function(rec) {
  1178.                 if (typeof newstate[rec[0]] == "undefined") {
  1179.                     return;
  1180.                 }
  1181.                 me.setHV(rec[0], newstate[rec[0]], true);
  1182.             });
  1183.         };
  1184.  
  1185.         var start_token = Ext.History.getToken();
  1186.         if (start_token) {
  1187.             history_change_cb(start_token);
  1188.         } else {
  1189.             var htext = me.encodeHToken(me.UIState);
  1190.             Ext.History.add(htext);
  1191.         }
  1192.  
  1193.         Ext.History.on('change', history_change_cb);
  1194.     },
  1195.  
  1196.     get: function(name, defaultValue){
  1197.         var me = this;
  1198.         var data;
  1199.  
  1200.         if (typeof me.UIState[name] != "undefined") {
  1201.             data = { value: me.UIState[name] };
  1202.         } else {
  1203.             data = me.callParent(arguments);
  1204.         }
  1205.  
  1206.         //console.log("GET " + name + " " + Ext.encode(data));
  1207.         return data;
  1208.     },
  1209.  
  1210.     clear: function(name){
  1211.         var me = this;
  1212.  
  1213.         if (typeof me.UIState[name] != "undefined") {
  1214.             me.UIState[name] = null;
  1215.         }
  1216.  
  1217.         me.callParent(arguments);
  1218.     },
  1219.  
  1220.     set: function(name, value){
  1221.         var me = this;
  1222.  
  1223.         //console.log("SET " + name + " " + Ext.encode(value));
  1224.         if (typeof me.UIState[name] != "undefined") {
  1225.             var newvalue = value ? value.value : null;
  1226.             if (me.setHV(name, newvalue, false)) {
  1227.                 var htext = me.encodeHToken(me.UIState);
  1228.                 Ext.History.add(htext);
  1229.             }
  1230.         } else {
  1231.             me.callParent(arguments);
  1232.         }
  1233.     }
  1234. });/* Button features:
  1235.  * - observe selection changes to enable/disable the button using enableFn()
  1236.  * - pop up confirmation dialog using confirmMsg()
  1237.  */
  1238. Ext.define('PVE.button.Button', {
  1239.     extend: 'Ext.button.Button',
  1240.     alias: 'widget.pveButton',
  1241.  
  1242.     // the selection model to observe
  1243.     selModel: undefined,
  1244.  
  1245.     // if 'false' handler will not be called (button disabled)
  1246.     enableFn: function(record) { },
  1247.  
  1248.     // function(record) or text
  1249.     confirmMsg: false,
  1250.  
  1251.     initComponent: function() {
  1252.         /*jslint confusion: true */
  1253.  
  1254.         var me = this;
  1255.  
  1256.         if (me.handler) {
  1257.             me.realHandler = me.handler;
  1258.  
  1259.             me.handler = function(button, event) {
  1260.                 var rec, msg;
  1261.                 if (me.selModel) {
  1262.                     rec = me.selModel.getSelection()[0];
  1263.                     if (!rec || (me.enableFn(rec) === false)) {
  1264.                         return;
  1265.                     }
  1266.                 }
  1267.  
  1268.                 if (me.confirmMsg) {
  1269.                     msg = me.confirmMsg;
  1270.                     if (Ext.isFunction(me.confirmMsg)) {
  1271.                         msg = me.confirmMsg(rec);
  1272.                     }
  1273.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1274.                         if (btn !== 'yes') {
  1275.                             return;
  1276.                         }
  1277.                         me.realHandler(button, event, rec);
  1278.                     });
  1279.                 } else {
  1280.                     me.realHandler(button, event, rec);
  1281.                 }
  1282.             };
  1283.         }
  1284.  
  1285.         me.callParent();
  1286.  
  1287.         if (me.selModel) {
  1288.  
  1289.             me.mon(me.selModel, "selectionchange", function() {
  1290.                 var rec = me.selModel.getSelection()[0];
  1291.                 if (!rec || (me.enableFn(rec) === false)) {
  1292.                     me.setDisabled(true);
  1293.                 } else  {
  1294.                     me.setDisabled(false);
  1295.                 }
  1296.             });
  1297.         }
  1298.     }
  1299. });
  1300. Ext.define('PVE.qemu.SendKeyMenu', {
  1301.     extend: 'Ext.button.Button',
  1302.     alias: ['widget.pveQemuSendKeyMenu'],
  1303.  
  1304.     initComponent : function() {
  1305.         var me = this;
  1306.  
  1307.         if (!me.nodename) {
  1308.             throw "no node name specified";
  1309.         }
  1310.  
  1311.         if (!me.vmid) {
  1312.             throw "no VM ID specified";
  1313.         }
  1314.  
  1315.         var sendKey = function(key) {
  1316.             PVE.Utils.API2Request({
  1317.                 params: { key: key },
  1318.                 url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/sendkey",
  1319.                 method: 'PUT',
  1320.                 waitMsgTarget: me,
  1321.                 failure: function(response, opts) {
  1322.                     Ext.Msg.alert('Error', response.htmlStatus);
  1323.                 }
  1324.             });
  1325.         };
  1326.  
  1327.         Ext.apply(me, {
  1328.             text: 'SendKey',
  1329.             menu: new Ext.menu.Menu({
  1330.                 height: 200,
  1331.                 items: [
  1332.                     {
  1333.                         text: 'Ctrl-Alt-Delete', handler: function() {
  1334.                             sendKey('ctrl-alt-delete');  
  1335.                         }
  1336.                     },
  1337.                     {
  1338.                         text: 'Ctrl-Alt-Backspace', handler: function() {
  1339.                             sendKey('ctrl-alt-backspace');  
  1340.                     }
  1341.                     },
  1342.                     {
  1343.                         text: 'Ctrl-Alt-F1', handler: function() {
  1344.                             sendKey('ctrl-alt-f1');
  1345.                         }
  1346.                     },
  1347.                     {
  1348.                         text: 'Ctrl-Alt-F2', handler: function() {
  1349.                             sendKey('ctrl-alt-f2');
  1350.                         }
  1351.                     },
  1352.                     {
  1353.                         text: 'Ctrl-Alt-F3', handler: function() {
  1354.                             sendKey('ctrl-alt-f3');
  1355.                         }
  1356.                     },
  1357.                     {
  1358.                         text: 'Ctrl-Alt-F4', handler: function() {
  1359.                         sendKey('ctrl-alt-f4');
  1360.                         }
  1361.                     },
  1362.                     {
  1363.                         text: 'Ctrl-Alt-F5', handler: function() {
  1364.                             sendKey('ctrl-alt-f5');
  1365.                         }
  1366.                     },
  1367.                     {
  1368.                         text: 'Ctrl-Alt-F6', handler: function() {
  1369.                             sendKey('ctrl-alt-f6');
  1370.                         }
  1371.                     },
  1372.                     {
  1373.                         text: 'Ctrl-Alt-F7', handler: function() {
  1374.                             sendKey('ctrl-alt-f7');
  1375.                         }
  1376.                     },
  1377.                     {
  1378.                         text: 'Ctrl-Alt-F8', handler: function() {
  1379.                             sendKey('ctrl-alt-f8');
  1380.                         }
  1381.                     },
  1382.                     {
  1383.                         text: 'Ctrl-Alt-F9', handler: function() {
  1384.                             sendKey('ctrl-alt-f9');
  1385.                         }
  1386.                     },
  1387.                     {
  1388.                         text: 'Ctrl-Alt-F10', handler: function() {
  1389.                             sendKey('ctrl-alt-f10');
  1390.                         }
  1391.                     },
  1392.                     {
  1393.                         text: 'Ctrl-Alt-F11', handler: function() {
  1394.                             sendKey('ctrl-alt-f11');
  1395.                         }
  1396.                     },
  1397.                     {
  1398.                         text: 'Ctrl-Alt-F12', handler: function() {
  1399.                             sendKey('ctrl-alt-f12');
  1400.                         }
  1401.                     }
  1402.                 ]
  1403.             })
  1404.         });
  1405.  
  1406.         me.callParent();
  1407.     }
  1408. });
  1409. Ext.define('PVE.qemu.CmdMenu', {
  1410.     extend: 'Ext.menu.Menu',
  1411.  
  1412.     initComponent: function() {
  1413.         var me = this;
  1414.  
  1415.         var nodename = me.pveSelNode.data.node;
  1416.         if (!nodename) {
  1417.             throw "no node name specified";
  1418.         }
  1419.  
  1420.         var vmid = me.pveSelNode.data.vmid;
  1421.         if (!vmid) {
  1422.             throw "no VM ID specified";
  1423.         }
  1424.  
  1425.         var vmname = me.pveSelNode.data.name;
  1426.  
  1427.         var vm_command = function(cmd, params) {
  1428.             PVE.Utils.API2Request({
  1429.                 params: params,
  1430.                 url: '/nodes/' + nodename + '/qemu/' + vmid + "/status/" + cmd,
  1431.                 method: 'POST',
  1432.                 failure: function(response, opts) {
  1433.                     Ext.Msg.alert('Error', response.htmlStatus);
  1434.                 }
  1435.             });
  1436.         };
  1437.  
  1438.         me.title = "VM " + vmid;
  1439.  
  1440.         me.items = [
  1441.             {
  1442.                 text: gettext('Start'),
  1443.                 icon: '/pve2/images/start.png',
  1444.                 handler: function() {
  1445.                     vm_command('start');
  1446.                 }
  1447.             },
  1448.             {
  1449.                 text: gettext('Migrate'),
  1450.                 icon: '/pve2/images/forward.png',
  1451.                 handler: function() {
  1452.                     var win = Ext.create('PVE.window.Migrate', {
  1453.                         vmtype: 'qemu',
  1454.                         nodename: nodename,
  1455.                         vmid: vmid
  1456.                     });
  1457.                     win.show();
  1458.                 }
  1459.             },
  1460.             {
  1461.                 text: gettext('Shutdown'),
  1462.                 icon: '/pve2/images/stop.png',
  1463.                 handler: function() {
  1464.                     var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid);
  1465.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1466.                         if (btn !== 'yes') {
  1467.                             return;
  1468.                         }
  1469.  
  1470.                         vm_command('shutdown', { timeout: 30 });
  1471.                     });
  1472.                 }                          
  1473.             },
  1474.             {
  1475.                 text: gettext('Stop'),
  1476.                 icon: '/pve2/images/gtk-stop.png',
  1477.                 handler: function() {
  1478.                     var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid);
  1479.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1480.                         if (btn !== 'yes') {
  1481.                             return;
  1482.                         }
  1483.  
  1484.                         vm_command("stop", { timeout: 30 });
  1485.                     });            
  1486.                 }
  1487.             },
  1488.             {
  1489.                 text: gettext('Console'),
  1490.                 icon: '/pve2/images/display.png',
  1491.                 handler: function() {
  1492.                     PVE.Utils.openConoleWindow('kvm', vmid, nodename, vmname);
  1493.                 }
  1494.             }
  1495.         ];
  1496.  
  1497.         me.callParent();
  1498.     }
  1499. });
  1500. Ext.define('PVE.openvz.CmdMenu', {
  1501.     extend: 'Ext.menu.Menu',
  1502.  
  1503.     initComponent: function() {
  1504.         var me = this;
  1505.  
  1506.         var nodename = me.pveSelNode.data.node;
  1507.         if (!nodename) {
  1508.             throw "no node name specified";
  1509.         }
  1510.  
  1511.         var vmid = me.pveSelNode.data.vmid;
  1512.         if (!vmid) {
  1513.             throw "no VM ID specified";
  1514.         }
  1515.  
  1516.         var vmname = me.pveSelNode.data.name;
  1517.  
  1518.         var vm_command = function(cmd, params) {
  1519.             PVE.Utils.API2Request({
  1520.                 params: params,
  1521.                 url: '/nodes/' + nodename + '/openvz/' + vmid + "/status/" + cmd,
  1522.                 method: 'POST',
  1523.                 failure: function(response, opts) {
  1524.                     Ext.Msg.alert('Error', response.htmlStatus);
  1525.                 }
  1526.             });
  1527.         };
  1528.  
  1529.         me.title = "CT " + vmid;
  1530.  
  1531.         me.items = [
  1532.             {
  1533.                 text: gettext('Start'),
  1534.                 icon: '/pve2/images/start.png',
  1535.                 handler: function() {
  1536.                     vm_command('start');
  1537.                 }
  1538.             },
  1539.             {
  1540.                 text: gettext('Migrate'),
  1541.                 icon: '/pve2/images/forward.png',
  1542.                 handler: function() {
  1543.                     var win = Ext.create('PVE.window.Migrate', {
  1544.                         vmtype: 'openvz',
  1545.                         nodename: nodename,
  1546.                         vmid: vmid
  1547.                     });
  1548.                     win.show();
  1549.                 }
  1550.             },
  1551.             {
  1552.                 text: gettext('Shutdown'),
  1553.                 icon: '/pve2/images/stop.png',
  1554.                 handler: function() {
  1555.                     var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid);
  1556.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1557.                         if (btn !== 'yes') {
  1558.                             return;
  1559.                         }
  1560.  
  1561.                         vm_command('shutdown');
  1562.                     });
  1563.                 }                          
  1564.             },
  1565.             {
  1566.                 text: gettext('Stop'),
  1567.                 icon: '/pve2/images/gtk-stop.png',
  1568.                 handler: function() {
  1569.                     var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid);
  1570.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1571.                         if (btn !== 'yes') {
  1572.                             return;
  1573.                         }
  1574.  
  1575.                         vm_command("stop");
  1576.                     });
  1577.                 }
  1578.             },
  1579.             {
  1580.                 text: gettext('Console'),
  1581.                 icon: '/pve2/images/display.png',
  1582.                 handler: function() {
  1583.                     PVE.Utils.openConoleWindow('openvz', vmid, nodename, vmname);
  1584.                 }
  1585.             }
  1586.         ];
  1587.  
  1588.         me.callParent();
  1589.     }
  1590. });
  1591. PVE_vnc_console_event = function(appletid, action, err) {
  1592.     //console.log("TESTINIT param1 " + appletid + " action " + action);
  1593.  
  1594.     if (action === "error") {
  1595.         var compid = appletid.replace("-vncapp", "");
  1596.         var comp = Ext.getCmp(compid);
  1597.  
  1598.         if (!comp || !comp.vmid || !comp.toplevel) {
  1599.             return;
  1600.         }
  1601.  
  1602.         // try to detect migrated VM
  1603.         PVE.Utils.API2Request({
  1604.             url: '/cluster/resources',
  1605.             method: 'GET',
  1606.             success: function(response) {
  1607.                 var list = response.result.data;
  1608.                 Ext.Array.each(list, function(item) {
  1609.                     if (item.type === 'qemu' && item.vmid == comp.vmid) {
  1610.                         if (item.node !== comp.nodename) {
  1611.                             //console.log("MOVED VM to node " + item.node);
  1612.                             comp.nodename = item.node;
  1613.                             comp.url = "/nodes/" + comp.nodename + "/" + item.type + "/" + comp.vmid + "/vncproxy";
  1614.                             //console.log("NEW URL " + comp.url);
  1615.                             comp.reloadApplet();
  1616.                         }
  1617.                         return false; // break
  1618.                     }
  1619.                 });
  1620.             }
  1621.         });
  1622.     }
  1623.  
  1624.     return;
  1625.     /*
  1626.       var el = Ext.get(appletid);
  1627.       if (!el)
  1628.       return;
  1629.  
  1630.       if (action === "close") {
  1631.       //        el.remove();
  1632.       } else if (action === "error") {
  1633.       //        console.log("TESTERROR: " + err);
  1634.       //        var compid = appletid.replace("-vncapp", "");
  1635.       //        var comp = Ext.getCmp(compid);
  1636.       }
  1637.  
  1638.       //Ext.get('mytestid').remove();
  1639.       */
  1640.  
  1641. };
  1642.  
  1643. Ext.define('PVE.VNCConsole', {
  1644.     extend: 'Ext.panel.Panel',
  1645.     alias: ['widget.pveVNCConsole'],
  1646.  
  1647.     initComponent : function() {
  1648.         var me = this;
  1649.  
  1650.         if (!me.url) {
  1651.             throw "no url specified";
  1652.         }
  1653.  
  1654.         var myid = me.id + "-vncapp";
  1655.  
  1656.         me.appletID = myid;
  1657.  
  1658.         var box = Ext.create('Ext.Component', {
  1659.             border: false,
  1660.             html: ""
  1661.         });
  1662.  
  1663.         var resize_window = function() {
  1664.             //console.log("resize");
  1665.  
  1666.             var applet = Ext.getDom(myid);
  1667.             //console.log("resize " + myid + " " + applet);
  1668.            
  1669.             // try again when dom element is available
  1670.             if (!(applet && Ext.isFunction(applet.getPreferredSize))) {
  1671.                 return Ext.Function.defer(resize_window, 1000);
  1672.             }
  1673.  
  1674.             var tbar = me.getDockedItems("[dock=top]")[0];
  1675.             var tbh = tbar ? tbar.getHeight() : 0;
  1676.             var ps = applet.getPreferredSize();
  1677.             var aw = ps.width;
  1678.             var ah = ps.height;
  1679.  
  1680.             if (aw < 640) { aw = 640; }
  1681.             if (ah < 400) { ah = 400; }
  1682.  
  1683.             var oh;
  1684.             var ow;
  1685.  
  1686.             //console.log("size0 " + aw + " " + ah + " tbh " + tbh);
  1687.  
  1688.             if (window.innerHeight) {
  1689.                 oh = window.innerHeight;
  1690.                 ow = window.innerWidth;
  1691.             } else if (document.documentElement &&
  1692.                        document.documentElement.clientHeight) {
  1693.                 oh = document.documentElement.clientHeight;
  1694.                 ow = document.documentElement.clientWidth;
  1695.             } else if (document.body) {
  1696.                 oh = document.body.clientHeight;
  1697.                 ow = document.body.clientWidth;
  1698.             }  else {
  1699.                 throw "can't get window size";
  1700.             }
  1701.  
  1702.             Ext.fly(applet).setSize(aw, ah + tbh);
  1703.  
  1704.             var offsetw = aw - ow;
  1705.             var offseth = ah + tbh - oh;
  1706.  
  1707.             if (offsetw !== 0 || offseth !== 0) {
  1708.                 //console.log("try resize by " + offsetw + " " + offseth);
  1709.                 try { window.resizeBy(offsetw, offseth); } catch (e) {}
  1710.             }
  1711.  
  1712.             Ext.Function.defer(resize_window, 1000);
  1713.         };
  1714.  
  1715.         var resize_box = function() {
  1716.             var applet = Ext.getDom(myid);
  1717.  
  1718.             if ((applet && Ext.isFunction(applet.getPreferredSize))) {
  1719.                 var ps = applet.getPreferredSize();
  1720.                 Ext.fly(applet).setSize(ps.width, ps.height);
  1721.             }
  1722.  
  1723.             Ext.Function.defer(resize_box, 1000);
  1724.         };
  1725.  
  1726.         var start_vnc_viewer = function(param) {
  1727.             var cert = param.cert;
  1728.             cert = cert.replace(/\n/g, "|");
  1729.  
  1730.             box.update({
  1731.                 id: myid,
  1732.                 border: false,
  1733.                 tag: 'applet',
  1734.                 code: 'com.tigervnc.vncviewer.VncViewer',
  1735.                 archive: '/vncterm/VncViewer.jar',
  1736.                 // NOTE: set size to '100%' -  else resize does not work
  1737.                 width: "100%",
  1738.                 height: "100%",
  1739.                 cn: [
  1740.                     {tag: 'param', name: 'id', value: myid},
  1741.                     {tag: 'param', name: 'PORT', value: param.port},
  1742.                     {tag: 'param', name: 'PASSWORD', value: param.ticket},
  1743.                     {tag: 'param', name: 'USERNAME', value: param.user},
  1744.                     {tag: 'param', name: 'Show Controls', value: 'No'},
  1745.                     {tag: 'param', name: 'Offer Relogin', value: 'No'},
  1746.                     {tag: 'param', name: 'PVECert', value: cert}
  1747.                 ]
  1748.             });
  1749.             if (me.toplevel) {
  1750.                 Ext.Function.defer(resize_window, 1000);
  1751.             } else {
  1752.                 Ext.Function.defer(resize_box, 1000);
  1753.             }
  1754.         };
  1755.  
  1756.         Ext.apply(me, {
  1757.             layout: 'fit',
  1758.             border: false,
  1759.             autoScroll: me.toplevel ? false : true,
  1760.             items: box,
  1761.             reloadApplet: function() {
  1762.                 PVE.Utils.API2Request({
  1763.                     url: me.url,
  1764.                     params: me.params,
  1765.                     method: me.method || 'POST',
  1766.                     failure: function(response, opts) {
  1767.                         box.update(gettext('Error') + ' ' + response.htmlStatus);
  1768.                     },
  1769.                     success: function(response, opts) {
  1770.                         start_vnc_viewer(response.result.data);
  1771.                     }
  1772.                 });
  1773.             }
  1774.         });
  1775.  
  1776.         me.callParent();
  1777.  
  1778.         if (me.toplevel) {
  1779.             me.on("render", function() { me.reloadApplet();});
  1780.         } else {
  1781.             me.on("show", function() { me.reloadApplet();});
  1782.             me.on("hide", function() { box.update(""); });
  1783.         }
  1784.     }
  1785. });
  1786.  
  1787. Ext.define('PVE.KVMConsole', {
  1788.     extend: 'PVE.VNCConsole',
  1789.     alias: ['widget.pveKVMConsole'],
  1790.  
  1791.     initComponent : function() {
  1792.         var me = this;
  1793.  
  1794.         if (!me.nodename) {
  1795.             throw "no node name specified";
  1796.         }
  1797.  
  1798.         if (!me.vmid) {
  1799.             throw "no VM ID specified";
  1800.         }
  1801.  
  1802.         var vm_command = function(cmd, params, reload_applet) {
  1803.             PVE.Utils.API2Request({
  1804.                 params: params,
  1805.                 url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/status/" + cmd,
  1806.                 method: 'POST',
  1807.                 waitMsgTarget: me,
  1808.                 failure: function(response, opts) {
  1809.                     Ext.Msg.alert('Error', response.htmlStatus);
  1810.                 },
  1811.                 success: function() {
  1812.                     if (reload_applet) {
  1813.                         Ext.Function.defer(me.reloadApplet, 1000, me);
  1814.                     }
  1815.                 }
  1816.             });
  1817.         };
  1818.  
  1819.         var tbar = [
  1820.             {
  1821.                 text: gettext('Start'),
  1822.                 handler: function() {
  1823.                     vm_command("start", {}, 1);
  1824.                 }
  1825.             },
  1826.             {
  1827.                 text: gettext('Shutdown'),
  1828.                 handler: function() {
  1829.                     var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), me.vmid);
  1830.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1831.                         if (btn !== 'yes') {
  1832.                             return;
  1833.                         }
  1834.                         vm_command('shutdown', {timeout: 30});
  1835.                     });
  1836.                 }                          
  1837.             },
  1838.             {
  1839.                 text: gettext('Stop'),
  1840.                 handler: function() {
  1841.                     var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), me.vmid);
  1842.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1843.                         if (btn !== 'yes') {
  1844.                             return;
  1845.                         }
  1846.                         vm_command("stop", { timeout: 30});
  1847.                     });
  1848.                 }
  1849.             },
  1850.             {
  1851.                 xtype: 'pveQemuSendKeyMenu',
  1852.                 nodename: me.nodename,
  1853.                 vmid: me.vmid
  1854.             },
  1855.             {
  1856.                 text: gettext('Reset'),
  1857.                 handler: function() {
  1858.                     var msg = Ext.String.format(gettext("Do you really want to reset VM {0}?"), me.vmid);
  1859.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1860.                         if (btn !== 'yes') {
  1861.                             return;
  1862.                         }
  1863.                         vm_command("reset");
  1864.                     });
  1865.                 }
  1866.             },
  1867.             {
  1868.                 text: gettext('Suspend'),
  1869.                 handler: function() {
  1870.                     var msg = Ext.String.format(gettext("Do you really want to suspend VM {0}?"), me.vmid);
  1871.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1872.                         if (btn !== 'yes') {
  1873.                             return;
  1874.                         }
  1875.                         vm_command("suspend");
  1876.                     });
  1877.                 }
  1878.             },
  1879.             {
  1880.                 text: gettext('Resume'),
  1881.                 handler: function() {
  1882.                     vm_command("resume");
  1883.                 }
  1884.             },
  1885.             // Note: no migrate here, because we can't display migrate log
  1886.             {
  1887.                 text: gettext('Console'),
  1888.                 handler: function() {
  1889.                     PVE.Utils.openConoleWindow('kvm', me.vmid, me.nodename, me.vmname);
  1890.                 }
  1891.             },
  1892.             '->',
  1893.             {
  1894.                 text: gettext('Refresh'),
  1895.                 handler: function() {
  1896.                     var applet = Ext.getDom(me.appletID);
  1897.                     applet.sendRefreshRequest();
  1898.                 }
  1899.             },
  1900.             {
  1901.                 text: gettext('Reload'),
  1902.                 handler: function () {
  1903.                     me.reloadApplet();
  1904.                 }
  1905.             }
  1906.         ];
  1907.  
  1908.         Ext.apply(me, {
  1909.             tbar: tbar,
  1910.             url: "/nodes/" + me.nodename + "/qemu/" + me.vmid + "/vncproxy"
  1911.         });
  1912.  
  1913.         me.callParent();
  1914.     }
  1915. });
  1916.  
  1917. Ext.define('PVE.OpenVZConsole', {
  1918.     extend: 'PVE.VNCConsole',
  1919.     alias: ['widget.pveOpenVZConsole'],
  1920.  
  1921.     initComponent : function() {
  1922.         var me = this;
  1923.  
  1924.         if (!me.nodename) {
  1925.             throw "no node name specified";
  1926.         }
  1927.  
  1928.         if (!me.vmid) {
  1929.             throw "no VM ID specified";
  1930.         }
  1931.  
  1932.         var vm_command = function(cmd, params, reload_applet) {
  1933.             PVE.Utils.API2Request({
  1934.                 params: params,
  1935.                 url: '/nodes/' + me.nodename + '/openvz/' + me.vmid + "/status/" + cmd,
  1936.                 waitMsgTarget: me,
  1937.                 method: 'POST',
  1938.                 failure: function(response, opts) {
  1939.                     Ext.Msg.alert('Error', response.htmlStatus);
  1940.                 },
  1941.                 success: function() {
  1942.                     if (reload_applet) {
  1943.                         Ext.Function.defer(me.reloadApplet, 1000, me);
  1944.                     }
  1945.                 }
  1946.             });
  1947.         };
  1948.  
  1949.         var tbar = [
  1950.             {
  1951.                 text: gettext('Start'),
  1952.                 handler: function() {
  1953.                     vm_command("start", {}, 1);
  1954.                 }
  1955.             },
  1956.             {
  1957.                 text: gettext('Shutdown'),
  1958.                 handler: function() {
  1959.                     var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), me.vmid);
  1960.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1961.                         if (btn !== 'yes') {
  1962.                             return;
  1963.                         }
  1964.                         vm_command("shutdown");
  1965.                     });
  1966.                 }
  1967.             },
  1968.             {
  1969.                 text: gettext('Stop'),
  1970.                 handler: function() {
  1971.                     var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), me.vmid);
  1972.                     Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1973.                         if (btn !== 'yes') {
  1974.                             return;
  1975.                         }
  1976.                         vm_command("stop");
  1977.                     });
  1978.                 }
  1979.             },
  1980.             // Note: no migrate here, because we can't display migrate log
  1981.             // and openvz migrate does not work if console is open
  1982.             {
  1983.                 text: gettext('Console'),
  1984.                 handler: function() {
  1985.                     PVE.Utils.openConoleWindow('openvz', me.vmid, me.nodename, me.vmname);
  1986.                 }
  1987.             },
  1988.             '->',
  1989.             {
  1990.                 text: gettext('Refresh'),
  1991.                 handler: function() {
  1992.                     var applet = Ext.getDom(me.appletID);
  1993.                     applet.sendRefreshRequest();
  1994.                 }
  1995.             },
  1996.             {
  1997.                 text: gettext('Reload'),
  1998.                 handler: function () {
  1999.                     me.reloadApplet();
  2000.                 }
  2001.             }
  2002.         ];
  2003.  
  2004.         Ext.apply(me, {
  2005.             tbar: tbar,
  2006.             url: "/nodes/" + me.nodename + "/openvz/" + me.vmid + "/vncproxy"
  2007.         });
  2008.  
  2009.         me.callParent();
  2010.     }
  2011. });
  2012.  
  2013. Ext.define('PVE.Shell', {
  2014.     extend: 'PVE.VNCConsole',
  2015.     alias: ['widget.pveShell'],
  2016.  
  2017.     initComponent : function() {
  2018.         var me = this;
  2019.  
  2020.         if (!me.nodename) {
  2021.             throw "no node name specified";
  2022.         }
  2023.  
  2024.         var tbar = [
  2025.            '->',
  2026.             {
  2027.                 text: gettext('Refresh'),
  2028.                 handler: function() {
  2029.                     var applet = Ext.getDom(me.appletID);
  2030.                     applet.sendRefreshRequest();
  2031.                 }
  2032.             },
  2033.             {
  2034.                 text: gettext('Reload'),
  2035.                 handler: function () { me.reloadApplet(); }
  2036.             },
  2037.             {
  2038.                 text: gettext('Shell'),
  2039.                 handler: function() {
  2040.                     PVE.Utils.openConoleWindow('shell', undefined, me.nodename);
  2041.                 }
  2042.             }
  2043.         ];
  2044.  
  2045.         Ext.apply(me, {
  2046.             tbar: tbar,
  2047.             url: "/nodes/" + me.nodename + "/vncshell"
  2048.         });
  2049.  
  2050.         me.callParent();
  2051.     }
  2052. });Ext.define('PVE.data.TimezoneStore', {
  2053.     extend: 'Ext.data.Store',
  2054.  
  2055.     statics: {
  2056.         timezones: [
  2057.             ['Africa/Abidjan'],
  2058.             ['Africa/Accra'],
  2059.             ['Africa/Addis_Ababa'],
  2060.             ['Africa/Algiers'],
  2061.             ['Africa/Asmara'],
  2062.             ['Africa/Bamako'],
  2063.             ['Africa/Bangui'],
  2064.             ['Africa/Banjul'],
  2065.             ['Africa/Bissau'],
  2066.             ['Africa/Blantyre'],
  2067.             ['Africa/Brazzaville'],
  2068.             ['Africa/Bujumbura'],
  2069.             ['Africa/Cairo'],
  2070.             ['Africa/Casablanca'],
  2071.             ['Africa/Ceuta'],
  2072.             ['Africa/Conakry'],
  2073.             ['Africa/Dakar'],
  2074.             ['Africa/Dar_es_Salaam'],
  2075.             ['Africa/Djibouti'],
  2076.             ['Africa/Douala'],
  2077.             ['Africa/El_Aaiun'],
  2078.             ['Africa/Freetown'],
  2079.             ['Africa/Gaborone'],
  2080.             ['Africa/Harare'],
  2081.             ['Africa/Johannesburg'],
  2082.             ['Africa/Kampala'],
  2083.             ['Africa/Khartoum'],
  2084.             ['Africa/Kigali'],
  2085.             ['Africa/Kinshasa'],
  2086.             ['Africa/Lagos'],
  2087.             ['Africa/Libreville'],
  2088.             ['Africa/Lome'],
  2089.             ['Africa/Luanda'],
  2090.             ['Africa/Lubumbashi'],
  2091.             ['Africa/Lusaka'],
  2092.             ['Africa/Malabo'],
  2093.             ['Africa/Maputo'],
  2094.             ['Africa/Maseru'],
  2095.             ['Africa/Mbabane'],
  2096.             ['Africa/Mogadishu'],
  2097.             ['Africa/Monrovia'],
  2098.             ['Africa/Nairobi'],
  2099.             ['Africa/Ndjamena'],
  2100.             ['Africa/Niamey'],
  2101.             ['Africa/Nouakchott'],
  2102.             ['Africa/Ouagadougou'],
  2103.             ['Africa/Porto-Novo'],
  2104.             ['Africa/Sao_Tome'],
  2105.             ['Africa/Tripoli'],
  2106.             ['Africa/Tunis'],
  2107.             ['Africa/Windhoek'],
  2108.             ['America/Adak'],
  2109.             ['America/Anchorage'],
  2110.             ['America/Anguilla'],
  2111.             ['America/Antigua'],
  2112.             ['America/Araguaina'],
  2113.             ['America/Argentina/Buenos_Aires'],
  2114.             ['America/Argentina/Catamarca'],
  2115.             ['America/Argentina/Cordoba'],
  2116.             ['America/Argentina/Jujuy'],
  2117.             ['America/Argentina/La_Rioja'],
  2118.             ['America/Argentina/Mendoza'],
  2119.             ['America/Argentina/Rio_Gallegos'],
  2120.             ['America/Argentina/Salta'],
  2121.             ['America/Argentina/San_Juan'],
  2122.             ['America/Argentina/San_Luis'],
  2123.             ['America/Argentina/Tucuman'],
  2124.             ['America/Argentina/Ushuaia'],
  2125.             ['America/Aruba'],
  2126.             ['America/Asuncion'],
  2127.             ['America/Atikokan'],
  2128.             ['America/Bahia'],
  2129.             ['America/Bahia_Banderas'],
  2130.             ['America/Barbados'],
  2131.             ['America/Belem'],
  2132.             ['America/Belize'],
  2133.             ['America/Blanc-Sablon'],
  2134.             ['America/Boa_Vista'],
  2135.             ['America/Bogota'],
  2136.             ['America/Boise'],
  2137.             ['America/Cambridge_Bay'],
  2138.             ['America/Campo_Grande'],
  2139.             ['America/Cancun'],
  2140.             ['America/Caracas'],
  2141.             ['America/Cayenne'],
  2142.             ['America/Cayman'],
  2143.             ['America/Chicago'],
  2144.             ['America/Chihuahua'],
  2145.             ['America/Costa_Rica'],
  2146.             ['America/Cuiaba'],
  2147.             ['America/Curacao'],
  2148.             ['America/Danmarkshavn'],
  2149.             ['America/Dawson'],
  2150.             ['America/Dawson_Creek'],
  2151.             ['America/Denver'],
  2152.             ['America/Detroit'],
  2153.             ['America/Dominica'],
  2154.             ['America/Edmonton'],
  2155.             ['America/Eirunepe'],
  2156.             ['America/El_Salvador'],
  2157.             ['America/Fortaleza'],
  2158.             ['America/Glace_Bay'],
  2159.             ['America/Godthab'],
  2160.             ['America/Goose_Bay'],
  2161.             ['America/Grand_Turk'],
  2162.             ['America/Grenada'],
  2163.             ['America/Guadeloupe'],
  2164.             ['America/Guatemala'],
  2165.             ['America/Guayaquil'],
  2166.             ['America/Guyana'],
  2167.             ['America/Halifax'],
  2168.             ['America/Havana'],
  2169.             ['America/Hermosillo'],
  2170.             ['America/Indiana/Indianapolis'],
  2171.             ['America/Indiana/Knox'],
  2172.             ['America/Indiana/Marengo'],
  2173.             ['America/Indiana/Petersburg'],
  2174.             ['America/Indiana/Tell_City'],
  2175.             ['America/Indiana/Vevay'],
  2176.             ['America/Indiana/Vincennes'],
  2177.             ['America/Indiana/Winamac'],
  2178.             ['America/Inuvik'],
  2179.             ['America/Iqaluit'],
  2180.             ['America/Jamaica'],
  2181.             ['America/Juneau'],
  2182.             ['America/Kentucky/Louisville'],
  2183.             ['America/Kentucky/Monticello'],
  2184.             ['America/La_Paz'],
  2185.             ['America/Lima'],
  2186.             ['America/Los_Angeles'],
  2187.             ['America/Maceio'],
  2188.             ['America/Managua'],
  2189.             ['America/Manaus'],
  2190.             ['America/Marigot'],
  2191.             ['America/Martinique'],
  2192.             ['America/Matamoros'],
  2193.             ['America/Mazatlan'],
  2194.             ['America/Menominee'],
  2195.             ['America/Merida'],
  2196.             ['America/Mexico_City'],
  2197.             ['America/Miquelon'],
  2198.             ['America/Moncton'],
  2199.             ['America/Monterrey'],
  2200.             ['America/Montevideo'],
  2201.             ['America/Montreal'],
  2202.             ['America/Montserrat'],
  2203.             ['America/Nassau'],
  2204.             ['America/New_York'],
  2205.             ['America/Nipigon'],
  2206.             ['America/Nome'],
  2207.             ['America/Noronha'],
  2208.             ['America/North_Dakota/Center'],
  2209.             ['America/North_Dakota/New_Salem'],
  2210.             ['America/Ojinaga'],
  2211.             ['America/Panama'],
  2212.             ['America/Pangnirtung'],
  2213.             ['America/Paramaribo'],
  2214.             ['America/Phoenix'],
  2215.             ['America/Port-au-Prince'],
  2216.             ['America/Port_of_Spain'],
  2217.             ['America/Porto_Velho'],
  2218.             ['America/Puerto_Rico'],
  2219.             ['America/Rainy_River'],
  2220.             ['America/Rankin_Inlet'],
  2221.             ['America/Recife'],
  2222.             ['America/Regina'],
  2223.             ['America/Resolute'],
  2224.             ['America/Rio_Branco'],
  2225.             ['America/Santa_Isabel'],
  2226.             ['America/Santarem'],
  2227.             ['America/Santiago'],
  2228.             ['America/Santo_Domingo'],
  2229.             ['America/Sao_Paulo'],
  2230.             ['America/Scoresbysund'],
  2231.             ['America/Shiprock'],
  2232.             ['America/St_Barthelemy'],
  2233.             ['America/St_Johns'],
  2234.             ['America/St_Kitts'],
  2235.             ['America/St_Lucia'],
  2236.             ['America/St_Thomas'],
  2237.             ['America/St_Vincent'],
  2238.             ['America/Swift_Current'],
  2239.             ['America/Tegucigalpa'],
  2240.             ['America/Thule'],
  2241.             ['America/Thunder_Bay'],
  2242.             ['America/Tijuana'],
  2243.             ['America/Toronto'],
  2244.             ['America/Tortola'],
  2245.             ['America/Vancouver'],
  2246.             ['America/Whitehorse'],
  2247.             ['America/Winnipeg'],
  2248.             ['America/Yakutat'],
  2249.             ['America/Yellowknife'],
  2250.             ['Antarctica/Casey'],
  2251.             ['Antarctica/Davis'],
  2252.             ['Antarctica/DumontDUrville'],
  2253.             ['Antarctica/Macquarie'],
  2254.             ['Antarctica/Mawson'],
  2255.             ['Antarctica/McMurdo'],
  2256.             ['Antarctica/Palmer'],
  2257.             ['Antarctica/Rothera'],
  2258.             ['Antarctica/South_Pole'],
  2259.             ['Antarctica/Syowa'],
  2260.             ['Antarctica/Vostok'],
  2261.             ['Arctic/Longyearbyen'],
  2262.             ['Asia/Aden'],
  2263.             ['Asia/Almaty'],
  2264.             ['Asia/Amman'],
  2265.             ['Asia/Anadyr'],
  2266.             ['Asia/Aqtau'],
  2267.             ['Asia/Aqtobe'],
  2268.             ['Asia/Ashgabat'],
  2269.             ['Asia/Baghdad'],
  2270.             ['Asia/Bahrain'],
  2271.             ['Asia/Baku'],
  2272.             ['Asia/Bangkok'],
  2273.             ['Asia/Beirut'],
  2274.             ['Asia/Bishkek'],
  2275.             ['Asia/Brunei'],
  2276.             ['Asia/Choibalsan'],
  2277.             ['Asia/Chongqing'],
  2278.             ['Asia/Colombo'],
  2279.             ['Asia/Damascus'],
  2280.             ['Asia/Dhaka'],
  2281.             ['Asia/Dili'],
  2282.             ['Asia/Dubai'],
  2283.             ['Asia/Dushanbe'],
  2284.             ['Asia/Gaza'],
  2285.             ['Asia/Harbin'],
  2286.             ['Asia/Ho_Chi_Minh'],
  2287.             ['Asia/Hong_Kong'],
  2288.             ['Asia/Hovd'],
  2289.             ['Asia/Irkutsk'],
  2290.             ['Asia/Jakarta'],
  2291.             ['Asia/Jayapura'],
  2292.             ['Asia/Jerusalem'],
  2293.             ['Asia/Kabul'],
  2294.             ['Asia/Kamchatka'],
  2295.             ['Asia/Karachi'],
  2296.             ['Asia/Kashgar'],
  2297.             ['Asia/Kathmandu'],
  2298.             ['Asia/Kolkata'],
  2299.             ['Asia/Krasnoyarsk'],
  2300.             ['Asia/Kuala_Lumpur'],
  2301.             ['Asia/Kuching'],
  2302.             ['Asia/Kuwait'],
  2303.             ['Asia/Macau'],
  2304.             ['Asia/Magadan'],
  2305.             ['Asia/Makassar'],
  2306.             ['Asia/Manila'],
  2307.             ['Asia/Muscat'],
  2308.             ['Asia/Nicosia'],
  2309.             ['Asia/Novokuznetsk'],
  2310.             ['Asia/Novosibirsk'],
  2311.             ['Asia/Omsk'],
  2312.             ['Asia/Oral'],
  2313.             ['Asia/Phnom_Penh'],
  2314.             ['Asia/Pontianak'],
  2315.             ['Asia/Pyongyang'],
  2316.             ['Asia/Qatar'],
  2317.             ['Asia/Qyzylorda'],
  2318.             ['Asia/Rangoon'],
  2319.             ['Asia/Riyadh'],
  2320.             ['Asia/Sakhalin'],
  2321.             ['Asia/Samarkand'],
  2322.             ['Asia/Seoul'],
  2323.             ['Asia/Shanghai'],
  2324.             ['Asia/Singapore'],
  2325.             ['Asia/Taipei'],
  2326.             ['Asia/Tashkent'],
  2327.             ['Asia/Tbilisi'],
  2328.             ['Asia/Tehran'],
  2329.             ['Asia/Thimphu'],
  2330.             ['Asia/Tokyo'],
  2331.             ['Asia/Ulaanbaatar'],
  2332.             ['Asia/Urumqi'],
  2333.             ['Asia/Vientiane'],
  2334.             ['Asia/Vladivostok'],
  2335.             ['Asia/Yakutsk'],
  2336.             ['Asia/Yekaterinburg'],
  2337.             ['Asia/Yerevan'],
  2338.             ['Atlantic/Azores'],
  2339.             ['Atlantic/Bermuda'],
  2340.             ['Atlantic/Canary'],
  2341.             ['Atlantic/Cape_Verde'],
  2342.             ['Atlantic/Faroe'],
  2343.             ['Atlantic/Madeira'],
  2344.             ['Atlantic/Reykjavik'],
  2345.             ['Atlantic/South_Georgia'],
  2346.             ['Atlantic/St_Helena'],
  2347.             ['Atlantic/Stanley'],
  2348.             ['Australia/Adelaide'],
  2349.             ['Australia/Brisbane'],
  2350.             ['Australia/Broken_Hill'],
  2351.             ['Australia/Currie'],
  2352.             ['Australia/Darwin'],
  2353.             ['Australia/Eucla'],
  2354.             ['Australia/Hobart'],
  2355.             ['Australia/Lindeman'],
  2356.             ['Australia/Lord_Howe'],
  2357.             ['Australia/Melbourne'],
  2358.             ['Australia/Perth'],
  2359.             ['Australia/Sydney'],
  2360.             ['Europe/Amsterdam'],
  2361.             ['Europe/Andorra'],
  2362.             ['Europe/Athens'],
  2363.             ['Europe/Belgrade'],
  2364.             ['Europe/Berlin'],
  2365.             ['Europe/Bratislava'],
  2366.             ['Europe/Brussels'],
  2367.             ['Europe/Bucharest'],
  2368.             ['Europe/Budapest'],
  2369.             ['Europe/Chisinau'],
  2370.             ['Europe/Copenhagen'],
  2371.             ['Europe/Dublin'],
  2372.             ['Europe/Gibraltar'],
  2373.             ['Europe/Guernsey'],
  2374.             ['Europe/Helsinki'],
  2375.             ['Europe/Isle_of_Man'],
  2376.             ['Europe/Istanbul'],
  2377.             ['Europe/Jersey'],
  2378.             ['Europe/Kaliningrad'],
  2379.             ['Europe/Kiev'],
  2380.             ['Europe/Lisbon'],
  2381.             ['Europe/Ljubljana'],
  2382.             ['Europe/London'],
  2383.             ['Europe/Luxembourg'],
  2384.             ['Europe/Madrid'],
  2385.             ['Europe/Malta'],
  2386.             ['Europe/Mariehamn'],
  2387.             ['Europe/Minsk'],
  2388.             ['Europe/Monaco'],
  2389.             ['Europe/Moscow'],
  2390.             ['Europe/Oslo'],
  2391.             ['Europe/Paris'],
  2392.             ['Europe/Podgorica'],
  2393.             ['Europe/Prague'],
  2394.             ['Europe/Riga'],
  2395.             ['Europe/Rome'],
  2396.             ['Europe/Samara'],
  2397.             ['Europe/San_Marino'],
  2398.             ['Europe/Sarajevo'],
  2399.             ['Europe/Simferopol'],
  2400.             ['Europe/Skopje'],
  2401.             ['Europe/Sofia'],
  2402.             ['Europe/Stockholm'],
  2403.             ['Europe/Tallinn'],
  2404.             ['Europe/Tirane'],
  2405.             ['Europe/Uzhgorod'],
  2406.             ['Europe/Vaduz'],
  2407.             ['Europe/Vatican'],
  2408.             ['Europe/Vienna'],
  2409.             ['Europe/Vilnius'],
  2410.             ['Europe/Volgograd'],
  2411.             ['Europe/Warsaw'],
  2412.             ['Europe/Zagreb'],
  2413.             ['Europe/Zaporozhye'],
  2414.             ['Europe/Zurich'],
  2415.             ['Indian/Antananarivo'],
  2416.             ['Indian/Chagos'],
  2417.             ['Indian/Christmas'],
  2418.             ['Indian/Cocos'],
  2419.             ['Indian/Comoro'],
  2420.             ['Indian/Kerguelen'],
  2421.             ['Indian/Mahe'],
  2422.             ['Indian/Maldives'],
  2423.             ['Indian/Mauritius'],
  2424.             ['Indian/Mayotte'],
  2425.             ['Indian/Reunion'],
  2426.             ['Pacific/Apia'],
  2427.             ['Pacific/Auckland'],
  2428.             ['Pacific/Chatham'],
  2429.             ['Pacific/Chuuk'],
  2430.             ['Pacific/Easter'],
  2431.             ['Pacific/Efate'],
  2432.             ['Pacific/Enderbury'],
  2433.             ['Pacific/Fakaofo'],
  2434.             ['Pacific/Fiji'],
  2435.             ['Pacific/Funafuti'],
  2436.             ['Pacific/Galapagos'],
  2437.             ['Pacific/Gambier'],
  2438.             ['Pacific/Guadalcanal'],
  2439.             ['Pacific/Guam'],
  2440.             ['Pacific/Honolulu'],
  2441.             ['Pacific/Johnston'],
  2442.             ['Pacific/Kiritimati'],
  2443.             ['Pacific/Kosrae'],
  2444.             ['Pacific/Kwajalein'],
  2445.             ['Pacific/Majuro'],
  2446.             ['Pacific/Marquesas'],
  2447.             ['Pacific/Midway'],
  2448.             ['Pacific/Nauru'],
  2449.             ['Pacific/Niue'],
  2450.             ['Pacific/Norfolk'],
  2451.             ['Pacific/Noumea'],
  2452.             ['Pacific/Pago_Pago'],
  2453.             ['Pacific/Palau'],
  2454.             ['Pacific/Pitcairn'],
  2455.             ['Pacific/Pohnpei'],
  2456.             ['Pacific/Port_Moresby'],
  2457.             ['Pacific/Rarotonga'],
  2458.             ['Pacific/Saipan'],
  2459.             ['Pacific/Tahiti'],
  2460.             ['Pacific/Tarawa'],
  2461.             ['Pacific/Tongatapu'],
  2462.             ['Pacific/Wake'],
  2463.             ['Pacific/Wallis']
  2464.         ]
  2465.     },
  2466.  
  2467.     constructor: function(config) {
  2468.         var me = this;
  2469.  
  2470.         config = config || {};
  2471.  
  2472.         Ext.regModel('Timezone', {
  2473.             fields: ['zone'],
  2474.             proxy: {
  2475.                 type: 'memory',
  2476.                 reader: 'array'
  2477.             }
  2478.         });
  2479.  
  2480.         Ext.apply(config, {
  2481.             model: 'Timezone',
  2482.             data: PVE.data.TimezoneStore.timezones
  2483.         });
  2484.  
  2485.         me.callParent([config]);       
  2486.     }
  2487. });/* A reader to store a single JSON Object (hash) into a storage.
  2488.  * Also accepts an array containing a single hash.
  2489.  * So it can read:
  2490.  *
  2491.  * example1: { data: "xyz" }
  2492.  * example2: [ {  data: "xyz" } ]
  2493.  */
  2494.  
  2495. Ext.define('PVE.data.reader.JsonObject', {
  2496.     extend: 'Ext.data.reader.Json',
  2497.     alias : 'reader.jsonobject',
  2498.    
  2499.     root: 'data',
  2500.  
  2501.     constructor: function(config) {
  2502.         var me = this;
  2503.  
  2504.         Ext.apply(me, config || {});
  2505.  
  2506.         me.callParent([config]);
  2507.     },
  2508.  
  2509.     getResponseData: function(response) {
  2510.         var me = this;
  2511.  
  2512.         var data = [];
  2513.         try {
  2514.             var result = Ext.decode(response.responseText);
  2515.             var root = me.getRoot(result);
  2516.             var org_root = root;
  2517.  
  2518.             if (Ext.isArray(org_root)) {
  2519.                 if (org_root.length == 1) {
  2520.                     root = org_root[0];
  2521.                 } else {
  2522.                     root = {};
  2523.                 }
  2524.             }
  2525.  
  2526.             if (me.rows) {
  2527.                 Ext.Object.each(me.rows, function(key, rowdef) {
  2528.                     if (Ext.isDefined(root[key])) {
  2529.                         data.push({key: key, value: root[key]});
  2530.                     } else if (Ext.isDefined(rowdef.defaultValue)) {
  2531.                         data.push({key: key, value: rowdef.defaultValue});
  2532.                     } else if (rowdef.required) {
  2533.                         data.push({key: key, value: undefined});
  2534.                     }
  2535.                 });
  2536.             } else {
  2537.                 Ext.Object.each(root, function(key, value) {
  2538.                     data.push({key: key, value: value });
  2539.                 });
  2540.             }
  2541.         }
  2542.         catch (ex) {
  2543.             Ext.Error.raise({
  2544.                 response: response,
  2545.                 json: response.responseText,
  2546.                 parseError: ex,
  2547.                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
  2548.             });
  2549.         }
  2550.  
  2551.         return data;
  2552.     }
  2553. });
  2554.  
  2555. Ext.define('PVE.RestProxy', {
  2556.     extend: 'Ext.data.RestProxy',
  2557.     alias : 'proxy.pve',
  2558.  
  2559.     constructor: function(config) {
  2560.         var me = this;
  2561.  
  2562.         config = config || {};
  2563.  
  2564.         Ext.applyIf(config, {
  2565.             pageParam : null,
  2566.             startParam: null,
  2567.             limitParam: null,
  2568.             groupParam: null,
  2569.             sortParam: null,
  2570.             filterParam: null,
  2571.             noCache : false,
  2572.             reader: {
  2573.                 type: 'json',
  2574.                 root: config.root || 'data'
  2575.             },
  2576.             afterRequest: function(request, success) {
  2577.                 me.fireEvent('afterload', me, request, success);
  2578.                 return;
  2579.             }
  2580.         });
  2581.  
  2582.         me.callParent([config]);
  2583.     }
  2584.  
  2585. }, function() {
  2586.  
  2587.     Ext.define('pve-domains', {
  2588.         extend: "Ext.data.Model",
  2589.         fields: [ 'realm', 'type', 'comment', 'default',
  2590.                   {
  2591.                       name: 'descr',
  2592.                       // Note: We use this in the RealmComboBox.js
  2593.                       // (see Bug #125)
  2594.                       convert: function(value, record) {
  2595.                           var info = record.data;
  2596.                           var text;
  2597.  
  2598.                           if (value) {
  2599.                               return value;
  2600.                           }
  2601.                           // return realm if there is no comment
  2602.                           return info.comment || info.realm;
  2603.                       }
  2604.                   }
  2605.                 ],
  2606.         proxy: {
  2607.             type: 'pve',
  2608.             url: "/api2/json/access/domains"
  2609.         }
  2610.     });
  2611.  
  2612.     Ext.define('KeyValue', {
  2613.         extend: "Ext.data.Model",
  2614.         fields: [ 'key', 'value' ],
  2615.         idProperty: 'key'
  2616.     });
  2617.  
  2618.     Ext.define('pve-string-list', {
  2619.         extend: 'Ext.data.Model',
  2620.         fields:  [ 'n', 't' ],
  2621.         idProperty: 'n'
  2622.     });
  2623.  
  2624.     Ext.define('pve-tasks', {
  2625.         extend: 'Ext.data.Model',
  2626.         fields:  [
  2627.             { name: 'starttime', type : 'date', dateFormat: 'timestamp' },
  2628.             { name: 'endtime', type : 'date', dateFormat: 'timestamp' },
  2629.             { name: 'pid', type: 'int' },
  2630.             'node', 'upid', 'user', 'status', 'type', 'id'
  2631.         ],
  2632.         idProperty: 'upid'
  2633.     });
  2634.  
  2635.     Ext.define('pve-cluster-log', {
  2636.         extend: 'Ext.data.Model',
  2637.         fields:  [
  2638.             { name: 'uid' , type: 'int' },
  2639.             { name: 'time', type : 'date', dateFormat: 'timestamp' },
  2640.             { name: 'pri', type: 'int' },
  2641.             { name: 'pid', type: 'int' },
  2642.             'node', 'user', 'tag', 'msg',
  2643.             {
  2644.                 name: 'id',
  2645.                 convert: function(value, record) {
  2646.                     var info = record.data;
  2647.                     var text;
  2648.  
  2649.                     if (value) {
  2650.                         return value;
  2651.                     }
  2652.                     // compute unique ID
  2653.                     return info.uid + ':' + info.node;
  2654.                 }
  2655.             }
  2656.         ],
  2657.         idProperty: 'id'
  2658.     });
  2659. });
  2660. // Serialize load (avoid too many parallel connections)
  2661. Ext.define('PVE.data.UpdateQueue', {
  2662.     singleton: true,
  2663.  
  2664.     constructor : function(){
  2665.         var me = this;
  2666.  
  2667.         var queue = [];
  2668.         var queue_idx = {};
  2669.  
  2670.         var idle = true;
  2671.  
  2672.         var start_update = function() {
  2673.             if (!idle) {
  2674.                 return;
  2675.             }
  2676.  
  2677.             var store = queue.shift();
  2678.             if (!store) {
  2679.                 return;
  2680.             }
  2681.  
  2682.             queue_idx[store.storeid] = null;
  2683.  
  2684.             idle = false;
  2685.             store.load({
  2686.                 callback: function(records, operation, success) {
  2687.                     idle = true;
  2688.                     start_update();
  2689.                 }
  2690.             });
  2691.         };
  2692.  
  2693.         Ext.apply(me, {
  2694.             queue: function(store) {
  2695.                 if (!store.storeid) {
  2696.                     throw "unable to queue store without storeid";
  2697.                 }
  2698.                 if (!queue_idx[store.storeid]) {
  2699.                     queue_idx[store.storeid] = store;
  2700.                     queue.push(store);
  2701.                 }
  2702.                 start_update();
  2703.             }
  2704.         });
  2705.     }
  2706. });
  2707. Ext.define('PVE.data.UpdateStore', {
  2708.     extend: 'Ext.data.Store',
  2709.  
  2710.     constructor: function(config) {
  2711.         var me = this;
  2712.  
  2713.         config = config || {};
  2714.  
  2715.         if (!config.interval) {
  2716.             config.interval = 3000;
  2717.         }
  2718.  
  2719.         if (!config.storeid) {
  2720.             throw "no storeid specified";
  2721.         }
  2722.  
  2723.         var load_task = new Ext.util.DelayedTask();
  2724.  
  2725.         var run_load_task = function() {
  2726.             if (PVE.Utils.authOK()) {
  2727.                 PVE.data.UpdateQueue.queue(me);
  2728.                 load_task.delay(config.interval, run_load_task);
  2729.             } else {
  2730.                 load_task.delay(200, run_load_task);
  2731.             }
  2732.         };
  2733.  
  2734.         Ext.apply(config, {
  2735.             startUpdate: function() {
  2736.                 run_load_task();
  2737.             },
  2738.             stopUpdate: function() {
  2739.                 load_task.cancel();
  2740.             }
  2741.         });
  2742.  
  2743.         me.callParent([config]);
  2744.  
  2745.         me.on('destroy', function() {
  2746.             load_task.cancel();
  2747.         });
  2748.     }
  2749. });
  2750. /* Config properties:
  2751.  * rstore: A storage to track changes
  2752.  * Only works if rstore has a model and use 'idProperty'
  2753.  */
  2754. Ext.define('PVE.data.DiffStore', {
  2755.     extend: 'Ext.data.Store',
  2756.  
  2757.     constructor: function(config) {
  2758.         var me = this;
  2759.  
  2760.         config = config || {};
  2761.  
  2762.         if (!config.rstore) {
  2763.             throw "no rstore specified";
  2764.         }
  2765.  
  2766.         if (!config.rstore.model) {
  2767.             throw "no rstore model specified";
  2768.         }
  2769.  
  2770.         var rstore = config.rstore;
  2771.  
  2772.         Ext.apply(config, {
  2773.             model: rstore.model,
  2774.             proxy: { type: 'memory' }
  2775.         });
  2776.  
  2777.         me.callParent([config]);
  2778.  
  2779.         var first_load = true;
  2780.  
  2781.         var cond_add_item = function(data, id) {
  2782.             var olditem = me.getById(id);
  2783.             if (olditem) {
  2784.                 olditem.beginEdit();
  2785.                 me.model.prototype.fields.eachKey(function(field) {
  2786.                     if (olditem.data[field] !== data[field]) {
  2787.                         olditem.set(field, data[field]);
  2788.                     }
  2789.                 });
  2790.                 olditem.endEdit(true);
  2791.                 olditem.commit();
  2792.             } else {
  2793.                 var newrec = Ext.ModelMgr.create(data, me.model, id);
  2794.                 var pos = (me.appendAtStart && !first_load) ? 0 : me.data.length;
  2795.                 me.insert(pos, newrec);
  2796.             }
  2797.         };
  2798.  
  2799.         me.mon(rstore, 'load', function(s, records, success) {
  2800.  
  2801.             if (!success) {
  2802.                 return;
  2803.             }
  2804.  
  2805.             me.suspendEvents();
  2806.  
  2807.             // remove vanished items
  2808.             (me.snapshot || me.data).each(function(olditem) {
  2809.                 var item = rstore.getById(olditem.getId());
  2810.                 if (!item) {
  2811.                     me.remove(olditem);
  2812.                 }
  2813.             });
  2814.                    
  2815.             rstore.each(function(item) {
  2816.                 cond_add_item(item.data, item.getId());
  2817.             });
  2818.  
  2819.             me.filter();
  2820.  
  2821.             first_load = false;
  2822.  
  2823.             me.resumeEvents();
  2824.             me.fireEvent('datachanged', me);
  2825.         });
  2826.     }
  2827. });
  2828. Ext.define('PVE.data.ObjectStore',  {
  2829.     extend: 'PVE.data.UpdateStore',
  2830.  
  2831.     constructor: function(config) {
  2832.         var me = this;
  2833.  
  2834.         config = config || {};
  2835.  
  2836.         if (!config.storeid) {
  2837.             config.storeid =  'pve-store-' + (++Ext.idSeed);
  2838.         }
  2839.  
  2840.         Ext.applyIf(config, {
  2841.             model: 'KeyValue',
  2842.             proxy: {
  2843.                 type: 'pve',
  2844.                 url: config.url,
  2845.                 extraParams: config.extraParams,
  2846.                 reader: {
  2847.                     type: 'jsonobject',
  2848.                     rows: config.rows
  2849.                 }
  2850.             }
  2851.         });
  2852.  
  2853.         me.callParent([config]);
  2854.     }
  2855. });
  2856. Ext.define('PVE.data.ResourceStore', {
  2857.     extend: 'PVE.data.UpdateStore',
  2858.     singleton: true,
  2859.  
  2860.     findNextVMID: function() {
  2861.         var me = this, i;
  2862.        
  2863.         for (i = 100; i < 10000; i++) {
  2864.             if (me.findExact('vmid', i) < 0) {
  2865.                 return i;
  2866.             }
  2867.         }
  2868.     },
  2869.  
  2870.     findVMID: function(vmid) {
  2871.         var me = this, i;
  2872.        
  2873.         return (me.findExact('vmid', parseInt(vmid, 10)) >= 0);
  2874.     },
  2875.  
  2876.  
  2877.     constructor: function(config) {
  2878.         // fixme: how to avoid those warnings
  2879.         /*jslint confusion: true */
  2880.  
  2881.         var me = this;
  2882.  
  2883.         config = config || {};
  2884.  
  2885.         var field_defaults = {
  2886.             type: {
  2887.                 header: gettext('Type'),
  2888.                 type: 'text',
  2889.                 renderer: PVE.Utils.render_resource_type,
  2890.                 sortable: true,
  2891.                 hideable: false,
  2892.                 width: 80
  2893.             },
  2894.             id: {
  2895.                 header: 'ID',
  2896.                 type: 'text',
  2897.                 hidden: true,
  2898.                 sortable: true,
  2899.                 width: 80
  2900.             },
  2901.             running: {
  2902.                 header: gettext('Online'),
  2903.                 type: 'boolean',
  2904.                 hidden: true,
  2905.                 convert: function(value, record) {
  2906.                     var info = record.data;
  2907.                     if (info.type === 'qemu' || info.type === 'openvz' || info.type === 'node') {
  2908.                         return (Ext.isNumeric(info.uptime) && (info.uptime > 0));
  2909.                     } else {
  2910.                         return false;
  2911.                     }
  2912.                 }
  2913.             },
  2914.             text: {
  2915.                 header: gettext('Description'),
  2916.                 type: 'text',
  2917.                 sortable: true,
  2918.                 width: 200,
  2919.                 convert: function(value, record) {
  2920.                     var info = record.data;
  2921.                     var text;
  2922.  
  2923.                     if (value) {
  2924.                         return value;
  2925.                     }
  2926.  
  2927.                     if (info.type === 'node') {
  2928.                         text = info.node;
  2929.                     } else if (info.type === 'pool') {
  2930.                         text = info.pool;
  2931.                     } else if (info.type === 'storage') {
  2932.                         text = info.storage + ' (' + info.node + ')';
  2933.                     } else if (info.type === 'qemu' || info.type === 'openvz') {
  2934.                         text = String(info.vmid);
  2935.                         if (info.name) {
  2936.                             text += " (" + info.name + ')';
  2937.                         }
  2938.                     } else {
  2939.                         text = info.id;
  2940.                     }
  2941.                     return text;
  2942.                 }
  2943.             },
  2944.             vmid: {
  2945.                 header: 'VMID',
  2946.                 type: 'integer',
  2947.                 hidden: true,
  2948.                 sortable: true,
  2949.                 width: 80
  2950.             },
  2951.             name: {
  2952.                 header: gettext('Name'),
  2953.                 hidden: true,
  2954.                 sortable: true,
  2955.                 type: 'text'
  2956.             },
  2957.             disk: {
  2958.                 header: gettext('Disk usage'),
  2959.                 type: 'integer',
  2960.                 renderer: PVE.Utils.render_disk_usage,
  2961.                 sortable: true,
  2962.                 width: 100
  2963.             },
  2964.             maxdisk: {
  2965.                 header: gettext('Disk size'),
  2966.                 type: 'integer',
  2967.                 renderer: PVE.Utils.render_size,
  2968.                 sortable: true,
  2969.                 hidden: true,
  2970.                 width: 100
  2971.             },
  2972.             mem: {
  2973.                 header: gettext('Memory usage'),
  2974.                 type: 'integer',
  2975.                 renderer: PVE.Utils.render_mem_usage,
  2976.                 sortable: true,
  2977.                 width: 100
  2978.             },
  2979.             maxmem: {
  2980.                 header: gettext('Memory size'),
  2981.                 type: 'integer',
  2982.                 renderer: PVE.Utils.render_size,
  2983.                 hidden: true,
  2984.                 sortable: true,
  2985.                 width: 100
  2986.             },
  2987.             cpu: {
  2988.                 header: gettext('CPU usage'),
  2989.                 type: 'float',
  2990.                 renderer: PVE.Utils.render_cpu,
  2991.                 sortable: true,
  2992.                 width: 100
  2993.             },
  2994.             maxcpu: {
  2995.                 header: 'maxcpu',
  2996.                 type: 'integer',
  2997.                 hidden: true,
  2998.                 sortable: true,
  2999.                 width: 60
  3000.             },
  3001.             uptime: {
  3002.                 header: gettext('Uptime'),
  3003.                 type: 'integer',
  3004.                 renderer: PVE.Utils.render_uptime,
  3005.                 sortable: true,
  3006.                 width: 110
  3007.             },
  3008.             node: {
  3009.                 header: gettext('Node'),
  3010.                 type: 'text',
  3011.                 hidden: true,
  3012.                 sortable: true,
  3013.                 width: 110
  3014.             },
  3015.             storage: {
  3016.                 header: gettext('Storage'),
  3017.                 type: 'text',
  3018.                 hidden: true,
  3019.                 sortable: true,
  3020.                 width: 110
  3021.             },
  3022.             pool: {
  3023.                 header: gettext('Pool'),
  3024.                 type: 'text',
  3025.                 hidden: true,
  3026.                 sortable: true,
  3027.                 width: 110
  3028.             }
  3029.         };
  3030.  
  3031.         var fields = [];
  3032.         var fieldNames = [];
  3033.         Ext.Object.each(field_defaults, function(key, value) {
  3034.             if (!Ext.isDefined(value.convert)) {
  3035.                 fields.push({name: key, type: value.type});
  3036.                 fieldNames.push(key);
  3037.             } else if (key === 'text' || key === 'running') {
  3038.                 fields.push({name: key, type: value.type, convert: value.convert});
  3039.                 fieldNames.push(key);
  3040.             }          
  3041.         });
  3042.  
  3043.         Ext.define('PVEResources', {
  3044.             extend: "Ext.data.Model",
  3045.             fields: fields,
  3046.             proxy: {
  3047.                 type: 'pve',
  3048.                 url: '/api2/json/cluster/resources'
  3049.             }
  3050.         });
  3051.  
  3052.         Ext.define('PVETree', {
  3053.             extend: "Ext.data.Model",
  3054.             fields: fields,
  3055.             proxy: { type: 'memory' }
  3056.         });
  3057.  
  3058.         Ext.apply(config, {
  3059.             storeid: 'PVEResources',
  3060.             model: 'PVEResources',
  3061.             autoDestory: false,
  3062.             defaultColums: function() {
  3063.                 var res = [];
  3064.                 Ext.Object.each(field_defaults, function(field, info) {
  3065.                     var fi = Ext.apply({ dataIndex: field }, info);
  3066.                     res.push(fi);
  3067.                 });
  3068.                 return res;
  3069.             },
  3070.             fieldNames: fieldNames
  3071.         });
  3072.  
  3073.         me.callParent([config]);
  3074.     }
  3075. });
  3076. Ext.define('PVE.form.Checkbox', {
  3077.     extend: 'Ext.form.field.Checkbox',
  3078.     alias: ['widget.pvecheckbox'],
  3079.  
  3080.     defaultValue: undefined,
  3081.  
  3082.     deleteDefaultValue: false,
  3083.     deleteEmpty: false,
  3084.  
  3085.     inputValue: '1',
  3086.  
  3087.     height: 22, // hack: set same height as text fields
  3088.  
  3089.     getSubmitData: function() {
  3090.         var me = this,
  3091.             data = null,
  3092.             val;
  3093.         if (!me.disabled && me.submitValue) {
  3094.             val = me.getSubmitValue();
  3095.             if (val !== null) {
  3096.                 data = {};
  3097.                 if ((val == me.defaultValue) && me.deleteDefaultValue) {
  3098.                     data['delete'] = me.getName();
  3099.                 } else {
  3100.                     data[me.getName()] = val;
  3101.                 }
  3102.             } else if (me.deleteEmpty) {
  3103.                data = {};
  3104.                data['delete'] = me.getName();
  3105.             }
  3106.         }
  3107.         return data;
  3108.     },
  3109.  
  3110.     // also accept integer 1 as true
  3111.     setRawValue: function(value) {
  3112.         var me = this;
  3113.  
  3114.         if (value === 1) {
  3115.             me.callParent([true]);
  3116.         } else {
  3117.             me.callParent([value]);
  3118.         }
  3119.     }
  3120.  
  3121. });Ext.define('PVE.form.Textfield', {
  3122.     extend: 'Ext.form.field.Text',
  3123.     alias: ['widget.pvetextfield'],
  3124.  
  3125.     skipEmptyText: true,
  3126.    
  3127.     deleteEmpty: false,
  3128.    
  3129.     getSubmitData: function() {
  3130.         var me = this,
  3131.             data = null,
  3132.             val;
  3133.         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  3134.             val = me.getSubmitValue();
  3135.             if (val !== null) {
  3136.                 data = {};
  3137.                 data[me.getName()] = val;
  3138.             } else if (me.deleteEmpty) {
  3139.                 data = {};
  3140.                 data['delete'] = me.getName();
  3141.             }
  3142.         }
  3143.         return data;
  3144.     },
  3145.  
  3146.     getSubmitValue: function() {
  3147.         var me = this;
  3148.  
  3149.         var value = this.processRawValue(this.getRawValue());
  3150.         if (value !== '') {
  3151.             return value;
  3152.         }
  3153.  
  3154.         return me.skipEmptyText ? null: value;
  3155.     }
  3156. });Ext.define('PVE.form.RRDTypeSelector', {
  3157.     extend: 'Ext.form.field.ComboBox',
  3158.     alias: ['widget.pveRRDTypeSelector'],
  3159.  
  3160.     initComponent: function() {
  3161.         var me = this;
  3162.        
  3163.         var store = new Ext.data.ArrayStore({
  3164.             fields: [ 'id', 'timeframe', 'cf', 'text' ],
  3165.             data : [
  3166.                 [ 'hour', 'hour', 'AVERAGE', "Hour (average)" ],
  3167.                 [ 'hourmax', 'hour', 'MAX', "Hour (max)" ],
  3168.                 [ 'day', 'day', 'AVERAGE', "Day (average)" ],
  3169.                 [ 'daymax', 'day', 'MAX', "Day (max)" ],
  3170.                 [ 'week', 'week', 'AVERAGE', "Week (average)" ],
  3171.                 [ 'weekmax', 'week', 'MAX', "Week (max)" ],
  3172.                 [ 'month', 'month', 'AVERAGE', "Month (average)" ],
  3173.                 [ 'monthmax', 'month', 'MAX', "Month (max)" ],
  3174.                 [ 'year', 'year', 'AVERAGE', "Year (average)" ],
  3175.                 [ 'yearmax', 'year', 'MAX', "Year (max)" ]
  3176.             ]
  3177.         });
  3178.  
  3179.         Ext.apply(me, {
  3180.             store: store,
  3181.             displayField: 'text',
  3182.             valueField: 'id',
  3183.             editable: false,
  3184.             queryMode: 'local',
  3185.             value: 'hour',
  3186.             getState: function() {
  3187.                 var ind = store.findExact('id', me.getValue());
  3188.                 var rec = store.getAt(ind);
  3189.                 if (!rec) {
  3190.                     return;
  3191.                 }
  3192.                 return {
  3193.                     id: rec.data.id,
  3194.                     timeframe: rec.data.timeframe,
  3195.                     cf: rec.data.cf
  3196.                 };
  3197.             },
  3198.             applyState : function(state) {
  3199.                 if (state && state.id) {
  3200.                     me.setValue(state.id);
  3201.                 }
  3202.             },
  3203.             stateEvents: [ 'select' ],
  3204.             stateful: true,
  3205.             id: 'pveRRDTypeSelection'        
  3206.         });
  3207.  
  3208.         me.callParent();
  3209.  
  3210.         var statechange = function(sp, key, value) {
  3211.             if (key === me.id) {
  3212.                 me.applyState(value);
  3213.             }
  3214.         };
  3215.  
  3216.         var sp = Ext.state.Manager.getProvider();
  3217.         me.mon(sp, 'statechange', statechange, me);
  3218.     }
  3219. });
  3220.  
  3221. Ext.define('PVE.form.ComboGrid', {
  3222.     extend: 'Ext.form.field.ComboBox',
  3223.     alias: ['widget.PVE.form.ComboGrid'],
  3224.  
  3225.     computeHeight: function() {
  3226.         var me = this;
  3227.         var lh = PVE.Utils.gridLineHeigh();
  3228.         var count = me.store.getCount();
  3229.         return (count > 10) ? 10*lh : 26+count*lh;
  3230.     },
  3231.  
  3232.     // hack: allow to select empty value
  3233.     // seems extjs does not allow that when 'editable == false'
  3234.     onKeyUp: function(e, t) {
  3235.         var me = this;
  3236.         var key = e.getKey();
  3237.  
  3238.         if (!me.editable && me.allowBlank && !me.multiSelect &&
  3239.             (key == e.BACKSPACE || key == e.DELETE)) {
  3240.             me.setValue('');
  3241.         }
  3242.  
  3243.         me.callParent(arguments);      
  3244.     },
  3245.  
  3246.     // copied from ComboBox
  3247.     createPicker: function() {
  3248.         var me = this,
  3249.         picker,
  3250.         menuCls = Ext.baseCSSPrefix + 'menu',
  3251.  
  3252.         opts = Ext.apply({
  3253.             selModel: {
  3254.                 mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
  3255.             },
  3256.             floating: true,
  3257.             hidden: true,
  3258.             ownerCt: me.ownerCt,
  3259.             cls: me.el.up('.' + menuCls) ? menuCls : '',
  3260.             store: me.store,
  3261.             displayField: me.displayField,
  3262.             focusOnToFront: false,
  3263.             height: me.computeHeight(),
  3264.             pageSize: me.pageSize
  3265.         }, me.listConfig, me.defaultListConfig);
  3266.  
  3267.         // NOTE: we simply use a grid panel
  3268.         //picker = me.picker = Ext.create('Ext.view.BoundList', opts);
  3269.         picker = me.picker = Ext.create('Ext.grid.Panel', opts);
  3270.  
  3271.         // pass getNode() to the view
  3272.         picker.getNode = function() {
  3273.             picker.getView().getNode(arguments);
  3274.         };
  3275.  
  3276.         me.mon(picker, {
  3277.             itemclick: me.onItemClick,
  3278.             refresh: me.onListRefresh,
  3279.             show: function() {
  3280.                 picker.setHeight(me.computeHeight());
  3281.                 me.syncSelection();
  3282.             },
  3283.             scope: me
  3284.         });
  3285.  
  3286.         me.mon(picker.getSelectionModel(), 'selectionchange', me.onListSelectionChange, me);
  3287.  
  3288.         return picker;
  3289.     },
  3290.  
  3291.     initComponent: function() {
  3292.         var me = this;
  3293.  
  3294.         Ext.apply(me, {
  3295.             queryMode: 'local',
  3296.             editable: false,
  3297.             matchFieldWidth: false
  3298.         });
  3299.  
  3300.         Ext.applyIf(me, { value: ''}); // hack: avoid ExtJS validate() bug
  3301.  
  3302.         Ext.applyIf(me.listConfig, { width: 400 });
  3303.  
  3304.         me.callParent();
  3305.  
  3306.         me.store.on('beforeload', function() {   
  3307.             if (!me.isDisabled()) {
  3308.                 me.setDisabled(true);
  3309.                 me.enableAfterLoad = true;
  3310.             }
  3311.         });
  3312.  
  3313.         // hack: autoSelect does not work
  3314.         me.store.on('load', function(store, r, success, o) {
  3315.             if (success) {
  3316.                 me.clearInvalid();
  3317.                
  3318.                 if (me.enableAfterLoad) {
  3319.                     delete me.enableAfterLoad;
  3320.                     me.setDisabled(false);
  3321.                 }
  3322.  
  3323.                 var def = me.getValue();
  3324.                 if (def) {
  3325.                     me.setValue(def, true); // sync with grid
  3326.                 }
  3327.                 var found = false;
  3328.                 if (def) {
  3329.                     if (Ext.isArray(def)) {
  3330.                         Ext.Array.each(def, function(v) {
  3331.                             if (store.findRecord(me.valueField, v)) {
  3332.                                 found = true;
  3333.                                 return false; // break
  3334.                             }
  3335.                         });
  3336.                     } else {
  3337.                         found = store.findRecord(me.valueField, def);
  3338.                     }
  3339.                 }
  3340.  
  3341.                 if (!found) {
  3342.                     var rec = me.store.first();
  3343.                     if (me.autoSelect && rec && rec.data) {
  3344.                         def = rec.data[me.valueField];
  3345.                         me.setValue(def, true);
  3346.                     } else {
  3347.                         me.setValue('', true);
  3348.                     }
  3349.                 }
  3350.             }
  3351.         });
  3352.     }
  3353. });
  3354. Ext.define('PVE.form.KVComboBox', {
  3355.     extend: 'Ext.form.field.ComboBox',
  3356.     alias: 'widget.pveKVComboBox',
  3357.  
  3358.     deleteEmpty: true,
  3359.    
  3360.     getSubmitData: function() {
  3361.         var me = this,
  3362.             data = null,
  3363.             val;
  3364.         if (!me.disabled && me.submitValue) {
  3365.             val = me.getSubmitValue();
  3366.             if (val !== null && val !== '') {
  3367.                 data = {};
  3368.                 data[me.getName()] = val;
  3369.             } else if (me.deleteEmpty) {
  3370.                 data = {};
  3371.                 data['delete'] = me.getName();
  3372.             }
  3373.         }
  3374.         return data;
  3375.     },
  3376.  
  3377.     initComponent: function() {
  3378.         var me = this;
  3379.  
  3380.         me.store = Ext.create('Ext.data.ArrayStore', {
  3381.             model: 'KeyValue',
  3382.             data : me.data
  3383.         });
  3384.  
  3385.         Ext.apply(me, {
  3386.             displayField: 'value',
  3387.             valueField: 'key',
  3388.             queryMode: 'local',
  3389.             editable: false
  3390.         });
  3391.  
  3392.         me.callParent();
  3393.     }
  3394. });
  3395. // boolean type including 'Default' (delete property from file)
  3396. Ext.define('PVE.form.Boolean', {
  3397.     extend: 'PVE.form.KVComboBox',
  3398.     alias: ['widget.booleanfield'],
  3399.  
  3400.     initComponent: function() {
  3401.         var me = this;
  3402.  
  3403.         me.data = [
  3404.             ['', gettext('Default')],
  3405.             [1, gettext('Yes')],
  3406.             [0, gettext('No')]
  3407.         ];
  3408.  
  3409.         me.callParent();
  3410.     }
  3411. });
  3412. Ext.define('PVE.form.CompressionSelector', {
  3413.     extend: 'PVE.form.KVComboBox',
  3414.     alias: ['widget.pveCompressionSelector'],
  3415.  
  3416.     initComponent: function() {
  3417.         var me = this;
  3418.  
  3419.         me.data = [
  3420.             ['', gettext('none')],
  3421.             ['lzo', 'LZO (' + gettext('fast') + ')'],
  3422.             ['gzip', 'GZIP (' + gettext('good') + ')']
  3423.         ];
  3424.  
  3425.         me.callParent();
  3426.     }
  3427. });
  3428. Ext.define('PVE.form.PoolSelector', {
  3429.     extend: 'PVE.form.ComboGrid',
  3430.     alias: ['widget.pvePoolSelector'],
  3431.  
  3432.     allowBlank: false,
  3433.  
  3434.     initComponent: function() {
  3435.         var me = this;
  3436.  
  3437.         var store = new Ext.data.Store({
  3438.             model: 'pve-pools'
  3439.         });
  3440.  
  3441.         Ext.apply(me, {
  3442.             store: store,
  3443.             autoSelect: false,
  3444.             valueField: 'poolid',
  3445.             displayField: 'poolid',
  3446.             listConfig: {
  3447.                 columns: [
  3448.                     {
  3449.                         header: gettext('Pool'),
  3450.                         sortable: true,
  3451.                         dataIndex: 'poolid',
  3452.                         flex: 1
  3453.                     },
  3454.                     {
  3455.                         id: 'comment',
  3456.                         header: 'Comment',
  3457.                         sortable: false,
  3458.                         dataIndex: 'comment',
  3459.                         flex: 1
  3460.                     }
  3461.                 ]
  3462.             }
  3463.         });
  3464.  
  3465.         me.callParent();
  3466.  
  3467.         store.load();
  3468.     }
  3469.  
  3470. }, function() {
  3471.  
  3472.     Ext.define('pve-pools', {
  3473.         extend: 'Ext.data.Model',
  3474.         fields: [ 'poolid', 'comment' ],
  3475.         proxy: {
  3476.             type: 'pve',
  3477.             url: "/api2/json/pools"
  3478.         },
  3479.         idProperty: 'poolid'
  3480.     });
  3481.  
  3482. });
  3483. Ext.define('PVE.form.GroupSelector', {
  3484.     extend: 'PVE.form.ComboGrid',
  3485.     alias: ['widget.pveGroupSelector'],
  3486.  
  3487.     allowBlank: false,
  3488.  
  3489.     initComponent: function() {
  3490.         var me = this;
  3491.  
  3492.         var store = new Ext.data.Store({
  3493.             model: 'pve-groups'
  3494.         });
  3495.  
  3496.         Ext.apply(me, {
  3497.             store: store,
  3498.             autoSelect: false,
  3499.             valueField: 'groupid',
  3500.             displayField: 'groupid',
  3501.             listConfig: {
  3502.                 columns: [
  3503.                     {
  3504.                         header: gettext('Group'),
  3505.                         sortable: true,
  3506.                         dataIndex: 'groupid',
  3507.                         flex: 1
  3508.                     },
  3509.                     {
  3510.                         id: 'comment',
  3511.                         header: 'Comment',
  3512.                         sortable: false,
  3513.                         dataIndex: 'comment',
  3514.                         flex: 1
  3515.                     }
  3516.                 ]
  3517.             }
  3518.         });
  3519.  
  3520.         me.callParent();
  3521.  
  3522.         store.load();
  3523.     }
  3524.  
  3525. }, function() {
  3526.  
  3527.     Ext.define('pve-groups', {
  3528.         extend: 'Ext.data.Model',
  3529.         fields: [ 'groupid', 'comment' ],
  3530.         proxy: {
  3531.             type: 'pve',
  3532.             url: "/api2/json/access/groups"
  3533.         },
  3534.         idProperty: 'groupid'
  3535.     });
  3536.  
  3537. });
  3538. Ext.define('PVE.form.UserSelector', {
  3539.     extend: 'PVE.form.ComboGrid',
  3540.     alias: ['widget.pveUserSelector'],
  3541.  
  3542.     initComponent: function() {
  3543.         var me = this;
  3544.  
  3545.         var store = new Ext.data.Store({
  3546.             model: 'pve-users'
  3547.         });
  3548.  
  3549.         var render_full_name = function(firstname, metaData, record) {
  3550.  
  3551.             var first = firstname || '';
  3552.             var last = record.data.lastname || '';
  3553.             return first + " " + last;
  3554.         };
  3555.  
  3556.         Ext.apply(me, {
  3557.             store: store,
  3558.             allowBlank: false,
  3559.             autoSelect: false,
  3560.             valueField: 'userid',
  3561.             displayField: 'userid',
  3562.             listConfig: {
  3563.                 columns: [
  3564.                     {
  3565.                         header: gettext('User'),
  3566.                         sortable: true,
  3567.                         dataIndex: 'userid',
  3568.                         flex: 1
  3569.                     },
  3570.                     {
  3571.                         header: 'Name',
  3572.                         sortable: true,
  3573.                         renderer: render_full_name,
  3574.                         dataIndex: 'firstname',
  3575.                         flex: 1
  3576.                     },
  3577.                     {
  3578.                         id: 'comment',
  3579.                         header: 'Comment',
  3580.                         sortable: false,
  3581.                         dataIndex: 'comment',
  3582.                         flex: 1
  3583.                     }
  3584.                 ]
  3585.             }
  3586.         });
  3587.  
  3588.         me.callParent();
  3589.  
  3590.         store.load({ params: { enabled: 1 }});
  3591.     }
  3592.  
  3593. }, function() {
  3594.  
  3595.     Ext.define('pve-users', {
  3596.         extend: 'Ext.data.Model',
  3597.         fields: [
  3598.             'userid', 'firstname', 'lastname' , 'email', 'comment',
  3599.             { type: 'boolean', name: 'enable' },
  3600.             { type: 'date', dateFormat: 'timestamp', name: 'expire' }
  3601.         ],
  3602.         proxy: {
  3603.             type: 'pve',
  3604.             url: "/api2/json/access/users"
  3605.         },
  3606.         idProperty: 'userid'
  3607.     });
  3608.  
  3609. });
  3610.  
  3611.  
  3612. Ext.define('PVE.form.RoleSelector', {
  3613.     extend: 'PVE.form.ComboGrid',
  3614.     alias: ['widget.pveRoleSelector'],
  3615.  
  3616.     initComponent: function() {
  3617.         var me = this;
  3618.  
  3619.         var store = new Ext.data.Store({
  3620.             model: 'pve-roles'
  3621.         });
  3622.  
  3623.         Ext.apply(me, {
  3624.             store: store,
  3625.             allowBlank: false,
  3626.             autoSelect: false,
  3627.             valueField: 'roleid',
  3628.             displayField: 'roleid',
  3629.             listConfig: {
  3630.                 columns: [
  3631.                     {
  3632.                         header: gettext('Role'),
  3633.                         sortable: true,
  3634.                         dataIndex: 'roleid',
  3635.                         flex: 1
  3636.                     }
  3637.                 ]
  3638.             }
  3639.         });
  3640.  
  3641.         me.callParent();
  3642.  
  3643.         store.load();
  3644.     }
  3645.  
  3646. }, function() {
  3647.  
  3648.     Ext.define('pve-roles', {
  3649.         extend: 'Ext.data.Model',
  3650.         fields: [ 'roleid', 'privs' ],
  3651.         proxy: {
  3652.             type: 'pve',
  3653.             url: "/api2/json/access/roles"
  3654.         },
  3655.         idProperty: 'roleid'
  3656.     });
  3657.  
  3658. });
  3659. Ext.define('PVE.form.VMIDSelector', {
  3660.     extend: 'Ext.form.field.Number',
  3661.     alias: 'widget.pveVMIDSelector',
  3662.  
  3663.     allowBlank: false,
  3664.  
  3665.     minValue: 100,
  3666.  
  3667.     maxValue: 999999999,
  3668.  
  3669.     validateExists: undefined,
  3670.  
  3671.     validator: function(value) {
  3672.         /*jslint confusion: true */
  3673.         var me = this;
  3674.  
  3675.         if (!Ext.isDefined(me.validateExists)) {
  3676.             return true;
  3677.         }
  3678.         if (PVE.data.ResourceStore.findVMID(value)) {
  3679.             if (me.validateExists === true) {
  3680.                 return true;
  3681.             }
  3682.             return "This VM ID is already in use.";
  3683.         } else {
  3684.             if (me.validateExists === false) {
  3685.                 return true;
  3686.             }
  3687.             return "This VM ID does not exists.";
  3688.         }
  3689.     },
  3690.  
  3691.     initComponent: function() {
  3692.         var me = this;
  3693.  
  3694.         Ext.applyIf(me, {
  3695.             fieldLabel: 'VM ID'
  3696.         });
  3697.  
  3698.         me.callParent();
  3699.     }
  3700. });
  3701. Ext.define('PVE.form.NetworkCardSelector', {
  3702.     extend: 'PVE.form.KVComboBox',
  3703.     alias: ['widget.PVE.form.NetworkCardSelector'],
  3704.  
  3705.     initComponent: function() {
  3706.         var me = this;
  3707.  
  3708.         me.data = [
  3709.             ['rtl8139', 'Realtec RTL8139'],
  3710.             ['e1000', 'Intel E1000'],
  3711.             ['virtio', 'VirtIO (paravirtualized)']
  3712.         ];
  3713.  
  3714.         me.callParent();
  3715.     }
  3716. });
  3717. Ext.define('PVE.form.DiskFormatSelector', {
  3718.     extend: 'PVE.form.KVComboBox',
  3719.     alias: ['widget.PVE.form.DiskFormatSelector'],
  3720.  
  3721.     initComponent: function() {
  3722.         var me = this;
  3723.  
  3724.         me.data = [
  3725.             ['raw', 'Raw disk image (raw)'],
  3726.             ['qcow2', 'QEMU image format (qcow2)'],
  3727.             ['vmdk', 'VMware image format (vmdk)']
  3728.         ];
  3729.  
  3730.         me.callParent();
  3731.     }
  3732. });
  3733. Ext.define('PVE.form.BusTypeSelector', {
  3734.     extend: 'PVE.form.KVComboBox',
  3735.     alias: ['widget.PVE.form.BusTypeSelector'],
  3736.  
  3737.     noVirtIO: false,
  3738.  
  3739.     noScsi: false,
  3740.  
  3741.     initComponent: function() {
  3742.         var me = this;
  3743.  
  3744.         me.data = [['ide', 'IDE']];
  3745.  
  3746.         if (!me.noVirtIO) {
  3747.             me.data.push(['virtio', 'VIRTIO']);
  3748.         }
  3749.  
  3750.         if (!me.noScsi) {
  3751.             me.data.push(['scsi', 'SCSI']);
  3752.         }
  3753.  
  3754.         me.callParent();
  3755.     }
  3756. });
  3757. Ext.define('PVE.form.ControllerSelector', {
  3758.     extend: 'Ext.form.FieldContainer',
  3759.     alias: ['widget.PVE.form.ControllerSelector'],
  3760.    
  3761.     statics: {
  3762.         maxIds: {
  3763.             ide: 3,
  3764.             virtio: 5,
  3765.             scsi: 13
  3766.         }
  3767.     },
  3768.  
  3769.     noVirtIO: false,
  3770.  
  3771.     noScsi: false,
  3772.  
  3773.     vmconfig: {}, // used to check for existing devices
  3774.  
  3775.     setVMConfig: function(vmconfig, autoSelect) {
  3776.         var me = this;
  3777.  
  3778.         me.vmconfig = Ext.apply({}, vmconfig);
  3779.         if (autoSelect) {
  3780.             var clist = ['ide', 'virtio', 'scsi'];
  3781.             if (autoSelect === 'cdrom') {
  3782.                 clist = ['ide', 'scsi'];
  3783.                 if (!Ext.isDefined(me.vmconfig.ide2)) {
  3784.                     me.down('field[name=controller]').setValue('ide');
  3785.                     me.down('field[name=deviceid]').setValue(2);
  3786.                     return;
  3787.                 }
  3788.             } else if (me.vmconfig.ostype === 'l26') {
  3789.                 clist = ['virtio', 'ide', 'scsi'];
  3790.             }
  3791.  
  3792.             Ext.Array.each(clist, function(controller) {
  3793.                 var confid, i;
  3794.                 if ((controller === 'virtio' && me.noVirtIO) ||
  3795.                     (controller === 'scsi' && me.noScsi)) {
  3796.                     return; //continue
  3797.                 }
  3798.                 me.down('field[name=controller]').setValue(controller);
  3799.                 for (i = 0; i <= PVE.form.ControllerSelector.maxIds[controller]; i++) {
  3800.                     confid = controller + i.toString();
  3801.                     if (!Ext.isDefined(me.vmconfig[confid])) {
  3802.                         me.down('field[name=deviceid]').setValue(i);
  3803.                         return false; // break
  3804.                     }
  3805.                 }
  3806.             });
  3807.         }
  3808.         me.down('field[name=deviceid]').validate();
  3809.     },
  3810.  
  3811.     initComponent: function() {
  3812.         var me = this;
  3813.  
  3814.         Ext.apply(me, {
  3815.             fieldLabel: 'Bus/Device',
  3816.             layout: 'hbox',
  3817.             height: 22, // hack: set to same height as other fields
  3818.             defaults: {
  3819.                 flex: 1,
  3820.                 hideLabel: true
  3821.             },
  3822.             items: [
  3823.                 {
  3824.                     xtype: 'PVE.form.BusTypeSelector',
  3825.                     name: 'controller',
  3826.                     value: 'ide',
  3827.                     noVirtIO: me.noVirtIO,
  3828.                     noScsi: me.noScsi,
  3829.                     allowBlank: false,
  3830.                     listeners: {
  3831.                         change: function(t, value) {
  3832.                             if (!me.rendered || !value) {
  3833.                                 return;
  3834.                             }
  3835.                             var field = me.down('field[name=deviceid]');
  3836.                             field.setMaxValue(PVE.form.ControllerSelector.maxIds[value]);
  3837.                             field.validate();
  3838.                         }
  3839.                     }
  3840.                 },
  3841.                 {
  3842.                     xtype: 'numberfield',
  3843.                     name: 'deviceid',
  3844.                     minValue: 0,
  3845.                     maxValue: PVE.form.ControllerSelector.maxIds.ide,
  3846.                     value: '0',
  3847.                     validator: function(value) {
  3848.                         /*jslint confusion: true */
  3849.                         if (!me.rendered) {
  3850.                             return;
  3851.                         }
  3852.                         var field = me.down('field[name=controller]');
  3853.                         var controller = field.getValue();
  3854.                         var confid = controller + value;
  3855.                         if (Ext.isDefined(me.vmconfig[confid])) {
  3856.                             return "This device is already in use.";
  3857.                         }
  3858.                         return true;
  3859.                     }
  3860.                 }
  3861.             ]
  3862.         });
  3863.  
  3864.         me.callParent();
  3865.     }
  3866. });   Ext.define('PVE.form.RealmComboBox', {
  3867.     extend: 'Ext.form.field.ComboBox',
  3868.     alias: ['widget.pveRealmComboBox'],
  3869.  
  3870.     initComponent: function() {
  3871.         var me = this;
  3872.  
  3873.         var stateid = 'pveloginrealm';
  3874.  
  3875.         var realmstore = Ext.create('Ext.data.Store', {
  3876.             model: 'pve-domains',
  3877.             autoDestory: true
  3878.         });
  3879.  
  3880.         Ext.apply(me, {
  3881.             fieldLabel: 'Realm',
  3882.             name: 'realm',
  3883.             store: realmstore,
  3884.             queryMode: 'local',
  3885.             allowBlank: false,
  3886.             forceSelection: true,
  3887.             autoSelect: false,
  3888.             triggerAction: 'all',
  3889.             valueField: 'realm',
  3890.             displayField: 'descr',
  3891.             getState: function() {
  3892.                 return { value: this.getValue() };
  3893.             },
  3894.             applyState : function(state) {
  3895.                 if (state && state.value) {
  3896.                     this.setValue(state.value);
  3897.                 }
  3898.             },
  3899.             stateEvents: [ 'select' ],
  3900.             stateful: true,
  3901.             id: stateid, // fixme: remove (Stateful does not work without)  
  3902.             stateID: stateid
  3903.         });
  3904.  
  3905.         me.callParent();
  3906.  
  3907.         realmstore.load({
  3908.             callback: function(r, o, success) {
  3909.                 if (success) {
  3910.                     var def = me.getValue();
  3911.                     if (!def || !realmstore.findRecord('realm', def)) {
  3912.                         def = 'pam';
  3913.                         Ext.each(r, function(record) {
  3914.                             if (record.data && record.data["default"]) {
  3915.                                 def = record.data.realm;
  3916.                             }
  3917.                         });
  3918.                     }
  3919.                     if (def) {
  3920.                         me.setValue(def);
  3921.                     }
  3922.                 }
  3923.             }
  3924.         });
  3925.     }
  3926. });Ext.define('PVE.form.BondModeSelector', {
  3927.     extend: 'PVE.form.KVComboBox',
  3928.     alias: ['widget.bondModeSelector'],
  3929.  
  3930.     initComponent: function() {
  3931.         var me = this;
  3932.  
  3933.         me.data = [
  3934.             ['balance-rr', 'balance-rr'],
  3935.             ['active-backup', 'active-backup'],
  3936.             ['balance-xor', 'balance-xor'],
  3937.             ['broadcast', 'broadcast'],
  3938.             ['802.3ad', '802.3ad'],
  3939.             ['balance-tlb', 'balance-tlb'],
  3940.             ['balance-alb', 'balance-alb']
  3941.         ];
  3942.  
  3943.         me.callParent();
  3944.     }
  3945. });
  3946. Ext.define('PVE.form.ViewSelector', {
  3947.     extend: 'Ext.form.field.ComboBox',
  3948.     alias: ['widget.pveViewSelector'],
  3949.  
  3950.     initComponent: function() {
  3951.         var me = this;
  3952.  
  3953.         var default_views = {
  3954.             server: {
  3955.                 text: gettext('Server View'),
  3956.                 groups: ['node']
  3957.             },
  3958.             folder: {
  3959.                 text: gettext('Folder View'),
  3960.                 groups: ['type']
  3961.             },
  3962.             storage: {
  3963.                 text: gettext('Storage View'),
  3964.                 groups: ['node'],
  3965.                 filterfn: function(node) {
  3966.                     return node.data.type === 'storage';
  3967.                 }
  3968.             }
  3969.         };
  3970.  
  3971.         var groupdef = [];
  3972.         Ext.Object.each(default_views, function(viewname, value) {
  3973.             groupdef.push([viewname, value.text]);
  3974.         });
  3975.  
  3976.         var store = Ext.create('Ext.data.Store', {
  3977.             model: 'KeyValue',
  3978.             proxy: {
  3979.                 type: 'memory',
  3980.                 reader: 'array'
  3981.             },
  3982.             data: groupdef,
  3983.             autoload: true,
  3984.             autoDestory: true
  3985.         });
  3986.  
  3987.         Ext.apply(me, {
  3988.             hideLabel: true,
  3989.             store: store,
  3990.             value: groupdef[0][0],
  3991.             editable: false,
  3992.             queryMode: 'local',
  3993.             allowBlank: false,
  3994.             forceSelection: true,
  3995.             autoSelect: false,
  3996.             triggerAction: 'all',
  3997.             valueField: 'key',
  3998.             displayField: 'value',
  3999.  
  4000.             getViewFilter: function() {
  4001.                 var view = me.getValue();
  4002.                 return Ext.apply({ id: view }, default_views[view] || default_views.server);
  4003.             },
  4004.  
  4005.             getState: function() {
  4006.                 return { value: me.getValue() };
  4007.             },
  4008.  
  4009.             applyState : function(state, doSelect) {
  4010.                 var view = me.getValue();
  4011.                 if (state && state.value && (view != state.value)) {
  4012.                     var record = store.findRecord('key', state.value);
  4013.                     if (record) {
  4014.                         me.setValue(state.value, true);
  4015.                         if (doSelect) {
  4016.                             me.fireEvent('select', me, [record]);
  4017.                         }
  4018.                     }
  4019.                 }
  4020.             },
  4021.             stateEvents: [ 'select' ],
  4022.             stateful: true,
  4023.             id: 'view'
  4024.         });
  4025.  
  4026.         me.callParent();
  4027.  
  4028.         var statechange = function(sp, key, value) {
  4029.             if (key === me.id) {
  4030.                 me.applyState(value, true);
  4031.             }
  4032.         };
  4033.  
  4034.         var sp = Ext.state.Manager.getProvider();
  4035.  
  4036.         me.mon(sp, 'statechange', statechange, me);
  4037.     }
  4038. });Ext.define('PVE.form.NodeSelector', {
  4039.     extend: 'PVE.form.ComboGrid',
  4040.     alias: ['widget.PVE.form.NodeSelector'],
  4041.  
  4042.     // invalidate nodes which are offline
  4043.     onlineValidator: false,
  4044.  
  4045.     initComponent: function() {
  4046.         var me = this;
  4047.  
  4048.         var store = Ext.create('Ext.data.Store', {
  4049.             fields: [ 'name', 'cpu', 'maxcpu', 'mem', 'maxmem', 'uptime' ],
  4050.             autoLoad: true,
  4051.             proxy: {
  4052.                 type: 'pve',
  4053.                 url: '/api2/json/nodes'
  4054.             },
  4055.             autoDestory: true,
  4056.             sorters: [
  4057.                 {
  4058.                     property : 'mem',
  4059.                     direction: 'DESC'
  4060.                 },
  4061.                 {
  4062.                     property : 'name',
  4063.                     direction: 'ASC'
  4064.                 }
  4065.             ]
  4066.         });
  4067.  
  4068.         Ext.apply(me, {
  4069.             store: store,
  4070.             valueField: 'name',
  4071.             displayField: 'name',
  4072.             listConfig: {
  4073.                 columns: [
  4074.                     {
  4075.                         header: 'Node',
  4076.                         dataIndex: 'name',
  4077.                         hideable: false,
  4078.                         flex: 1
  4079.                     },
  4080.                     {
  4081.                         header: 'Memory usage',                
  4082.                         renderer: PVE.Utils.render_mem_usage,
  4083.                         width: 100,
  4084.                         dataIndex: 'mem'
  4085.                     },
  4086.                     {
  4087.                         header: 'CPU usage',
  4088.                         renderer: PVE.Utils.render_cpu,
  4089.                         sortable: true,
  4090.                         width: 100,
  4091.                         dataIndex: 'cpu'
  4092.                     }
  4093.                 ]
  4094.             },
  4095.             validator: function(value) {
  4096.                 /*jslint confusion: true */
  4097.                 if (!me.onlineValidator || (me.allowBlank && !value)) {
  4098.                     return true;
  4099.                 }
  4100.                
  4101.                 var offline = [];
  4102.                 Ext.Array.each(value.split(/\s*,\s*/), function(node) {
  4103.                     var rec = me.store.findRecord(me.valueField, node);
  4104.                     if (!(rec && rec.data) || !Ext.isNumeric(rec.data.mem)) {
  4105.                         offline.push(node);
  4106.                     }
  4107.                 });
  4108.  
  4109.                 if (offline.length == 0) {
  4110.                     return true;
  4111.                 }
  4112.  
  4113.                 return "Node " + offline.join(', ') + " seems to be offline!";
  4114.             }
  4115.         });
  4116.  
  4117.         me.callParent();
  4118.     }
  4119. });Ext.define('PVE.form.FileSelector', {
  4120.     extend: 'PVE.form.ComboGrid',
  4121.     alias: ['widget.pveFileSelector'],
  4122.  
  4123.     setStorage: function(storage, nodename) {
  4124.         var me = this;
  4125.  
  4126.         var change = false;
  4127.         if (storage && (me.storage !== storage)) {
  4128.             me.storage = storage;
  4129.             change = true;
  4130.         }
  4131.  
  4132.         if (nodename && (me.nodename !== nodename)) {
  4133.             me.nodename = nodename;
  4134.             change = true;
  4135.         }
  4136.  
  4137.         if (!(me.storage && me.nodename && change)) {
  4138.             return;
  4139.         }
  4140.  
  4141.         var url = '/api2/json/nodes/' + me.nodename + '/storage/' + me.storage + '/content';
  4142.         if (me.storageContent) {
  4143.             url += '?content=' + me.storageContent;
  4144.         }
  4145.  
  4146.         me.store.setProxy({
  4147.             type: 'pve',
  4148.             url: url
  4149.         });
  4150.  
  4151.         me.store.load();
  4152.     },
  4153.  
  4154.     initComponent: function() {
  4155.         var me = this;
  4156.  
  4157.         var store = Ext.create('Ext.data.Store', {
  4158.             model: 'pve-storage-content'
  4159.         });
  4160.  
  4161.         Ext.apply(me, {
  4162.             store: store,
  4163.             allowBlank: false,
  4164.             autoSelect: false,
  4165.             valueField: 'volid',
  4166.             displayField: 'text',
  4167.             listConfig: {
  4168.                 columns: [
  4169.                     {
  4170.                         header: 'Name',
  4171.                         dataIndex: 'text',
  4172.                         hideable: false,
  4173.                         flex: 1
  4174.                     },
  4175.                     {
  4176.                         header: 'Format',  
  4177.                         width: 60,
  4178.                         dataIndex: 'format'
  4179.                     },
  4180.                     {
  4181.                         header: 'Size',  
  4182.                         width: 60,
  4183.                         dataIndex: 'size',
  4184.                         renderer: PVE.Utils.format_size
  4185.                     }
  4186.                 ]
  4187.             }
  4188.         });
  4189.  
  4190.         me.callParent();
  4191.  
  4192.         me.setStorage(me.storage, me.nodename);
  4193.     }
  4194. });Ext.define('PVE.form.StorageSelector', {
  4195.     extend: 'PVE.form.ComboGrid',
  4196.     alias: ['widget.PVE.form.StorageSelector'],
  4197.  
  4198.     setNodename: function(nodename) {
  4199.         var me = this;
  4200.  
  4201.         if (!nodename || (me.nodename === nodename)) {
  4202.             return;
  4203.         }
  4204.  
  4205.         me.nodename = nodename;
  4206.  
  4207.         var url = '/api2/json/nodes/' + me.nodename + '/storage';
  4208.         if (me.storageContent) {
  4209.             url += '?content=' + me.storageContent;
  4210.         }
  4211.  
  4212.         me.store.setProxy({
  4213.             type: 'pve',
  4214.             url: url
  4215.         });
  4216.  
  4217.         me.store.load();
  4218.     },
  4219.  
  4220.     initComponent: function() {
  4221.         var me = this;
  4222.  
  4223.         var nodename = me.nodename;
  4224.         me.nodename = undefined;
  4225.  
  4226.         var store = Ext.create('Ext.data.Store', {
  4227.             model: 'pve-storage-status',
  4228.             sorters: {
  4229.                 property: 'storage',
  4230.                 order: 'DESC'
  4231.             }
  4232.         });
  4233.  
  4234.         Ext.apply(me, {
  4235.             store: store,
  4236.             allowBlank: false,
  4237.             valueField: 'storage',
  4238.             displayField: 'storage',
  4239.             listConfig: {
  4240.                 columns: [
  4241.                     {
  4242.                         header: 'Name',
  4243.                         dataIndex: 'storage',
  4244.                         hideable: false,
  4245.                         flex: 1
  4246.                     },
  4247.                     {
  4248.                         header: 'Type',  
  4249.                         width: 60,
  4250.                         dataIndex: 'type'
  4251.                     },
  4252.                     {
  4253.                         header: 'Avail',  
  4254.                         width: 80,
  4255.                         dataIndex: 'avail',
  4256.                         renderer: PVE.Utils.format_size
  4257.                     },
  4258.                     {
  4259.                         header: 'Capacity',  
  4260.                         width: 80,
  4261.                         dataIndex: 'total',
  4262.                         renderer: PVE.Utils.format_size
  4263.                     }
  4264.                 ]
  4265.             }
  4266.         });
  4267.  
  4268.         me.callParent();
  4269.  
  4270.         if (nodename) {
  4271.             me.setNodename(nodename);
  4272.         }
  4273.     }
  4274. }, function() {
  4275.  
  4276.     Ext.define('pve-storage-status', {
  4277.         extend: 'Ext.data.Model',
  4278.         fields: [ 'storage', 'active', 'type', 'avail', 'total' ],
  4279.         idProperty: 'storage'
  4280.     });
  4281.  
  4282. });
  4283. Ext.define('PVE.form.BridgeSelector', {
  4284.     extend: 'PVE.form.ComboGrid',
  4285.     alias: ['widget.PVE.form.BridgeSelector'],
  4286.  
  4287.     setNodename: function(nodename) {
  4288.         var me = this;
  4289.  
  4290.         if (!nodename || (me.nodename === nodename)) {
  4291.             return;
  4292.         }
  4293.  
  4294.         me.nodename = nodename;
  4295.  
  4296.         me.store.setProxy({
  4297.             type: 'pve',
  4298.             url: '/api2/json/nodes/' + me.nodename + '/network?type=bridge'
  4299.         });
  4300.  
  4301.         me.store.load();
  4302.     },
  4303.  
  4304.     initComponent: function() {
  4305.         var me = this;
  4306.  
  4307.         var nodename = me.nodename;
  4308.         me.nodename = undefined;
  4309.  
  4310.         var store = Ext.create('Ext.data.Store', {
  4311.             fields: [ 'iface', 'active', 'type' ],
  4312.             filterOnLoad: true
  4313.         });
  4314.  
  4315.         Ext.apply(me, {
  4316.             store: store,
  4317.             valueField: 'iface',
  4318.             displayField: 'iface',
  4319.             listConfig: {
  4320.                 columns: [
  4321.                     {
  4322.                         header: 'Bridge',
  4323.                         dataIndex: 'iface',
  4324.                         hideable: false,
  4325.                         flex: 1
  4326.                     },
  4327.                     {
  4328.                         header: gettext('Active'),  
  4329.                         width: 60,
  4330.                         dataIndex: 'active',
  4331.                         renderer: PVE.Utils.format_boolean
  4332.                     }
  4333.                 ]
  4334.             }
  4335.         });
  4336.  
  4337.         me.callParent();
  4338.  
  4339.         if (nodename) {
  4340.             me.setNodename(nodename);
  4341.         }
  4342.     }
  4343. });
  4344.  
  4345. Ext.define('PVE.form.CPUModelSelector', {
  4346.     extend: 'PVE.form.KVComboBox',
  4347.     alias: ['widget.CPUModelSelector'],
  4348.  
  4349.     initComponent: function() {
  4350.         var me = this;
  4351.  
  4352.         me.data = [
  4353.             ['', 'Default (qemu64)'],
  4354.             ['486', '486'],
  4355.             ['athlon', 'athlon'],
  4356.             ['core2duo', 'core2duo'],
  4357.             ['coreduo', 'coreduo'],
  4358.             ['kvm32', 'kvm32'],
  4359.             ['kvm64', 'kvm64'],
  4360.             ['pentium', 'pentium'],
  4361.             ['pentium2', 'pentium2'],
  4362.             ['pentium3', 'pentium3'],
  4363.             ['phenom', 'phenom'],
  4364.             ['qemu32', 'qemu32'],
  4365.             ['qemu64', 'qemu64'],
  4366.             ['cpu64-rhel6', 'cpu64-rhel6'],
  4367.             ['cpu64-rhel5', 'cpu64-rhel5'],
  4368.             ['Conroe', 'Conroe'],
  4369.             ['Penryn', 'Penryn'],
  4370.             ['Nehalem', 'Nehalem'],
  4371.             ['Westmere', 'Westmere'],
  4372.             ['Opteron_G1', 'Opteron_G1'],
  4373.             ['Opteron_G2', 'Opteron_G2'],
  4374.             ['Opteron_G3', 'Opteron_G3'],
  4375.             ['host', 'host']
  4376.         ];
  4377.  
  4378.         me.callParent();
  4379.     }
  4380. });
  4381. Ext.define('PVE.form.VNCKeyboardSelector', {
  4382.     extend: 'PVE.form.KVComboBox',
  4383.     alias: ['widget.VNCKeyboardSelector'],
  4384.  
  4385.     initComponent: function() {
  4386.         var me = this;
  4387.         me.data = PVE.Utils.kvm_keymap_array();
  4388.         me.callParent();
  4389.     }
  4390. });
  4391. Ext.define('PVE.form.LanguageSelector', {
  4392.     extend: 'PVE.form.KVComboBox',
  4393.     alias: ['widget.pveLanguageSelector'],
  4394.  
  4395.     initComponent: function() {
  4396.         var me = this;
  4397.         me.data = PVE.Utils.language_array();
  4398.         me.callParent();
  4399.     }
  4400. });
  4401. Ext.define('PVE.form.DisplaySelector', {
  4402.     extend: 'PVE.form.KVComboBox',
  4403.     alias: ['widget.DisplaySelector'],
  4404.  
  4405.     initComponent: function() {
  4406.         var me = this;
  4407.  
  4408.         me.data = PVE.Utils.kvm_vga_driver_array();
  4409.         me.callParent();
  4410.     }
  4411. });
  4412. Ext.define('PVE.form.CacheTypeSelector', {
  4413.     extend: 'PVE.form.KVComboBox',
  4414.     alias: ['widget.CacheTypeSelector'],
  4415.  
  4416.     initComponent: function() {
  4417.         var me = this;
  4418.  
  4419.         me.data = [
  4420.             ['', 'Default (no cache)'],
  4421.             ['writethrough', 'Write through'],
  4422.             ['writeback', 'Write back'],
  4423.             ['unsafe', 'Write back (unsafe)'],
  4424.             ['none', 'No cache']
  4425.         ];
  4426.  
  4427.         me.callParent();
  4428.     }
  4429. });
  4430. Ext.define('PVE.form.ContentTypeSelector', {
  4431.     extend: 'PVE.form.KVComboBox',
  4432.     alias: ['widget.pveContentTypeSelector'],
  4433.  
  4434.     initComponent: function() {
  4435.         var me = this;
  4436.  
  4437.         me.data = [];
  4438.  
  4439.         var cts = ['images', 'iso', 'vztmpl', 'backup', 'rootdir'];
  4440.         Ext.Array.each(cts, function(ct) {
  4441.             me.data.push([ct, PVE.Utils.format_content_types(ct)]);
  4442.         });
  4443.  
  4444.         me.callParent();
  4445.     }
  4446. });
  4447. Ext.define('PVE.form.DayOfWeekSelector', {
  4448.     extend: 'PVE.form.KVComboBox',
  4449.     alias: ['widget.pveDayOfWeekSelector'],
  4450.  
  4451.     initComponent: function() {
  4452.         var me = this;
  4453.  
  4454.         me.data = [
  4455.             ['sun', Ext.Date.dayNames[0]],
  4456.             ['mon', Ext.Date.dayNames[1]],
  4457.             ['tue', Ext.Date.dayNames[2]],
  4458.             ['wed', Ext.Date.dayNames[3]],
  4459.             ['thu', Ext.Date.dayNames[4]],
  4460.             ['fri', Ext.Date.dayNames[5]],
  4461.             ['sat', Ext.Date.dayNames[6]]
  4462.         ];
  4463.  
  4464.         me.callParent();
  4465.     }
  4466. });
  4467. Ext.define('PVE.form.BackupModeSelector', {
  4468.     extend: 'PVE.form.KVComboBox',
  4469.     alias: ['widget.pveBackupModeSelector'],
  4470.  
  4471.     initComponent: function() {
  4472.         var me = this;
  4473.  
  4474.         me.data = [
  4475.             ['snapshot', 'Snapshot'],
  4476.             ['suspend', 'Suspend'],
  4477.             ['stop', 'Stop']
  4478.         ];
  4479.  
  4480.         me.callParent();
  4481.     }
  4482. });
  4483. Ext.define('PVE.dc.Tasks', {
  4484.     extend: 'Ext.grid.GridPanel',
  4485.  
  4486.     alias: ['widget.pveClusterTasks'],
  4487.  
  4488.     initComponent : function() {
  4489.         var me = this;
  4490.  
  4491.         var taskstore = new PVE.data.UpdateStore({
  4492.             storeid: 'pve-cluster-tasks',
  4493.             model: 'pve-tasks',
  4494.             proxy: {
  4495.                 type: 'pve',
  4496.                 url: '/api2/json/cluster/tasks'
  4497.             },
  4498.             sorters: [
  4499.                 {
  4500.                     property : 'starttime',
  4501.                     direction: 'DESC'
  4502.                 }
  4503.             ]
  4504.         });
  4505.  
  4506.         var store = Ext.create('PVE.data.DiffStore', {
  4507.             rstore: taskstore,
  4508.             appendAtStart: true
  4509.         });
  4510.  
  4511.         var run_task_viewer = function() {
  4512.             var sm = me.getSelectionModel();
  4513.             var rec = sm.getSelection()[0];
  4514.             if (!rec) {
  4515.                 return;
  4516.             }
  4517.  
  4518.             var win = Ext.create('PVE.window.TaskViewer', {
  4519.                 upid: rec.data.upid
  4520.             });
  4521.             win.show();
  4522.         };
  4523.  
  4524.         Ext.apply(me, {
  4525.             store: store,
  4526.             stateful: false,
  4527.  
  4528.             viewConfig: {
  4529.                 trackOver: false,
  4530.                 stripeRows: false, // does not work with getRowClass()
  4531.  
  4532.                 getRowClass: function(record, index) {
  4533.                     var status = record.get('status');
  4534.  
  4535.                     if (status && status != 'OK') {
  4536.                         return "x-form-invalid-field";
  4537.                     }
  4538.                 }
  4539.             },
  4540.             sortableColumns: false,
  4541.             columns: [
  4542.                 {
  4543.                     header: gettext("Start Time"),
  4544.                     dataIndex: 'starttime',
  4545.                     width: 100,
  4546.                     renderer: function(value) {
  4547.                         return Ext.Date.format(value, "M d H:i:s");
  4548.                     }
  4549.                 },
  4550.                 {
  4551.                     header: gettext("End Time"),
  4552.                     dataIndex: 'endtime',
  4553.                     width: 100,
  4554.                     renderer: function(value, metaData, record) {
  4555.                         if (record.data.pid) {
  4556.                             metaData.tdCls =  "x-grid-row-loading";
  4557.                             return "";
  4558.                         }
  4559.                         return Ext.Date.format(value, "M d H:i:s");
  4560.                     }
  4561.                 },
  4562.                 {
  4563.                     header: gettext("Node"),
  4564.                     dataIndex: 'node',
  4565.                     width: 100
  4566.                 },
  4567.                 {
  4568.                     header: gettext("User name"),
  4569.                     dataIndex: 'user',
  4570.                     width: 150
  4571.                 },
  4572.                 {
  4573.                     header: gettext("Description"),
  4574.                     dataIndex: 'upid',
  4575.                     flex: 1,             
  4576.                     renderer: PVE.Utils.render_upid
  4577.                 },
  4578.                 {
  4579.                     header: gettext("Status"),
  4580.                     dataIndex: 'status',
  4581.                     width: 200,
  4582.                     renderer: function(value, metaData, record) {
  4583.                         if (record.data.pid) {
  4584.                             metaData.tdCls =  "x-grid-row-loading";
  4585.                             return "";
  4586.                         }
  4587.                         if (value == 'OK') {
  4588.                             return 'OK';
  4589.                         }
  4590.                         // metaData.attr = 'style="color:red;"';
  4591.                         return PVE.Utils.errorText + ': ' + value;
  4592.                     }
  4593.                 }
  4594.             ],
  4595.             listeners: {
  4596.                 itemdblclick: run_task_viewer,
  4597.                 show: taskstore.startUpdate,
  4598.                 hide: taskstore.stopUpdate,
  4599.                 destroy: taskstore.stopUpdate
  4600.             }
  4601.         });
  4602.  
  4603.         me.callParent();
  4604.     }
  4605. });Ext.define('PVE.dc.Log', {
  4606.     extend: 'Ext.grid.GridPanel',
  4607.  
  4608.     alias: ['widget.pveClusterLog'],
  4609.  
  4610.     initComponent : function() {
  4611.         var me = this;
  4612.  
  4613.         var logstore = new PVE.data.UpdateStore({
  4614.             storeid: 'pve-cluster-log',
  4615.             model: 'pve-cluster-log',
  4616.             proxy: {
  4617.                 type: 'pve',
  4618.                 url: '/api2/json/cluster/log'
  4619.             }
  4620.         });
  4621.  
  4622.         var store = Ext.create('PVE.data.DiffStore', {
  4623.             rstore: logstore,
  4624.             appendAtStart: true
  4625.         });
  4626.  
  4627.         Ext.apply(me, {
  4628.             store: store,
  4629.             stateful: false,
  4630.  
  4631.             viewConfig: {
  4632.                 trackOver: false,
  4633.                 stripeRows: false, // does not work with getRowClass()
  4634.  
  4635.                 getRowClass: function(record, index) {
  4636.                     var pri = record.get('pri');
  4637.  
  4638.                     if (pri && pri <= 3) {
  4639.                         return "x-form-invalid-field";
  4640.                     }
  4641.                 }
  4642.             },
  4643.             sortableColumns: false,
  4644.             columns: [
  4645.                 {
  4646.                     header: gettext("Time"),
  4647.                     dataIndex: 'time',
  4648.                     width: 100,
  4649.                     renderer: function(value) {
  4650.                         return Ext.Date.format(value, "M d H:i:s");
  4651.                     }
  4652.                 },
  4653.                 {
  4654.                     header: gettext("Node"),
  4655.                     dataIndex: 'node',
  4656.                     width: 100
  4657.                 },
  4658.                 {
  4659.                     header: gettext("Service"),
  4660.                     dataIndex: 'tag',
  4661.                     width: 100
  4662.                 },
  4663.                 {
  4664.                     header: "PID",
  4665.                     dataIndex: 'pid',
  4666.                     width: 100
  4667.                 },
  4668.                 {
  4669.                     header: gettext("User name"),
  4670.                     dataIndex: 'user',
  4671.                     width: 150
  4672.                 },
  4673.                 {
  4674.                     header: gettext("Severity"),
  4675.                     dataIndex: 'pri',
  4676.                     renderer: PVE.Utils.render_serverity,
  4677.                     width: 100
  4678.                 },
  4679.                 {
  4680.                     header: gettext("Message"),
  4681.                     dataIndex: 'msg',
  4682.                     flex: 1      
  4683.                 }
  4684.             ],
  4685.             listeners: {
  4686.                 show: logstore.startUpdate,
  4687.                 hide: logstore.stopUpdate,
  4688.                 destroy: logstore.stopUpdate
  4689.             }
  4690.         });
  4691.  
  4692.         me.callParent();
  4693.     }
  4694. });Ext.define('PVE.panel.StatusPanel', {
  4695.     extend: 'Ext.tab.Panel',
  4696.     alias: 'widget.pveStatusPanel',
  4697.  
  4698.    
  4699.     //title: "Logs",
  4700.     //tabPosition: 'bottom',
  4701.  
  4702.     initComponent: function() {
  4703.         var me = this;
  4704.  
  4705.         var stateid = 'ltab';
  4706.         var sp = Ext.state.Manager.getProvider();
  4707.  
  4708.         var state = sp.get(stateid);
  4709.         if (state && state.value) {
  4710.             me.activeTab = state.value;
  4711.         }
  4712.  
  4713.         Ext.apply(me, {
  4714.             listeners: {
  4715.                 tabchange: function() {
  4716.                     var atab = me.getActiveTab().itemId;
  4717.                     var state = { value: atab };
  4718.                     sp.set(stateid, state);
  4719.                 }
  4720.             },
  4721.             items: [
  4722.                 {
  4723.                     itemId: 'tasks',
  4724.                     title: gettext('Tasks'),
  4725.                     xtype: 'pveClusterTasks'
  4726.                 },
  4727.                 {
  4728.                     itemId: 'clog',
  4729.                     title: gettext('Cluster log'),
  4730.                     xtype: 'pveClusterLog'
  4731.                 }
  4732.             ]
  4733.         });
  4734.  
  4735.         me.callParent();
  4736.  
  4737.         me.items.get(0).fireEvent('show', me.items.get(0));
  4738.  
  4739.         var statechange = function(sp, key, state) {
  4740.             if (key === stateid) {
  4741.                 var atab = me.getActiveTab().itemId;
  4742.                 var ntab = state.value;
  4743.                 if (state && ntab && (atab != ntab)) {
  4744.                     me.setActiveTab(ntab);
  4745.                 }
  4746.             }
  4747.         };
  4748.  
  4749.         sp.on('statechange', statechange);
  4750.         me.on('destroy', function() {
  4751.             sp.un('statechange', statechange);             
  4752.         });
  4753.  
  4754.     }
  4755. });
  4756. Ext.define('PVE.panel.RRDView', {
  4757.     extend: 'Ext.panel.Panel',
  4758.     alias: 'widget.pveRRDView',
  4759.  
  4760.     initComponent : function() {
  4761.         var me = this;
  4762.  
  4763.         if (!me.timeframe) {
  4764.             me.timeframe = 'hour';
  4765.         }
  4766.  
  4767.         if (!me.rrdcffn) {
  4768.             me.rrdcffn = 'AVERAGE';
  4769.         }
  4770.  
  4771.         if (!me.datasource) {
  4772.             throw "no datasource specified";
  4773.         }
  4774.  
  4775.         if (!me.rrdurl) {
  4776.             throw "no rrdurl specified";
  4777.         }
  4778.  
  4779.         var datasource = me.datasource;
  4780.  
  4781.         // fixme: dcindex??
  4782.         var dcindex = 0;
  4783.         var create_url = function() {
  4784.             var url = me.rrdurl + "?ds=" + datasource +
  4785.                 "&timeframe=" + me.timeframe + "&cf=" + me.rrdcffn +
  4786.                 "&_dc=" + dcindex.toString();
  4787.             dcindex++;
  4788.             return url;
  4789.         };
  4790.  
  4791.         var stateid = 'pveRRDTypeSelection';
  4792.  
  4793.         Ext.apply(me, {
  4794.             layout: 'fit',
  4795.             html: {
  4796.                 tag: 'img',
  4797.                 width: 800,
  4798.                 height: 200,
  4799.                 src:  create_url()
  4800.             },
  4801.             applyState : function(state) {
  4802.                 if (state && state.id) {
  4803.                     me.timeframe = state.timeframe;
  4804.                     me.rrdcffn = state.cf;
  4805.                     me.reload_task.delay(10);
  4806.                 }
  4807.             }
  4808.         });
  4809.        
  4810.         me.callParent();
  4811.    
  4812.         me.reload_task = new Ext.util.DelayedTask(function() {
  4813.             if (me.rendered) {
  4814.                 try {
  4815.                     var html = {
  4816.                         tag: 'img',
  4817.                         width: 800,
  4818.                         height: 200,
  4819.                         src:  create_url()
  4820.                     };
  4821.                     me.update(html);
  4822.                 } catch (e) {
  4823.                     // fixme:
  4824.                     console.log(e);
  4825.                 }
  4826.                 me.reload_task.delay(30000);
  4827.             } else {
  4828.                 me.reload_task.delay(1000);
  4829.             }
  4830.         });
  4831.  
  4832.         me.reload_task.delay(30000);
  4833.  
  4834.         me.on('destroy', function() {
  4835.             me.reload_task.cancel();
  4836.         });
  4837.  
  4838.         var sp = Ext.state.Manager.getProvider();
  4839.         me.applyState(sp.get(stateid));
  4840.  
  4841.         var state_change_fn = function(prov, key, value) {
  4842.             if (key == stateid) {
  4843.                 me.applyState(value);
  4844.             }
  4845.         };
  4846.  
  4847.         me.mon(sp, 'statechange', state_change_fn);
  4848.     }
  4849. });
  4850. Ext.define('PVE.panel.InputPanel', {
  4851.     extend: 'Ext.panel.Panel',
  4852.     alias: ['widget.inputpanel'],
  4853.  
  4854.     border: false,
  4855.  
  4856.     // overwrite this to modify submit data
  4857.     onGetValues: function(values) {
  4858.         return values;
  4859.     },
  4860.  
  4861.     getValues: function(dirtyOnly) {
  4862.         var me = this;
  4863.  
  4864.         if (Ext.isFunction(me.onGetValues)) {
  4865.             dirtyOnly = false;
  4866.         }
  4867.  
  4868.         var values = {};
  4869.  
  4870.         Ext.Array.each(me.query('[isFormField]'), function(field) {
  4871.             if (!dirtyOnly || field.isDirty()) {
  4872.                 PVE.Utils.assemble_field_data(values, field.getSubmitData());
  4873.             }
  4874.         });
  4875.  
  4876.         return me.onGetValues(values);
  4877.     },
  4878.  
  4879.     setValues: function(values) {
  4880.         var me = this;
  4881.  
  4882.         var form = me.up('form');
  4883.  
  4884.         Ext.iterate(values, function(fieldId, val) {
  4885.             var field = me.query('[isFormField][name=' + fieldId + ']')[0];
  4886.             if (field) {
  4887.                 field.setValue(val);
  4888.                 if (form.trackResetOnLoad) {
  4889.                     field.resetOriginalValue();
  4890.                 }
  4891.             }
  4892.         });
  4893.     },
  4894.  
  4895.     initComponent: function() {
  4896.         var me = this;
  4897.  
  4898.         var items;
  4899.        
  4900.         if (me.items) {
  4901.             me.columns = 1;
  4902.             items = [
  4903.                 {
  4904.                     columnWidth: 1,
  4905.                     layout: 'anchor',
  4906.                     items: me.items
  4907.                 }
  4908.             ];
  4909.             me.items = undefined;
  4910.         } else if (me.column1) {
  4911.             me.columns = 2;
  4912.             items = [
  4913.                 {
  4914.                     columnWidth: 0.5,
  4915.                     padding: '0 10 0 0',
  4916.                     layout: 'anchor',
  4917.                     items: me.column1
  4918.                 },
  4919.                 {
  4920.                     columnWidth: 0.5,
  4921.                     padding: '0 0 0 10',
  4922.                     layout: 'anchor',
  4923.                     items: me.column2 || [] // allow empty column
  4924.                 }
  4925.             ];
  4926.         } else {
  4927.             throw "unsupported config";
  4928.         }
  4929.  
  4930.         if (me.useFieldContainer) {
  4931.             Ext.apply(me, {
  4932.                 layout: 'fit',
  4933.                 items: Ext.apply(me.useFieldContainer, {
  4934.                     layout: 'column',
  4935.                     defaultType: 'container',
  4936.                     items: items
  4937.                 })
  4938.             });
  4939.         } else {
  4940.             Ext.apply(me, {
  4941.                 layout: 'column',
  4942.                 defaultType: 'container',
  4943.                 items: items
  4944.             });
  4945.         }
  4946.        
  4947.         me.callParent();
  4948.     }
  4949. });
  4950. // fixme: how can we avoid those lint errors?
  4951. /*jslint confusion: true */
  4952. Ext.define('PVE.window.Edit', {
  4953.     extend: 'Ext.window.Window',
  4954.     alias: 'widget.pveWindowEdit',
  4955.  
  4956.     resizable: false,
  4957.  
  4958.     // use this tio atimatically generate a title like
  4959.     // Create: <subject>
  4960.     subject: undefined,
  4961.  
  4962.     // set create to true if you want a Create button (instead
  4963.     // OK and RESET)
  4964.     create: false,
  4965.  
  4966.     // set to true if you want an Add button (instead of Create)
  4967.     isAdd: false,
  4968.  
  4969.     isValid: function() {
  4970.         var me = this;
  4971.  
  4972.         var form = me.formPanel.getForm();
  4973.         return form.isValid();
  4974.     },
  4975.  
  4976.     getValues: function(dirtyOnly) {
  4977.         var me = this;
  4978.  
  4979.         var values = {};
  4980.  
  4981.         var form = me.formPanel.getForm();
  4982.  
  4983.         form.getFields().each(function(field) {
  4984.             if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
  4985.                 PVE.Utils.assemble_field_data(values, field.getSubmitData());
  4986.             }
  4987.         });
  4988.  
  4989.         Ext.Array.each(me.query('inputpanel'), function(panel) {
  4990.             PVE.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
  4991.         });
  4992.  
  4993.         return values;
  4994.     },
  4995.  
  4996.     setValues: function(values) {
  4997.         var me = this;
  4998.  
  4999.         var form = me.formPanel.getForm();
  5000.  
  5001.         Ext.iterate(values, function(fieldId, val) {
  5002.             var field = form.findField(fieldId);
  5003.             if (field && !field.up('inputpanel')) {
  5004.                field.setValue(val);
  5005.                 if (form.trackResetOnLoad) {
  5006.                     field.resetOriginalValue();
  5007.                 }
  5008.             }
  5009.         });
  5010.  
  5011.         Ext.Array.each(me.query('inputpanel'), function(panel) {
  5012.             panel.setValues(values);
  5013.         });
  5014.     },
  5015.  
  5016.     submit: function() {
  5017.         var me = this;
  5018.  
  5019.         var form = me.formPanel.getForm();
  5020.  
  5021.         var values = me.getValues();
  5022.         Ext.Object.each(values, function(name, val) {
  5023.             if (values.hasOwnProperty(name)) {
  5024.                 if (Ext.isArray(val) && !val.length) {
  5025.                     values[name] = '';
  5026.                 }
  5027.             }
  5028.         });
  5029.  
  5030.         if (me.digest) {
  5031.             values.digest = me.digest;
  5032.         }
  5033.  
  5034.         PVE.Utils.API2Request({
  5035.             url: me.url,
  5036.             waitMsgTarget: me,
  5037.             method: me.method || 'PUT',
  5038.             params: values,
  5039.             failure: function(response, options) {
  5040.                 if (response.result.errors) {
  5041.                     form.markInvalid(response.result.errors);
  5042.                 }
  5043.                 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  5044.             },
  5045.             success: function(response, options) {
  5046.                 me.close();
  5047.             }
  5048.         });
  5049.     },
  5050.  
  5051.     load: function(options) {
  5052.         var me = this;
  5053.  
  5054.         var form = me.formPanel.getForm();
  5055.  
  5056.         options = options || {};
  5057.  
  5058.         var newopts = Ext.apply({
  5059.             waitMsgTarget: me
  5060.         }, options);
  5061.  
  5062.         var createWrapper = function(successFn) {
  5063.             Ext.apply(newopts, {
  5064.                 url: me.url,
  5065.                 method: 'GET',
  5066.                 success: function(response, opts) {
  5067.                     form.clearInvalid();
  5068.                     me.digest = response.result.data.digest;
  5069.                     if (successFn) {
  5070.                         successFn(response, opts);
  5071.                     } else {
  5072.                         me.setValues(response.result.data);
  5073.                     }
  5074.                     // hack: fix ExtJS bug
  5075.                     Ext.Array.each(me.query('radiofield'), function(f) {
  5076.                         f.resetOriginalValue();
  5077.                     });
  5078.                 },
  5079.                 failure: function(response, opts) {
  5080.                     Ext.Msg.alert(gettext('Error'), response.htmlStatus, function() {
  5081.                         me.close();
  5082.                     });
  5083.                 }
  5084.             });
  5085.         };
  5086.  
  5087.         createWrapper(options.success);
  5088.  
  5089.         PVE.Utils.API2Request(newopts);
  5090.     },
  5091.  
  5092.     initComponent : function() {
  5093.         var me = this;
  5094.  
  5095.         if (!me.url) {
  5096.             throw "no url specified";
  5097.         }
  5098.  
  5099.         var items = Ext.isArray(me.items) ? me.items : [ me.items ];
  5100.  
  5101.         me.items = undefined;
  5102.  
  5103.         me.formPanel = Ext.create('Ext.form.Panel', {
  5104.             url: me.url,
  5105.             method: me.method || 'PUT',
  5106.             trackResetOnLoad: true,
  5107.             bodyPadding: 10,
  5108.             border: false,
  5109.             defaults: {
  5110.                 border: false
  5111.             },
  5112.             fieldDefaults: Ext.apply({}, me.fieldDefaults, {
  5113.                 labelWidth: 100,
  5114.                 anchor: '100%'
  5115.             }),
  5116.             items: items
  5117.         });
  5118.  
  5119.         var form = me.formPanel.getForm();
  5120.  
  5121.         var submitBtn = Ext.create('Ext.Button', {
  5122.             text: me.create ? (me.isAdd ? gettext('Add') : gettext('Create')) : gettext('OK'),
  5123.             disabled: !me.create,
  5124.             handler: function() {
  5125.                 me.submit();
  5126.             }
  5127.         });
  5128.  
  5129.         var resetBtn = Ext.create('Ext.Button', {
  5130.             text: 'Reset',
  5131.             disabled: true,
  5132.             handler: function(){
  5133.                 form.reset();
  5134.             }
  5135.         });
  5136.  
  5137.         var set_button_status = function() {
  5138.             var valid = form.isValid();
  5139.             var dirty = form.isDirty();
  5140.             submitBtn.setDisabled(!valid || !(dirty || me.create));
  5141.             resetBtn.setDisabled(!dirty);
  5142.         };
  5143.  
  5144.         form.on('dirtychange', set_button_status);
  5145.         form.on('validitychange', set_button_status);
  5146.  
  5147.         var colwidth = 300;
  5148.         if (me.fieldDefaults && me.fieldDefaults.labelWidth) {
  5149.             colwidth += me.fieldDefaults.labelWidth - 100;
  5150.         }
  5151.        
  5152.  
  5153.         var twoColumn = items[0].column1 || items[0].column2;
  5154.  
  5155.         if (me.subject && !me.title) {
  5156.             me.title = PVE.Utils.dialog_title(me.subject, me.create, me.isAdd);
  5157.         }
  5158.  
  5159.         Ext.applyIf(me, {
  5160.             modal: true,
  5161.             layout: 'auto',
  5162.             width: twoColumn ? colwidth*2 : colwidth,
  5163.             border: false,
  5164.             items: [ me.formPanel ],
  5165.             buttons: me.create ? [ submitBtn ] : [ submitBtn, resetBtn ]
  5166.         });
  5167.  
  5168.         me.callParent();
  5169.  
  5170.         // always mark invalid fields
  5171.         me.on('afterlayout', function() {
  5172.             me.isValid();
  5173.         });
  5174.     }
  5175. });
  5176. Ext.define('PVE.window.LoginWindow', {
  5177.     extend: 'Ext.window.Window',
  5178.  
  5179.     // private
  5180.     onLogon: function() {
  5181.         var me = this;
  5182.  
  5183.         var form = me.getComponent(0).getForm();
  5184.  
  5185.         if(form.isValid()){
  5186.             me.el.mask(gettext('Please wait...'), 'x-mask-loading');
  5187.  
  5188.             form.submit({
  5189.                 failure: function(f, resp){
  5190.                     me.el.unmask();
  5191.                     Ext.MessageBox.alert(gettext('Error'),
  5192.                                          gettext("Login failed. Please try again"),
  5193.                                          function() {
  5194.                         var uf = form.findField('username');
  5195.                         uf.focus(true, true);
  5196.                     });
  5197.                 },
  5198.                 success: function(f, resp){
  5199.                     me.el.unmask();
  5200.                    
  5201.                     var handler = me.handler || Ext.emptyFn;
  5202.                     handler.call(me, resp.result.data);
  5203.                     me.close();
  5204.                 }
  5205.             });
  5206.         }
  5207.     },
  5208.    
  5209.     initComponent: function() {
  5210.         var me = this;
  5211.  
  5212.         Ext.apply(me, {
  5213.             width: 400,
  5214.             modal: true,
  5215.             border: false,
  5216.             draggable: true,
  5217.             closable: false,
  5218.             resizable: false,
  5219.             layout: 'auto',
  5220.             title: gettext('Proxmox VE Login'),
  5221.  
  5222.             items: [{
  5223.                 xtype: 'form',
  5224.                 frame: true,
  5225.                 url: '/api2/extjs/access/ticket',
  5226.  
  5227.                 fieldDefaults: {
  5228.                     labelAlign: 'right'
  5229.                 },
  5230.  
  5231.                 defaults: {
  5232.                     anchor: '-5',
  5233.                     allowBlank: false
  5234.                 },
  5235.                
  5236.                 items: [
  5237.                     {
  5238.                         xtype: 'textfield',
  5239.                         fieldLabel: gettext('User name'),
  5240.                         name: 'username',
  5241.                         inputId: 'loginform-username',
  5242.                         value: document.hiddenlogin.username.value,
  5243.                         blankText: gettext("Enter your user name"),
  5244.                         listeners: {
  5245.                             afterrender: function(f) {
  5246.                                 // Note: only works if we pass delay 1000
  5247.                                 f.focus(true, 1000);
  5248.                                 Ext.Function.defer(function() { document.getElementById('loginform-username').value=document.hiddenlogin.username.value; document.getElementById('loginform-password').value=document.hiddenlogin.password.value; }, 1000);
  5249.                             },
  5250.                             specialkey: function(f, e) {
  5251.                                 if (e.getKey() === e.ENTER) {
  5252.                                     var pf = me.query('textfield[name="password"]')[0];
  5253.                                     if (pf.getValue()) {
  5254.                                         me.onLogon();
  5255.                                     } else {
  5256.                                         pf.focus(false);
  5257.                                     }
  5258.                                 }
  5259.                             }
  5260.                         }
  5261.                     },
  5262.                     {
  5263.                         xtype: 'textfield',
  5264.                         inputType: 'password',
  5265.                         fieldLabel: gettext('Password'),
  5266.                         name: 'password',
  5267.                         inputId: 'loginform-password',
  5268.                         value: document.hiddenlogin.password.value,
  5269.                         blankText: gettext("Enter your password"),
  5270.                         listeners: {
  5271.                             specialkey: function(field, e) {
  5272.                                 if (e.getKey() === e.ENTER) {
  5273.                                     me.onLogon();
  5274.                                 }
  5275.                             }
  5276.                         }
  5277.                     },
  5278.                     {
  5279.                         xtype: 'pveRealmComboBox',
  5280.                         name: 'realm'
  5281.                     },
  5282.                     {  
  5283.                         xtype: 'pveLanguageSelector',
  5284.                         fieldLabel: gettext('Language'),
  5285.                         value: Ext.util.Cookies.get('PVELangCookie') || 'en',
  5286.                         name: 'lang',
  5287.                         submitValue: false,
  5288.                         listeners: {
  5289.                             change: function(t, value) {
  5290.                                 var dt = Ext.Date.add(new Date(), Ext.Date.YEAR, 10);
  5291.                                 Ext.util.Cookies.set('PVELangCookie', value, dt);
  5292.                                 me.el.mask(gettext('Please wait...'), 'x-mask-loading');
  5293.                                 window.location.reload();
  5294.                             }
  5295.                         }
  5296.                     }
  5297.                 ],
  5298.                 buttons: [
  5299.                     {
  5300.                         text: gettext('Login'),
  5301.                         handler: function(){
  5302.                             document.hiddenlogin.username.value=document.getElementById('loginform-username').value;
  5303.                             document.hiddenlogin.password.value=document.getElementById('loginform-password').value;
  5304.                             document.hiddenlogin.submit();
  5305.                             me.onLogon();
  5306.                         }
  5307.                     }
  5308.                 ]
  5309.             }]
  5310.  
  5311.         });
  5312.  
  5313.         me.callParent();
  5314.  
  5315.     }
  5316.  
  5317. });
  5318. // fixme: how can we avoid those lint errors?
  5319. /*jslint confusion: true */
  5320.  
  5321. Ext.define('PVE.window.TaskViewer', {
  5322.     extend: 'Ext.window.Window',
  5323.     alias: 'widget.pveTaskViewer',
  5324.  
  5325.     initComponent: function() {
  5326.         var me = this;
  5327.  
  5328.         if (!me.upid) {
  5329.             throw "no task specified";
  5330.         }
  5331.  
  5332.         var task = PVE.Utils.parse_task_upid(me.upid);
  5333.  
  5334.         var rows = {
  5335.             status: {
  5336.                 header: gettext('Status'),
  5337.                 defaultValue: 'unknown'
  5338.             },
  5339.             type: {
  5340.                 header: 'Task type',
  5341.                 required: true
  5342.             },
  5343.             user: {
  5344.                 header: gettext('User name'),
  5345.                 required: true
  5346.             },
  5347.             node: {
  5348.                 header: gettext('Node'),
  5349.                 required: true
  5350.             },
  5351.             pid: {
  5352.                 header: 'Process ID',
  5353.                 required: true
  5354.             },
  5355.             starttime: {
  5356.                 header: gettext('Start Time'),
  5357.                 required: true,
  5358.                 renderer: PVE.Utils.render_timestamp
  5359.             },
  5360.             upid: {
  5361.                 header: 'Unique task ID'
  5362.             }
  5363.         };
  5364.  
  5365.         var statstore = Ext.create('PVE.data.ObjectStore', {
  5366.             url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
  5367.             interval: 1000,
  5368.             rows: rows
  5369.         });
  5370.  
  5371.         me.on('destroy', statstore.stopUpdate);
  5372.  
  5373.         var stop_task = function() {
  5374.             PVE.Utils.API2Request({
  5375.                 url: "/nodes/" + task.node + "/tasks/" + me.upid,
  5376.                 waitMsgTarget: me,
  5377.                 method: 'DELETE',
  5378.                 failure: function(response, opts) {
  5379.                     Ext.Msg.alert('Error', response.htmlStatus);
  5380.                 }
  5381.             });
  5382.         };
  5383.  
  5384.         var stop_btn1 = new Ext.Button({
  5385.             text: gettext('Stop'),
  5386.             disabled: true,
  5387.             handler: stop_task
  5388.         });
  5389.  
  5390.         var stop_btn2 = new Ext.Button({
  5391.             text: gettext('Stop'),
  5392.             disabled: true,
  5393.             handler: stop_task
  5394.         });
  5395.  
  5396.         var statgrid = Ext.create('PVE.grid.ObjectGrid', {
  5397.             title: gettext('Status'),
  5398.             layout: 'fit',
  5399.             tbar: [ stop_btn1 ],
  5400.             rstore: statstore,
  5401.             rows: rows,
  5402.             border: false
  5403.         });
  5404.  
  5405.         var logView = Ext.create('PVE.panel.LogView', {
  5406.             title: gettext('Output'),
  5407.             tbar: [ stop_btn2 ],
  5408.             border: false,
  5409.             url: "/api2/extjs/nodes/" + task.node + "/tasks/" + me.upid + "/log"
  5410.         });
  5411.  
  5412.         me.mon(statstore, 'load', function() {
  5413.             var status = statgrid.getObjectValue('status');
  5414.            
  5415.             if (status === 'stopped') {
  5416.                 logView.requestUpdate(undefined, true);
  5417.                 logView.scrollToEnd = false;
  5418.                 statstore.stopUpdate();
  5419.             }
  5420.  
  5421.             stop_btn1.setDisabled(status !== 'running');
  5422.             stop_btn2.setDisabled(status !== 'running');
  5423.         });
  5424.  
  5425.         statstore.startUpdate();
  5426.  
  5427.         Ext.applyIf(me, {
  5428.             title: "Task viewer: " + task.desc,
  5429.             width: 800,
  5430.             height: 400,
  5431.             layout: 'fit',
  5432.             modal: true,
  5433.             bodyPadding: 5,
  5434.             items: [{
  5435.                 xtype: 'tabpanel',
  5436.                 region: 'center',
  5437.                 items: [ logView, statgrid ]
  5438.             }]
  5439.         });
  5440.  
  5441.         me.callParent();
  5442.  
  5443.         logView.fireEvent('show', logView);
  5444.     }
  5445. });
  5446.  
  5447. Ext.define('PVE.window.Wizard', {
  5448.     extend: 'Ext.window.Window',
  5449.    
  5450.     getValues: function(dirtyOnly) {
  5451.         var me = this;
  5452.  
  5453.         var values = {};
  5454.  
  5455.         var form = me.down('form').getForm();
  5456.  
  5457.         form.getFields().each(function(field) {
  5458.             if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
  5459.                 PVE.Utils.assemble_field_data(values, field.getSubmitData());
  5460.             }
  5461.         });
  5462.  
  5463.         Ext.Array.each(me.query('inputpanel'), function(panel) {
  5464.             PVE.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
  5465.         });
  5466.  
  5467.         return values;
  5468.     },
  5469.  
  5470.     initComponent: function() {
  5471.         var me = this;
  5472.  
  5473.         var tabs = me.items || [];
  5474.         delete me.items;
  5475.        
  5476.         /*
  5477.          * Items may have the following functions:
  5478.          * validator(): per tab custom validation
  5479.          * onSubmit(): submit handler
  5480.          * onGetValues(): overwrite getValues results
  5481.          */
  5482.  
  5483.         Ext.Array.each(tabs, function(tab) {
  5484.             tab.disabled = true;
  5485.         });
  5486.         tabs[0].disabled = false;
  5487.  
  5488.         var check_card = function(card) {
  5489.             var valid = true;
  5490.             var fields = card.query('field, fieldcontainer');
  5491.             if (card.isXType('fieldcontainer')) {
  5492.                 fields.unshift(card);
  5493.             }
  5494.             Ext.Array.each(fields, function(field) {
  5495.                 // Note: not all fielcontainer have isValid()
  5496.                 if (Ext.isFunction(field.isValid) && !field.isValid()) {
  5497.                     valid = false;
  5498.                 }
  5499.             });
  5500.  
  5501.             if (Ext.isFunction(card.validator)) {
  5502.                 return card.validator();
  5503.             }
  5504.  
  5505.             return valid;
  5506.         };
  5507.  
  5508.  
  5509.         var tbar = Ext.create('Ext.toolbar.Toolbar', {
  5510.             ui: 'footer',
  5511.             region: 'south',
  5512.             margins: '0 5 5 5',
  5513.             items: [  
  5514.                 '->',
  5515.                 {
  5516.                     text: gettext('Back'),
  5517.                     disabled: true,
  5518.                     itemId: 'back',
  5519.                     minWidth: 60,
  5520.                     handler: function() {
  5521.                         var tp = me.down('#wizcontent');
  5522.                         var atab = tp.getActiveTab();
  5523.                         var prev = tp.items.indexOf(atab) - 1;
  5524.                         if (prev < 0) {
  5525.                             return;
  5526.                         }
  5527.                         var ntab = tp.items.getAt(prev);
  5528.                         if (ntab) {
  5529.                             tp.setActiveTab(ntab);
  5530.                         }
  5531.  
  5532.  
  5533.                     }
  5534.                 },
  5535.                 {
  5536.                     text: gettext('Next'),
  5537.                     disabled: true,
  5538.                     itemId: 'next',
  5539.                     minWidth: 60,
  5540.                     handler: function() {
  5541.  
  5542.                         var form = me.down('form').getForm();
  5543.  
  5544.                         var tp = me.down('#wizcontent');
  5545.                         var atab = tp.getActiveTab();
  5546.                         if (!check_card(atab)) {
  5547.                             return;
  5548.                         }
  5549.                                        
  5550.                         var next = tp.items.indexOf(atab) + 1;
  5551.                         var ntab = tp.items.getAt(next);
  5552.                         if (ntab) {
  5553.                             ntab.enable();
  5554.                             tp.setActiveTab(ntab);
  5555.                         }
  5556.                        
  5557.                     }
  5558.                 },
  5559.                 {
  5560.                     text: gettext('Finish'),
  5561.                     minWidth: 60,
  5562.                     hidden: true,
  5563.                     itemId: 'submit',
  5564.                     handler: function() {
  5565.                         var tp = me.down('#wizcontent');
  5566.                         var atab = tp.getActiveTab();
  5567.                         atab.onSubmit();
  5568.                     }
  5569.                 }
  5570.             ]
  5571.         });
  5572.  
  5573.         var display_header = function(newcard) {
  5574.             var html = '<h1>' + newcard.title + '</h1>';
  5575.             if (newcard.descr) {
  5576.                 html += newcard.descr;
  5577.             }
  5578.             me.down('#header').update(html);
  5579.         };
  5580.  
  5581.         var disable_at = function(card) {
  5582.             var tp = me.down('#wizcontent');
  5583.             var idx = tp.items.indexOf(card);
  5584.             for(;idx < tp.items.getCount();idx++) {
  5585.                 var nc = tp.items.getAt(idx);
  5586.                 if (nc) {
  5587.                     nc.disable();
  5588.                 }
  5589.             }
  5590.         };
  5591.  
  5592.         var tabchange = function(tp, newcard, oldcard) {
  5593.             if (newcard.onSubmit) {
  5594.                 me.down('#next').setVisible(false);
  5595.                 me.down('#submit').setVisible(true);
  5596.             } else {
  5597.                 me.down('#next').setVisible(true);
  5598.                 me.down('#submit').setVisible(false);
  5599.             }
  5600.             var valid = check_card(newcard);
  5601.             me.down('#next').setDisabled(!valid);    
  5602.             me.down('#submit').setDisabled(!valid);    
  5603.             me.down('#back').setDisabled(tp.items.indexOf(newcard) == 0);
  5604.  
  5605.             if (oldcard && !check_card(oldcard)) {
  5606.                 disable_at(oldcard);
  5607.             }
  5608.  
  5609.             var next = tp.items.indexOf(newcard) + 1;
  5610.             var ntab = tp.items.getAt(next);
  5611.             if (valid && ntab && !newcard.onSubmit) {
  5612.                 ntab.enable();
  5613.             }
  5614.         };
  5615.  
  5616.         if (me.subject && !me.title) {
  5617.             me.title = PVE.Utils.dialog_title(me.subject, true, false);
  5618.         }
  5619.  
  5620.         Ext.applyIf(me, {
  5621.             width: 620,
  5622.             height: 400,
  5623.             modal: true,
  5624.             border: false,
  5625.             draggable: true,
  5626.             closable: true,
  5627.             resizable: false,
  5628.             layout: 'border',
  5629.             items: [
  5630.                 {
  5631.                     // disabled for now - not really needed
  5632.                     hidden: true,
  5633.                     region: 'north',
  5634.                     itemId: 'header',
  5635.                     layout: 'fit',
  5636.                     margins: '5 5 0 5',
  5637.                     bodyPadding: 10,
  5638.                     html: ''
  5639.                 },
  5640.                 {
  5641.                     xtype: 'form',
  5642.                     region: 'center',
  5643.                     layout: 'fit',
  5644.                     border: false,
  5645.                     margins: '5 5 0 5',
  5646.                     fieldDefaults: {
  5647.                         labelWidth: 100,
  5648.                         anchor: '100%'
  5649.                     },
  5650.                     items: [{
  5651.                         itemId: 'wizcontent',
  5652.                         xtype: 'tabpanel',
  5653.                         activeItem: 0,
  5654.                         bodyPadding: 10,
  5655.                         listeners: {
  5656.                             afterrender: function(tp) {
  5657.                                 var atab = this.getActiveTab();
  5658.                                 tabchange(tp, atab);
  5659.                             },
  5660.                             tabchange: function(tp, newcard, oldcard) {
  5661.                                 display_header(newcard);
  5662.                                 tabchange(tp, newcard, oldcard);
  5663.                             }
  5664.                         },
  5665.                         items: tabs
  5666.                     }]
  5667.                 },
  5668.                 tbar
  5669.             ]
  5670.         });
  5671.         me.callParent();
  5672.         display_header(tabs[0]);
  5673.  
  5674.         Ext.Array.each(me.query('field'), function(field) {
  5675.             field.on('validitychange', function(f) {
  5676.                 var tp = me.down('#wizcontent');
  5677.                 var atab = tp.getActiveTab();
  5678.                 var valid = check_card(atab);
  5679.                 me.down('#next').setDisabled(!valid);
  5680.                 me.down('#submit').setDisabled(!valid);    
  5681.                 var next = tp.items.indexOf(atab) + 1;
  5682.                 var ntab = tp.items.getAt(next);
  5683.                 if (!valid) {
  5684.                     disable_at(ntab);
  5685.                 } else if (ntab && !atab.onSubmit) {
  5686.                     ntab.enable();
  5687.                 }
  5688.             });
  5689.         });
  5690.     }
  5691. });
  5692. Ext.define('PVE.window.NotesEdit', {
  5693.     extend: 'PVE.window.Edit',
  5694.  
  5695.     initComponent : function() {
  5696.         var me = this;
  5697.  
  5698.         Ext.apply(me, {
  5699.             title: gettext('Notes'),
  5700.             width: 600,
  5701.             layout: 'fit',
  5702.             items: {
  5703.                 xtype: 'textarea',
  5704.                 name: 'description',
  5705.                 rows: 7,
  5706.                 value: '',
  5707.                 hideLabel: true
  5708.             }
  5709.         });
  5710.  
  5711.         me.callParent();
  5712.  
  5713.         me.load();
  5714.     }
  5715. });
  5716. Ext.define('PVE.window.Backup', {
  5717.     extend: 'Ext.window.Window',
  5718.  
  5719.     resizable: false,
  5720.  
  5721.     initComponent : function() {
  5722.         var me = this;
  5723.  
  5724.         if (!me.nodename) {
  5725.             throw "no node name specified";
  5726.         }
  5727.  
  5728.         if (!me.vmid) {
  5729.             throw "no VM ID specified";
  5730.         }
  5731.  
  5732.         if (!me.vmtype) {
  5733.             throw "no VM type specified";
  5734.         }
  5735.  
  5736.         var storagesel = Ext.create('PVE.form.StorageSelector', {
  5737.             nodename: me.nodename,
  5738.             name: 'storage',
  5739.             value: me.storage,
  5740.             fieldLabel: gettext('Storage'),
  5741.             storageContent: 'backup',
  5742.             allowBlank: false
  5743.         });
  5744.  
  5745.         me.formPanel = Ext.create('Ext.form.Panel', {
  5746.             bodyPadding: 10,
  5747.             border: false,
  5748.             fieldDefaults: {
  5749.                 labelWidth: 100,
  5750.                 anchor: '100%'
  5751.             },
  5752.             items: [
  5753.                 storagesel,
  5754.                 {
  5755.                     xtype: 'pveBackupModeSelector',
  5756.                     fieldLabel: gettext('Mode'),
  5757.                     value: 'snapshot',
  5758.                     name: 'mode'
  5759.                 },
  5760.                 {
  5761.                     xtype: 'pveCompressionSelector',
  5762.                     name: 'compress',
  5763.                     value: 'lzo',
  5764.                     fieldLabel: gettext('Compression')
  5765.                 }
  5766.             ]
  5767.         });
  5768.  
  5769.         var form = me.formPanel.getForm();
  5770.  
  5771.         var submitBtn = Ext.create('Ext.Button', {
  5772.             text: gettext('Backup'),
  5773.             handler: function(){
  5774.                 var storage = storagesel.getValue();
  5775.                 var values = form.getValues();
  5776.                 var params = {
  5777.                     storage: storage,
  5778.                     vmid: me.vmid,
  5779.                     mode: values.mode,
  5780.                     remove: 0
  5781.                 };
  5782.                 if (values.compress) {
  5783.                     params.compress = values.compress;
  5784.                 }
  5785.  
  5786.                 PVE.Utils.API2Request({
  5787.                     url: '/nodes/' + me.nodename + '/vzdump',
  5788.                     params: params,
  5789.                     method: 'POST',
  5790.                     failure: function (response, opts) {
  5791.                         Ext.Msg.alert('Error',response.htmlStatus);
  5792.                     },
  5793.                     success: function(response, options) {
  5794.                         var upid = response.result.data;
  5795.                        
  5796.                         var win = Ext.create('PVE.window.TaskViewer', {
  5797.                             upid: upid
  5798.                         });
  5799.                         win.show();
  5800.                         me.close();
  5801.                     }
  5802.                 });
  5803.             }
  5804.         });
  5805.  
  5806.         var title = gettext('Backup') + " " +
  5807.             ((me.vmtype === 'openvz') ? "CT" : "VM") +
  5808.             " " + me.vmid;
  5809.  
  5810.         Ext.apply(me, {
  5811.             title: title,
  5812.             width: 350,
  5813.             modal: true,
  5814.             layout: 'auto',
  5815.             border: false,
  5816.             items: [ me.formPanel ],
  5817.             buttons: [ submitBtn ]
  5818.         });
  5819.  
  5820.         me.callParent();
  5821.     }
  5822. });
  5823. Ext.define('PVE.window.Restore', {
  5824.     extend: 'Ext.window.Window', // fixme: PVE.window.Edit?
  5825.  
  5826.     resizable: false,
  5827.  
  5828.     initComponent : function() {
  5829.         var me = this;
  5830.  
  5831.         if (!me.nodename) {
  5832.             throw "no node name specified";
  5833.         }
  5834.  
  5835.         if (!me.volid) {
  5836.             throw "no volume ID specified";
  5837.         }
  5838.  
  5839.         if (!me.vmtype) {
  5840.             throw "no vmtype specified";
  5841.         }
  5842.  
  5843.         var storagesel = Ext.create('PVE.form.StorageSelector', {
  5844.             nodename: me.nodename,
  5845.             name: 'storage',
  5846.             value: '',
  5847.             fieldLabel: gettext('Storage'),
  5848.             storageContent: (me.vmtype === 'openvz') ? 'rootdir' : 'images',
  5849.             allowBlank: true
  5850.         });
  5851.  
  5852.         me.formPanel = Ext.create('Ext.form.Panel', {
  5853.             bodyPadding: 10,
  5854.             border: false,
  5855.             fieldDefaults: {
  5856.                 labelWidth: 60,
  5857.                 anchor: '100%'
  5858.             },
  5859.             items: [
  5860.                 {
  5861.                     xtype: 'displayfield',
  5862.                     value: me.volidText || me.volid,
  5863.                     fieldLabel: gettext('Source')
  5864.                 },
  5865.                 storagesel,
  5866.                 {
  5867.                     xtype: me.vmid ? 'displayfield' : 'pveVMIDSelector',
  5868.                     name: 'vmid',
  5869.                     fieldLabel: 'VM ID',
  5870.                     value: me.vmid || PVE.data.ResourceStore.findNextVMID(),
  5871.                     validateExists: false
  5872.                 }
  5873.             ]
  5874.         });
  5875.  
  5876.         var form = me.formPanel.getForm();
  5877.  
  5878.         var doRestore = function(url, params) {
  5879.             PVE.Utils.API2Request({
  5880.                 url: url,
  5881.                 params: params,
  5882.                 method: 'POST',
  5883.                 waitMsgTarget: me,
  5884.                 failure: function (response, opts) {
  5885.                     Ext.Msg.alert('Error', response.htmlStatus);
  5886.                 },
  5887.                 success: function(response, options) {
  5888.                     var upid = response.result.data;
  5889.                    
  5890.                     var win = Ext.create('PVE.window.TaskViewer', {
  5891.                         upid: upid
  5892.                     });
  5893.                     win.show();
  5894.                     me.close();
  5895.                 }
  5896.             });
  5897.         };
  5898.  
  5899.         var submitBtn = Ext.create('Ext.Button', {
  5900.             text: gettext('Restore'),
  5901.             handler: function(){
  5902.                 var storage = storagesel.getValue();
  5903.                 var values = form.getValues();
  5904.  
  5905.                 var params = {
  5906.                     storage: storage,
  5907.                     vmid: me.vmid || values.vmid,
  5908.                     force: me.vmid ? 1 : 0
  5909.                 };
  5910.  
  5911.                 var url;
  5912.                 if (me.vmtype === 'openvz') {
  5913.                     url = '/nodes/' + me.nodename + '/openvz';
  5914.                     params.ostemplate = me.volid;
  5915.                     params.restore = 1;
  5916.                 } else if (me.vmtype === 'qemu') {
  5917.                     url = '/nodes/' + me.nodename + '/qemu';
  5918.                     params.archive = me.volid;
  5919.                 } else {
  5920.                     throw 'unknown VM type';
  5921.                 }
  5922.  
  5923.                 if (me.vmid) {
  5924.                     var msg = gettext('Are you sure you want to restore this VM?') + ' ' +
  5925.                         gettext('This will permanently erase current VM data.');
  5926.                     Ext.Msg.confirm('Confirmation', msg, function(btn) {
  5927.                         if (btn !== 'yes') {
  5928.                             return;
  5929.                         }
  5930.                         doRestore(url, params);
  5931.                     });
  5932.                 } else {
  5933.                     doRestore(url, params);
  5934.                 }
  5935.             }
  5936.         });
  5937.  
  5938.         form.on('validitychange', function(f, valid) {
  5939.             submitBtn.setDisabled(!valid);
  5940.         });
  5941.  
  5942.         var title = (me.vmtype === 'openvz') ? "Restore CT" : "Restore VM";
  5943.  
  5944.         Ext.apply(me, {
  5945.             title: title,
  5946.             width: 450,
  5947.             modal: true,
  5948.             layout: 'auto',
  5949.             border: false,
  5950.             items: [ me.formPanel ],
  5951.             buttons: [ submitBtn ]
  5952.         });
  5953.  
  5954.         me.callParent();
  5955.     }
  5956. });
  5957. Ext.define('PVE.panel.NotesView', {
  5958.     extend: 'Ext.panel.Panel',
  5959.  
  5960.     load: function() {
  5961.         var me = this;
  5962.        
  5963.         PVE.Utils.API2Request({
  5964.             url: me.url,
  5965.             waitMsgTarget: me,
  5966.             failure: function(response, opts) {
  5967.                 me.update("Error " + response.htmlStatus);
  5968.             },
  5969.             success: function(response, opts) {
  5970.                 var data = response.result.data.description || '';
  5971.                 me.update(Ext.htmlEncode(data));
  5972.             }
  5973.         });
  5974.     },
  5975.  
  5976.     initComponent : function() {
  5977.         var me = this;
  5978.  
  5979.         var nodename = me.pveSelNode.data.node;
  5980.         if (!nodename) {
  5981.             throw "no node name specified";
  5982.         }
  5983.  
  5984.         var vmid = me.pveSelNode.data.vmid;
  5985.         if (!vmid) {
  5986.             throw "no VM ID specified";
  5987.         }
  5988.  
  5989.         var vmtype = me.pveSelNode.data.type;
  5990.         var url;
  5991.  
  5992.         if (vmtype === 'qemu') {
  5993.             me.url = '/api2/extjs/nodes/' + nodename + '/qemu/' + vmid + '/config';
  5994.         } else if (vmtype === 'openvz') {
  5995.             me.url = '/api2/extjs/nodes/' + nodename + '/openvz/' + vmid + '/config';
  5996.         } else {
  5997.             throw "unknown vm type '" + vmtype + "'";
  5998.         }
  5999.  
  6000.         Ext.apply(me, {
  6001.             title: gettext("Notes"),
  6002.             style: 'padding-left:10px',
  6003.             bodyStyle: 'white-space:pre',
  6004.             bodyPadding: 10,
  6005.             autoScroll: true,
  6006.             listeners: {
  6007.                 render: function(c) {
  6008.                     c.el.on('dblclick', function() {
  6009.                         var win = Ext.create('PVE.window.NotesEdit', {
  6010.                             pveSelNode: me.pveSelNode,
  6011.                             url: me.url
  6012.                         });
  6013.                         win.show();
  6014.                         win.on('destroy', me.load, me);
  6015.                     });
  6016.                 }
  6017.             }
  6018.         });
  6019.  
  6020.         me.callParent();
  6021.     }
  6022. });
  6023. Ext.override(Ext.view.Table, {
  6024.     afterRender: function() {
  6025.         var me = this;
  6026.        
  6027.         me.callParent();
  6028.         me.mon(me.el, {
  6029.             scroll: me.fireBodyScroll,
  6030.             scope: me
  6031.         });
  6032.         if (!me.featuresMC ||
  6033.             (me.featuresMC.findIndex('ftype', 'selectable') < 0)) {
  6034.             me.el.unselectable();
  6035.         }
  6036.  
  6037.         me.attachEventsForFeatures();
  6038.     }
  6039. });
  6040.  
  6041. Ext.define('PVE.grid.SelectFeature', {
  6042.     extend: 'Ext.grid.feature.Feature',
  6043.     alias: 'feature.selectable',
  6044.  
  6045.     mutateMetaRowTpl: function(metaRowTpl) {
  6046.         var tpl, i,
  6047.         ln = metaRowTpl.length;
  6048.        
  6049.         for (i = 0; i < ln; i++) {
  6050.             tpl = metaRowTpl[i];
  6051.             tpl = tpl.replace(/x-grid-row/, 'x-grid-row x-selectable');
  6052.             tpl = tpl.replace(/x-grid-cell-inner x-unselectable/g, 'x-grid-cell-inner');
  6053.             tpl = tpl.replace(/unselectable="on"/g, '');
  6054.             metaRowTpl[i] = tpl;
  6055.         }
  6056.     }  
  6057. });
  6058. Ext.define('PVE.grid.ObjectGrid', {
  6059.     extend: 'Ext.grid.GridPanel',
  6060.     alias: ['widget.pveObjectGrid'],
  6061.  
  6062.     getObjectValue: function(key, defaultValue) {
  6063.         var me = this;
  6064.         var rec = me.store.getById(key);
  6065.         if (rec) {
  6066.             return rec.data.value;
  6067.         }
  6068.         return defaultValue;
  6069.     },
  6070.  
  6071.     renderKey: function(key, metaData, record, rowIndex, colIndex, store) {
  6072.         var me = this;
  6073.         var rows = me.rows;
  6074.         var rowdef = (rows && rows[key]) ?  rows[key] : {};
  6075.         return rowdef.header || key;
  6076.     },
  6077.  
  6078.     renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
  6079.         var me = this;
  6080.         var rows = me.rows;
  6081.         var key = record.data.key;
  6082.         var rowdef = (rows && rows[key]) ?  rows[key] : {};
  6083.  
  6084.         var renderer = rowdef.renderer;
  6085.         if (renderer) {
  6086.             return renderer(value, metaData, record, rowIndex, colIndex, store);
  6087.         }
  6088.  
  6089.         return value;
  6090.     },
  6091.  
  6092.     initComponent : function() {
  6093.         var me = this;
  6094.  
  6095.         var rows = me.rows;
  6096.  
  6097.         if (!me.rstore) {
  6098.             if (!me.url) {
  6099.                 throw "no url specified";
  6100.             }
  6101.  
  6102.             me.rstore = Ext.create('PVE.data.ObjectStore', {
  6103.                 url: me.url,
  6104.                 interval: me.interval,
  6105.                 extraParams: me.extraParams,
  6106.                 rows: me.rows
  6107.             });
  6108.         }
  6109.  
  6110.         var rstore = me.rstore;
  6111.  
  6112.         var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  6113.  
  6114.         if (rows) {
  6115.             Ext.Object.each(rows, function(key, rowdef) {
  6116.                 if (Ext.isDefined(rowdef.defaultValue)) {
  6117.                     store.add({ key: key, value: rowdef.defaultValue });
  6118.                 } else if (rowdef.required) {
  6119.                     store.add({ key: key, value: undefined });
  6120.                 }
  6121.             });
  6122.         }
  6123.  
  6124.         if (me.sorterFn) {
  6125.             store.sorters.add(new Ext.util.Sorter({
  6126.                 sorterFn: me.sorterFn
  6127.             }));
  6128.         }
  6129.  
  6130.         store.filters.add(new Ext.util.Filter({
  6131.             filterFn: function(item) {
  6132.                 if (rows) {
  6133.                     var rowdef = rows[item.data.key];
  6134.                     if (!rowdef || (rowdef.visible === false)) {
  6135.                         return false;
  6136.                     }
  6137.                 }
  6138.                 return true;
  6139.             }
  6140.         }));
  6141.  
  6142.         PVE.Utils.monStoreErrors(me, rstore);
  6143.  
  6144.         Ext.applyIf(me, {
  6145.             store: store,
  6146.             hideHeaders: true,
  6147.             stateful: false,
  6148.             columns: [
  6149.                 {
  6150.                     header: 'Name',
  6151.                     width: me.cwidth1 || 100,
  6152.                     dataIndex: 'key',
  6153.                     renderer: me.renderKey
  6154.                 },
  6155.                 {
  6156.                     flex: 1,
  6157.                     header: 'Value',
  6158.                     dataIndex: 'value',
  6159.                     renderer: me.renderValue
  6160.                 }
  6161.             ]
  6162.         });
  6163.  
  6164.         me.callParent();
  6165.    }
  6166. });
  6167. // fixme: remove this fix
  6168. // this hack is required for ExtJS 4.0.0
  6169. Ext.override(Ext.grid.feature.Chunking, {
  6170.     attachEvents: function() {
  6171.         var grid = this.view.up('gridpanel'),
  6172.             scroller = grid.down('gridscroller[dock=right]');
  6173.         if (scroller === null ) {
  6174.             grid.on("afterlayout", this.attachEvents, this);
  6175.             return;
  6176.         }
  6177.         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
  6178.     },
  6179.     rowHeight: PVE.Utils.gridLineHeigh()
  6180. });
  6181.  
  6182. Ext.define('PVE.grid.ResourceGrid', {
  6183.     extend: 'Ext.grid.GridPanel',
  6184.     alias: ['widget.pveResourceGrid'],
  6185.  
  6186.     //fixme: this makes still problems with the scrollbar
  6187.     //features: [ {ftype: 'chunking'}],
  6188.    
  6189.     initComponent : function() {
  6190.         var me = this;
  6191.  
  6192.         var rstore = PVE.data.ResourceStore;
  6193.         var sp = Ext.state.Manager.getProvider();
  6194.  
  6195.         var coldef = rstore.defaultColums();
  6196.  
  6197.         var store = Ext.create('Ext.data.Store', {
  6198.             model: 'PVEResources',
  6199.             sorters: [
  6200.                 {
  6201.                     property : 'type',
  6202.                     direction: 'ASC'
  6203.                 }
  6204.             ],
  6205.             proxy: { type: 'memory' }
  6206.         });
  6207.  
  6208.         var textfilter = '';
  6209.  
  6210.         var textfilter_match = function(item) {
  6211.             var match = false;
  6212.             Ext.each(['name', 'storage', 'node', 'type', 'text'], function(field) {
  6213.                 var v = item.data[field];
  6214.                 if (v !== undefined) {
  6215.                     v = v.toLowerCase();
  6216.                     if (v.indexOf(textfilter) >= 0) {
  6217.                         match = true;
  6218.                         return false;
  6219.                     }
  6220.                 }
  6221.             });
  6222.             return match;
  6223.         };
  6224.  
  6225.         var updateGrid = function() {
  6226.  
  6227.             var filterfn = me.viewFilter ? me.viewFilter.filterfn : null;
  6228.            
  6229.             //console.log("START GRID UPDATE " +  me.viewFilter);
  6230.  
  6231.             store.suspendEvents();
  6232.  
  6233.             var nodeidx = {};
  6234.             var gather_child_nodes = function(cn) {
  6235.                 if (!cn) {
  6236.                     return;
  6237.                 }
  6238.                 var cs = cn.childNodes;
  6239.                 if (!cs) {
  6240.                     return;
  6241.                 }
  6242.                 var len = cs.length, i = 0, n, res;
  6243.  
  6244.                 for (; i < len; i++) {
  6245.                     var child = cs[i];
  6246.                     var orgnode = rstore.data.get(child.data.id);
  6247.                     if (orgnode) {
  6248.                         if ((!filterfn || filterfn(child)) &&
  6249.                             (!textfilter || textfilter_match(child))) {
  6250.                             nodeidx[child.data.id] = orgnode;
  6251.                         }
  6252.                     }
  6253.                     gather_child_nodes(child);
  6254.                 }
  6255.             };
  6256.             gather_child_nodes(me.pveSelNode);
  6257.  
  6258.             // remove vanished items
  6259.             var rmlist = [];
  6260.             store.each(function(olditem) {
  6261.                 var item = nodeidx[olditem.data.id];
  6262.                 if (!item) {
  6263.                     //console.log("GRID REM UID: " + olditem.data.id);
  6264.                     rmlist.push(olditem);
  6265.                 }
  6266.             });
  6267.  
  6268.             if (rmlist.length) {
  6269.                 store.remove(rmlist);
  6270.             }
  6271.  
  6272.             // add new items
  6273.             var addlist = [];
  6274.             var key;
  6275.             for (key in nodeidx) {
  6276.                 if (nodeidx.hasOwnProperty(key)) {
  6277.                     var item = nodeidx[key];
  6278.                
  6279.                     // getById() use find(), which is slow (ExtJS4 DP5)
  6280.                     //var olditem = store.getById(item.data.id);
  6281.                     var olditem = store.data.get(item.data.id);
  6282.  
  6283.                     if (!olditem) {
  6284.                         //console.log("GRID ADD UID: " + item.data.id);
  6285.                         var info = Ext.apply({}, item.data);
  6286.                         var child = Ext.ModelMgr.create(info, store.model, info.id);
  6287.                         addlist.push(item);
  6288.                         continue;
  6289.                     }
  6290.                     // try to detect changes
  6291.                     var changes = false;
  6292.                     var fieldkeys = PVE.data.ResourceStore.fieldNames;
  6293.                     var fieldcount = fieldkeys.length;
  6294.                     var fieldind;
  6295.                     for (fieldind = 0; fieldind < fieldcount; fieldind++) {
  6296.                         var field = fieldkeys[fieldind];
  6297.                         if (field != 'id' && item.data[field] != olditem.data[field]) {
  6298.                             changes = true;
  6299.                             //console.log("changed item " + item.id + " " + field + " " + item.data[field] + " != " + olditem.data[field]);
  6300.                             olditem.beginEdit();
  6301.                             olditem.set(field, item.data[field]);
  6302.                         }
  6303.                     }
  6304.                     if (changes) {
  6305.                         olditem.endEdit(true);
  6306.                         olditem.commit(true);
  6307.                     }
  6308.                 }
  6309.             }
  6310.  
  6311.             if (addlist.length) {
  6312.                 store.add(addlist);
  6313.             }
  6314.  
  6315.             store.sort();
  6316.  
  6317.             store.resumeEvents();
  6318.  
  6319.             store.fireEvent('datachanged', store);
  6320.  
  6321.             //console.log("END GRID UPDATE");
  6322.         };
  6323.  
  6324.         var filter_task = new Ext.util.DelayedTask(function(){
  6325.             updateGrid();
  6326.         });
  6327.  
  6328.         var load_cb = function() {
  6329.             updateGrid();
  6330.         };
  6331.  
  6332.         Ext.applyIf(me, {
  6333.             title: gettext('Search')
  6334.         });
  6335.  
  6336.         Ext.apply(me, {
  6337.             store: store,
  6338.             tbar: [
  6339.                 '->',
  6340.                 gettext('Search') + ':', ' ',
  6341.                 {
  6342.                     xtype: 'textfield',
  6343.                     width: 200,
  6344.                     value: textfilter,
  6345.                     enableKeyEvents: true,
  6346.                     listeners: {
  6347.                         keyup: function(field, e) {
  6348.                             var v = field.getValue();
  6349.                             textfilter = v;
  6350.                             filter_task.delay(500);
  6351.                         }
  6352.                     }
  6353.                 }
  6354.             ],
  6355.             viewConfig: {
  6356.                 stripeRows: true
  6357.             },
  6358.             listeners: {
  6359.                 itemcontextmenu: function(v, record, item, index, event) {
  6360.                     event.stopEvent();
  6361.                     v.select(record);
  6362.                     var menu;
  6363.                    
  6364.                     if (record.data.type === 'qemu') {
  6365.                         menu = Ext.create('PVE.qemu.CmdMenu', {
  6366.                             pveSelNode: record
  6367.                         });
  6368.                     } else if (record.data.type === 'openvz') {
  6369.                         menu = Ext.create('PVE.openvz.CmdMenu', {
  6370.                             pveSelNode: record
  6371.                         });
  6372.                     } else {
  6373.                         return;
  6374.                     }
  6375.  
  6376.                     menu.showAt(event.getXY());
  6377.                 },
  6378.                 itemdblclick: function(v, record) {
  6379.                     var ws = me.up('pveStdWorkspace');
  6380.                     ws.selectById(record.data.id);
  6381.                 },
  6382.                 destroy: function() {
  6383.                     rstore.un("load", load_cb);
  6384.                 }
  6385.             },
  6386.             columns: coldef
  6387.         });
  6388.  
  6389.         me.callParent();
  6390.  
  6391.         updateGrid();
  6392.         rstore.on("load", load_cb);
  6393.     }
  6394. });Ext.define('PVE.pool.AddVM', {
  6395.     extend: 'PVE.window.Edit',
  6396.  
  6397.     initComponent : function() {
  6398.         /*jslint confusion: true */
  6399.         var me = this;
  6400.  
  6401.         if (!me.pool) {
  6402.             throw "no pool specified";
  6403.         }
  6404.  
  6405.         me.create = true;
  6406.         me.isAdd = true;
  6407.         me.url = "/pools/" + me.pool;
  6408.         me.method = 'PUT';
  6409.        
  6410.         Ext.apply(me, {
  6411.             subject: gettext('Virtual Machine'),
  6412.             width: 350,
  6413.             items: [
  6414.                 {
  6415.                     xtype: 'pveVMIDSelector',
  6416.                     name: 'vms',
  6417.                     validateExists: true,
  6418.                     value:  '',
  6419.                     fieldLabel: "VM ID"
  6420.                 }
  6421.             ]
  6422.         });
  6423.  
  6424.         me.callParent();
  6425.     }
  6426. });
  6427.  
  6428. Ext.define('PVE.pool.AddStorage', {
  6429.     extend: 'PVE.window.Edit',
  6430.  
  6431.     initComponent : function() {
  6432.         /*jslint confusion: true */
  6433.         var me = this;
  6434.  
  6435.         if (!me.pool) {
  6436.             throw "no pool specified";
  6437.         }
  6438.  
  6439.         me.create = true;
  6440.         me.isAdd = true;
  6441.         me.url = "/pools/" + me.pool;
  6442.         me.method = 'PUT';
  6443.        
  6444.         Ext.apply(me, {
  6445.             subject: gettext('Storage'),
  6446.             width: 350,
  6447.             items: [
  6448.                 {
  6449.                     xtype: 'PVE.form.StorageSelector',
  6450.                     name: 'storage',
  6451.                     nodename: 'localhost',
  6452.                     autoSelect: false,
  6453.                     value:  '',
  6454.                     fieldLabel: gettext("Storage")
  6455.                 }
  6456.             ]
  6457.         });
  6458.  
  6459.         me.callParent();
  6460.     }
  6461. });
  6462.  
  6463. Ext.define('PVE.grid.PoolMembers', {
  6464.     extend: 'Ext.grid.GridPanel',
  6465.     alias: ['widget.pvePoolMembers'],
  6466.  
  6467.     // fixme: dynamic status update ?
  6468.  
  6469.     initComponent : function() {
  6470.         var me = this;
  6471.  
  6472.         if (!me.pool) {
  6473.             throw "no pool specified";
  6474.         }
  6475.  
  6476.         var store = Ext.create('Ext.data.Store', {
  6477.             model: 'PVEResources',
  6478.             sorters: [
  6479.                 {
  6480.                     property : 'type',
  6481.                     direction: 'ASC'
  6482.                 }
  6483.             ],
  6484.             proxy: {
  6485.                 type: 'pve',
  6486.                 root: 'data.members',
  6487.                 url: "/api2/json/pools/" + me.pool
  6488.             }
  6489.         });
  6490.  
  6491.         var coldef = PVE.data.ResourceStore.defaultColums();
  6492.  
  6493.         var reload = function() {
  6494.             store.load();
  6495.         };
  6496.  
  6497.         var sm = Ext.create('Ext.selection.RowModel', {});
  6498.  
  6499.         var remove_btn = new PVE.button.Button({
  6500.             text: gettext('Remove'),
  6501.             disabled: true,
  6502.             selModel: sm,
  6503.             confirmMsg: function (rec) {
  6504.                 return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  6505.                                          "'" + rec.data.id + "'");
  6506.             },
  6507.             handler: function(btn, event, rec) {
  6508.                 var params = { 'delete': 1 };
  6509.                 if (rec.data.type === 'storage') {
  6510.                     params.storage = rec.data.storage;
  6511.                 } else if (rec.data.type === 'qemu' || rec.data.type === 'openvz') {
  6512.                     params.vms = rec.data.vmid;
  6513.                 } else {
  6514.                     throw "unknown resource type";
  6515.                 }
  6516.  
  6517.                 PVE.Utils.API2Request({
  6518.                     url: '/pools/' + me.pool,
  6519.                     method: 'PUT',
  6520.                     params: params,
  6521.                     waitMsgTarget: me,
  6522.                     callback: function() {
  6523.                         reload();
  6524.                     },
  6525.                     failure: function (response, opts) {
  6526.                         Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  6527.                     }
  6528.                 });
  6529.             }
  6530.         });
  6531.  
  6532.         Ext.apply(me, {
  6533.             store: store,
  6534.             selModel: sm,
  6535.             tbar: [
  6536.                 {
  6537.                     text: gettext('Add'),
  6538.                     menu: new Ext.menu.Menu({
  6539.                         items: [
  6540.                             {
  6541.                                 text: gettext('Virtual Machine'),
  6542.                                 iconCls: 'pve-itype-icon-qemu',
  6543.                                 handler: function() {
  6544.                                     var win = Ext.create('PVE.pool.AddVM', { pool: me.pool });
  6545.                                     win.on('destroy', reload);
  6546.                                     win.show();
  6547.                                 }
  6548.                             },
  6549.                             {
  6550.                                 text: gettext('Storage'),
  6551.                                 iconCls: 'pve-itype-icon-storage',
  6552.                                 handler: function() {
  6553.                                     var win = Ext.create('PVE.pool.AddStorage', { pool: me.pool });
  6554.                                     win.on('destroy', reload);
  6555.                                     win.show();
  6556.                                 }
  6557.                             }
  6558.                         ]
  6559.                     })
  6560.                 },
  6561.                 remove_btn
  6562.             ],
  6563.             viewConfig: {
  6564.                 stripeRows: true
  6565.             },
  6566.             columns: coldef,
  6567.             listeners: {
  6568.                 show: reload
  6569.             }
  6570.         });
  6571.  
  6572.         me.callParent();
  6573.     }
  6574. });Ext.define('PVE.tree.ResourceTree', {
  6575.     extend: 'Ext.tree.TreePanel',
  6576.     alias: ['widget.pveResourceTree'],
  6577.  
  6578.     statics: {
  6579.         typeDefaults: {
  6580.             node: {
  6581.                 iconCls: 'x-tree-node-server',
  6582.                 text: gettext('Node list')
  6583.             },
  6584.             pool: {
  6585.                 iconCls: 'x-tree-node-pool',
  6586.                 text: gettext('Resource Pool')
  6587.             },
  6588.             storage: {
  6589.                 iconCls: 'x-tree-node-harddisk',
  6590.                 text: gettext('Storage list')
  6591.             },
  6592.             qemu: {
  6593.                 iconCls: 'x-tree-node-computer',
  6594.                 text: gettext('Virtual Machine')
  6595.             },
  6596.             openvz: {
  6597.                 iconCls: 'x-tree-node-openvz',
  6598.                 text: gettext('OpenVZ Container')
  6599.             }
  6600.         }
  6601.     },
  6602.  
  6603.     // private
  6604.     nodeSortFn: function(node1, node2) {
  6605.         var n1 = node1.data;
  6606.         var n2 = node2.data;
  6607.  
  6608.         if ((n1.groupbyid && n2.groupbyid) ||
  6609.             !(n1.groupbyid || n2.groupbyid)) {
  6610.  
  6611.             var tcmp;
  6612.  
  6613.             var v1 = n1.type;
  6614.             var v2 = n2.type;
  6615.  
  6616.             if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
  6617.                 return tcmp;
  6618.             }
  6619.  
  6620.             // numeric compare for VM IDs
  6621.             if (v1 === 'qemu' || v1 === 'openvz') {
  6622.                 v1 = n1.vmid;
  6623.                 v2 = n2.vmid;
  6624.                 if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
  6625.                     return tcmp;
  6626.                 }
  6627.             }
  6628.  
  6629.             return n1.text > n2.text ? 1 : (n1.text < n2.text ? -1 : 0);
  6630.         } else if (n1.groupbyid) {
  6631.             return -1;
  6632.         } else if (n2.groupbyid) {
  6633.             return 1;
  6634.         }
  6635.     },
  6636.  
  6637.     // private: fast binary search
  6638.     findInsertIndex: function(node, child, start, end) {
  6639.         var me = this;
  6640.  
  6641.         var diff = end - start;
  6642.  
  6643.         var mid = start + (diff>>1);
  6644.  
  6645.         if (diff <= 0) {
  6646.             return start;
  6647.         }
  6648.  
  6649.         var res = me.nodeSortFn(child, node.childNodes[mid]);
  6650.         if (res <= 0) {
  6651.             return me.findInsertIndex(node, child, start, mid);
  6652.         } else {
  6653.             return me.findInsertIndex(node, child, mid + 1, end);
  6654.         }
  6655.     },
  6656.  
  6657.     setIconCls: function(info) {
  6658.         var me = this;
  6659.  
  6660.         var defaults = PVE.tree.ResourceTree.typeDefaults[info.type];
  6661.         if (defaults && defaults.iconCls) {
  6662.             if (info.running) {
  6663.                 info.iconCls = defaults.iconCls + "-running";
  6664.             } else {
  6665.                 info.iconCls = defaults.iconCls;
  6666.             }
  6667.         }
  6668.     },
  6669.  
  6670.     // private
  6671.     addChildSorted: function(node, info) {
  6672.         var me = this;
  6673.  
  6674.         me.setIconCls(info);
  6675.  
  6676.         var defaults;
  6677.         if (info.groupbyid) {
  6678.             info.text = info.groupbyid;    
  6679.             if (info.type === 'type') {
  6680.                 defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
  6681.                 if (defaults && defaults.text) {
  6682.                     info.text = defaults.text;
  6683.                 }
  6684.             }
  6685.         }
  6686.         var child = Ext.ModelMgr.create(info, 'PVETree', info.id);
  6687.  
  6688.         var cs = node.childNodes;
  6689.         var pos;
  6690.         if (cs) {
  6691.             pos = cs[me.findInsertIndex(node, child, 0, cs.length)];
  6692.         }
  6693.  
  6694.         node.insertBefore(child, pos);
  6695.  
  6696.         return child;
  6697.     },
  6698.  
  6699.     // private
  6700.     groupChild: function(node, info, groups, level) {
  6701.         var me = this;
  6702.  
  6703.         var groupby = groups[level];
  6704.         var v = info[groupby];
  6705.  
  6706.         if (v) {
  6707.             var group = node.findChild('groupbyid', v);
  6708.             if (!group) {
  6709.                 var groupinfo;
  6710.                 if (info.type === groupby) {
  6711.                     groupinfo = info;
  6712.                 } else {
  6713.                     groupinfo = {
  6714.                         type: groupby,
  6715.                         id : groupby + "/" + v
  6716.                     };
  6717.                     if (groupby !== 'type') {
  6718.                         groupinfo[groupby] = v;
  6719.                     }
  6720.                 }
  6721.                 groupinfo.leaf = false;
  6722.                 groupinfo.groupbyid = v;
  6723.                 group = me.addChildSorted(node, groupinfo);
  6724.                 // fixme: remove when EXTJS has fixed those bugs?!
  6725.                 group.expand(); group.collapse();
  6726.             }
  6727.             if (info.type === groupby) {
  6728.                 return group;
  6729.             }
  6730.             if (group) {
  6731.                 return me.groupChild(group, info, groups, level + 1);
  6732.             }
  6733.         }
  6734.  
  6735.         return me.addChildSorted(node, info);
  6736.     },
  6737.  
  6738.     initComponent : function() {
  6739.         var me = this;
  6740.  
  6741.         var rstore = PVE.data.ResourceStore;
  6742.         var sp = Ext.state.Manager.getProvider();
  6743.  
  6744.         if (!me.viewFilter) {
  6745.             me.viewFilter = {};
  6746.         }
  6747.  
  6748.         var pdata = {
  6749.             dataIndex: {},
  6750.             updateCount: 0
  6751.         };
  6752.  
  6753.         var store = Ext.create('Ext.data.TreeStore', {
  6754.             model: 'PVETree',
  6755.             root: {
  6756.                 expanded: true,
  6757.                 id: 'root',
  6758.                 text: gettext('Datacenter')
  6759.             }
  6760.         });
  6761.  
  6762.         var stateid = 'rid';
  6763.  
  6764.         var updateTree = function() {
  6765.             var tmp;
  6766.  
  6767.             // fixme: suspend events ?
  6768.  
  6769.             var rootnode = me.store.getRootNode();
  6770.            
  6771.             // remember selected node (and all parents)
  6772.             var sm = me.getSelectionModel();
  6773.  
  6774.             var lastsel = sm.getSelection()[0];
  6775.             var parents = [];
  6776.             var p = lastsel;
  6777.             while (p && !!(p = p.parentNode)) {
  6778.                 parents.push(p);
  6779.             }
  6780.  
  6781.             var index = pdata.dataIndex;
  6782.  
  6783.             var groups = me.viewFilter.groups || [];
  6784.             var filterfn = me.viewFilter.filterfn;
  6785.  
  6786.             // remove vanished or changed items
  6787.             var key;
  6788.             for (key in index) {
  6789.                 if (index.hasOwnProperty(key)) {
  6790.                     var olditem = index[key];
  6791.  
  6792.                     // getById() use find(), which is slow (ExtJS4 DP5)
  6793.                     //var item = rstore.getById(olditem.data.id);
  6794.                     var item = rstore.data.get(olditem.data.id);
  6795.  
  6796.                     var changed = false;
  6797.                     if (item) {
  6798.                         // test if any grouping attributes changed
  6799.                         var i, len;
  6800.                         for (i = 0, len = groups.length; i < len; i++) {
  6801.                             var attr = groups[i];
  6802.                             if (item.data[attr] != olditem.data[attr]) {
  6803.                                 //console.log("changed " + attr);
  6804.                                 changed = true;
  6805.                                 break;
  6806.                             }
  6807.                         }
  6808.                         if ((item.data.text !== olditem.data.text) ||
  6809.                             (item.data.node !== olditem.data.node) ||
  6810.                             (item.data.running !== olditem.data.running)) {
  6811.                             //console.log("changed node/text/running " + olditem.data.id);
  6812.                             changed = true;
  6813.                         }
  6814.  
  6815.                         // fixme: also test filterfn()?
  6816.                     }
  6817.  
  6818.                     if (!item || changed) {
  6819.                         //console.log("REM UID: " + key + " ITEM " + olditem.data.id);
  6820.                         if (olditem.isLeaf()) {
  6821.                             delete index[key];
  6822.                             var parentNode = olditem.parentNode;
  6823.                             parentNode.removeChild(olditem, true);
  6824.                         } else {
  6825.                             if (item && changed) {
  6826.                                 olditem.beginEdit();
  6827.                                 //console.log("REM UPDATE UID: " + key + " ITEM " + item.data.running);
  6828.                                 var info = olditem.data;
  6829.                                 Ext.apply(info, item.data);
  6830.                                 me.setIconCls(info);
  6831.                                 olditem.commit();
  6832.                             }
  6833.                         }
  6834.                     }
  6835.                 }
  6836.             }
  6837.  
  6838.             // add new items
  6839.             rstore.each(function(item) {
  6840.                 var olditem = index[item.data.id];
  6841.                 if (olditem) {
  6842.                     return;
  6843.                 }
  6844.  
  6845.                 if (filterfn && !filterfn(item)) {
  6846.                     return;
  6847.                 }
  6848.  
  6849.                 //console.log("ADD UID: " + item.data.id);
  6850.  
  6851.                 var info = Ext.apply({ leaf: true }, item.data);
  6852.  
  6853.                 var child = me.groupChild(rootnode, info, groups, 0);
  6854.                 if (child) {
  6855.                     index[item.data.id] = child;
  6856.                 }
  6857.             });
  6858.  
  6859.             // select parent node is selection vanished
  6860.             if (lastsel && !rootnode.findChild('id', lastsel.data.id, true)) {
  6861.                 lastsel = rootnode;
  6862.                 while (!!(p = parents.shift())) {
  6863.                     if (!!(tmp = rootnode.findChild('id', p.data.id, true))) {
  6864.                         lastsel = tmp;
  6865.                         break;
  6866.                     }
  6867.                 }
  6868.                 me.selectById(lastsel.data.id);
  6869.             }
  6870.  
  6871.             if (!pdata.updateCount) {
  6872.                 rootnode.collapse();
  6873.                 rootnode.expand();
  6874.                 me.applyState(sp.get(stateid));
  6875.             }
  6876.  
  6877.             pdata.updateCount++;
  6878.         };
  6879.  
  6880.         var statechange = function(sp, key, value) {
  6881.             if (key === stateid) {
  6882.                 me.applyState(value);
  6883.             }
  6884.         };
  6885.  
  6886.         sp.on('statechange', statechange);
  6887.  
  6888.         Ext.apply(me, {
  6889.             store: store,
  6890.             viewConfig: {
  6891.                 // note: animate cause problems with applyState
  6892.                 animate: false
  6893.             },
  6894.             //useArrows: true,
  6895.             //rootVisible: false,
  6896.             //title: 'Resource Tree',
  6897.             listeners: {
  6898.                 itemcontextmenu: function(v, record, item, index, event) {
  6899.                     event.stopEvent();
  6900.                     //v.select(record);
  6901.                     var menu;
  6902.                    
  6903.                     if (record.data.type === 'qemu') {
  6904.                         menu = Ext.create('PVE.qemu.CmdMenu', {
  6905.                             pveSelNode: record
  6906.                         });
  6907.                     } else if (record.data.type === 'openvz') {
  6908.                         menu = Ext.create('PVE.openvz.CmdMenu', {
  6909.                             pveSelNode: record
  6910.                         });
  6911.                     } else {
  6912.                         return;
  6913.                     }
  6914.  
  6915.                     menu.showAt(event.getXY());
  6916.                 },
  6917.                 destroy: function() {
  6918.                     rstore.un("load", updateTree);
  6919.                 }
  6920.             },
  6921.             setViewFilter: function(view) {
  6922.                 me.viewFilter = view;
  6923.                 me.clearTree();
  6924.                 updateTree();
  6925.             },
  6926.             clearTree: function() {
  6927.                 pdata.updateCount = 0;
  6928.                 var rootnode = me.store.getRootNode();
  6929.                 rootnode.collapse();
  6930.                 rootnode.removeAll(true);
  6931.                 pdata.dataIndex = {};
  6932.                 me.getSelectionModel().deselectAll();
  6933.             },
  6934.             selectExpand: function(node) {
  6935.                 var sm = me.getSelectionModel();
  6936.                 if (!sm.isSelected(node)) {
  6937.                     sm.select(node);
  6938.                     var cn = node;
  6939.                     while (!!(cn = cn.parentNode)) {
  6940.                         if (!cn.isExpanded()) {
  6941.                             cn.expand();
  6942.                         }
  6943.                     }
  6944.                 }
  6945.             },
  6946.             selectById: function(nodeid) {
  6947.                 var rootnode = me.store.getRootNode();
  6948.                 var sm = me.getSelectionModel();
  6949.                 var node;
  6950.                 if (nodeid === 'root') {
  6951.                     node = rootnode;
  6952.                 } else {
  6953.                     node = rootnode.findChild('id', nodeid, true);
  6954.                 }
  6955.                 if (node) {
  6956.                     me.selectExpand(node);
  6957.                 }
  6958.             },
  6959.             checkVmMigration: function(record) {
  6960.                 if (!(record.data.type === 'qemu' || record.data.type === 'openvz')) {
  6961.                     throw "not a vm type";
  6962.                 }
  6963.  
  6964.                 var rootnode = me.store.getRootNode();
  6965.                 var node = rootnode.findChild('id', record.data.id, true);
  6966.  
  6967.                 if (node && node.data.type === record.data.type &&
  6968.                     node.data.node !== record.data.node) {
  6969.                     // defer select (else we get strange errors)
  6970.                     Ext.defer(function() { me.selectExpand(node); }, 100, me);
  6971.                 }
  6972.             },
  6973.             applyState : function(state) {
  6974.                 var sm = me.getSelectionModel();
  6975.                 if (state && state.value) {
  6976.                     me.selectById(state.value);
  6977.                 } else {
  6978.                     sm.deselectAll();
  6979.                 }
  6980.             }
  6981.         });
  6982.  
  6983.         me.callParent();
  6984.  
  6985.         var sm = me.getSelectionModel();
  6986.         sm.on('select', function(sm, n) {                  
  6987.             sp.set(stateid, { value: n.data.id});
  6988.         });
  6989.  
  6990.         rstore.on("load", updateTree);
  6991.         rstore.startUpdate();
  6992.         //rstore.stopUpdate();
  6993.     }
  6994.  
  6995. });
  6996. Ext.define('PVE.panel.Config', {
  6997.     extend: 'Ext.panel.Panel',
  6998.     alias: 'widget.pvePanelConfig',
  6999.  
  7000.     initComponent: function() {
  7001.         var me = this;
  7002.  
  7003.         var stateid = me.hstateid;
  7004.  
  7005.         var sp = Ext.state.Manager.getProvider();
  7006.  
  7007.         var activeTab;
  7008.  
  7009.         if (stateid) {
  7010.             var state = sp.get(stateid);
  7011.             if (state && state.value) {
  7012.                 activeTab = state.value;
  7013.             }
  7014.         }
  7015.  
  7016.         var items = me.items || [];
  7017.         me.items = undefined;
  7018.  
  7019.         var tbar = me.tbar || [];
  7020.         me.tbar = undefined;
  7021.  
  7022.         var title = me.title || me.pveSelNode.data.text;
  7023.         me.title = undefined;
  7024.  
  7025.         tbar.unshift('->');
  7026.         tbar.unshift({
  7027.             xtype: 'tbtext',
  7028.             text: title,
  7029.             baseCls: 'x-panel-header-text',
  7030.             padding: '0 0 5 0'
  7031.         });
  7032.  
  7033.         Ext.applyIf(me, { showSearch: true });
  7034.  
  7035.         if (me.showSearch) {
  7036.             items.unshift({
  7037.                 itemId: 'search',
  7038.                 xtype: 'pveResourceGrid'
  7039.             });
  7040.         }
  7041.  
  7042.         var toolbar = Ext.create('Ext.toolbar.Toolbar', {
  7043.             items: tbar,
  7044.             style: 'border:0px;',
  7045.             height: 28
  7046.         });
  7047.  
  7048.         var tab = Ext.create('Ext.tab.Panel', {
  7049.             flex: 1,
  7050.             border: true,
  7051.             activeTab: activeTab,
  7052.             defaults: Ext.apply(me.defaults ||  {}, {
  7053.                 pveSelNode: me.pveSelNode,
  7054.                 viewFilter: me.viewFilter,
  7055.                 workspace: me.workspace,
  7056.                 border: false
  7057.             }),
  7058.             items: items,
  7059.             listeners: {
  7060.                 afterrender: function(tp) {
  7061.                     var first =  tp.items.get(0);
  7062.                     if (first) {
  7063.                         first.fireEvent('show', first);
  7064.                     }
  7065.                 },
  7066.                 tabchange: function(tp, newcard, oldcard) {
  7067.                     var ntab = newcard.itemId;
  7068.                     // Note: '' is alias for first tab.
  7069.                     // First tab can be 'search' or something else
  7070.                     if (newcard.itemId === items[0].itemId) {
  7071.                         ntab = '';
  7072.                     }
  7073.                     var state = { value: ntab };
  7074.                     if (stateid) {
  7075.                         sp.set(stateid, state);
  7076.                     }
  7077.                 }
  7078.             }
  7079.         });
  7080.  
  7081.         Ext.apply(me, {
  7082.             layout: { type: 'vbox', align: 'stretch' },
  7083.             items: [ toolbar, tab]
  7084.         });
  7085.  
  7086.         me.callParent();
  7087.  
  7088.         var statechange = function(sp, key, state) {
  7089.             if (stateid && key === stateid) {
  7090.                 var atab = tab.getActiveTab().itemId;
  7091.                 var ntab = state.value || items[0].itemId;
  7092.                 if (state && ntab && (atab != ntab)) {
  7093.                     tab.setActiveTab(ntab);
  7094.                 }
  7095.             }
  7096.         };
  7097.  
  7098.         if (stateid) {
  7099.             me.mon(sp, 'statechange', statechange);
  7100.         }
  7101.     }
  7102. });
  7103. Ext.define('PVE.grid.BackupView', {
  7104.     extend: 'Ext.grid.GridPanel',
  7105.  
  7106.     alias: ['widget.pveBackupView'],
  7107.  
  7108.  
  7109.     initComponent : function() {
  7110.         var me = this;
  7111.  
  7112.         var nodename = me.pveSelNode.data.node;
  7113.         if (!nodename) {
  7114.             throw "no node name specified";
  7115.         }
  7116.  
  7117.         var vmid = me.pveSelNode.data.vmid;
  7118.         if (!vmid) {
  7119.             throw "no VM ID specified";
  7120.         }
  7121.  
  7122.         var vmtype = me.pveSelNode.data.type;
  7123.         if (!vmtype) {
  7124.             throw "no VM type specified";
  7125.         }
  7126.  
  7127.         var filterFn;
  7128.         if (vmtype === 'openvz') {
  7129.             filterFn = function(item) {
  7130.                 return item.data.volid.match(':backup/vzdump-openvz-');
  7131.             };
  7132.         } else if (vmtype === 'qemu') {
  7133.             filterFn = function(item) {
  7134.                 return item.data.volid.match(':backup/vzdump-qemu-');
  7135.             };
  7136.         } else {
  7137.             throw "unsupported VM type '" + vmtype + "'";
  7138.         }
  7139.  
  7140.         me.store = Ext.create('Ext.data.Store', {
  7141.             model: 'pve-storage-content',
  7142.             sorters: {
  7143.                 property: 'volid',
  7144.                 order: 'DESC'
  7145.             },
  7146.             filters: { filterFn: filterFn }
  7147.         });
  7148.  
  7149.         var reload = Ext.Function.createBuffered(function() {
  7150.             if (me.store.proxy.url) {
  7151.                 me.store.load();
  7152.             }
  7153.         }, 100);
  7154.  
  7155.         var setStorage = function(storage) {
  7156.             var url = '/api2/json/nodes/' + nodename + '/storage/' + storage + '/content';
  7157.             url += '?content=backup';
  7158.  
  7159.             me.store.setProxy({
  7160.                 type: 'pve',
  7161.                 url: url
  7162.             });
  7163.  
  7164.             reload();
  7165.         };
  7166.  
  7167.         var storagesel = Ext.create('PVE.form.StorageSelector', {
  7168.             nodename: nodename,
  7169.             fieldLabel: gettext('Storage'),
  7170.             labelAlign: 'right',
  7171.             storageContent: 'backup',
  7172.             allowBlank: false,
  7173.             listeners: {
  7174.                 change: function(f, value) {
  7175.                     setStorage(value);
  7176.                 }
  7177.             }
  7178.         });
  7179.  
  7180.         var sm = Ext.create('Ext.selection.RowModel', {});
  7181.  
  7182.         var backup_btn = Ext.create('Ext.button.Button', {
  7183.             text: gettext('Backup now'),
  7184.             handler: function() {
  7185.                 var win = Ext.create('PVE.window.Backup', {
  7186.                     nodename: nodename,
  7187.                     vmid: vmid,
  7188.                     vmtype: vmtype,
  7189.                     storage: storagesel.getValue()
  7190.                 });
  7191.                 win.show();
  7192.             }
  7193.         });
  7194.  
  7195.         var restore_btn = Ext.create('PVE.button.Button', {
  7196.             text: gettext('Restore'),
  7197.             disabled: true,
  7198.             selModel: sm,
  7199.             enableFn: function(rec) {
  7200.                 return !!rec;
  7201.             },
  7202.             handler: function(b, e, rec) {
  7203.                 var volid = rec.data.volid;
  7204.  
  7205.                 var win = Ext.create('PVE.window.Restore', {
  7206.                     nodename: nodename,
  7207.                     vmid: vmid,
  7208.                     volid: rec.data.volid,
  7209.                     volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
  7210.                     vmtype: vmtype
  7211.                 });
  7212.                 win.show();
  7213.                 win.on('destroy', reload);
  7214.             }
  7215.         });
  7216.  
  7217.         var delete_btn = Ext.create('PVE.button.Button', {
  7218.             text: gettext('Remove'),
  7219.             disabled: true,
  7220.             selModel: sm,
  7221.             confirmMsg: function(rec) {
  7222.                 var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  7223.                                             "'" + rec.data.volid + "'");
  7224.                 msg += " " + gettext('This will permanently erase all image data.');
  7225.  
  7226.                 return msg;
  7227.             },
  7228.             enableFn: function(rec) {
  7229.                 return !!rec;
  7230.             },
  7231.             handler: function(b, e, rec){
  7232.                 var storage = storagesel.getValue();
  7233.                 if (!storage) {
  7234.                     return;
  7235.                 }
  7236.  
  7237.                 var volid = rec.data.volid;
  7238.                 PVE.Utils.API2Request({
  7239.                     url: "/nodes/" + nodename + "/storage/" + storage + "/content/" + volid,
  7240.                     method: 'DELETE',
  7241.                     waitMsgTarget: me,
  7242.                     failure: function(response, opts) {
  7243.                         Ext.Msg.alert('Error', response.htmlStatus);
  7244.                     },
  7245.                     success: function(response, options) {
  7246.                         reload();
  7247.                     }
  7248.                 });
  7249.             }
  7250.         });
  7251.  
  7252.         Ext.apply(me, {
  7253.             stateful: false,
  7254.             selModel: sm,
  7255.             tbar: [ backup_btn, restore_btn, delete_btn, '->', storagesel ],
  7256.             columns: [
  7257.                 {
  7258.                     header: gettext('Name'),
  7259.                     flex: 1,
  7260.                     sortable: true,
  7261.                     renderer: PVE.Utils.render_storage_content,
  7262.                     dataIndex: 'volid'
  7263.                 },
  7264.                 {
  7265.                     header: gettext('Format'),
  7266.                     width: 100,
  7267.                     dataIndex: 'format'
  7268.                 },
  7269.                 {
  7270.                     header: gettext('Size'),
  7271.                     width: 100,
  7272.                     renderer: PVE.Utils.format_size,
  7273.                     dataIndex: 'size'
  7274.                 }
  7275.             ],
  7276.             listeners: {
  7277.                 show: reload
  7278.             }
  7279.         });
  7280.  
  7281.         me.callParent();
  7282.     }
  7283. });
  7284. Ext.define('PVE.panel.LogView', {
  7285.     extend: 'Ext.panel.Panel',
  7286.  
  7287.     alias: ['widget.pveLogView'],
  7288.  
  7289.     pageSize: 500,
  7290.  
  7291.     lineHeight: 16,
  7292.  
  7293.     viewInfo: undefined,
  7294.  
  7295.     scrollToEnd: true,
  7296.  
  7297.     getMaxDown: function(scrollToEnd) {
  7298.         var me = this;
  7299.  
  7300.         var target = me.getTargetEl();
  7301.         var dom = target.dom;
  7302.         if (scrollToEnd) {
  7303.             dom.scrollTop = dom.scrollHeight - dom.clientHeight;
  7304.         }
  7305.  
  7306.         var maxDown = dom.scrollHeight - dom.clientHeight -
  7307.             dom.scrollTop;
  7308.  
  7309.         return maxDown;
  7310.     },
  7311.  
  7312.     updateView: function(start, end, total, text) {
  7313.         var me = this;
  7314.         var el = me.dataCmp.el;
  7315.  
  7316.         if (me.viewInfo && me.viewInfo.start === start &&
  7317.             me.viewInfo.end === end && me.viewInfo.total === total &&
  7318.             me.viewInfo.textLength === text.length) {
  7319.             return; // same content
  7320.         }
  7321.  
  7322.         var maxDown = me.getMaxDown();
  7323.         var scrollToEnd = (maxDown <= 0) && me.scrollToEnd;
  7324.  
  7325.         el.setStyle('padding-top', start*me.lineHeight);
  7326.         el.update(text);
  7327.         me.dataCmp.setHeight(total*me.lineHeight);
  7328.  
  7329.         if (scrollToEnd) {
  7330.             me.getMaxDown(true);
  7331.         }
  7332.  
  7333.         me.viewInfo = {
  7334.             start: start,
  7335.             end: end,
  7336.             total: total,
  7337.             textLength:  text.length
  7338.         };
  7339.     },
  7340.  
  7341.     doAttemptLoad: function(start) {
  7342.         var me = this;
  7343.  
  7344.         PVE.Utils.API2Request({
  7345.             url: me.url,
  7346.             params: {
  7347.                 start: start,
  7348.                 limit: me.pageSize
  7349.             },
  7350.             method: 'GET',
  7351.             success: function(response) {
  7352.                 PVE.Utils.setErrorMask(me, false);
  7353.                 var list = response.result.data;
  7354.                 var total = response.result.total;
  7355.                 var first = 0, last = 0;
  7356.                 var text = '';
  7357.                 Ext.Array.each(list, function(item) {
  7358.                     if (!first|| item.n < first) {
  7359.                         first = item.n;
  7360.                     }
  7361.                     if (!last || item.n > last) {
  7362.                         last = item.n;
  7363.                     }
  7364.                     text = text + Ext.htmlEncode(item.t) + "<br>";
  7365.                 });
  7366.  
  7367.                 if (first && last && total) {
  7368.                     me.updateView(first -1 , last -1, total, text);
  7369.                 } else {
  7370.                     me.updateView(0, 0, 0, '');
  7371.                 }
  7372.             },
  7373.             failure: function(response) {
  7374.                 var msg = response.htmlStatus;
  7375.                 PVE.Utils.setErrorMask(me, msg);
  7376.             }
  7377.         });                          
  7378.     },
  7379.  
  7380.     attemptLoad: function(start) {
  7381.         var me = this;
  7382.         if (!me.loadTask) {
  7383.             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
  7384.         }
  7385.         me.loadTask.delay(200, me.doAttemptLoad, me, [start]);
  7386.     },
  7387.  
  7388.     requestUpdate: function(top, force) {
  7389.         var me = this;
  7390.  
  7391.         if (top === undefined) {
  7392.             var target = me.getTargetEl();
  7393.             top = target.dom.scrollTop;
  7394.         }
  7395.  
  7396.         var viewStart = parseInt((top / me.lineHeight) - 1, 10);
  7397.         if (viewStart < 0) {
  7398.             viewStart = 0;
  7399.         }
  7400.         var viewEnd = parseInt(((top + me.getHeight())/ me.lineHeight) + 1, 10);
  7401.         var info = me.viewInfo;
  7402.  
  7403.         if (info && !force) {
  7404.             if (viewStart >= info.start && viewEnd <= info.end) {
  7405.                 return;
  7406.             }
  7407.         }
  7408.  
  7409.         var line = parseInt((top / me.lineHeight) - (me.pageSize / 2) + 10, 10);
  7410.         if (line < 0) {
  7411.             line = 0;
  7412.         }
  7413.  
  7414.         me.attemptLoad(line);
  7415.     },
  7416.  
  7417.     afterRender: function() {
  7418.         var me = this;
  7419.  
  7420.         me.callParent(arguments);
  7421.  
  7422.         Ext.Function.defer(function() {
  7423.             var target = me.getTargetEl();
  7424.             target.on('scroll',  function(e) {
  7425.                 me.requestUpdate();
  7426.             });
  7427.             me.requestUpdate(0);
  7428.         }, 20);
  7429.     },
  7430.  
  7431.     initComponent : function() {
  7432.         /*jslint confusion: true */
  7433.  
  7434.         var me = this;
  7435.  
  7436.         if (!me.url) {
  7437.             throw "no url specified";
  7438.         }
  7439.  
  7440.         me.dataCmp = Ext.create('Ext.Component', {
  7441.             style: 'font:normal 11px tahoma, arial, verdana, sans-serif;' +
  7442.                 'line-height: ' + me.lineHeight.toString() + 'px; white-space: pre;'
  7443.         });
  7444.  
  7445.         me.task = Ext.TaskManager.start({
  7446.             run: function() {
  7447.                 if (!me.isVisible() || !me.scrollToEnd || !me.viewInfo) {
  7448.                     return;
  7449.                 }
  7450.                
  7451.                 var maxDown = me.getMaxDown();
  7452.                 if (maxDown > 0) {
  7453.                     return;
  7454.                 }
  7455.  
  7456.                 me.requestUpdate(undefined, true);
  7457.             },
  7458.             interval: 1000
  7459.         });
  7460.  
  7461.         Ext.apply(me, {
  7462.             autoScroll: true,
  7463.             layout: 'auto',
  7464.             items: me.dataCmp,
  7465.             bodyStyle: 'padding: 5px;',
  7466.             listeners: {
  7467.                 show: function() {
  7468.                     var target = me.getTargetEl();
  7469.                     if (target && target.dom) {
  7470.                         target.dom.scrollTop = me.savedScrollTop;
  7471.                     }
  7472.                 },
  7473.                 beforehide: function() {
  7474.                     // Hack: chrome reset scrollTop to 0, so we save/restore
  7475.                     var target = me.getTargetEl();
  7476.                     if (target && target.dom) {
  7477.                         me.savedScrollTop = target.dom.scrollTop;
  7478.                     }
  7479.                 },
  7480.                 destroy: function() {
  7481.                     Ext.TaskManager.stop(me.task);
  7482.                 }
  7483.             }
  7484.         });
  7485.  
  7486.         me.callParent();
  7487.     }
  7488. });
  7489. Ext.define('PVE.node.DNSEdit', {
  7490.     extend: 'PVE.window.Edit',
  7491.     alias: ['widget.pveNodeDNSEdit'],
  7492.  
  7493.     initComponent : function() {
  7494.         var me = this;
  7495.  
  7496.         var nodename = me.pveSelNode.data.node;
  7497.         if (!nodename) {
  7498.             throw "no node name specified";
  7499.         }
  7500.  
  7501.         me.items = [
  7502.             {
  7503.                 xtype: 'textfield',
  7504.                 fieldLabel: 'Search domain',
  7505.                 name: 'search',
  7506.                 allowBlank: false
  7507.             },
  7508.             {
  7509.                 xtype: 'pvetextfield',
  7510.                 fieldLabel: gettext('DNS server') + " 1",
  7511.                 vtype: 'IPAddress',
  7512.                 skipEmptyText: true,
  7513.                 name: 'dns1'
  7514.             },
  7515.             {
  7516.                 xtype: 'pvetextfield',
  7517.                 fieldLabel: gettext('DNS server') + " 2",
  7518.                 vtype: 'IPAddress',
  7519.                 skipEmptyText: true,
  7520.                 name: 'dns2'
  7521.             },
  7522.             {
  7523.                 xtype: 'pvetextfield',
  7524.                 fieldLabel: gettext('DNS server') + " 3",
  7525.                 vtype: 'IPAddress',
  7526.                 skipEmptyText: true,
  7527.                 name: 'dns3'
  7528.             }
  7529.         ];
  7530.  
  7531.         Ext.applyIf(me, {
  7532.             subject: 'DNS',
  7533.             url: "/api2/extjs/nodes/" + nodename + "/dns",
  7534.             fieldDefaults: {
  7535.                 labelWidth: 120
  7536.             }
  7537.         });
  7538.  
  7539.         me.callParent();
  7540.  
  7541.         me.load();
  7542.     }
  7543. });
  7544. Ext.define('PVE.node.DNSView', {
  7545.     extend: 'PVE.grid.ObjectGrid',
  7546.     alias: ['widget.pveNodeDNSView'],
  7547.  
  7548.     initComponent : function() {
  7549.         var me = this;
  7550.  
  7551.         var nodename = me.pveSelNode.data.node;
  7552.         if (!nodename) {
  7553.             throw "no node name specified";
  7554.         }
  7555.  
  7556.         var run_editor = function() {
  7557.             var win = Ext.create('PVE.node.DNSEdit', {
  7558.                 pveSelNode: me.pveSelNode
  7559.             });
  7560.             win.show();
  7561.         };
  7562.  
  7563.         Ext.applyIf(me, {
  7564.             url: "/api2/json/nodes/" + nodename + "/dns",
  7565.             cwidth1: 130,
  7566.             interval: 1000,
  7567.             rows: {
  7568.                 search: { header: 'Search domain', required: true },
  7569.                 dns1: { header: gettext('DNS server') + " 1", required: true },
  7570.                 dns2: { header: gettext('DNS server') + " 2" },
  7571.                 dns3: { header: gettext('DNS server') + " 3" }
  7572.             },
  7573.             tbar: [
  7574.                 {
  7575.                     text: gettext("Edit"),
  7576.                     handler: run_editor
  7577.                 }
  7578.             ],
  7579.             listeners: {
  7580.                 itemdblclick: run_editor
  7581.             }
  7582.         });
  7583.  
  7584.         me.callParent();
  7585.  
  7586.         me.on('show', me.rstore.startUpdate);
  7587.         me.on('hide', me.rstore.stopUpdate);
  7588.         me.on('destroy', me.rstore.stopUpdate);
  7589.     }
  7590. });
  7591. Ext.define('PVE.node.TimeView', {
  7592.     extend: 'PVE.grid.ObjectGrid',
  7593.     alias: ['widget.pveNodeTimeView'],
  7594.  
  7595.     initComponent : function() {
  7596.         var me = this;
  7597.  
  7598.         var nodename = me.pveSelNode.data.node;
  7599.         if (!nodename) {
  7600.             throw "no node name specified";
  7601.         }
  7602.  
  7603.         var tzoffset = (new Date()).getTimezoneOffset()*60000;
  7604.         var renderlocaltime = function(value) {
  7605.             var servertime = new Date((value * 1000) + tzoffset);
  7606.             return Ext.Date.format(servertime, 'Y-m-d H:i:s');
  7607.         };
  7608.  
  7609.         var run_editor = function() {
  7610.             var win = Ext.create('PVE.node.TimeEdit', {
  7611.                 pveSelNode: me.pveSelNode
  7612.             });
  7613.             win.show();
  7614.         };
  7615.  
  7616.         Ext.applyIf(me, {
  7617.             url: "/api2/json/nodes/" + nodename + "/time",
  7618.             cwidth1: 150,
  7619.             interval: 1000,
  7620.             rows: {
  7621.                 timezone: {
  7622.                     header: gettext('Time zone'),
  7623.                     required: true
  7624.                 },
  7625.                 localtime: {
  7626.                     header: gettext('Server time'),
  7627.                     required: true,
  7628.                     renderer: renderlocaltime
  7629.                 }
  7630.             },
  7631.             tbar: [
  7632.                 {
  7633.                     text: gettext("Edit"),
  7634.                     handler: run_editor
  7635.                 }
  7636.             ],
  7637.             listeners: {
  7638.                 itemdblclick: run_editor
  7639.             }
  7640.         });
  7641.  
  7642.         me.callParent();
  7643.  
  7644.         me.on('show', me.rstore.startUpdate);
  7645.         me.on('hide', me.rstore.stopUpdate);
  7646.         me.on('destroy', me.rstore.stopUpdate);
  7647.     }
  7648. });
  7649. Ext.define('PVE.node.TimeEdit', {
  7650.     extend: 'PVE.window.Edit',
  7651.     alias: ['widget.pveNodeTimeEdit'],
  7652.  
  7653.     initComponent : function() {
  7654.         var me = this;
  7655.  
  7656.         var nodename = me.pveSelNode.data.node;
  7657.         if (!nodename) {
  7658.             throw "no node name specified";
  7659.         }
  7660.  
  7661.         Ext.applyIf(me, {
  7662.             subject: gettext('Time zone'),
  7663.             url: "/api2/extjs/nodes/" + nodename + "/time",
  7664.             fieldDefaults: {
  7665.                 labelWidth: 70
  7666.             },
  7667.             width: 400,
  7668.             items: {
  7669.                 xtype: 'combo',
  7670.                 fieldLabel: gettext('Time zone'),
  7671.                 name: 'timezone',
  7672.                 queryMode: 'local',
  7673.                 store: new PVE.data.TimezoneStore({autoDestory: true}),
  7674.                 valueField: 'zone',
  7675.                 displayField: 'zone',
  7676.                 triggerAction: 'all',
  7677.                 forceSelection: true,
  7678.                 editable: false,
  7679.                 allowBlank: false
  7680.             }
  7681.         });
  7682.  
  7683.         me.callParent();
  7684.  
  7685.         me.load();
  7686.     }
  7687. });
  7688. Ext.define('PVE.node.StatusView', {
  7689.     extend: 'PVE.grid.ObjectGrid',
  7690.     alias: ['widget.pveNodeStatusView'],
  7691.  
  7692.     initComponent : function() {
  7693.         var me = this;
  7694.  
  7695.         var nodename = me.pveSelNode.data.node;
  7696.         if (!nodename) {
  7697.             throw "no node name specified";
  7698.         }
  7699.  
  7700.         var render_cpuinfo = function(value) {
  7701.             return value.cpus + " x " + value.model;
  7702.         };
  7703.  
  7704.         var render_loadavg = function(value) {
  7705.             return value[0] + ", " + value[1] + ", " + value[2];
  7706.         };
  7707.  
  7708.         var render_cpu = function(value) {
  7709.             var per = value * 100;
  7710.             return per.toFixed(2) + "%";
  7711.         };
  7712.  
  7713.         var render_meminfo = function(value) {
  7714.             var per = (value.used / value.total)*100;
  7715.             var text = "<div>Total: " + PVE.Utils.format_size(value.total) + "</div>" +
  7716.                 "<div>Used: " + PVE.Utils.format_size(value.used) + "</div>";
  7717.             return text;
  7718.         };
  7719.  
  7720.         var rows = {
  7721.             uptime: { header: 'Uptime', required: true, renderer: PVE.Utils.format_duration_long },
  7722.             loadavg: { header: 'Load average', required: true, renderer: render_loadavg },
  7723.             cpuinfo: { header: 'CPUs', required: true, renderer: render_cpuinfo },
  7724.             cpu: { header: 'CPU usage',required: true,  renderer: render_cpu },
  7725.             wait: { header: 'IO delay', required: true, renderer: render_cpu },
  7726.             memory: { header: 'RAM usage', required: true, renderer: render_meminfo },
  7727.             swap: { header: 'SWAP usage', required: true, renderer: render_meminfo },
  7728.             rootfs: { header: 'HD space (root)', required: true, renderer: render_meminfo },
  7729.             pveversion: { header: 'PVE Manager version', required: true },
  7730.             kversion: { header: 'Kernel version', required: true }
  7731.         };
  7732.  
  7733.         Ext.applyIf(me, {
  7734.             cwidth1: 150,
  7735.             //height: 276,
  7736.             rows: rows
  7737.         });
  7738.  
  7739.         me.callParent();
  7740.     }
  7741. });
  7742. /*jslint confusion: true */
  7743. Ext.define('PVE.node.BCFailCnt', {
  7744.     extend: 'Ext.grid.GridPanel',
  7745.     alias: ['widget.pveNodeBCFailCnt'],
  7746.  
  7747.     initComponent : function() {
  7748.         var me = this;
  7749.  
  7750.         var nodename = me.pveSelNode.data.node;
  7751.         if (!nodename) {
  7752.             throw "no node name specified";
  7753.         }
  7754.  
  7755.         var store = new Ext.data.Store({
  7756.             model: 'pve-openvz-ubc',
  7757.             proxy: {
  7758.                 type: 'pve',
  7759.                 url: '/api2/json/nodes/' + nodename + '/ubcfailcnt'
  7760.             },
  7761.             sorters: [
  7762.                 {
  7763.                     property : 'id',
  7764.                     direction: 'ASC'
  7765.                 }
  7766.             ]
  7767.         });
  7768.  
  7769.         var reload = function() {
  7770.             store.load();
  7771.         };
  7772.  
  7773.         Ext.applyIf(me, {
  7774.             store: store,
  7775.             stateful: false,
  7776.             columns: [
  7777.                 {
  7778.                     header: 'Container',
  7779.                     width: 100,
  7780.                     dataIndex: 'id'
  7781.                 },
  7782.                 {
  7783.                     header: 'failcnt',
  7784.                     flex: 1,
  7785.                     dataIndex: 'failcnt'
  7786.                 }
  7787.             ],
  7788.             listeners: {
  7789.                 show: reload,
  7790.                 itemdblclick: function(v, record) {
  7791.                     var ws = me.up('pveStdWorkspace');
  7792.                     ws.selectById('openvz/' + record.data.id);
  7793.                 }
  7794.             }
  7795.         });
  7796.  
  7797.         me.callParent();
  7798.  
  7799.    }
  7800. }, function() {
  7801.  
  7802.     Ext.define('pve-openvz-ubc', {
  7803.         extend: "Ext.data.Model",
  7804.         fields: [ 'id', { name: 'failcnt', type: 'number' } ]
  7805.     });
  7806.  
  7807. });
  7808. Ext.define('PVE.node.Summary', {
  7809.     extend: 'Ext.panel.Panel',
  7810.     alias: 'widget.pveNodeSummary',
  7811.  
  7812.     initComponent: function() {
  7813.         var me = this;
  7814.  
  7815.         var nodename = me.pveSelNode.data.node;
  7816.         if (!nodename) {
  7817.             throw "no node name specified";
  7818.         }
  7819.  
  7820.         if (!me.statusStore) {
  7821.             throw "no status storage specified";
  7822.         }
  7823.  
  7824.         var rstore = me.statusStore;
  7825.  
  7826.         var statusview = Ext.create('PVE.node.StatusView', {
  7827.             title: 'Status',
  7828.             pveSelNode: me.pveSelNode,
  7829.             style: 'padding-top:0px',
  7830.             rstore: rstore
  7831.         });
  7832.  
  7833.         var rrdurl = "/api2/png/nodes/" + nodename + "/rrd";
  7834.  
  7835.         Ext.apply(me, {
  7836.             autoScroll: true,
  7837.             bodyStyle: 'padding:10px',
  7838.             defaults: {
  7839.                 width: 800,
  7840.                 style: 'padding-top:10px'
  7841.             },         
  7842.             tbar: [ '->', { xtype: 'pveRRDTypeSelector' } ],
  7843.             items: [
  7844.                 statusview,
  7845.                 {
  7846.                     xtype: 'pveRRDView',
  7847.                     title: "CPU usage %",
  7848.                     datasource: 'cpu,iowait',
  7849.                     rrdurl: rrdurl
  7850.                 },
  7851.                 {
  7852.                     xtype: 'pveRRDView',
  7853.                     title: "Server load",
  7854.                     datasource: 'loadavg',
  7855.                     rrdurl: rrdurl
  7856.                 },
  7857.                 {
  7858.                     xtype: 'pveRRDView',
  7859.                     title: "Memory usage",
  7860.                     datasource: 'memtotal,memused',
  7861.                     rrdurl: rrdurl
  7862.                 },
  7863.                 {
  7864.                     xtype: 'pveRRDView',
  7865.                     title: "Network traffic",
  7866.                     datasource: 'netin,netout',
  7867.                     rrdurl: rrdurl
  7868.                 }
  7869.             ],
  7870.             listeners: {
  7871.                 show: rstore.startUpdate,
  7872.                 hide: rstore.stopUpdate,
  7873.                 destroy: rstore.stopUpdate
  7874.             }
  7875.         });
  7876.  
  7877.         me.callParent();
  7878.     }
  7879. });
  7880. Ext.define('PVE.node.ServiceView', {
  7881.     extend: 'Ext.grid.GridPanel',
  7882.  
  7883.     alias: ['widget.pveNodeServiceView'],
  7884.  
  7885.     initComponent : function() {
  7886.         var me = this;
  7887.  
  7888.         var nodename = me.pveSelNode.data.node;
  7889.         if (!nodename) {
  7890.             throw "no node name specified";
  7891.         }
  7892.  
  7893.         var rstore = Ext.create('PVE.data.UpdateStore', {
  7894.             interval: 1000,
  7895.             storeid: 'pve-services',
  7896.             model: 'pve-services',
  7897.             proxy: {
  7898.                 type: 'pve',
  7899.                 url: "/api2/json/nodes/" + nodename + "/services"
  7900.             }
  7901.         });
  7902.  
  7903.         var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  7904.  
  7905.         var service_cmd = function(cmd) {
  7906.             var sm = me.getSelectionModel();
  7907.             var rec = sm.getSelection()[0];
  7908.             PVE.Utils.API2Request({
  7909.                 url: "/nodes/" + nodename + "/services/" + rec.data.service + "/" + cmd,
  7910.                 method: 'POST',
  7911.                 failure: function(response, opts) {
  7912.                     Ext.Msg.alert('Error', response.htmlStatus);
  7913.                     me.loading = true;
  7914.                 },
  7915.                 success: function(response, opts) {
  7916.                     rstore.startUpdate();
  7917.                     var upid = response.result.data;
  7918.  
  7919.                     var win = Ext.create('PVE.window.TaskViewer', {
  7920.                         upid: upid
  7921.                     });
  7922.                     win.show();
  7923.                 }
  7924.             });
  7925.         };
  7926.  
  7927.         var start_btn = new Ext.Button({
  7928.             text: gettext('Start'),
  7929.             disabled: true,
  7930.             handler: function(){
  7931.                 service_cmd("start");
  7932.             }
  7933.         });
  7934.  
  7935.         var stop_btn = new Ext.Button({
  7936.             text: gettext('Stop'),
  7937.             disabled: true,
  7938.             handler: function(){
  7939.                 service_cmd("stop");
  7940.             }
  7941.         });
  7942.  
  7943.         var restart_btn = new Ext.Button({
  7944.             text: gettext('Restart'),
  7945.             disabled: true,
  7946.             handler: function(){
  7947.                 service_cmd("restart");
  7948.             }
  7949.         });
  7950.  
  7951.         var set_button_status = function() {
  7952.             var sm = me.getSelectionModel();
  7953.             var rec = sm.getSelection()[0];
  7954.  
  7955.             if (!rec) {
  7956.                 start_btn.disable();
  7957.                 stop_btn.disable();
  7958.                 restart_btn.disable();
  7959.                 return;
  7960.             }
  7961.             var service = rec.data.service;
  7962.             var state = rec.data.state;
  7963.             if (service == 'apache' ||
  7964.                 service == 'pvecluster' ||
  7965.                 service == 'pvedaemon') {
  7966.                 if (state == 'running') {
  7967.                     start_btn.disable();
  7968.                     restart_btn.enable();
  7969.                 } else {
  7970.                     start_btn.enable();
  7971.                     restart_btn.disable();
  7972.                 }
  7973.                 stop_btn.disable();
  7974.             } else {
  7975.                 if (state == 'running') {
  7976.                     start_btn.disable();
  7977.                     restart_btn.enable();
  7978.                     stop_btn.enable();
  7979.                 } else {
  7980.                     start_btn.enable();
  7981.                     restart_btn.disable();
  7982.                     stop_btn.disable();
  7983.                 }
  7984.             }
  7985.         };
  7986.  
  7987.         me.mon(store, 'datachanged', set_button_status);
  7988.  
  7989.         PVE.Utils.monStoreErrors(me, rstore);
  7990.  
  7991.         Ext.apply(me, {
  7992.             store: store,
  7993.             stateful: false,
  7994.             tbar: [ start_btn, stop_btn, restart_btn ],
  7995.             columns: [
  7996.                 {
  7997.                     header: gettext('Name'),
  7998.                     width: 100,
  7999.                     sortable: true,
  8000.                     dataIndex: 'name'
  8001.                 },
  8002.                 {
  8003.                     header: gettext('Status'),
  8004.                     width: 100,
  8005.                     sortable: true,
  8006.                     dataIndex: 'state'
  8007.                 },
  8008.                 {
  8009.                     header: gettext('Description'),
  8010.                     dataIndex: 'desc',
  8011.                     flex: 1
  8012.                 }
  8013.             ],
  8014.             listeners: {
  8015.                 selectionchange: set_button_status,
  8016.                 show: rstore.startUpdate,
  8017.                 hide: rstore.stopUpdate,
  8018.                 destroy: rstore.stopUpdate
  8019.             }
  8020.         });
  8021.  
  8022.         me.callParent();
  8023.     }
  8024. }, function() {
  8025.  
  8026.     Ext.define('pve-services', {
  8027.         extend: 'Ext.data.Model',
  8028.         fields: [ 'service', 'name', 'desc', 'state' ],
  8029.         idProperty: 'service'
  8030.     });
  8031.  
  8032. });
  8033. Ext.define('PVE.node.NetworkEdit', {
  8034.     extend: 'PVE.window.Edit',
  8035.     alias: ['widget.pveNodeNetworkEdit'],
  8036.  
  8037.     initComponent : function() {
  8038.         var me = this;
  8039.  
  8040.         var nodename = me.pveSelNode.data.node;
  8041.         if (!nodename) {
  8042.             throw "no node name specified";
  8043.         }
  8044.  
  8045.         if (!me.iftype) {
  8046.             throw "no network device type specified";
  8047.         }
  8048.  
  8049.         me.create = !me.iface;
  8050.  
  8051.         var iface_vtype;
  8052.  
  8053.         if (me.iftype === 'bridge') {
  8054.             me.subject = "Bridge";
  8055.             iface_vtype = 'BridgeName';
  8056.         } else if (me.iftype === 'bond') {
  8057.             me.subject = "Bond";
  8058.             iface_vtype = 'BondName';
  8059.         } else if (me.iftype === 'eth' && !me.create) {
  8060.             me.subject = gettext("Network Device");
  8061.         } else {
  8062.             throw "no known network device type specified";
  8063.         }
  8064.  
  8065.         var column2 = [
  8066.             {
  8067.                 xtype: 'pvecheckbox',
  8068.                 fieldLabel: 'Autostart',
  8069.                 name: 'autostart',
  8070.                 uncheckedValue: 0,
  8071.                 checked: me.create ? true : undefined
  8072.             }
  8073.         ];
  8074.  
  8075.         if (me.iftype === 'bridge') {
  8076.             column2.push({
  8077.                 xtype: 'textfield',
  8078.                 fieldLabel: 'Bridge ports',
  8079.                 name: 'bridge_ports'
  8080.             });  
  8081.         } else if (me.iftype === 'bond') {
  8082.             column2.push({
  8083.                 xtype: 'textfield',
  8084.                 fieldLabel: 'Slaves',
  8085.                 name: 'slaves'
  8086.             });
  8087.             column2.push({
  8088.                 xtype: 'bondModeSelector',
  8089.                 fieldLabel: 'Mode',
  8090.                 name: 'bond_mode',
  8091.                 value: me.create ? 'balance-rr' : undefined,
  8092.                 allowBlank: false
  8093.             });
  8094.         }
  8095.  
  8096.         var url;
  8097.         var method;
  8098.  
  8099.         if (me.create) {
  8100.             url = "/api2/extjs/nodes/" + nodename + "/network";
  8101.             method = 'POST';
  8102.         } else {
  8103.             url = "/api2/extjs/nodes/" + nodename + "/network/" + me.iface;
  8104.             method = 'PUT';
  8105.         }
  8106.  
  8107.         var column1 = [
  8108.             {
  8109.                 xtype: me.create ? 'textfield' : 'displayfield',
  8110.                 fieldLabel: gettext('Name'),
  8111.                 height: 22, // hack: set same height as text fields
  8112.                 name: 'iface',
  8113.                 value: me.iface,
  8114.                 vtype: iface_vtype,
  8115.                 allowBlank: false
  8116.             },
  8117.             {
  8118.                 xtype: 'pvetextfield',
  8119.                 deleteEmpty: !me.create,
  8120.                 fieldLabel: gettext('IP address'),
  8121.                 vtype: 'IPAddress',
  8122.                 name: 'address'
  8123.             },
  8124.             {
  8125.                 xtype: 'pvetextfield',
  8126.                 deleteEmpty: !me.create,
  8127.                 fieldLabel: gettext('Subnet mask'),
  8128.                 vtype: 'IPAddress',
  8129.                 name: 'netmask',
  8130.                 validator: function(value) {
  8131.                     /*jslint confusion: true */
  8132.                     if (!me.items) {
  8133.                         return true;
  8134.                     }
  8135.                     var address = me.down('field[name=address]').getValue();
  8136.                     if (value !== '') {
  8137.                         if (address === '') {
  8138.                             return "Subnet mask requires option 'IP address'";
  8139.                         }
  8140.                     } else {
  8141.                         if (address !== '') {
  8142.                             return "Option 'IP address' requires a subnet mask";
  8143.                         }
  8144.                     }
  8145.                    
  8146.                     return true;
  8147.                 }
  8148.             },
  8149.             {
  8150.                 xtype: 'pvetextfield',
  8151.                 deleteEmpty: !me.create,
  8152.                 fieldLabel: 'Gateway',
  8153.                 vtype: 'IPAddress',
  8154.                 name: 'gateway'
  8155.             }
  8156.         ];
  8157.  
  8158.         Ext.applyIf(me, {
  8159.             url: url,
  8160.             method: method,
  8161.             items: {
  8162.                 xtype: 'inputpanel',
  8163.                 column1: column1,
  8164.                 column2: column2
  8165.             }
  8166.         });
  8167.  
  8168.         me.callParent();
  8169.  
  8170.         if (me.create) {
  8171.             me.down('field[name=iface]').setValue(me.iface_default);
  8172.         } else {
  8173.             me.load({
  8174.                 success: function(response, options) {
  8175.                     var data = response.result.data;
  8176.                     if (data.type !== me.iftype) {
  8177.                         var msg = "Got unexpected device type";
  8178.                         Ext.Msg.alert(gettext('Error'), msg, function() {
  8179.                             me.close();
  8180.                         });
  8181.                         return;
  8182.                     }
  8183.                     me.setValues(data);
  8184.                     me.isValid(); // trigger validation
  8185.                 }
  8186.             });
  8187.         }
  8188.     }
  8189. });
  8190. Ext.define('PVE.node.NetworkView', {
  8191.     extend: 'Ext.panel.Panel',
  8192.  
  8193.     alias: ['widget.pveNodeNetworkView'],
  8194.  
  8195.     initComponent : function() {
  8196.         var me = this;
  8197.  
  8198.         var nodename = me.pveSelNode.data.node;
  8199.         if (!nodename) {
  8200.             throw "no node name specified";
  8201.         }
  8202.  
  8203.         var store = Ext.create('Ext.data.Store', {
  8204.             model: 'pve-networks',
  8205.             proxy: {
  8206.                 type: 'pve',
  8207.                 url: "/api2/json/nodes/" + nodename + "/network"
  8208.             },
  8209.             sorters: [
  8210.                 {
  8211.                     property : 'iface',
  8212.                     direction: 'ASC'
  8213.                 }
  8214.             ]
  8215.         });
  8216.  
  8217.         var reload = function() {
  8218.             var changeitem = me.down('#changes');
  8219.             PVE.Utils.API2Request({
  8220.                 url: '/nodes/' + nodename + '/network',
  8221.                 failure: function(response, opts) {
  8222.                     changeitem.update('Error: ' + response.htmlStatus);
  8223.                     store.loadData({});
  8224.                 },
  8225.                 success: function(response, opts) {
  8226.                     var result = Ext.decode(response.responseText);
  8227.                     store.loadData(result.data);
  8228.                     var changes = result.changes;
  8229.                     if (changes === undefined || changes === '') {
  8230.                         changes = gettext("No changes");
  8231.                     }
  8232.                     changeitem.update("<pre>" + Ext.htmlEncode(changes) + "</pre>");
  8233.                 }
  8234.             });
  8235.         };
  8236.  
  8237.         var run_editor = function() {
  8238.             var grid = me.down('gridpanel');
  8239.             var sm = grid.getSelectionModel();
  8240.             var rec = sm.getSelection()[0];
  8241.             if (!rec) {
  8242.                 return;
  8243.             }
  8244.  
  8245.             var win = Ext.create('PVE.node.NetworkEdit', {
  8246.                 pveSelNode: me.pveSelNode,
  8247.                 iface: rec.data.iface,
  8248.                 iftype: rec.data.type
  8249.             });
  8250.             win.show();
  8251.             win.on('destroy', reload);
  8252.         };
  8253.  
  8254.         var edit_btn = new Ext.Button({
  8255.             text: gettext('Edit'),
  8256.             disabled: true,
  8257.             handler: run_editor
  8258.         });
  8259.  
  8260.         var del_btn = new Ext.Button({
  8261.             text: gettext('Remove'),
  8262.             disabled: true,
  8263.             handler: function(){
  8264.                 var grid = me.down('gridpanel');
  8265.                 var sm = grid.getSelectionModel();
  8266.                 var rec = sm.getSelection()[0];
  8267.                 if (!rec) {
  8268.                     return;
  8269.                 }
  8270.  
  8271.                 var iface = rec.data.iface;
  8272.  
  8273.                 PVE.Utils.API2Request({
  8274.                     url: '/nodes/' + nodename + '/network/' + iface,
  8275.                     method: 'DELETE',
  8276.                     waitMsgTarget: me,
  8277.                     callback: function() {
  8278.                         reload();
  8279.                     },
  8280.                     failure: function(response, opts) {
  8281.                         Ext.Msg.alert('Error', response.htmlStatus);
  8282.                     }
  8283.                 });
  8284.             }
  8285.         });
  8286.  
  8287.         var set_button_status = function() {
  8288.             var grid = me.down('gridpanel');
  8289.             var sm = grid.getSelectionModel();
  8290.             var rec = sm.getSelection()[0];
  8291.  
  8292.             edit_btn.setDisabled(!rec);
  8293.             del_btn.setDisabled(!rec);
  8294.         };
  8295.  
  8296.         PVE.Utils.monStoreErrors(me, store);
  8297.  
  8298.         var render_ports = function(value, metaData, record) {
  8299.             if (value === 'bridge') {
  8300.                 return record.data.bridge_ports;
  8301.             } else if (value === 'bond') {
  8302.                 return record.data.slaves;
  8303.             }
  8304.         };
  8305.  
  8306.         Ext.apply(me, {
  8307.             layout: 'border',
  8308.             tbar: [
  8309.                 {
  8310.                     text: gettext('Create'),
  8311.                     menu: new Ext.menu.Menu({
  8312.                         items: [
  8313.                             {
  8314.                                 text: 'Bridge',
  8315.                                 handler: function() {
  8316.                                     var next;
  8317.                                     for (next = 0; next <= 9999; next++) {
  8318.                                         if (!store.data.get('vmbr' + next.toString())) {
  8319.                                             break;
  8320.                                         }
  8321.                                     }
  8322.                                    
  8323.                                     var win = Ext.create('PVE.node.NetworkEdit', {
  8324.                                         pveSelNode: me.pveSelNode,
  8325.                                         iftype: 'bridge',
  8326.                                         iface_default: 'vmbr' + next.toString()
  8327.                                     });
  8328.                                     win.on('destroy', reload);
  8329.                                     win.show();
  8330.                                 }
  8331.                             },
  8332.                             {
  8333.                                 text: 'Bond',
  8334.                                 handler: function() {
  8335.                                     var next;
  8336.                                     for (next = 0; next <= 9999; next++) {
  8337.                                         if (!store.data.get('bond' + next.toString())) {
  8338.                                             break;
  8339.                                         }
  8340.                                     }
  8341.                                     var win = Ext.create('PVE.node.NetworkEdit', {
  8342.                                         pveSelNode: me.pveSelNode,
  8343.                                         iftype: 'bond',
  8344.                                         iface_default: 'bond' + next.toString()
  8345.                                     });
  8346.                                     win.on('destroy', reload);
  8347.                                     win.show();
  8348.                                 }
  8349.                             }
  8350.                         ]
  8351.                     })
  8352.                 }, ' ',
  8353.                 {
  8354.                     text: gettext('Revert changes'),
  8355.                     handler: function() {
  8356.                         PVE.Utils.API2Request({
  8357.                             url: '/nodes/' + nodename + '/network',
  8358.                             method: 'DELETE',
  8359.                             waitMsgTarget: me,
  8360.                             callback: function() {
  8361.                                 reload();
  8362.                             },
  8363.                             failure: function(response, opts) {
  8364.                                 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  8365.                             }
  8366.                         });
  8367.                     }
  8368.                 },
  8369.                 edit_btn,
  8370.                 del_btn
  8371.             ],
  8372.             items: [
  8373.                 {
  8374.                     xtype: 'gridpanel',
  8375.                     stateful: false,
  8376.                     store: store,
  8377.                     region: 'center',
  8378.                     border: false,
  8379.                     columns: [
  8380.                         {
  8381.                             header: gettext('Name'),
  8382.                             width: 100,
  8383.                             sortable: true,
  8384.                             dataIndex: 'iface'
  8385.                         },
  8386.                         {
  8387.                             xtype: 'booleancolumn',
  8388.                             header: gettext('Active'),
  8389.                             width: 80,
  8390.                             sortable: true,
  8391.                             dataIndex: 'active',
  8392.                             trueText: 'Yes',
  8393.                             falseText: 'No',
  8394.                             undefinedText: 'No'
  8395.                         },
  8396.                         {
  8397.                             xtype: 'booleancolumn',
  8398.                             header: 'Autostart',
  8399.                             width: 80,
  8400.                             sortable: true,
  8401.                             dataIndex: 'autostart',
  8402.                             trueText: 'Yes',
  8403.                             falseText: 'No',
  8404.                             undefinedText: 'No'
  8405.                         },
  8406.                         {
  8407.                             header: 'Ports/Slaves',
  8408.                             dataIndex: 'type',
  8409.                             renderer: render_ports
  8410.                         },
  8411.                         {
  8412.                             header: gettext('IP address'),
  8413.                             sortable: true,
  8414.                             dataIndex: 'address'
  8415.                         },
  8416.                         {
  8417.                             header: gettext('Subnet mask'),
  8418.                             sortable: true,
  8419.                             dataIndex: 'netmask'
  8420.                         },
  8421.                         {
  8422.                             header: 'Gateway',
  8423.                             sortable: true,
  8424.                             dataIndex: 'gateway'
  8425.                         }
  8426.                     ],
  8427.                     listeners: {
  8428.                         selectionchange: set_button_status,
  8429.                         itemdblclick: run_editor
  8430.                     }
  8431.                 },
  8432.                 {
  8433.                     border: false,
  8434.                     region: 'south',
  8435.                     autoScroll: true,
  8436.                     itemId: 'changes',
  8437.                     tbar: [
  8438.                         gettext('Pending changes') + ' (' +
  8439.                             gettext('Please reboot to activate changes') + ')'
  8440.                     ],
  8441.                     split: true,
  8442.                     bodyPadding: 5,
  8443.                     flex: 0.6,
  8444.                     html: gettext("No changes")
  8445.                 }
  8446.             ],
  8447.             listeners: {
  8448.                 show: reload
  8449.             }
  8450.         });
  8451.  
  8452.         me.callParent();
  8453.     }
  8454. }, function() {
  8455.  
  8456.     Ext.define('pve-networks', {
  8457.         extend: 'Ext.data.Model',
  8458.         fields: [
  8459.             'iface', 'type', 'active', 'autostart',
  8460.             'bridge_ports', 'slaves', 'address',
  8461.             'netmask', 'gateway'
  8462.         ],
  8463.         idProperty: 'iface'
  8464.     });
  8465.  
  8466. });
  8467.     Ext.define('PVE.node.Tasks', {
  8468.     extend: 'Ext.grid.GridPanel',
  8469.  
  8470.     alias: ['widget.pveNodeTasks'],
  8471.  
  8472.     initComponent : function() {
  8473.         var me = this;
  8474.  
  8475.         var nodename = me.pveSelNode.data.node;
  8476.         if (!nodename) {
  8477.             throw "no node name specified";
  8478.         }
  8479.  
  8480.         var store = Ext.create('Ext.data.Store', {
  8481.             pageSize: 500,
  8482.             buffered: true,
  8483.             remoteFilter: true,
  8484.             model: 'pve-tasks',
  8485.             proxy: {
  8486.                 type: 'pve',
  8487.                 startParam: 'start',
  8488.                 limitParam: 'limit',
  8489.                 url: "/api2/json/nodes/" + nodename + "/tasks"
  8490.             }
  8491.         });
  8492.  
  8493.         var userfilter = '';
  8494.         var filter_errors = 0;
  8495.  
  8496.         // fixme: scroller update fails
  8497.         // http://www.sencha.com/forum/showthread.php?133677-scroller-does-not-adjust-to-the-filtered-grid-data&p=602887
  8498.         var reload_task = new Ext.util.DelayedTask(function() {
  8499.             var params = {
  8500.                 errors: filter_errors
  8501.             };
  8502.             if (userfilter) {
  8503.                 params.userfilter = userfilter;
  8504.             }
  8505.             store.proxy.extraParams = params;
  8506.             store.filter();
  8507.         });
  8508.  
  8509.         var run_task_viewer = function() {
  8510.             var sm = me.getSelectionModel();
  8511.             var rec = sm.getSelection()[0];
  8512.             if (!rec) {
  8513.                 return;
  8514.             }
  8515.  
  8516.             var win = Ext.create('PVE.window.TaskViewer', {
  8517.                 upid: rec.data.upid
  8518.             });
  8519.             win.show();
  8520.         };
  8521.  
  8522.         var view_btn = new Ext.Button({
  8523.             text: gettext('View'),
  8524.             disabled: true,
  8525.             handler: run_task_viewer
  8526.         });
  8527.  
  8528.  
  8529.         Ext.apply(me, {
  8530.             store: store,
  8531.             stateful: false,
  8532.             verticalScrollerType: 'paginggridscroller',
  8533.             loadMask: true,
  8534.             invalidateScrollerOnRefresh: false,
  8535.             viewConfig: {
  8536.                 trackOver: false,
  8537.                 stripeRows: false, // does not work with getRowClass()
  8538.  
  8539.                 getRowClass: function(record, index) {
  8540.                     var status = record.get('status');
  8541.  
  8542.                     if (status && status != 'OK') {
  8543.                         return "x-form-invalid-field";
  8544.                     }
  8545.                 }
  8546.             },
  8547.             tbar: [
  8548.                 view_btn, '->', gettext('User name') +':', ' ',
  8549.                 {
  8550.                     xtype: 'textfield',
  8551.                     width: 200,
  8552.                     value: userfilter,
  8553.                     enableKeyEvents: true,
  8554.                     listeners: {
  8555.                         keyup: function(field, e) {
  8556.                             userfilter = field.getValue();
  8557.                             reload_task.delay(500);
  8558.                         }
  8559.                     }
  8560.                 }, ' ', gettext('Only Errors') + ':', ' ',
  8561.                 {
  8562.                     xtype: 'checkbox',
  8563.                     hideLabel: true,
  8564.                     checked: filter_errors,
  8565.                     listeners: {
  8566.                         change: function(field, checked) {
  8567.                             filter_errors = checked ? 1 : 0;
  8568.                             reload_task.delay(10);
  8569.                         }
  8570.                     }
  8571.                 }, ' '
  8572.             ],
  8573.             sortableColumns: false,
  8574.             columns: [
  8575.                 {
  8576.                     header: gettext("Start Time"),
  8577.                     dataIndex: 'starttime',
  8578.                     width: 100,
  8579.                     renderer: function(value) {
  8580.                         return Ext.Date.format(value, "M d H:i:s");
  8581.                     }
  8582.                 },
  8583.                 {
  8584.                     header: gettext("End Time"),
  8585.                     dataIndex: 'endtime',
  8586.                     width: 100,
  8587.                     renderer: function(value, metaData, record) {
  8588.                         return  Ext.Date.format(value,"M d H:i:s");
  8589.                     }
  8590.                 },
  8591.                 {
  8592.                     header: gettext("Node"),
  8593.                     dataIndex: 'node',
  8594.                     width: 100
  8595.                 },
  8596.                 {
  8597.                     header: gettext("User name"),
  8598.                     dataIndex: 'user',
  8599.                     width: 150
  8600.                 },
  8601.                 {
  8602.                     header: gettext("Description"),
  8603.                     dataIndex: 'upid',
  8604.                     flex: 1,
  8605.                     renderer: PVE.Utils.render_upid
  8606.                 },
  8607.                 {
  8608.                     header: gettext("Status"),
  8609.                     dataIndex: 'status',
  8610.                     width: 200,
  8611.                     renderer: function(value, metaData, record) {
  8612.                         if (value == 'OK') {
  8613.                             return 'OK';
  8614.                         }
  8615.                         // metaData.attr = 'style="color:red;"';
  8616.                         return "ERROR: " + value;
  8617.                     }
  8618.                 }
  8619.             ],
  8620.             listeners: {
  8621.                 itemdblclick: run_task_viewer,
  8622.                 selectionchange: function(v, selections) {
  8623.                     view_btn.setDisabled(!(selections && selections[0]));
  8624.                 },
  8625.                 show: function() { reload_task.delay(10); }
  8626.             }
  8627.         });
  8628.  
  8629.         me.callParent();
  8630.  
  8631.         store.guaranteeRange(0, store.pageSize - 1);
  8632.     }
  8633. });
  8634.  
  8635. Ext.define('PVE.node.Config', {
  8636.     extend: 'PVE.panel.Config',
  8637.     alias: 'widget.PVE.node.Config',
  8638.  
  8639.     initComponent: function() {
  8640.         var me = this;
  8641.  
  8642.         var nodename = me.pveSelNode.data.node;
  8643.         if (!nodename) {
  8644.             throw "no node name specified";
  8645.         }
  8646.  
  8647.         me.statusStore = Ext.create('PVE.data.ObjectStore', {
  8648.             url: "/api2/json/nodes/" + nodename + "/status",
  8649.             interval: 1000
  8650.         });
  8651.  
  8652.         var node_command = function(cmd) {
  8653.             PVE.Utils.API2Request({
  8654.                 params: { command: cmd },
  8655.                 url: '/nodes/' + nodename + '/status',
  8656.                 method: 'POST',
  8657.                 waitMsgTarget: me,
  8658.                 failure: function(response, opts) {
  8659.                     Ext.Msg.alert('Error', response.htmlStatus);
  8660.                 }
  8661.             });
  8662.         };
  8663.  
  8664.         var restartBtn = Ext.create('PVE.button.Button', {
  8665.             text: gettext('Restart'),
  8666.             confirmMsg: Ext.String.format(gettext("Do you really want to restart node {0}?"), nodename),
  8667.             handler: function() {
  8668.                 node_command('reboot');
  8669.             }
  8670.         });
  8671.  
  8672.         var shutdownBtn = Ext.create('PVE.button.Button', {
  8673.             text: gettext('Shutdown'),
  8674.             confirmMsg: Ext.String.format(gettext("Do you really want to shutdown node {0}?"), nodename),
  8675.             handler: function() {
  8676.                 node_command('shutdown');
  8677.             }
  8678.         });
  8679.  
  8680.         var shellBtn = Ext.create('Ext.Button', {
  8681.             text: gettext('Shell'),
  8682.             handler: function() {
  8683.                 var url = Ext.urlEncode({
  8684.                     console: 'shell',
  8685.                     node: nodename
  8686.                 });
  8687.                 var nw = window.open("?" + url, '_blank',
  8688.                                      "innerWidth=745,innerheight=427");
  8689.                 nw.focus();
  8690.             }
  8691.         });
  8692.  
  8693.         Ext.apply(me, {
  8694.             title: gettext('Node') + " '" + nodename + "'",
  8695.             hstateid: 'nodetab',
  8696.             defaults: { statusStore: me.statusStore },
  8697.             tbar: [ restartBtn, shutdownBtn, shellBtn ],
  8698.             items: [
  8699.                 {
  8700.                     title: gettext('Summary'),
  8701.                     itemId: 'summary',
  8702.                     xtype: 'pveNodeSummary'
  8703.                 },
  8704.                 {
  8705.                     title: gettext('Services'),
  8706.                     itemId: 'services',
  8707.                     xtype: 'pveNodeServiceView'
  8708.                 },
  8709.                 {
  8710.                     title: gettext('Network'),
  8711.                     itemId: 'network',
  8712.                     xtype: 'pveNodeNetworkView'
  8713.                 },
  8714.                 {
  8715.                     title: 'DNS',
  8716.                     itemId: 'dns',
  8717.                     xtype: 'pveNodeDNSView'
  8718.                 },
  8719.                 {
  8720.                     title: gettext('Time'),
  8721.                     itemId: 'time',
  8722.                     xtype: 'pveNodeTimeView'
  8723.                 },
  8724.                 {
  8725.                     title: 'Syslog',
  8726.                     itemId: 'syslog',
  8727.                     xtype: 'pveLogView',
  8728.                     url: "/api2/extjs/nodes/" + nodename + "/syslog"
  8729.                 },
  8730.                 {
  8731.                     title: 'Task History',
  8732.                     itemId: 'tasks',
  8733.                     xtype: 'pveNodeTasks'
  8734.                 },
  8735.                 {
  8736.                     title: 'UBC',
  8737.                     itemId: 'ubc',
  8738.                     xtype: 'pveNodeBCFailCnt'
  8739.                 }
  8740.             ]
  8741.         });
  8742.  
  8743.         me.callParent();
  8744.  
  8745.         me.statusStore.on('load', function(s, records, success) {
  8746.             var uptimerec = s.data.get('uptime');
  8747.             var uptime = uptimerec ? uptimerec.data.value : false;
  8748.  
  8749.             restartBtn.setDisabled(!uptime);
  8750.             shutdownBtn.setDisabled(!uptime);
  8751.             shellBtn.setDisabled(!uptime);
  8752.         });
  8753.  
  8754.         me.on('afterrender', function() {
  8755.             me.statusStore.startUpdate();
  8756.         });
  8757.  
  8758.         me.on('destroy', function() {
  8759.             me.statusStore.stopUpdate();
  8760.         });
  8761.     }
  8762. });
  8763. Ext.define('PVE.qemu.StatusView', {
  8764.     extend: 'PVE.grid.ObjectGrid',
  8765.     alias: ['widget.pveQemuStatusView'],
  8766.  
  8767.     initComponent : function() {
  8768.         var me = this;
  8769.  
  8770.         var nodename = me.pveSelNode.data.node;
  8771.         if (!nodename) {
  8772.             throw "no node name specified";
  8773.         }
  8774.  
  8775.         var vmid = me.pveSelNode.data.vmid;
  8776.         if (!vmid) {
  8777.             throw "no VM ID specified";
  8778.         }
  8779.  
  8780.         var render_cpu = function(value, metaData, record, rowIndex, colIndex, store) {
  8781.             if (!me.getObjectValue('uptime')) {
  8782.                 return '-';
  8783.             }
  8784.  
  8785.             var maxcpu = me.getObjectValue('cpus', 1);
  8786.  
  8787.             if (!(Ext.isNumeric(value) && Ext.isNumeric(maxcpu) && (maxcpu >= 1))) {
  8788.                 return '-';
  8789.             }
  8790.  
  8791.             var per = (value * 100);
  8792.  
  8793.             return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
  8794.         };
  8795.  
  8796.         var render_mem = function(value, metaData, record, rowIndex, colIndex, store) {
  8797.             var maxmem = me.getObjectValue('maxmem', 0);
  8798.             var per = (value / maxmem)*100;
  8799.             var text = "<div>Total: " + PVE.Utils.format_size(maxmem) + "</div>" +
  8800.                 "<div>Used: " + PVE.Utils.format_size(value) + "</div>";
  8801.             return text;
  8802.         };
  8803.  
  8804.         var rows = {
  8805.             name: { header: gettext('Name'), defaultValue: 'no name specified' },
  8806.             status: { header: gettext('Status'), defaultValue: 'unknown' },
  8807.             cpu: { header: 'CPU usage', required: true,  renderer: render_cpu },
  8808.             cpus: { visible: false },
  8809.             mem: { header: 'Memory usage', required: true,  renderer: render_mem },
  8810.             maxmem: { visible: false },
  8811.             uptime: { header: gettext('Uptime'), required: true, renderer: PVE.Utils.render_uptime },
  8812.             ha: { header: 'Managed by HA', required: true, renderer: PVE.Utils.format_boolean }
  8813.         };
  8814.  
  8815.         Ext.applyIf(me, {
  8816.             cwidth1: 150,
  8817.             height: 166,
  8818.             rows: rows
  8819.         });
  8820.  
  8821.         me.callParent();
  8822.     }
  8823. });
  8824. Ext.define('PVE.window.Migrate', {
  8825.     extend: 'Ext.window.Window',
  8826.  
  8827.     resizable: false,
  8828.  
  8829.     migrate: function(target, online) {
  8830.         var me = this;
  8831.         PVE.Utils.API2Request({
  8832.             params: { target: target, online: online },
  8833.             url: '/nodes/' + me.nodename + '/' + me.vmtype + '/' + me.vmid + "/migrate",
  8834.             waitMsgTarget: me,
  8835.             method: 'POST',
  8836.             failure: function(response, opts) {
  8837.                 Ext.Msg.alert('Error', response.htmlStatus);
  8838.             },
  8839.             success: function(response, options) {
  8840.                 var upid = response.result.data;
  8841.  
  8842.                 var win = Ext.create('PVE.window.TaskViewer', {
  8843.                     upid: upid
  8844.                 });
  8845.                 win.show();
  8846.                 me.close();
  8847.             }
  8848.         });
  8849.     },
  8850.  
  8851.     initComponent : function() {
  8852.         var me = this;
  8853.  
  8854.         if (!me.nodename) {
  8855.             throw "no node name specified";
  8856.         }
  8857.  
  8858.         if (!me.vmid) {
  8859.             throw "no VM ID specified";
  8860.         }
  8861.  
  8862.         if (!me.vmtype) {
  8863.             throw "no VM type specified";
  8864.         }
  8865.  
  8866.         me.formPanel = Ext.create('Ext.form.Panel', {
  8867.             bodyPadding: 10,
  8868.             border: false,
  8869.             fieldDefaults: {
  8870.                 labelWidth: 100,
  8871.                 anchor: '100%'
  8872.             },
  8873.             items: [
  8874.                 {
  8875.                     xtype: 'PVE.form.NodeSelector',
  8876.                     name: 'target',
  8877.                     fieldLabel: 'Target node',
  8878.                     allowBlank: false,
  8879.                     onlineValidator: true
  8880.                 },
  8881.                 {
  8882.                     xtype: 'pvecheckbox',
  8883.                     name: 'online',
  8884.                     uncheckedValue: 0,
  8885.                     defaultValue: 0,
  8886.                     fieldLabel: 'Online'
  8887.                 }
  8888.             ]
  8889.         });
  8890.  
  8891.         var form = me.formPanel.getForm();
  8892.  
  8893.         var submitBtn = Ext.create('Ext.Button', {
  8894.             text: 'Migrate',
  8895.             handler: function() {
  8896.                 var values = form.getValues();
  8897.                 me.migrate(values.target, values.online);
  8898.             }
  8899.         });
  8900.  
  8901.         Ext.apply(me, {
  8902.             title: "Migrate VM " + me.vmid,
  8903.             width: 350,
  8904.             modal: true,
  8905.             layout: 'auto',
  8906.             border: false,
  8907.             items: [ me.formPanel ],
  8908.             buttons: [ submitBtn ]
  8909.         });
  8910.  
  8911.         me.callParent();
  8912.     }
  8913. });
  8914. Ext.define('PVE.qemu.Monitor', {
  8915.     extend: 'Ext.panel.Panel',
  8916.  
  8917.     alias: 'widget.pveQemuMonitor',
  8918.  
  8919.     maxLines: 500,
  8920.  
  8921.     initComponent : function() {
  8922.         var me = this;
  8923.  
  8924.         var nodename = me.pveSelNode.data.node;
  8925.         if (!nodename) {
  8926.             throw "no node name specified";
  8927.         }
  8928.  
  8929.         var vmid = me.pveSelNode.data.vmid;
  8930.         if (!vmid) {
  8931.             throw "no VM ID specified";
  8932.         }
  8933.  
  8934.         var lines = [];
  8935.  
  8936.         var textbox = Ext.createWidget('panel', {
  8937.             region: 'center',
  8938.             xtype: 'panel',
  8939.             autoScroll: true,
  8940.             border: true,
  8941.             margins: '5 5 5 5',
  8942.             bodyStyle: 'font-family: monospace;'
  8943.         });
  8944.  
  8945.         var scrollToEnd = function() {
  8946.             var el = textbox.getTargetEl();
  8947.             var dom = Ext.getDom(el);
  8948.  
  8949.             var clientHeight = dom.clientHeight;
  8950.             // BrowserBug: clientHeight reports 0 in IE9 StrictMode
  8951.             // Instead we are using offsetHeight and hardcoding borders
  8952.             if (Ext.isIE9 && Ext.isStrict) {
  8953.                 clientHeight = dom.offsetHeight + 2;
  8954.             }
  8955.             dom.scrollTop = dom.scrollHeight - clientHeight;
  8956.         };
  8957.  
  8958.         var refresh = function() {
  8959.             textbox.update('<pre>' + lines.join('\n') + '</pre>');
  8960.             scrollToEnd();
  8961.         };
  8962.  
  8963.         var addLine = function(line) {
  8964.             lines.push(line);
  8965.             if (lines.length > me.maxLines) {
  8966.                 lines.shift();
  8967.             }
  8968.         };
  8969.  
  8970.         var executeCmd = function(cmd) {
  8971.             addLine("# " + Ext.htmlEncode(cmd));
  8972.             refresh();
  8973.             PVE.Utils.API2Request({
  8974.                 params: { command: cmd },
  8975.                 url: '/nodes/' + nodename + '/qemu/' + vmid + "/monitor",
  8976.                 method: 'POST',
  8977.                 waitMsgTarget: me,
  8978.                 success: function(response, opts) {
  8979.                     var res = response.result.data;
  8980.                     Ext.Array.each(res.split('\n'), function(line) {
  8981.                         addLine(Ext.htmlEncode(line));
  8982.                     });
  8983.                     refresh();
  8984.                 },
  8985.                 failure: function(response, opts) {
  8986.                     Ext.Msg.alert('Error', response.htmlStatus);
  8987.                 }
  8988.             });
  8989.         };
  8990.  
  8991.         Ext.apply(me, {
  8992.             layout: { type: 'border' },
  8993.             border: false,
  8994.             items: [
  8995.                 textbox,
  8996.                 {
  8997.                     region: 'south',
  8998.                     margins:'0 5 5 5',
  8999.                     border: false,
  9000.                     xtype: 'textfield',
  9001.                     name: 'cmd',
  9002.                     value: '',
  9003.                     fieldStyle: 'font-family: monospace;',
  9004.                     allowBlank: true,
  9005.                     listeners: {
  9006.                         afterrender: function(f) {
  9007.                             f.focus(false);
  9008.                             addLine("Type 'help' for help.");
  9009.                             refresh();
  9010.                         },
  9011.                         specialkey: function(f, e) {
  9012.                             if (e.getKey() === e.ENTER) {
  9013.                                 var cmd = f.getValue();
  9014.                                 f.setValue('');
  9015.                                 executeCmd(cmd);
  9016.                             }
  9017.                         }
  9018.                     }
  9019.                 }
  9020.             ],
  9021.             listeners: {
  9022.                 show: function() {
  9023.                     var field = me.query('textfield[name="cmd"]')[0];
  9024.                     field.focus(false, true);
  9025.                 }
  9026.             }
  9027.         });            
  9028.  
  9029.         me.callParent();
  9030.     }
  9031. });
  9032. Ext.define('PVE.qemu.Summary', {
  9033.     extend: 'Ext.panel.Panel',
  9034.     alias: 'widget.pveQemuSummary',
  9035.  
  9036.     initComponent: function() {
  9037.         var me = this;
  9038.  
  9039.         var nodename = me.pveSelNode.data.node;
  9040.         if (!nodename) {
  9041.             throw "no node name specified";
  9042.         }
  9043.  
  9044.         var vmid = me.pveSelNode.data.vmid;
  9045.         if (!vmid) {
  9046.             throw "no VM ID specified";
  9047.         }
  9048.  
  9049.         if (!me.workspace) {
  9050.             throw "no workspace specified";
  9051.         }
  9052.  
  9053.         if (!me.statusStore) {
  9054.             throw "no status storage specified";
  9055.         }
  9056.  
  9057.         var rstore = me.statusStore;
  9058.  
  9059.         var statusview = Ext.create('PVE.qemu.StatusView', {
  9060.             title: 'Status',
  9061.             pveSelNode: me.pveSelNode,
  9062.             width: 400,
  9063.             rstore: rstore
  9064.         });
  9065.  
  9066.         var rrdurl = "/api2/png/nodes/" + nodename + "/qemu/" + vmid + "/rrd";
  9067.  
  9068.         var notesview = Ext.create('PVE.panel.NotesView', {
  9069.             pveSelNode: me.pveSelNode,
  9070.             flex: 1
  9071.         });
  9072.  
  9073.         Ext.apply(me, {
  9074.             tbar: [ '->', { xtype: 'pveRRDTypeSelector' } ],
  9075.             autoScroll: true,
  9076.             bodyStyle: 'padding:10px',
  9077.             defaults: {
  9078.                 style: 'padding-top:10px',
  9079.                 width: 800
  9080.             },         
  9081.             items: [
  9082.                 {
  9083.                     style: 'padding-top:0px',
  9084.                     layout: {
  9085.                         type: 'hbox',
  9086.                         align: 'stretchmax'
  9087.                     },
  9088.                     border: false,
  9089.                     items: [ statusview, notesview ]
  9090.                 },
  9091.                 {
  9092.                     xtype: 'pveRRDView',
  9093.                     title: "CPU usage %",
  9094.                     pveSelNode: me.pveSelNode,
  9095.                     datasource: 'cpu',
  9096.                     rrdurl: rrdurl
  9097.                 },
  9098.                 {
  9099.                     xtype: 'pveRRDView',
  9100.                     title: "Memory usage",
  9101.                     pveSelNode: me.pveSelNode,
  9102.                     datasource: 'mem,maxmem',
  9103.                     rrdurl: rrdurl
  9104.                 },
  9105.                 {
  9106.                     xtype: 'pveRRDView',
  9107.                     title: "Network traffic",
  9108.                     pveSelNode: me.pveSelNode,
  9109.                     datasource: 'netin,netout',
  9110.                     rrdurl: rrdurl
  9111.                 },
  9112.                 {
  9113.                     xtype: 'pveRRDView',
  9114.                     title: "Disk IO",
  9115.                     pveSelNode: me.pveSelNode,
  9116.                     datasource: 'diskread,diskwrite',
  9117.                     rrdurl: rrdurl
  9118.                 }
  9119.             ]
  9120.         });
  9121.  
  9122.         me.on('show', function() {
  9123.             notesview.load();
  9124.         });
  9125.  
  9126.         me.callParent();
  9127.     }
  9128. });
  9129. Ext.define('PVE.qemu.OSTypeInputPanel', {
  9130.     extend: 'PVE.panel.InputPanel',
  9131.     alias: 'widget.PVE.qemu.OSTypeInputPanel',
  9132.  
  9133.     initComponent : function() {
  9134.         var me = this;
  9135.  
  9136.         me.column1 = [
  9137.             {
  9138.                 xtype: 'component',
  9139.                 html: 'Microsoft Windows',
  9140.                 cls:'x-form-check-group-label'
  9141.             },
  9142.             {
  9143.                 xtype: 'radiofield',
  9144.                 name: 'ostype',
  9145.                 inputValue: 'win7'
  9146.             },
  9147.             {
  9148.                 xtype: 'radiofield',
  9149.                 name: 'ostype',
  9150.                 inputValue: 'w2k8'
  9151.             },
  9152.             {
  9153.                 xtype: 'radiofield',
  9154.                 name: 'ostype',
  9155.                 inputValue: 'wxp'
  9156.             },
  9157.             {
  9158.                 xtype: 'radiofield',
  9159.                 name: 'ostype',
  9160.                 inputValue: 'w2k'
  9161.             }
  9162.         ];
  9163.  
  9164.         me.column2 = [
  9165.             {
  9166.                 xtype: 'component',
  9167.                 html: 'Linux/Other',
  9168.                 cls:'x-form-check-group-label'
  9169.             },
  9170.             {
  9171.                 xtype: 'radiofield',
  9172.                 name: 'ostype',
  9173.                 inputValue: 'l26'
  9174.             },
  9175.             {
  9176.                 xtype: 'radiofield',
  9177.                 name: 'ostype',
  9178.                 inputValue: 'l24'
  9179.             },
  9180.             {
  9181.                 xtype: 'radiofield',
  9182.                 name: 'ostype',
  9183.                 inputValue: 'other'
  9184.             }
  9185.         ];
  9186.  
  9187.         Ext.Array.each(me.column1, function(def) {
  9188.             if (def.inputValue) {
  9189.                 def.boxLabel = PVE.Utils.render_kvm_ostype(def.inputValue);
  9190.             }
  9191.         });
  9192.         Ext.Array.each(me.column2, function(def) {
  9193.             if (def.inputValue) {
  9194.                 def.boxLabel = PVE.Utils.render_kvm_ostype(def.inputValue);
  9195.             }
  9196.         });
  9197.  
  9198.         Ext.apply(me, {
  9199.             useFieldContainer: {
  9200.                 xtype: 'radiogroup',
  9201.                 allowBlank: false
  9202.             }
  9203.         });
  9204.  
  9205.         me.callParent();
  9206.     }  
  9207. });
  9208.  
  9209. Ext.define('PVE.qemu.OSTypeEdit', {
  9210.     extend: 'PVE.window.Edit',
  9211.  
  9212.     initComponent : function() {
  9213.         var me = this;
  9214.        
  9215.         Ext.apply(me, {
  9216.             subject: 'OS Type',
  9217.             items: Ext.create('PVE.qemu.OSTypeInputPanel')
  9218.         });
  9219.  
  9220.         me.callParent();
  9221.  
  9222.         me.load({
  9223.             success: function(response, options) {
  9224.                 var value = response.result.data.ostype || 'other';
  9225.                 me.setValues({ ostype: value});
  9226.             }
  9227.         });
  9228.     }
  9229. });
  9230. Ext.define('PVE.qemu.ProcessorInputPanel', {
  9231.     extend: 'PVE.panel.InputPanel',
  9232.     alias: 'widget.PVE.qemu.ProcessorInputPanel',
  9233.  
  9234.     initComponent : function() {
  9235.         var me = this;
  9236.  
  9237.         me.column1 = [
  9238.             {
  9239.                 xtype: 'numberfield',
  9240.                 name: 'sockets',
  9241.                 minValue: 1,
  9242.                 maxValue: 4,
  9243.                 value: '1',
  9244.                 fieldLabel: 'Sockets',
  9245.                 allowBlank: false,
  9246.                 listeners: {
  9247.                     change: function(f, value) {
  9248.                         var sockets = me.down('field[name=sockets]').getValue();
  9249.                         var cores = me.down('field[name=cores]').getValue();
  9250.                         me.down('field[name=totalcores]').setValue(sockets*cores);
  9251.                     }
  9252.                 }
  9253.             },
  9254.             {
  9255.                 xtype: 'numberfield',
  9256.                 name: 'cores',
  9257.                 minValue: 1,
  9258.                 maxValue: 32,
  9259.                 value: '1',
  9260.                 fieldLabel: 'Cores',
  9261.                 allowBlank: false,
  9262.                 listeners: {
  9263.                     change: function(f, value) {
  9264.                         var sockets = me.down('field[name=sockets]').getValue();
  9265.                         var cores = me.down('field[name=cores]').getValue();
  9266.                         me.down('field[name=totalcores]').setValue(sockets*cores);
  9267.                     }
  9268.                 }
  9269.             }
  9270.         ];
  9271.  
  9272.  
  9273.         me.column2 = [
  9274.             {
  9275.                 xtype: 'CPUModelSelector',
  9276.                 name: 'cpu',
  9277.                 value: '',
  9278.                 fieldLabel: 'CPU type'
  9279.             },
  9280.             {
  9281.                 xtype: 'displayfield',
  9282.                 fieldLabel: 'Total cores',
  9283.                 name: 'totalcores',
  9284.                 value: '1'
  9285.             }
  9286.  
  9287.         ];
  9288.  
  9289.         me.callParent();
  9290.     }
  9291. });
  9292.  
  9293. Ext.define('PVE.qemu.ProcessorEdit', {
  9294.     extend: 'PVE.window.Edit',
  9295.  
  9296.     initComponent : function() {
  9297.         var me = this;
  9298.        
  9299.         Ext.apply(me, {
  9300.             subject: gettext('Processors'),
  9301.             items: Ext.create('PVE.qemu.ProcessorInputPanel')
  9302.         });
  9303.  
  9304.         me.callParent();
  9305.  
  9306.         me.load();
  9307.     }
  9308. });Ext.define('PVE.qemu.BootOrderPanel', {
  9309.     extend: 'PVE.panel.InputPanel',
  9310.  
  9311.     vmconfig: {}, // store loaded vm config
  9312.  
  9313.     bootdisk: undefined,
  9314.     curSel1: '',
  9315.     curSel2: '',
  9316.     curSel3: '',
  9317.  
  9318.     onGetValues: function(values) {
  9319.         var me = this;
  9320.  
  9321.         var order = '';
  9322.  
  9323.         if (me.curSel1) {
  9324.             order = order + me.curSel1;
  9325.         }
  9326.         if (me.curSel2) {
  9327.             order = order + me.curSel2;
  9328.         }
  9329.         if (me.curSel3) {
  9330.             order = order + me.curSel3;
  9331.         }
  9332.  
  9333.         var res = { boot: order };
  9334.         if (me.bootdisk && (me.curSel1 === 'c' || me.curSel2 === 'c' || me.curSel3 === 'c') ) {
  9335.             res.bootdisk =  me.bootdisk;
  9336.         } else {
  9337.             res['delete'] = 'bootdisk';
  9338.         }
  9339.  
  9340.         return res;
  9341.     },
  9342.  
  9343.     setVMConfig: function(vmconfig) {
  9344.         var me = this;
  9345.  
  9346.         me.vmconfig = vmconfig;
  9347.  
  9348.         var order = me.vmconfig.boot || 'cdn';
  9349.         me.bootdisk = me.vmconfig.bootdisk;
  9350.         if (!me.vmconfig[me.bootdisk]) {
  9351.             me.bootdisk = undefined;
  9352.         }
  9353.         me.curSel1 = order.substring(0, 1) || '';
  9354.         me.curSel2 = order.substring(1, 2) || '';
  9355.         me.curSel3 = order.substring(2, 3) || '';
  9356.  
  9357.         me.compute_sel1();
  9358.  
  9359.         me.kv1.resetOriginalValue();
  9360.         me.kv2.resetOriginalValue();
  9361.         me.kv3.resetOriginalValue();
  9362.     },
  9363.  
  9364.     genList: function(includeNone, sel1, sel2) {
  9365.         var me = this;
  9366.         var list = [];
  9367.  
  9368.         if (sel1 !== 'c' && (sel2 !== 'c')) {
  9369.             Ext.Object.each(me.vmconfig, function(key, value) {
  9370.                 if ((/^(ide|scsi|virtio)\d+$/).test(key) &&
  9371.                     !(/media=cdrom/).test(value)) {
  9372.                     list.push([key, "Disk '" + key + "'"]);
  9373.                 }
  9374.             });
  9375.         }
  9376.  
  9377.         if (sel1 !== 'd' && (sel2 !== 'd')) {
  9378.             list.push(['d', 'CD-ROM']);
  9379.         }
  9380.         if (sel1 !== 'n' && (sel2 !== 'n')) {
  9381.             list.push(['n', gettext('Network')]);
  9382.         }
  9383.         //if (sel1 !== 'a' && (sel2 !== 'a')) {
  9384.         //    list.push(['a', 'Floppy']);
  9385.         //}
  9386.        
  9387.         if (includeNone) {
  9388.             list.push(['', 'none']);
  9389.         }
  9390.  
  9391.         return list;
  9392.     },
  9393.  
  9394.     compute_sel3: function() {
  9395.         var me = this;
  9396.         var list = me.genList(true, me.curSel1, me.curSel2);
  9397.         me.kv3.store.loadData(list);
  9398.         me.kv3.setValue((me.curSel3 === 'c') ? me.bootdisk : me.curSel3);
  9399.     },
  9400.  
  9401.     compute_sel2: function() {
  9402.         var me = this;
  9403.         var list = me.genList(true, me.curSel1);
  9404.         me.kv2.store.loadData(list);
  9405.         me.kv2.setValue((me.curSel2 === 'c') ? me.bootdisk : me.curSel2);
  9406.         me.compute_sel3();
  9407.     },
  9408.  
  9409.     compute_sel1: function() {
  9410.         var me = this;
  9411.         var list = me.genList(false);
  9412.         me.kv1.store.loadData(list);
  9413.         me.kv1.setValue((me.curSel1 === 'c') ? me.bootdisk : me.curSel1);
  9414.         me.compute_sel2();
  9415.     },
  9416.  
  9417.     initComponent : function() {
  9418.         var me = this;
  9419.  
  9420.         me.kv1 = Ext.create('PVE.form.KVComboBox', {
  9421.             fieldLabel: gettext('Boot device') + " 1",
  9422.             labelWidth: 120,
  9423.             name: 'bd1',
  9424.             allowBlank: false,
  9425.             data: []
  9426.         });
  9427.  
  9428.         me.kv2 = Ext.create('PVE.form.KVComboBox', {
  9429.             fieldLabel: gettext('Boot device') + " 2",
  9430.             labelWidth: 120,
  9431.             name: 'bd2',
  9432.             allowBlank: false,
  9433.             data: []
  9434.         });
  9435.  
  9436.         me.kv3 = Ext.create('PVE.form.KVComboBox', {
  9437.             fieldLabel: gettext('Boot device') + " 3",
  9438.             labelWidth: 120,
  9439.             name: 'bd3',
  9440.             allowBlank: false,
  9441.             data: []
  9442.         });
  9443.  
  9444.         me.mon(me.kv1, 'change', function(t, value) {
  9445.             if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9446.                 me.curSel1 = 'c';
  9447.                 me.bootdisk = value;
  9448.             } else {
  9449.                 me.curSel1 = value;
  9450.             }
  9451.             me.compute_sel2();
  9452.         });
  9453.  
  9454.         me.mon(me.kv2, 'change', function(t, value) {
  9455.             if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9456.                 me.curSel2 = 'c';
  9457.                 me.bootdisk = value;
  9458.             } else {
  9459.                 me.curSel2 = value;
  9460.             }
  9461.             me.compute_sel3();
  9462.         });
  9463.  
  9464.         me.mon(me.kv3, 'change', function(t, value) {
  9465.             if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9466.                 me.curSel3 = 'c';
  9467.                 me.bootdisk = value;
  9468.             } else {
  9469.                 me.curSel3 = value;
  9470.             }
  9471.         });
  9472.  
  9473.         Ext.apply(me, {
  9474.             items: [ me.kv1, me.kv2, me.kv3 ]  
  9475.         });
  9476.        
  9477.         me.callParent();
  9478.     }
  9479. });
  9480.  
  9481. Ext.define('PVE.qemu.BootOrderEdit', {
  9482.     extend: 'PVE.window.Edit',
  9483.  
  9484.     initComponent : function() {
  9485.         var me = this;
  9486.        
  9487.         var ipanel = Ext.create('PVE.qemu.BootOrderPanel', {});
  9488.  
  9489.         me.items = [ ipanel ];
  9490.  
  9491.         me.subject = gettext('Boot order');
  9492.  
  9493.         me.callParent();
  9494.        
  9495.         me.load({
  9496.             success: function(response, options) {
  9497.                 ipanel.setVMConfig(response.result.data);
  9498.             }
  9499.         });
  9500.     }
  9501. });
  9502. Ext.define('PVE.qemu.MemoryInputPanel', {
  9503.     extend: 'PVE.panel.InputPanel',
  9504.     alias: 'widget.PVE.qemu.MemoryInputPanel',
  9505.  
  9506.     insideWizard: false,
  9507.  
  9508.     initComponent : function() {
  9509.         var me = this;
  9510.  
  9511.         var labelWidth = 120;
  9512.  
  9513.         var items = {
  9514.             xtype: 'numberfield',
  9515.             name: 'memory',
  9516.             minValue: 32,
  9517.             maxValue: 128*1024,
  9518.             value: '512',
  9519.             step: 32,
  9520.             fieldLabel: gettext('Memory') + ' (MB)',
  9521.             labelWidth: labelWidth,
  9522.             allowBlank: false
  9523.         };
  9524.  
  9525.         if (me.insideWizard) {
  9526.             me.column1 = items;
  9527.         } else {
  9528.             me.items = items;
  9529.         }
  9530.  
  9531.         me.callParent();
  9532.     }
  9533. });
  9534.  
  9535. Ext.define('PVE.qemu.MemoryEdit', {
  9536.     extend: 'PVE.window.Edit',
  9537.  
  9538.     initComponent : function() {
  9539.         var me = this;
  9540.        
  9541.         Ext.apply(me, {
  9542.             subject: gettext('Memory'),
  9543.             items: Ext.create('PVE.qemu.MemoryInputPanel')
  9544.         });
  9545.  
  9546.         me.callParent();
  9547.  
  9548.         me.load();
  9549.     }
  9550. });Ext.define('PVE.qemu.NetworkInputPanel', {
  9551.     extend: 'PVE.panel.InputPanel',
  9552.     alias: 'widget.PVE.qemu.NetworkInputPanel',
  9553.  
  9554.     insideWizard: false,
  9555.  
  9556.     onGetValues: function(values) {
  9557.         var me = this;
  9558.  
  9559.         me.network.model = values.model;
  9560.         if (values.networkmode === 'none') {
  9561.             return {};
  9562.         } else if (values.networkmode === 'bridge') {
  9563.             me.network.bridge = values.bridge;
  9564.             me.network.tag = values.tag;
  9565.         } else {
  9566.             me.network.bridge = undefined;
  9567.         }
  9568.         me.network.macaddr = values.macaddr;
  9569.  
  9570.         if (values.rate) {
  9571.             me.network.rate = values.rate;
  9572.         } else {
  9573.             delete me.network.rate;
  9574.         }
  9575.  
  9576.         var params = {};
  9577.  
  9578.         params[me.confid] = PVE.Parser.printQemuNetwork(me.network);
  9579.  
  9580.         return params;
  9581.     },
  9582.  
  9583.     setNetwork: function(confid, data) {
  9584.         var me = this;
  9585.  
  9586.         me.confid = confid;
  9587.  
  9588.         if (data) {
  9589.             data.networkmode = data.bridge ? 'bridge' : 'nat';
  9590.         } else {
  9591.             data = {};
  9592.             data.networkmode = 'bridge';
  9593.         }
  9594.         me.network = data;
  9595.        
  9596.         me.setValues(me.network);
  9597.     },
  9598.  
  9599.     setNodename: function(nodename) {
  9600.         var me = this;
  9601.  
  9602.         me.bridgesel.setNodename(nodename);
  9603.     },
  9604.  
  9605.     initComponent : function() {
  9606.         var me = this;
  9607.  
  9608.         me.network = {};
  9609.         me.confid = 'net0';
  9610.  
  9611.         me.bridgesel = Ext.create('PVE.form.BridgeSelector', {
  9612.             name: 'bridge',
  9613.             fieldLabel: 'Bridge',
  9614.             nodename: me.nodename,
  9615.             labelAlign: 'right',
  9616.             autoSelect: true,
  9617.             allowBlank: false
  9618.         });
  9619.  
  9620.         me.column1 = [
  9621.             {
  9622.                 xtype: 'radiofield',
  9623.                 name: 'networkmode',
  9624.                 height: 22, // hack: set same height as text fields
  9625.                 inputValue: 'bridge',
  9626.                 boxLabel: 'Bridged mode',
  9627.                 checked: true,
  9628.                 listeners: {
  9629.                     change: function(f, value) {
  9630.                         if (!me.rendered) {
  9631.                             return;
  9632.                         }
  9633.                         me.down('field[name=bridge]').setDisabled(!value);
  9634.                         me.down('field[name=bridge]').validate();
  9635.                         me.down('field[name=tag]').setDisabled(!value);
  9636.                     }
  9637.                 }
  9638.             },
  9639.             me.bridgesel,
  9640.             {
  9641.                 xtype: 'numberfield',
  9642.                 name: 'tag',
  9643.                 minValue: 1,
  9644.                 maxValue: 4094,
  9645.                 value: '',
  9646.                 emptyText: 'no VLAN',
  9647.                 fieldLabel: 'VLAN Tag',
  9648.                 labelAlign: 'right',
  9649.                 allowBlank: true
  9650.             },
  9651.             {
  9652.                 xtype: 'radiofield',
  9653.                 name: 'networkmode',
  9654.                 height: 22, // hack: set same height as text fields
  9655.                 inputValue: 'nat',
  9656.                 boxLabel: 'NAT mode'
  9657.             }
  9658.         ];
  9659.  
  9660.         if (me.insideWizard) {
  9661.             me.column1.push({
  9662.                 xtype: 'radiofield',
  9663.                 name: 'networkmode',
  9664.                 height: 22, // hack: set same height as text fields
  9665.                 inputValue: 'none',
  9666.                 boxLabel: 'No network device'
  9667.             });
  9668.         }
  9669.  
  9670.         me.column2 = [
  9671.             {
  9672.                 xtype: 'PVE.form.NetworkCardSelector',
  9673.                 name: 'model',
  9674.                 fieldLabel: 'Model',
  9675.                 value: 'rtl8139',
  9676.                 allowBlank: false
  9677.             },
  9678.             {
  9679.                 xtype: 'textfield',
  9680.                 name: 'macaddr',
  9681.                 fieldLabel: 'MAC address',
  9682.                 vtype: 'MacAddress',
  9683.                 allowBlank: true,
  9684.                 emptyText: 'auto'
  9685.             },
  9686.             {
  9687.                 xtype: 'numberfield',
  9688.                 name: 'rate',
  9689.                 fieldLabel: 'Rate limit (MB/s)',
  9690.                 minValue: 0,
  9691.                 maxValue: 10*1024,
  9692.                 value: '',
  9693.                 emptyText: 'unlimited',
  9694.                 allowBlank: true
  9695.             }
  9696.         ];
  9697.  
  9698.         me.callParent();
  9699.     }
  9700. });
  9701.  
  9702. Ext.define('PVE.qemu.NetworkEdit', {
  9703.     extend: 'PVE.window.Edit',
  9704.  
  9705.     isAdd: true,
  9706.  
  9707.     initComponent : function() {
  9708.         /*jslint confusion: true */
  9709.  
  9710.         var me = this;
  9711.  
  9712.         var nodename = me.pveSelNode.data.node;
  9713.         if (!nodename) {
  9714.             throw "no node name specified";        
  9715.         }
  9716.  
  9717.         me.create = me.confid ? false : true;
  9718.  
  9719.         var ipanel = Ext.create('PVE.qemu.NetworkInputPanel', {
  9720.             confid: me.confid,
  9721.             nodename: nodename
  9722.         });
  9723.  
  9724.         Ext.applyIf(me, {
  9725.             subject: gettext('Network Device'),
  9726.             items: ipanel
  9727.         });
  9728.  
  9729.         me.callParent();
  9730.  
  9731.         me.load({
  9732.             success: function(response, options) {
  9733.                 var i, confid;
  9734.                 me.vmconfig = response.result.data;
  9735.                 if (!me.create) {
  9736.                     var value = me.vmconfig[me.confid];
  9737.                     var network = PVE.Parser.parseQemuNetwork(me.confid, value);
  9738.                     if (!network) {
  9739.                         Ext.Msg.alert('Error', 'Unable to parse network options');
  9740.                         me.close();
  9741.                         return;
  9742.                     }
  9743.                     ipanel.setNetwork(me.confid, network);
  9744.                 } else {
  9745.                     for (i = 0; i < 100; i++) {
  9746.                         confid = 'net' + i.toString();
  9747.                         if (!Ext.isDefined(me.vmconfig[confid])) {
  9748.                             me.confid = confid;
  9749.                             break;
  9750.                         }
  9751.                     }
  9752.                     ipanel.setNetwork(me.confid);                  
  9753.                 }
  9754.             }
  9755.         });
  9756.     }
  9757. });
  9758. // fixme: howto avoid jslint type confusion?
  9759. /*jslint confusion: true */
  9760. Ext.define('PVE.qemu.CDInputPanel', {
  9761.     extend: 'PVE.panel.InputPanel',
  9762.     alias: 'widget.PVE.qemu.CDInputPanel',
  9763.  
  9764.     insideWizard: false,
  9765.  
  9766.     onGetValues: function(values) {
  9767.         var me = this;
  9768.  
  9769.         var confid = me.confid || (values.controller + values.deviceid);
  9770.        
  9771.         me.drive.media = 'cdrom';
  9772.         if (values.mediaType === 'iso') {
  9773.             me.drive.file = values.cdimage;
  9774.         } else if (values.mediaType === 'cdrom') {
  9775.             me.drive.file = 'cdrom';
  9776.         } else {
  9777.             me.drive.file = 'none';
  9778.         }
  9779.  
  9780.         var params = {};
  9781.                
  9782.         params[confid] = PVE.Parser.printQemuDrive(me.drive);
  9783.        
  9784.         return params; 
  9785.     },
  9786.  
  9787.     setVMConfig: function(vmconfig) {
  9788.         var me = this;
  9789.  
  9790.         if (me.bussel) {
  9791.             me.bussel.setVMConfig(vmconfig, 'cdrom');
  9792.         }
  9793.     },
  9794.  
  9795.     setDrive: function(drive) {
  9796.         var me = this;
  9797.  
  9798.         var values = {};
  9799.         if (drive.file === 'cdrom') {
  9800.             values.mediaType = 'cdrom';
  9801.         } else if (drive.file === 'none') {
  9802.             values.mediaType = 'none';
  9803.         } else {
  9804.             values.mediaType = 'iso';
  9805.             var match = drive.file.match(/^([^:]+):/);
  9806.             if (match) {
  9807.                 values.cdstorage = match[1];
  9808.                 values.cdimage = drive.file;
  9809.             }
  9810.         }
  9811.  
  9812.         me.drive = drive;
  9813.  
  9814.         me.setValues(values);
  9815.     },
  9816.  
  9817.     setNodename: function(nodename) {
  9818.         var me = this;
  9819.  
  9820.         me.cdstoragesel.setNodename(nodename);
  9821.         me.cdfilesel.setStorage(undefined, nodename);
  9822.     },
  9823.  
  9824.     initComponent : function() {
  9825.         var me = this;
  9826.  
  9827.         me.drive = {};
  9828.  
  9829.         var items = [];
  9830.  
  9831.         if (!me.confid) {
  9832.             me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
  9833.                 noVirtIO: true
  9834.             });
  9835.             items.push(me.bussel);
  9836.         }
  9837.  
  9838.         items.push({
  9839.             xtype: 'radiofield',
  9840.             name: 'mediaType',
  9841.             inputValue: 'iso',
  9842.             boxLabel: 'Use CD/DVD disc image file (iso)',
  9843.             checked: true,
  9844.             listeners: {
  9845.                 change: function(f, value) {
  9846.                     if (!me.rendered) {
  9847.                         return;
  9848.                     }
  9849.                     me.down('field[name=cdstorage]').setDisabled(!value);
  9850.                     me.down('field[name=cdimage]').setDisabled(!value);
  9851.                     me.down('field[name=cdimage]').validate();
  9852.                 }
  9853.             }
  9854.         });
  9855.  
  9856.         me.cdfilesel = Ext.create('PVE.form.FileSelector', {
  9857.             name: 'cdimage',
  9858.             nodename: me.nodename,
  9859.             storageContent: 'iso',
  9860.             fieldLabel: 'ISO Image',
  9861.             labelAlign: 'right',
  9862.             allowBlank: false
  9863.         });
  9864.        
  9865.         me.cdstoragesel = Ext.create('PVE.form.StorageSelector', {
  9866.             name: 'cdstorage',
  9867.             nodename: me.nodename,
  9868.             fieldLabel: gettext('Storage'),
  9869.             labelAlign: 'right',
  9870.             storageContent: 'iso',
  9871.             allowBlank: false,
  9872.             autoSelect: me.insideWizard,
  9873.             listeners: {
  9874.                 change: function(f, value) {
  9875.                     me.cdfilesel.setStorage(value);
  9876.                 }
  9877.             }
  9878.         });
  9879.  
  9880.         items.push(me.cdstoragesel);
  9881.         items.push(me.cdfilesel);
  9882.  
  9883.         items.push({
  9884.             xtype: 'radiofield',
  9885.             name: 'mediaType',
  9886.             inputValue: 'cdrom',
  9887.             boxLabel: 'Use physical CD/DVD Drive'
  9888.         });
  9889.  
  9890.         items.push({
  9891.             xtype: 'radiofield',
  9892.             name: 'mediaType',
  9893.             inputValue: 'none',
  9894.             boxLabel: 'Do not use any media'
  9895.         });
  9896.  
  9897.         if (me.insideWizard) {
  9898.             me.column1 = items;
  9899.         } else {
  9900.             me.items = items;
  9901.         }
  9902.  
  9903.         me.callParent();
  9904.     }
  9905. });
  9906.  
  9907. Ext.define('PVE.qemu.CDEdit', {
  9908.     extend: 'PVE.window.Edit',
  9909.  
  9910.     initComponent : function() {
  9911.         var me = this;
  9912.  
  9913.         var nodename = me.pveSelNode.data.node;
  9914.         if (!nodename) {
  9915.             throw "no node name specified";        
  9916.         }
  9917.  
  9918.         me.create = me.confid ? false : true;
  9919.  
  9920.         var ipanel = Ext.create('PVE.qemu.CDInputPanel', {
  9921.             confid: me.confid,
  9922.             nodename: nodename
  9923.         });
  9924.  
  9925.         Ext.applyIf(me, {
  9926.             subject: 'CD/DVD Drive',
  9927.             items: [ ipanel ]
  9928.         });
  9929.  
  9930.         me.callParent();
  9931.        
  9932.         me.load({
  9933.             success:  function(response, options) {
  9934.                 ipanel.setVMConfig(response.result.data);
  9935.                 if (me.confid) {
  9936.                     var value = response.result.data[me.confid];
  9937.                     var drive = PVE.Parser.parseQemuDrive(me.confid, value);
  9938.                     if (!drive) {
  9939.                         Ext.Msg.alert('Error', 'Unable to parse drive options');
  9940.                         me.close();
  9941.                         return;
  9942.                     }
  9943.                     ipanel.setDrive(drive);
  9944.                 }
  9945.             }
  9946.         });
  9947.     }
  9948. });
  9949. // fixme: howto avoid jslint type confusion?
  9950. /*jslint confusion: true */
  9951. Ext.define('PVE.qemu.HDInputPanel', {
  9952.     extend: 'PVE.panel.InputPanel',
  9953.     alias: 'widget.PVE.qemu.HDInputPanel',
  9954.  
  9955.     insideWizard: false,
  9956.  
  9957.     unused: false, // ADD usused disk imaged
  9958.  
  9959.     vmconfig: {}, // used to select usused disks
  9960.  
  9961.     onGetValues: function(values) {
  9962.         var me = this;
  9963.  
  9964.         var confid = me.confid || (values.controller + values.deviceid);
  9965.        
  9966.         if (me.unused) {
  9967.             me.drive.file = me.vmconfig[values.unusedId];
  9968.             confid = values.controller + values.deviceid;
  9969.         } else if (me.create) {
  9970.             if (values.hdimage) {
  9971.                 me.drive.file = values.hdimage;
  9972.             } else {
  9973.                 me.drive.file = values.hdstorage + ":" + values.disksize;
  9974.             }
  9975.             me.drive.format = values.diskformat;
  9976.         }
  9977.        
  9978.         if (values.cache) {
  9979.             me.drive.cache = values.cache;
  9980.         } else {
  9981.             delete me.drive.cache;
  9982.         }
  9983.  
  9984.         if (values.nobackup) {
  9985.             me.drive.backup = 'no';
  9986.         } else {
  9987.             delete me.drive.backup;
  9988.         }
  9989.  
  9990.         var params = {};
  9991.                
  9992.         params[confid] = PVE.Parser.printQemuDrive(me.drive);
  9993.        
  9994.         return params; 
  9995.     },
  9996.  
  9997.     setVMConfig: function(vmconfig) {
  9998.         var me = this;
  9999.  
  10000.         me.vmconfig = vmconfig;
  10001.  
  10002.         if (me.bussel) {
  10003.             me.bussel.setVMConfig(vmconfig, true);
  10004.         }
  10005.         if (me.unusedDisks) {
  10006.             var disklist = [];     
  10007.             Ext.Object.each(vmconfig, function(key, value) {
  10008.                 if (key.match(/^unused\d+$/)) {
  10009.                     disklist.push([key, value]);
  10010.                 }
  10011.             });
  10012.             me.unusedDisks.store.loadData(disklist);
  10013.             me.unusedDisks.setValue(me.confid);
  10014.         }
  10015.     },
  10016.  
  10017.     setDrive: function(drive) {
  10018.         var me = this;
  10019.  
  10020.         me.drive = drive;
  10021.  
  10022.         var values = {};
  10023.         var match = drive.file.match(/^([^:]+):/);
  10024.         if (match) {
  10025.             values.hdstorage = match[1];
  10026.         }
  10027.  
  10028.         values.hdimage = drive.file;
  10029.         values.nobackup = (drive.backup === 'no');
  10030.         values.diskformat = drive.format || 'raw';
  10031.         values.cache = drive.cache || '';
  10032.  
  10033.         me.setValues(values);
  10034.     },
  10035.  
  10036.     setNodename: function(nodename) {
  10037.         var me = this;
  10038.         me.hdstoragesel.setNodename(nodename);
  10039.         me.hdfilesel.setStorage(undefined, nodename);
  10040.     },
  10041.  
  10042.     initComponent : function() {
  10043.         var me = this;
  10044.  
  10045.         me.drive = {};
  10046.  
  10047.         me.column1 = [];
  10048.         me.column2 = [];
  10049.  
  10050.         if (!me.confid || me.unused) {
  10051.             me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
  10052.                 // boot from scsi does not work in kvm 1.0
  10053.                 noScsi: me.insideWizard ? true : false,
  10054.                 vmconfig: me.insideWizard ? {ide2: 'cdrom'} : {}
  10055.             });
  10056.             me.column1.push(me.bussel);
  10057.         }
  10058.  
  10059.         if (me.unused) {
  10060.             me.unusedDisks = Ext.create('PVE.form.KVComboBox', {
  10061.                 name: 'unusedId',      
  10062.                 fieldLabel: gettext('Disk image'),
  10063.                 matchFieldWidth: false,
  10064.                 listConfig: {
  10065.                     width: 350
  10066.                 },
  10067.                 data: [],
  10068.                 allowBlank: false
  10069.             });
  10070.             me.column1.push(me.unusedDisks);
  10071.         } else if (me.create) {
  10072.             me.formatsel = Ext.create('PVE.form.DiskFormatSelector', {
  10073.                 name: 'diskformat',
  10074.                 fieldLabel: gettext('Format'),
  10075.                 value: 'raw',
  10076.                 allowBlank: false
  10077.             });
  10078.  
  10079.             me.hdfilesel = Ext.create('PVE.form.FileSelector', {
  10080.                 name: 'hdimage',
  10081.                 nodename: me.nodename,
  10082.                 storageContent: 'images',
  10083.                 fieldLabel: gettext('Disk image'),
  10084.                 disabled: true,
  10085.                 hidden: true,
  10086.                 allowBlank: false
  10087.             });
  10088.  
  10089.             me.hdsizesel = Ext.createWidget('numberfield', {
  10090.                 name: 'disksize',
  10091.                 minValue: 1,
  10092.                 maxValue: 128*1024,
  10093.                 value: '32',
  10094.                 fieldLabel: gettext('Disk size') + ' (GB)',
  10095.                 allowBlank: false
  10096.             });
  10097.  
  10098.             me.hdstoragesel = Ext.create('PVE.form.StorageSelector', {
  10099.                 name: 'hdstorage',
  10100.                 nodename: me.nodename,
  10101.                 fieldLabel: gettext('Storage'),
  10102.                 storageContent: 'images',
  10103.                 autoSelect: me.insideWizard,
  10104.                 allowBlank: false,
  10105.                 listeners: {
  10106.                     change: function(f, value) {
  10107.                         var rec = f.store.getById(value);
  10108.                         if (rec.data.type === 'iscsi') {
  10109.                             me.hdfilesel.setStorage(value);
  10110.                             me.hdfilesel.setDisabled(false);
  10111.                             me.formatsel.setValue('raw');
  10112.                             me.formatsel.setDisabled(true);
  10113.                             me.hdfilesel.setVisible(true);
  10114.                             me.hdsizesel.setDisabled(true);
  10115.                             me.hdsizesel.setVisible(false);
  10116.                         } else if (rec.data.type === 'lvm') {
  10117.                             me.hdfilesel.setDisabled(true);
  10118.                             me.hdfilesel.setVisible(false);