Advertisement
Guest User

Untitled

a guest
Sep 14th, 2018
708
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 167.79 KB | None | 0 0
  1. // 1.0-19
  2. Ext.ns('Proxmox');
  3. Ext.ns('Proxmox.Setup');
  4.  
  5. if (!Ext.isDefined(Proxmox.Setup.auth_cookie_name)) {
  6. throw "Proxmox library not initialized";
  7. }
  8.  
  9. // avoid errors related to Accessible Rich Internet Applications
  10. // (access for people with disabilities)
  11. // TODO reenable after all components are upgraded
  12. Ext.enableAria = false;
  13. Ext.enableAriaButtons = false;
  14. Ext.enableAriaPanels = false;
  15.  
  16. // avoid errors when running without development tools
  17. if (!Ext.isDefined(Ext.global.console)) {
  18. var console = {
  19. dir: function() {},
  20. log: function() {}
  21. };
  22. }
  23.  
  24. Ext.Ajax.defaultHeaders = {
  25. 'Accept': 'application/json'
  26. };
  27.  
  28. Ext.Ajax.on('beforerequest', function(conn, options) {
  29. if (Proxmox.CSRFPreventionToken) {
  30. if (!options.headers) {
  31. options.headers = {};
  32. }
  33. options.headers.CSRFPreventionToken = Proxmox.CSRFPreventionToken;
  34. }
  35. });
  36.  
  37. Ext.define('Proxmox.Utils', { utilities: {
  38.  
  39. // this singleton contains miscellaneous utilities
  40.  
  41. yesText: gettext('Yes'),
  42. noText: gettext('No'),
  43. enabledText: gettext('Enabled'),
  44. disabledText: gettext('Disabled'),
  45. noneText: gettext('none'),
  46. errorText: gettext('Error'),
  47. unknownText: gettext('Unknown'),
  48. defaultText: gettext('Default'),
  49. daysText: gettext('days'),
  50. dayText: gettext('day'),
  51. runningText: gettext('running'),
  52. stoppedText: gettext('stopped'),
  53. neverText: gettext('never'),
  54. totalText: gettext('Total'),
  55. usedText: gettext('Used'),
  56. directoryText: gettext('Directory'),
  57. stateText: gettext('State'),
  58. groupText: gettext('Group'),
  59.  
  60. language_map: {
  61. zh_CN: 'Chinese (Simplified)',
  62. zh_TW: 'Chinese (Traditional)',
  63. ca: 'Catalan',
  64. da: 'Danish',
  65. en: 'English',
  66. eu: 'Euskera (Basque)',
  67. fr: 'French',
  68. de: 'German',
  69. it: 'Italian',
  70. es: 'Spanish',
  71. ja: 'Japanese',
  72. nb: 'Norwegian (Bokmal)',
  73. nn: 'Norwegian (Nynorsk)',
  74. fa: 'Persian (Farsi)',
  75. pl: 'Polish',
  76. pt_BR: 'Portuguese (Brazil)',
  77. ru: 'Russian',
  78. sl: 'Slovenian',
  79. sv: 'Swedish',
  80. tr: 'Turkish'
  81. },
  82.  
  83. render_language: function (value) {
  84. if (!value) {
  85. return Proxmox.Utils.defaultText + ' (English)';
  86. }
  87. var text = Proxmox.Utils.language_map[value];
  88. if (text) {
  89. return text + ' (' + value + ')';
  90. }
  91. return value;
  92. },
  93.  
  94. language_array: function() {
  95. var data = [['__default__', Proxmox.Utils.render_language('')]];
  96. Ext.Object.each(Proxmox.Utils.language_map, function(key, value) {
  97. data.push([key, Proxmox.Utils.render_language(value)]);
  98. });
  99.  
  100. return data;
  101. },
  102.  
  103. getNoSubKeyHtml: function(url) {
  104. return Ext.String.format('This server is the property of Anjolen Inc. All un-authorized access is strictly prohibited. All access attempts are logged. All activity on this server is logged and monitored.');
  105. },
  106.  
  107. format_boolean_with_default: function(value) {
  108. if (Ext.isDefined(value) && value !== '__default__') {
  109. return value ? Proxmox.Utils.yesText : Proxmox.Utils.noText;
  110. }
  111. return Proxmox.Utils.defaultText;
  112. },
  113.  
  114. format_boolean: function(value) {
  115. return value ? Proxmox.Utils.yesText : Proxmox.Utils.noText;
  116. },
  117.  
  118. format_neg_boolean: function(value) {
  119. return !value ? Proxmox.Utils.yesText : Proxmox.Utils.noText;
  120. },
  121.  
  122. format_enabled_toggle: function(value) {
  123. return value ? Proxmox.Utils.enabledText : Proxmox.Utils.disabledText;
  124. },
  125.  
  126. format_expire: function(date) {
  127. if (!date) {
  128. return Proxmox.Utils.neverText;
  129. }
  130. return Ext.Date.format(date, "Y-m-d");
  131. },
  132.  
  133. format_duration_long: function(ut) {
  134.  
  135. var days = Math.floor(ut / 86400);
  136. ut -= days*86400;
  137. var hours = Math.floor(ut / 3600);
  138. ut -= hours*3600;
  139. var mins = Math.floor(ut / 60);
  140. ut -= mins*60;
  141.  
  142. var hours_str = '00' + hours.toString();
  143. hours_str = hours_str.substr(hours_str.length - 2);
  144. var mins_str = "00" + mins.toString();
  145. mins_str = mins_str.substr(mins_str.length - 2);
  146. var ut_str = "00" + ut.toString();
  147. ut_str = ut_str.substr(ut_str.length - 2);
  148.  
  149. if (days) {
  150. var ds = days > 1 ? Proxmox.Utils.daysText : Proxmox.Utils.dayText;
  151. return days.toString() + ' ' + ds + ' ' +
  152. hours_str + ':' + mins_str + ':' + ut_str;
  153. } else {
  154. return hours_str + ':' + mins_str + ':' + ut_str;
  155. }
  156. },
  157.  
  158. format_subscription_level: function(level) {
  159. if (level === 'c') {
  160. return 'Community';
  161. } else if (level === 'b') {
  162. return 'Basic';
  163. } else if (level === 's') {
  164. return 'Standard';
  165. } else if (level === 'p') {
  166. return 'Premium';
  167. } else {
  168. return Proxmox.Utils.noneText;
  169. }
  170. },
  171.  
  172. compute_min_label_width: function(text, width) {
  173.  
  174. if (width === undefined) { width = 100; }
  175.  
  176. var tm = new Ext.util.TextMetrics();
  177. var min = tm.getWidth(text + ':');
  178.  
  179. return min < width ? width : min;
  180. },
  181.  
  182. authOK: function() {
  183. return (Proxmox.UserName !== '') && Ext.util.Cookies.get(Proxmox.Setup.auth_cookie_name);
  184. },
  185.  
  186. authClear: function() {
  187. Ext.util.Cookies.clear(Proxmox.Setup.auth_cookie_name);
  188. },
  189.  
  190. // comp.setLoading() is buggy in ExtJS 4.0.7, so we
  191. // use el.mask() instead
  192. setErrorMask: function(comp, msg) {
  193. var el = comp.el;
  194. if (!el) {
  195. return;
  196. }
  197. if (!msg) {
  198. el.unmask();
  199. } else {
  200. if (msg === true) {
  201. el.mask(gettext("Loading..."));
  202. } else {
  203. el.mask(msg);
  204. }
  205. }
  206. },
  207.  
  208. monStoreErrors: function(me, store, clearMaskBeforeLoad) {
  209. if (clearMaskBeforeLoad) {
  210. me.mon(store, 'beforeload', function(s, operation, eOpts) {
  211. Proxmox.Utils.setErrorMask(me, false);
  212. });
  213. } else {
  214. me.mon(store, 'beforeload', function(s, operation, eOpts) {
  215. if (!me.loadCount) {
  216. me.loadCount = 0; // make sure it is numeric
  217. Proxmox.Utils.setErrorMask(me, true);
  218. }
  219. });
  220. }
  221.  
  222. // only works with 'proxmox' proxy
  223. me.mon(store.proxy, 'afterload', function(proxy, request, success) {
  224. me.loadCount++;
  225.  
  226. if (success) {
  227. Proxmox.Utils.setErrorMask(me, false);
  228. return;
  229. }
  230.  
  231. var msg;
  232. /*jslint nomen: true */
  233. var operation = request._operation;
  234. var error = operation.getError();
  235. if (error.statusText) {
  236. msg = error.statusText + ' (' + error.status + ')';
  237. } else {
  238. msg = gettext('Connection error');
  239. }
  240. Proxmox.Utils.setErrorMask(me, msg);
  241. });
  242. },
  243.  
  244. extractRequestError: function(result, verbose) {
  245. var msg = gettext('Successful');
  246.  
  247. if (!result.success) {
  248. msg = gettext("Unknown error");
  249. if (result.message) {
  250. msg = result.message;
  251. if (result.status) {
  252. msg += ' (' + result.status + ')';
  253. }
  254. }
  255. if (verbose && Ext.isObject(result.errors)) {
  256. msg += "<br>";
  257. Ext.Object.each(result.errors, function(prop, desc) {
  258. msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
  259. Ext.htmlEncode(desc);
  260. });
  261. }
  262. }
  263.  
  264. return msg;
  265. },
  266.  
  267. // Ext.Ajax.request
  268. API2Request: function(reqOpts) {
  269.  
  270. var newopts = Ext.apply({
  271. waitMsg: gettext('Please wait...')
  272. }, reqOpts);
  273.  
  274. if (!newopts.url.match(/^\/api2/)) {
  275. newopts.url = '/api2/extjs' + newopts.url;
  276. }
  277. delete newopts.callback;
  278.  
  279. var createWrapper = function(successFn, callbackFn, failureFn) {
  280. Ext.apply(newopts, {
  281. success: function(response, options) {
  282. if (options.waitMsgTarget) {
  283. if (Proxmox.Utils.toolkit === 'touch') {
  284. options.waitMsgTarget.setMasked(false);
  285. } else {
  286. options.waitMsgTarget.setLoading(false);
  287. }
  288. }
  289. var result = Ext.decode(response.responseText);
  290. response.result = result;
  291. if (!result.success) {
  292. response.htmlStatus = Proxmox.Utils.extractRequestError(result, true);
  293. Ext.callback(callbackFn, options.scope, [options, false, response]);
  294. Ext.callback(failureFn, options.scope, [response, options]);
  295. return;
  296. }
  297. Ext.callback(callbackFn, options.scope, [options, true, response]);
  298. Ext.callback(successFn, options.scope, [response, options]);
  299. },
  300. failure: function(response, options) {
  301. if (options.waitMsgTarget) {
  302. if (Proxmox.Utils.toolkit === 'touch') {
  303. options.waitMsgTarget.setMasked(false);
  304. } else {
  305. options.waitMsgTarget.setLoading(false);
  306. }
  307. }
  308. response.result = {};
  309. try {
  310. response.result = Ext.decode(response.responseText);
  311. } catch(e) {}
  312. var msg = gettext('Connection error') + ' - server offline?';
  313. if (response.aborted) {
  314. msg = gettext('Connection error') + ' - aborted.';
  315. } else if (response.timedout) {
  316. msg = gettext('Connection error') + ' - Timeout.';
  317. } else if (response.status && response.statusText) {
  318. msg = gettext('Connection error') + ' ' + response.status + ': ' + response.statusText;
  319. }
  320. response.htmlStatus = msg;
  321. Ext.callback(callbackFn, options.scope, [options, false, response]);
  322. Ext.callback(failureFn, options.scope, [response, options]);
  323. }
  324. });
  325. };
  326.  
  327. createWrapper(reqOpts.success, reqOpts.callback, reqOpts.failure);
  328.  
  329. var target = newopts.waitMsgTarget;
  330. if (target) {
  331. if (Proxmox.Utils.toolkit === 'touch') {
  332. target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg} );
  333. } else {
  334. // Note: ExtJS bug - this does not work when component is not rendered
  335. target.setLoading(newopts.waitMsg);
  336. }
  337. }
  338. Ext.Ajax.request(newopts);
  339. },
  340.  
  341. checked_command: function(orig_cmd) {
  342. Proxmox.Utils.API2Request({
  343. url: '/nodes/localhost/subscription',
  344. method: 'GET',
  345. //waitMsgTarget: me,
  346. failure: function(response, opts) {
  347. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  348. },
  349. success: function(response, opts) {
  350. var data = response.result.data;
  351.  
  352. if (data.status !== 'Active') {
  353. Ext.Msg.show({
  354. title: gettext('Legal Notice'),
  355. icon: Ext.Msg.WARNING,
  356. msg: Proxmox.Utils.getNoSubKeyHtml(data.url),
  357. buttons: Ext.Msg.OK,
  358. callback: function(btn) {
  359. if (btn !== 'ok') {
  360. return;
  361. }
  362. orig_cmd();
  363. }
  364. });
  365. } else {
  366. orig_cmd();
  367. }
  368. }
  369. });
  370. },
  371.  
  372. assemble_field_data: function(values, data) {
  373. if (Ext.isObject(data)) {
  374. Ext.Object.each(data, function(name, val) {
  375. if (values.hasOwnProperty(name)) {
  376. var bucket = values[name];
  377. if (!Ext.isArray(bucket)) {
  378. bucket = values[name] = [bucket];
  379. }
  380. if (Ext.isArray(val)) {
  381. values[name] = bucket.concat(val);
  382. } else {
  383. bucket.push(val);
  384. }
  385. } else {
  386. values[name] = val;
  387. }
  388. });
  389. }
  390. },
  391.  
  392. dialog_title: function(subject, create, isAdd) {
  393. if (create) {
  394. if (isAdd) {
  395. return gettext('Add') + ': ' + subject;
  396. } else {
  397. return gettext('Create') + ': ' + subject;
  398. }
  399. } else {
  400. return gettext('Edit') + ': ' + subject;
  401. }
  402. },
  403.  
  404. network_iface_types: {
  405. eth: gettext("Network Device"),
  406. bridge: 'Linux Bridge',
  407. bond: 'Linux Bond',
  408. OVSBridge: 'OVS Bridge',
  409. OVSBond: 'OVS Bond',
  410. OVSPort: 'OVS Port',
  411. OVSIntPort: 'OVS IntPort'
  412. },
  413.  
  414. render_network_iface_type: function(value) {
  415. return Proxmox.Utils.network_iface_types[value] ||
  416. Proxmox.Utils.unknownText;
  417. },
  418.  
  419. task_desc_table: {
  420. acmenewcert: [ 'SRV', gettext('Order Certificate') ],
  421. acmeregister: [ 'ACME Account', gettext('Register') ],
  422. acmedeactivate: [ 'ACME Account', gettext('Deactivate') ],
  423. acmeupdate: [ 'ACME Account', gettext('Update') ],
  424. acmerefresh: [ 'ACME Account', gettext('Refresh') ],
  425. acmerenew: [ 'SRV', gettext('Renew Certificate') ],
  426. acmerevoke: [ 'SRV', gettext('Revoke Certificate') ],
  427. 'move_volume': [ 'CT', gettext('Move Volume') ],
  428. clustercreate: [ '', gettext('Create Cluster') ],
  429. clusterjoin: [ '', gettext('Join Cluster') ],
  430. diskinit: [ 'Disk', gettext('Initialize Disk with GPT') ],
  431. vncproxy: [ 'VM/CT', gettext('Console') ],
  432. spiceproxy: [ 'VM/CT', gettext('Console') + ' (Spice)' ],
  433. vncshell: [ '', gettext('Shell') ],
  434. spiceshell: [ '', gettext('Shell') + ' (Spice)' ],
  435. qmsnapshot: [ 'VM', gettext('Snapshot') ],
  436. qmrollback: [ 'VM', gettext('Rollback') ],
  437. qmdelsnapshot: [ 'VM', gettext('Delete Snapshot') ],
  438. qmcreate: [ 'VM', gettext('Create') ],
  439. qmrestore: [ 'VM', gettext('Restore') ],
  440. qmdestroy: [ 'VM', gettext('Destroy') ],
  441. qmigrate: [ 'VM', gettext('Migrate') ],
  442. qmclone: [ 'VM', gettext('Clone') ],
  443. qmmove: [ 'VM', gettext('Move disk') ],
  444. qmtemplate: [ 'VM', gettext('Convert to template') ],
  445. qmstart: [ 'VM', gettext('Start') ],
  446. qmstop: [ 'VM', gettext('Stop') ],
  447. qmreset: [ 'VM', gettext('Reset') ],
  448. qmshutdown: [ 'VM', gettext('Shutdown') ],
  449. qmsuspend: [ 'VM', gettext('Suspend') ],
  450. qmresume: [ 'VM', gettext('Resume') ],
  451. qmconfig: [ 'VM', gettext('Configure') ],
  452. vzsnapshot: [ 'CT', gettext('Snapshot') ],
  453. vzrollback: [ 'CT', gettext('Rollback') ],
  454. vzdelsnapshot: [ 'CT', gettext('Delete Snapshot') ],
  455. vzcreate: ['CT', gettext('Create') ],
  456. vzrestore: ['CT', gettext('Restore') ],
  457. vzdestroy: ['CT', gettext('Destroy') ],
  458. vzmigrate: [ 'CT', gettext('Migrate') ],
  459. vzclone: [ 'CT', gettext('Clone') ],
  460. vztemplate: [ 'CT', gettext('Convert to template') ],
  461. vzstart: ['CT', gettext('Start') ],
  462. vzstop: ['CT', gettext('Stop') ],
  463. vzmount: ['CT', gettext('Mount') ],
  464. vzumount: ['CT', gettext('Unmount') ],
  465. vzshutdown: ['CT', gettext('Shutdown') ],
  466. vzsuspend: [ 'CT', gettext('Suspend') ],
  467. vzresume: [ 'CT', gettext('Resume') ],
  468. hamigrate: [ 'HA', gettext('Migrate') ],
  469. hastart: [ 'HA', gettext('Start') ],
  470. hastop: [ 'HA', gettext('Stop') ],
  471. srvstart: ['SRV', gettext('Start') ],
  472. srvstop: ['SRV', gettext('Stop') ],
  473. srvrestart: ['SRV', gettext('Restart') ],
  474. srvreload: ['SRV', gettext('Reload') ],
  475. cephcreatemgr: ['Ceph Manager', gettext('Create') ],
  476. cephdestroymgr: ['Ceph Manager', gettext('Destroy') ],
  477. cephcreatemon: ['Ceph Monitor', gettext('Create') ],
  478. cephdestroymon: ['Ceph Monitor', gettext('Destroy') ],
  479. cephcreateosd: ['Ceph OSD', gettext('Create') ],
  480. cephdestroyosd: ['Ceph OSD', gettext('Destroy') ],
  481. cephcreatepool: ['Ceph Pool', gettext('Create') ],
  482. cephdestroypool: ['Ceph Pool', gettext('Destroy') ],
  483. imgcopy: ['', gettext('Copy data') ],
  484. imgdel: ['', gettext('Erase data') ],
  485. download: ['', gettext('Download') ],
  486. vzdump: ['', gettext('Backup') ],
  487. aptupdate: ['', gettext('Update package database') ],
  488. startall: [ '', gettext('Start all VMs and Containers') ],
  489. stopall: [ '', gettext('Stop all VMs and Containers') ],
  490. migrateall: [ '', gettext('Migrate all VMs and Containers') ]
  491. },
  492.  
  493. format_task_description: function(type, id) {
  494. var farray = Proxmox.Utils.task_desc_table[type];
  495. if (!farray) {
  496. var text = type;
  497. if (id) {
  498. type += ' ' + id;
  499. }
  500. return text;
  501. }
  502. var prefix = farray[0];
  503. var text = farray[1];
  504. if (prefix) {
  505. return prefix + ' ' + id + ' - ' + text;
  506. }
  507. return text;
  508. },
  509.  
  510. format_size: function(size) {
  511. /*jslint confusion: true */
  512.  
  513. var units = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
  514. var num = 0;
  515.  
  516. while (size >= 1024 && ((num++)+1) < units.length) {
  517. size = size / 1024;
  518. }
  519.  
  520. return size.toFixed((num > 0)?2:0) + " " + units[num] + "B";
  521. },
  522.  
  523. render_upid: function(value, metaData, record) {
  524. var type = record.data.type;
  525. var id = record.data.id;
  526.  
  527. return Proxmox.Utils.format_task_description(type, id);
  528. },
  529.  
  530. render_uptime: function(value) {
  531.  
  532. var uptime = value;
  533.  
  534. if (uptime === undefined) {
  535. return '';
  536. }
  537.  
  538. if (uptime <= 0) {
  539. return '-';
  540. }
  541.  
  542. return Proxmox.Utils.format_duration_long(uptime);
  543. },
  544.  
  545. parse_task_upid: function(upid) {
  546. var task = {};
  547.  
  548. var res = upid.match(/^UPID:(\S+):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8,9}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/);
  549. if (!res) {
  550. throw "unable to parse upid '" + upid + "'";
  551. }
  552. task.node = res[1];
  553. task.pid = parseInt(res[2], 16);
  554. task.pstart = parseInt(res[3], 16);
  555. task.starttime = parseInt(res[4], 16);
  556. task.type = res[5];
  557. task.id = res[6];
  558. task.user = res[7];
  559.  
  560. task.desc = Proxmox.Utils.format_task_description(task.type, task.id);
  561.  
  562. return task;
  563. },
  564.  
  565. render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
  566. var servertime = new Date(value * 1000);
  567. return Ext.Date.format(servertime, 'Y-m-d H:i:s');
  568. },
  569.  
  570. openXtermJsViewer: function(vmtype, vmid, nodename, vmname) {
  571. var url = Ext.urlEncode({
  572. console: vmtype, // kvm, lxc, upgrade or shell
  573. xtermjs: 1,
  574. vmid: vmid,
  575. vmname: vmname,
  576. node: nodename
  577. });
  578. var nw = window.open("?" + url, '_blank', 'toolbar=no,location=no,status=no,menubar=no,resizable=yes,width=800,height=420');
  579. nw.focus();
  580. }
  581.  
  582. },
  583.  
  584. singleton: true,
  585. constructor: function() {
  586. var me = this;
  587. Ext.apply(me, me.utilities);
  588.  
  589. var IPV4_OCTET = "(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])";
  590. var IPV4_REGEXP = "(?:(?:" + IPV4_OCTET + "\\.){3}" + IPV4_OCTET + ")";
  591. var IPV6_H16 = "(?:[0-9a-fA-F]{1,4})";
  592. var IPV6_LS32 = "(?:(?:" + IPV6_H16 + ":" + IPV6_H16 + ")|" + IPV4_REGEXP + ")";
  593.  
  594.  
  595. me.IP4_match = new RegExp("^(?:" + IPV4_REGEXP + ")$");
  596. me.IP4_cidr_match = new RegExp("^(?:" + IPV4_REGEXP + ")\/([0-9]{1,2})$");
  597.  
  598. var IPV6_REGEXP = "(?:" +
  599. "(?:(?:" + "(?:" + IPV6_H16 + ":){6})" + IPV6_LS32 + ")|" +
  600. "(?:(?:" + "::" + "(?:" + IPV6_H16 + ":){5})" + IPV6_LS32 + ")|" +
  601. "(?:(?:(?:" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){4})" + IPV6_LS32 + ")|" +
  602. "(?:(?:(?:(?:" + IPV6_H16 + ":){0,1}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){3})" + IPV6_LS32 + ")|" +
  603. "(?:(?:(?:(?:" + IPV6_H16 + ":){0,2}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){2})" + IPV6_LS32 + ")|" +
  604. "(?:(?:(?:(?:" + IPV6_H16 + ":){0,3}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){1})" + IPV6_LS32 + ")|" +
  605. "(?:(?:(?:(?:" + IPV6_H16 + ":){0,4}" + IPV6_H16 + ")?::" + ")" + IPV6_LS32 + ")|" +
  606. "(?:(?:(?:(?:" + IPV6_H16 + ":){0,5}" + IPV6_H16 + ")?::" + ")" + IPV6_H16 + ")|" +
  607. "(?:(?:(?:(?:" + IPV6_H16 + ":){0,7}" + IPV6_H16 + ")?::" + ")" + ")" +
  608. ")";
  609.  
  610. me.IP6_match = new RegExp("^(?:" + IPV6_REGEXP + ")$");
  611. me.IP6_cidr_match = new RegExp("^(?:" + IPV6_REGEXP + ")\/([0-9]{1,3})$");
  612. me.IP6_bracket_match = new RegExp("^\\[(" + IPV6_REGEXP + ")\\]");
  613.  
  614. me.IP64_match = new RegExp("^(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + ")$");
  615.  
  616. var DnsName_REGEXP = "(?:(([a-zA-Z0-9]([a-zA-Z0-9\\-]*[a-zA-Z0-9])?)\\.)*([A-Za-z0-9]([A-Za-z0-9\\-]*[A-Za-z0-9])?))";
  617. me.DnsName_match = new RegExp("^" + DnsName_REGEXP + "$");
  618.  
  619. me.HostPort_match = new RegExp("^(" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")(:\\d+)?$");
  620. me.HostPortBrackets_match = new RegExp("^\\[(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")\\](:\\d+)?$");
  621. me.IP6_dotnotation_match = new RegExp("^" + IPV6_REGEXP + "(\\.\\d+)?$");
  622. }
  623. });
  624. // ExtJS related things
  625.  
  626. // do not send '_dc' parameter
  627. Ext.Ajax.disableCaching = false;
  628.  
  629. // custom Vtypes
  630. Ext.apply(Ext.form.field.VTypes, {
  631. IPAddress: function(v) {
  632. return Proxmox.Utils.IP4_match.test(v);
  633. },
  634. IPAddressText: gettext('Example') + ': 192.168.1.1',
  635. IPAddressMask: /[\d\.]/i,
  636.  
  637. IPCIDRAddress: function(v) {
  638. var result = Proxmox.Utils.IP4_cidr_match.exec(v);
  639. // limits according to JSON Schema see
  640. // pve-common/src/PVE/JSONSchema.pm
  641. return (result !== null && result[1] >= 8 && result[1] <= 32);
  642. },
  643. IPCIDRAddressText: gettext('Example') + ': 192.168.1.1/24' + "<br>" + gettext('Valid CIDR Range') + ': 8-32',
  644. IPCIDRAddressMask: /[\d\.\/]/i,
  645.  
  646. IP6Address: function(v) {
  647. return Proxmox.Utils.IP6_match.test(v);
  648. },
  649. IP6AddressText: gettext('Example') + ': 2001:DB8::42',
  650. IP6AddressMask: /[A-Fa-f0-9:]/,
  651.  
  652. IP6CIDRAddress: function(v) {
  653. var result = Proxmox.Utils.IP6_cidr_match.exec(v);
  654. // limits according to JSON Schema see
  655. // pve-common/src/PVE/JSONSchema.pm
  656. return (result !== null && result[1] >= 8 && result[1] <= 120);
  657. },
  658. IP6CIDRAddressText: gettext('Example') + ': 2001:DB8::42/64' + "<br>" + gettext('Valid CIDR Range') + ': 8-120',
  659. IP6CIDRAddressMask: /[A-Fa-f0-9:\/]/,
  660.  
  661. IP6PrefixLength: function(v) {
  662. return v >= 0 && v <= 128;
  663. },
  664. IP6PrefixLengthText: gettext('Example') + ': X, where 0 <= X <= 128',
  665. IP6PrefixLengthMask: /[0-9]/,
  666.  
  667. IP64Address: function(v) {
  668. return Proxmox.Utils.IP64_match.test(v);
  669. },
  670. IP64AddressText: gettext('Example') + ': 192.168.1.1 2001:DB8::42',
  671. IP64AddressMask: /[A-Fa-f0-9\.:]/,
  672.  
  673. MacAddress: function(v) {
  674. return (/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/).test(v);
  675. },
  676. MacAddressMask: /[a-fA-F0-9:]/,
  677. MacAddressText: gettext('Example') + ': 01:23:45:67:89:ab',
  678.  
  679. MacPrefix: function(v) {
  680. return (/^[a-f0-9]{2}(?::[a-f0-9]{2}){0,2}:?$/i).test(v);
  681. },
  682. MacPrefixMask: /[a-fA-F0-9:]/,
  683. MacPrefixText: gettext('Example') + ': 02:8f',
  684.  
  685. BridgeName: function(v) {
  686. return (/^vmbr\d{1,4}$/).test(v);
  687. },
  688. BridgeNameText: gettext('Format') + ': vmbr<b>N</b>, where 0 <= <b>N</b> <= 9999',
  689.  
  690. BondName: function(v) {
  691. return (/^bond\d{1,4}$/).test(v);
  692. },
  693. BondNameText: gettext('Format') + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999',
  694.  
  695. InterfaceName: function(v) {
  696. return (/^[a-z][a-z0-9_]{1,20}$/).test(v);
  697. },
  698. InterfaceNameText: gettext("Allowed characters") + ": 'a-z', '0-9', '_'" + "<br />" +
  699. gettext("Minimum characters") + ": 2" + "<br />" +
  700. gettext("Maximum characters") + ": 21" + "<br />" +
  701. gettext("Must start with") + ": 'a-z'",
  702.  
  703. StorageId: function(v) {
  704. return (/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i).test(v);
  705. },
  706. StorageIdText: gettext("Allowed characters") + ": 'A-Z', 'a-z', '0-9', '-', '_', '.'" + "<br />" +
  707. gettext("Minimum characters") + ": 2" + "<br />" +
  708. gettext("Must start with") + ": 'A-Z', 'a-z'<br />" +
  709. gettext("Must end with") + ": 'A-Z', 'a-z', '0-9'<br />",
  710.  
  711. ConfigId: function(v) {
  712. return (/^[a-z][a-z0-9\_]+$/i).test(v);
  713. },
  714. ConfigIdText: gettext("Allowed characters") + ": 'A-Z', 'a-z', '0-9', '_'" + "<br />" +
  715. gettext("Minimum characters") + ": 2" + "<br />" +
  716. gettext("Must start with") + ": " + gettext("letter"),
  717.  
  718. HttpProxy: function(v) {
  719. return (/^http:\/\/.*$/).test(v);
  720. },
  721. HttpProxyText: gettext('Example') + ": http://username:password&#64;host:port/",
  722.  
  723. DnsName: function(v) {
  724. return Proxmox.Utils.DnsName_match.test(v);
  725. },
  726. DnsNameText: gettext('This is not a valid DNS name'),
  727.  
  728. // workaround for https://www.sencha.com/forum/showthread.php?302150
  729. proxmoxMail: function(v) {
  730. return (/^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,63}$/).test(v);
  731. },
  732. proxmoxMailText: gettext('Example') + ": user@example.com",
  733.  
  734. HostList: function(v) {
  735. var list = v.split(/[\ \,\;]+/);
  736. var i;
  737. for (i = 0; i < list.length; i++) {
  738. if (list[i] == "") {
  739. continue;
  740. }
  741.  
  742. if (!Proxmox.Utils.HostPort_match.test(list[i]) &&
  743. !Proxmox.Utils.HostPortBrackets_match.test(list[i]) &&
  744. !Proxmox.Utils.IP6_dotnotation_match.test(list[i])) {
  745. return false;
  746. }
  747. }
  748.  
  749. return true;
  750. },
  751. HostListText: gettext('Not a valid list of hosts'),
  752.  
  753. password: function(val, field) {
  754. if (field.initialPassField) {
  755. var pwd = field.up('form').down(
  756. '[name=' + field.initialPassField + ']');
  757. return (val == pwd.getValue());
  758. }
  759. return true;
  760. },
  761.  
  762. passwordText: gettext('Passwords do not match')
  763. });
  764.  
  765. // Firefox 52+ Touchscreen bug
  766. // see https://www.sencha.com/forum/showthread.php?336762-Examples-don-t-work-in-Firefox-52-touchscreen/page2
  767. // and https://bugzilla.proxmox.com/show_bug.cgi?id=1223
  768. Ext.define('EXTJS_23846.Element', {
  769. override: 'Ext.dom.Element'
  770. }, function(Element) {
  771. var supports = Ext.supports,
  772. proto = Element.prototype,
  773. eventMap = proto.eventMap,
  774. additiveEvents = proto.additiveEvents;
  775.  
  776. if (Ext.os.is.Desktop && supports.TouchEvents && !supports.PointerEvents) {
  777. eventMap.touchstart = 'mousedown';
  778. eventMap.touchmove = 'mousemove';
  779. eventMap.touchend = 'mouseup';
  780. eventMap.touchcancel = 'mouseup';
  781.  
  782. additiveEvents.mousedown = 'mousedown';
  783. additiveEvents.mousemove = 'mousemove';
  784. additiveEvents.mouseup = 'mouseup';
  785. additiveEvents.touchstart = 'touchstart';
  786. additiveEvents.touchmove = 'touchmove';
  787. additiveEvents.touchend = 'touchend';
  788. additiveEvents.touchcancel = 'touchcancel';
  789.  
  790. additiveEvents.pointerdown = 'mousedown';
  791. additiveEvents.pointermove = 'mousemove';
  792. additiveEvents.pointerup = 'mouseup';
  793. additiveEvents.pointercancel = 'mouseup';
  794. }
  795. });
  796.  
  797. Ext.define('EXTJS_23846.Gesture', {
  798. override: 'Ext.event.publisher.Gesture'
  799. }, function(Gesture) {
  800. var me = Gesture.instance;
  801.  
  802. if (Ext.supports.TouchEvents && !Ext.isWebKit && Ext.os.is.Desktop) {
  803. me.handledDomEvents.push('mousedown', 'mousemove', 'mouseup');
  804. me.registerEvents();
  805. }
  806. });
  807.  
  808. // we always want the number in x.y format and never in, e.g., x,y
  809. Ext.define('PVE.form.field.Number', {
  810. override: 'Ext.form.field.Number',
  811. submitLocaleSeparator: false
  812. });
  813.  
  814. // ExtJs 5-6 has an issue with caching
  815. // see https://www.sencha.com/forum/showthread.php?308989
  816. Ext.define('Proxmox.UnderlayPool', {
  817. override: 'Ext.dom.UnderlayPool',
  818.  
  819. checkOut: function () {
  820. var cache = this.cache,
  821. len = cache.length,
  822. el;
  823.  
  824. // do cleanup because some of the objects might have been destroyed
  825. while (len--) {
  826. if (cache[len].destroyed) {
  827. cache.splice(len, 1);
  828. }
  829. }
  830. // end do cleanup
  831.  
  832. el = cache.shift();
  833.  
  834. if (!el) {
  835. el = Ext.Element.create(this.elementConfig);
  836. el.setVisibilityMode(2);
  837. //<debug>
  838. // tell the spec runner to ignore this element when checking if the dom is clean
  839. el.dom.setAttribute('data-sticky', true);
  840. //</debug>
  841. }
  842.  
  843. return el;
  844. }
  845. });
  846.  
  847. // 'Enter' in Textareas and aria multiline fields should not activate the
  848. // defaultbutton, fixed in extjs 6.0.2
  849. Ext.define('PVE.panel.Panel', {
  850. override: 'Ext.panel.Panel',
  851.  
  852. fireDefaultButton: function(e) {
  853. if (e.target.getAttribute('aria-multiline') === 'true' ||
  854. e.target.tagName === "TEXTAREA") {
  855. return true;
  856. }
  857. return this.callParent(arguments);
  858. }
  859. });
  860.  
  861. // if the order of the values are not the same in originalValue and value
  862. // extjs will not overwrite value, but marks the field dirty and thus
  863. // the reset button will be enabled (but clicking it changes nothing)
  864. // so if the arrays are not the same after resetting, we
  865. // clear and set it
  866. Ext.define('Proxmox.form.ComboBox', {
  867. override: 'Ext.form.field.ComboBox',
  868.  
  869. reset: function() {
  870. // copied from combobox
  871. var me = this;
  872. me.callParent();
  873.  
  874. // clear and set when not the same
  875. var value = me.getValue();
  876. if (Ext.isArray(me.originalValue) && Ext.isArray(value) && !Ext.Array.equals(value, me.originalValue)) {
  877. me.clearValue();
  878. me.setValue(me.originalValue);
  879. }
  880. }
  881. });
  882.  
  883. // when refreshing a grid/tree view, restoring the focus moves the view back to
  884. // the previously focused item. Save scroll position before refocusing.
  885. Ext.define(null, {
  886. override: 'Ext.view.Table',
  887.  
  888. jumpToFocus: false,
  889.  
  890. saveFocusState: function() {
  891. var me = this,
  892. store = me.dataSource,
  893. actionableMode = me.actionableMode,
  894. navModel = me.getNavigationModel(),
  895. focusPosition = actionableMode ? me.actionPosition : navModel.getPosition(true),
  896. refocusRow, refocusCol;
  897.  
  898. if (focusPosition) {
  899. // Separate this from the instance that the nav model is using.
  900. focusPosition = focusPosition.clone();
  901.  
  902. // Exit actionable mode.
  903. // We must inform any Actionables that they must relinquish control.
  904. // Tabbability must be reset.
  905. if (actionableMode) {
  906. me.ownerGrid.setActionableMode(false);
  907. }
  908.  
  909. // Blur the focused descendant, but do not trigger focusLeave.
  910. me.el.dom.focus();
  911.  
  912. // Exiting actionable mode navigates to the owning cell, so in either focus mode we must
  913. // clear the navigation position
  914. navModel.setPosition();
  915.  
  916. // The following function will attempt to refocus back in the same mode to the same cell
  917. // as it was at before based upon the previous record (if it's still inthe store), or the row index.
  918. return function() {
  919. // If we still have data, attempt to refocus in the same mode.
  920. if (store.getCount()) {
  921.  
  922. // Adjust expectations of where we are able to refocus according to what kind of destruction
  923. // might have been wrought on this view's DOM during focus save.
  924. refocusRow = Math.min(focusPosition.rowIdx, me.all.getCount() - 1);
  925. refocusCol = Math.min(focusPosition.colIdx, me.getVisibleColumnManager().getColumns().length - 1);
  926. focusPosition = new Ext.grid.CellContext(me).setPosition(
  927. store.contains(focusPosition.record) ? focusPosition.record : refocusRow, refocusCol);
  928.  
  929. if (actionableMode) {
  930. me.ownerGrid.setActionableMode(true, focusPosition);
  931. } else {
  932. me.cellFocused = true;
  933.  
  934. // we sometimes want to scroll back to where we were
  935. var x = me.getScrollX();
  936. var y = me.getScrollY();
  937.  
  938. // Pass "preventNavigation" as true so that that does not cause selection.
  939. navModel.setPosition(focusPosition, null, null, null, true);
  940.  
  941. if (!me.jumpToFocus) {
  942. me.scrollTo(x,y);
  943. }
  944. }
  945. }
  946. // No rows - focus associated column header
  947. else {
  948. focusPosition.column.focus();
  949. }
  950. };
  951. }
  952. return Ext.emptyFn;
  953. }
  954. });
  955.  
  956. // should be fixed with ExtJS 6.0.2, see:
  957. // https://www.sencha.com/forum/showthread.php?307244-Bug-with-datefield-in-window-with-scroll
  958. Ext.define('Proxmox.Datepicker', {
  959. override: 'Ext.picker.Date',
  960. hideMode: 'visibility'
  961. });
  962.  
  963. // ExtJS 6.0.1 has no setSubmitValue() (although you find it in the docs).
  964. // Note: this.submitValue is a boolean flag, whereas getSubmitValue() returns
  965. // data to be submitted.
  966. Ext.define('Proxmox.form.field.Text', {
  967. override: 'Ext.form.field.Text',
  968.  
  969. setSubmitValue: function(v) {
  970. this.submitValue = v;
  971. },
  972. });
  973.  
  974. // this should be fixed with ExtJS 6.0.2
  975. // make mousescrolling work in firefox in the containers overflowhandler
  976. Ext.define(null, {
  977. override: 'Ext.layout.container.boxOverflow.Scroller',
  978.  
  979. createWheelListener: function() {
  980. var me = this;
  981. if (Ext.isFirefox) {
  982. me.wheelListener = me.layout.innerCt.on('wheel', me.onMouseWheelFirefox, me, {destroyable: true});
  983. } else {
  984. me.wheelListener = me.layout.innerCt.on('mousewheel', me.onMouseWheel, me, {destroyable: true});
  985. }
  986. },
  987.  
  988. // special wheel handler for firefox. differs from the default onMouseWheel
  989. // handler by using deltaY instead of wheelDeltaY and no normalizing,
  990. // because it is already
  991. onMouseWheelFirefox: function(e) {
  992. e.stopEvent();
  993. var delta = e.browserEvent.deltaY || 0;
  994. this.scrollBy(delta * this.wheelIncrement, false);
  995. }
  996.  
  997. });
  998.  
  999. // force alert boxes to be rendered with an Error Icon
  1000. // since Ext.Msg is an object and not a prototype, we need to override it
  1001. // after the framework has been initiated
  1002. Ext.onReady(function() {
  1003. /*jslint confusion: true */
  1004. Ext.override(Ext.Msg, {
  1005. alert: function(title, message, fn, scope) {
  1006. if (Ext.isString(title)) {
  1007. var config = {
  1008. title: title,
  1009. message: message,
  1010. icon: this.ERROR,
  1011. buttons: this.OK,
  1012. fn: fn,
  1013. scope : scope,
  1014. minWidth: this.minWidth
  1015. };
  1016. return this.show(config);
  1017. }
  1018. }
  1019. });
  1020. /*jslint confusion: false */
  1021. });
  1022. Ext.define('Ext.ux.IFrame', {
  1023. extend: 'Ext.Component',
  1024.  
  1025. alias: 'widget.uxiframe',
  1026.  
  1027. loadMask: 'Loading...',
  1028.  
  1029. src: 'about:blank',
  1030.  
  1031. renderTpl: [
  1032. '<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0" allowfullscreen="true"></iframe>'
  1033. ],
  1034. childEls: ['iframeEl'],
  1035.  
  1036. initComponent: function () {
  1037. this.callParent();
  1038.  
  1039. this.frameName = this.frameName || this.id + '-frame';
  1040. },
  1041.  
  1042. initEvents : function() {
  1043. var me = this;
  1044. me.callParent();
  1045. me.iframeEl.on('load', me.onLoad, me);
  1046. },
  1047.  
  1048. initRenderData: function() {
  1049. return Ext.apply(this.callParent(), {
  1050. src: this.src,
  1051. frameName: this.frameName
  1052. });
  1053. },
  1054.  
  1055. getBody: function() {
  1056. var doc = this.getDoc();
  1057. return doc.body || doc.documentElement;
  1058. },
  1059.  
  1060. getDoc: function() {
  1061. try {
  1062. return this.getWin().document;
  1063. } catch (ex) {
  1064. return null;
  1065. }
  1066. },
  1067.  
  1068. getWin: function() {
  1069. var me = this,
  1070. name = me.frameName,
  1071. win = Ext.isIE
  1072. ? me.iframeEl.dom.contentWindow
  1073. : window.frames[name];
  1074. return win;
  1075. },
  1076.  
  1077. getFrame: function() {
  1078. var me = this;
  1079. return me.iframeEl.dom;
  1080. },
  1081.  
  1082. beforeDestroy: function () {
  1083. this.cleanupListeners(true);
  1084. this.callParent();
  1085. },
  1086.  
  1087. cleanupListeners: function(destroying){
  1088. var doc, prop;
  1089.  
  1090. if (this.rendered) {
  1091. try {
  1092. doc = this.getDoc();
  1093. if (doc) {
  1094. /*jslint nomen: true*/
  1095. Ext.get(doc).un(this._docListeners);
  1096. /*jslint nomen: false*/
  1097. if (destroying && doc.hasOwnProperty) {
  1098. for (prop in doc) {
  1099. if (doc.hasOwnProperty(prop)) {
  1100. delete doc[prop];
  1101. }
  1102. }
  1103. }
  1104. }
  1105. } catch(e) { }
  1106. }
  1107. },
  1108.  
  1109. onLoad: function() {
  1110. var me = this,
  1111. doc = me.getDoc(),
  1112. fn = me.onRelayedEvent;
  1113.  
  1114. if (doc) {
  1115. try {
  1116. // These events need to be relayed from the inner document (where they stop
  1117. // bubbling) up to the outer document. This has to be done at the DOM level so
  1118. // the event reaches listeners on elements like the document body. The effected
  1119. // mechanisms that depend on this bubbling behavior are listed to the right
  1120. // of the event.
  1121. /*jslint nomen: true*/
  1122. Ext.get(doc).on(
  1123. me._docListeners = {
  1124. mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
  1125. mousemove: fn, // window resize drag detection
  1126. mouseup: fn, // window resize termination
  1127. click: fn, // not sure, but just to be safe
  1128. dblclick: fn, // not sure again
  1129. scope: me
  1130. }
  1131. );
  1132. /*jslint nomen: false*/
  1133. } catch(e) {
  1134. // cannot do this xss
  1135. }
  1136.  
  1137. // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
  1138. Ext.get(this.getWin()).on('beforeunload', me.cleanupListeners, me);
  1139.  
  1140. this.el.unmask();
  1141. this.fireEvent('load', this);
  1142.  
  1143. } else if (me.src) {
  1144.  
  1145. this.el.unmask();
  1146. this.fireEvent('error', this);
  1147. }
  1148.  
  1149.  
  1150. },
  1151.  
  1152. onRelayedEvent: function (event) {
  1153. // relay event from the iframe's document to the document that owns the iframe...
  1154.  
  1155. var iframeEl = this.iframeEl,
  1156.  
  1157. // Get the left-based iframe position
  1158. iframeXY = iframeEl.getTrueXY(),
  1159. originalEventXY = event.getXY(),
  1160.  
  1161. // Get the left-based XY position.
  1162. // This is because the consumer of the injected event will
  1163. // perform its own RTL normalization.
  1164. eventXY = event.getTrueXY();
  1165.  
  1166. // the event from the inner document has XY relative to that document's origin,
  1167. // so adjust it to use the origin of the iframe in the outer document:
  1168. event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
  1169.  
  1170. event.injectEvent(iframeEl); // blame the iframe for the event...
  1171.  
  1172. event.xy = originalEventXY; // restore the original XY (just for safety)
  1173. },
  1174.  
  1175. load: function (src) {
  1176. var me = this,
  1177. text = me.loadMask,
  1178. frame = me.getFrame();
  1179.  
  1180. if (me.fireEvent('beforeload', me, src) !== false) {
  1181. if (text && me.el) {
  1182. me.el.mask(text);
  1183. }
  1184.  
  1185. frame.src = me.src = (src || me.src);
  1186. }
  1187. }
  1188. });
  1189. Ext.define('Proxmox.Mixin.CBind', {
  1190. extend: 'Ext.Mixin',
  1191.  
  1192. mixinConfig: {
  1193. before: {
  1194. initComponent: 'cloneTemplates'
  1195. }
  1196. },
  1197.  
  1198. cloneTemplates: function() {
  1199. var me = this;
  1200.  
  1201. if (typeof(me.cbindData) == "function") {
  1202. me.cbindData = me.cbindData(me.initialConfig) || {};
  1203. }
  1204.  
  1205. var getConfigValue = function(cname) {
  1206.  
  1207. if (cname in me.initialConfig) {
  1208. return me.initialConfig[cname];
  1209. }
  1210. if (cname in me.cbindData) {
  1211. return me.cbindData[cname];
  1212. }
  1213. if (cname in me) {
  1214. return me[cname];
  1215. }
  1216. throw "unable to get cbind data for '" + cname + "'";
  1217. };
  1218.  
  1219. var applyCBind = function(obj) {
  1220. var cbind = obj.cbind, prop, cdata, cvalue, match, found;
  1221. if (!cbind) return;
  1222.  
  1223. for (prop in cbind) {
  1224. cdata = cbind[prop];
  1225.  
  1226. found = false;
  1227. if (match = /^\{(!)?([a-z_][a-z0-9_]*)\}$/i.exec(cdata)) {
  1228. var cvalue = getConfigValue(match[2]);
  1229. if (match[1]) cvalue = !cvalue;
  1230. obj[prop] = cvalue;
  1231. found = true;
  1232. } else if (match = /^\{(!)?([a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)+)\}$/i.exec(cdata)) {
  1233. var keys = match[2].split('.');
  1234. var cvalue = getConfigValue(keys.shift());
  1235. keys.forEach(function(k) {
  1236. if (k in cvalue) {
  1237. cvalue = cvalue[k];
  1238. } else {
  1239. throw "unable to get cbind data for '" + match[2] + "'";
  1240. }
  1241. });
  1242. if (match[1]) cvalue = !cvalue;
  1243. obj[prop] = cvalue;
  1244. found = true;
  1245. } else {
  1246. obj[prop] = cdata.replace(/{([a-z_][a-z0-9_]*)\}/ig, function(match, cname) {
  1247. var cvalue = getConfigValue(cname);
  1248. found = true;
  1249. return cvalue;
  1250. });
  1251. }
  1252. if (!found) {
  1253. throw "unable to parse cbind template '" + cdata + "'";
  1254. }
  1255.  
  1256. }
  1257. };
  1258.  
  1259. if (me.cbind) {
  1260. applyCBind(me);
  1261. }
  1262.  
  1263. var cloneTemplateArray = function(org) {
  1264. var copy, i, found, el, elcopy, arrayLength;
  1265.  
  1266. arrayLength = org.length;
  1267. found = false;
  1268. for (i = 0; i < arrayLength; i++) {
  1269. el = org[i];
  1270. if (el.constructor == Object && el.xtype) {
  1271. found = true;
  1272. break;
  1273. }
  1274. }
  1275.  
  1276. if (!found) return org; // no need to copy
  1277.  
  1278. copy = [];
  1279. for (i = 0; i < arrayLength; i++) {
  1280. el = org[i];
  1281. if (el.constructor == Object && el.xtype) {
  1282. elcopy = cloneTemplateObject(el);
  1283. if (elcopy.cbind) {
  1284. applyCBind(elcopy);
  1285. }
  1286. copy.push(elcopy);
  1287. } else if (el.constructor == Array) {
  1288. elcopy = cloneTemplateArray(el);
  1289. copy.push(elcopy);
  1290. } else {
  1291. copy.push(el);
  1292. }
  1293. }
  1294. return copy;
  1295. };
  1296.  
  1297. var cloneTemplateObject = function(org) {
  1298. var res = {}, prop, el, copy;
  1299. for (prop in org) {
  1300. el = org[prop];
  1301. if (el.constructor == Object && el.xtype) {
  1302. copy = cloneTemplateObject(el);
  1303. if (copy.cbind) {
  1304. applyCBind(copy);
  1305. }
  1306. res[prop] = copy;
  1307. } else if (el.constructor == Array) {
  1308. copy = cloneTemplateArray(el);
  1309. res[prop] = copy;
  1310. } else {
  1311. res[prop] = el;
  1312. }
  1313. }
  1314. return res;
  1315. };
  1316.  
  1317. var condCloneProperties = function() {
  1318. var prop, el, i, tmp;
  1319.  
  1320. for (prop in me) {
  1321. el = me[prop];
  1322. if (el === undefined || el === null) continue;
  1323. if (typeof(el) === 'object' && el.constructor == Object) {
  1324. if (el.xtype && prop != 'config') {
  1325. me[prop] = cloneTemplateObject(el);
  1326. }
  1327. } else if (el.constructor == Array) {
  1328. tmp = cloneTemplateArray(el);
  1329. me[prop] = tmp;
  1330. }
  1331. }
  1332. };
  1333.  
  1334. condCloneProperties();
  1335. }
  1336. });
  1337. /* A reader to store a single JSON Object (hash) into a storage.
  1338. * Also accepts an array containing a single hash.
  1339. *
  1340. * So it can read:
  1341. *
  1342. * example1: {data1: "xyz", data2: "abc"}
  1343. * returns [{key: "data1", value: "xyz"}, {key: "data2", value: "abc"}]
  1344. *
  1345. * example2: [ {data1: "xyz", data2: "abc"} ]
  1346. * returns [{key: "data1", value: "xyz"}, {key: "data2", value: "abc"}]
  1347. *
  1348. * If you set 'readArray', the reader expexts the object as array:
  1349. *
  1350. * example3: [ { key: "data1", value: "xyz", p2: "cde" }, { key: "data2", value: "abc", p2: "efg" }]
  1351. * returns [{key: "data1", value: "xyz", p2: "cde}, {key: "data2", value: "abc", p2: "efg"}]
  1352. *
  1353. * Note: The records can contain additional properties (like 'p2' above) when you use 'readArray'
  1354. *
  1355. * Additional feature: specify allowed properties with default values with 'rows' object
  1356. *
  1357. * var rows = {
  1358. * memory: {
  1359. * required: true,
  1360. * defaultValue: 512
  1361. * }
  1362. * }
  1363. *
  1364. */
  1365.  
  1366. Ext.define('Proxmox.data.reader.JsonObject', {
  1367. extend: 'Ext.data.reader.Json',
  1368. alias : 'reader.jsonobject',
  1369.  
  1370. readArray: false,
  1371.  
  1372. rows: undefined,
  1373.  
  1374. constructor: function(config) {
  1375. var me = this;
  1376.  
  1377. Ext.apply(me, config || {});
  1378.  
  1379. me.callParent([config]);
  1380. },
  1381.  
  1382. getResponseData: function(response) {
  1383. var me = this;
  1384.  
  1385. var data = [];
  1386. try {
  1387. var result = Ext.decode(response.responseText);
  1388. // get our data items inside the server response
  1389. var root = result[me.getRootProperty()];
  1390.  
  1391. if (me.readArray) {
  1392.  
  1393. var rec_hash = {};
  1394. Ext.Array.each(root, function(rec) {
  1395. if (Ext.isDefined(rec.key)) {
  1396. rec_hash[rec.key] = rec;
  1397. }
  1398. });
  1399.  
  1400. if (me.rows) {
  1401. Ext.Object.each(me.rows, function(key, rowdef) {
  1402. var rec = rec_hash[key];
  1403. if (Ext.isDefined(rec)) {
  1404. if (!Ext.isDefined(rec.value)) {
  1405. rec.value = rowdef.defaultValue;
  1406. }
  1407. data.push(rec);
  1408. } else if (Ext.isDefined(rowdef.defaultValue)) {
  1409. data.push({key: key, value: rowdef.defaultValue} );
  1410. } else if (rowdef.required) {
  1411. data.push({key: key, value: undefined });
  1412. }
  1413. });
  1414. } else {
  1415. Ext.Array.each(root, function(rec) {
  1416. if (Ext.isDefined(rec.key)) {
  1417. data.push(rec);
  1418. }
  1419. });
  1420. }
  1421.  
  1422. } else {
  1423.  
  1424. var org_root = root;
  1425.  
  1426. if (Ext.isArray(org_root)) {
  1427. if (root.length == 1) {
  1428. root = org_root[0];
  1429. } else {
  1430. root = {};
  1431. }
  1432. }
  1433.  
  1434. if (me.rows) {
  1435. Ext.Object.each(me.rows, function(key, rowdef) {
  1436. if (Ext.isDefined(root[key])) {
  1437. data.push({key: key, value: root[key]});
  1438. } else if (Ext.isDefined(rowdef.defaultValue)) {
  1439. data.push({key: key, value: rowdef.defaultValue});
  1440. } else if (rowdef.required) {
  1441. data.push({key: key, value: undefined});
  1442. }
  1443. });
  1444. } else {
  1445. Ext.Object.each(root, function(key, value) {
  1446. data.push({key: key, value: value });
  1447. });
  1448. }
  1449. }
  1450. }
  1451. catch (ex) {
  1452. Ext.Error.raise({
  1453. response: response,
  1454. json: response.responseText,
  1455. parseError: ex,
  1456. msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
  1457. });
  1458. }
  1459.  
  1460. return data;
  1461. }
  1462. });
  1463.  
  1464. Ext.define('Proxmox.RestProxy', {
  1465. extend: 'Ext.data.RestProxy',
  1466. alias : 'proxy.proxmox',
  1467.  
  1468. pageParam : null,
  1469. startParam: null,
  1470. limitParam: null,
  1471. groupParam: null,
  1472. sortParam: null,
  1473. filterParam: null,
  1474. noCache : false,
  1475.  
  1476. afterRequest: function(request, success) {
  1477. this.fireEvent('afterload', this, request, success);
  1478. return;
  1479. },
  1480.  
  1481. constructor: function(config) {
  1482.  
  1483. Ext.applyIf(config, {
  1484. reader: {
  1485. type: 'json',
  1486. rootProperty: config.root || 'data'
  1487. }
  1488. });
  1489.  
  1490. this.callParent([config]);
  1491. }
  1492. }, function() {
  1493.  
  1494. Ext.define('KeyValue', {
  1495. extend: "Ext.data.Model",
  1496. fields: [ 'key', 'value' ],
  1497. idProperty: 'key'
  1498. });
  1499.  
  1500. Ext.define('KeyValuePendingDelete', {
  1501. extend: "Ext.data.Model",
  1502. fields: [ 'key', 'value', 'pending', 'delete' ],
  1503. idProperty: 'key'
  1504. });
  1505.  
  1506. Ext.define('proxmox-tasks', {
  1507. extend: 'Ext.data.Model',
  1508. fields: [
  1509. { name: 'starttime', type : 'date', dateFormat: 'timestamp' },
  1510. { name: 'endtime', type : 'date', dateFormat: 'timestamp' },
  1511. { name: 'pid', type: 'int' },
  1512. 'node', 'upid', 'user', 'status', 'type', 'id'
  1513. ],
  1514. idProperty: 'upid'
  1515. });
  1516.  
  1517. Ext.define('proxmox-cluster-log', {
  1518. extend: 'Ext.data.Model',
  1519. fields: [
  1520. { name: 'uid' , type: 'int' },
  1521. { name: 'time', type : 'date', dateFormat: 'timestamp' },
  1522. { name: 'pri', type: 'int' },
  1523. { name: 'pid', type: 'int' },
  1524. 'node', 'user', 'tag', 'msg',
  1525. {
  1526. name: 'id',
  1527. convert: function(value, record) {
  1528. var info = record.data;
  1529. var text;
  1530.  
  1531. if (value) {
  1532. return value;
  1533. }
  1534. // compute unique ID
  1535. return info.uid + ':' + info.node;
  1536. }
  1537. }
  1538. ],
  1539. idProperty: 'id'
  1540. });
  1541.  
  1542. });
  1543. /* Extends the Ext.data.Store type
  1544. * with startUpdate() and stopUpdate() methods
  1545. * to refresh the store data in the background
  1546. * Components using this store directly will flicker
  1547. * due to the redisplay of the element ater 'config.interval' ms
  1548. *
  1549. * Note that you have to call yourself startUpdate() for the background load
  1550. * to begin
  1551. */
  1552. Ext.define('Proxmox.data.UpdateStore', {
  1553. extend: 'Ext.data.Store',
  1554. alias: 'store.update',
  1555.  
  1556. isStopped: true,
  1557.  
  1558. autoStart: false,
  1559.  
  1560. destroy: function() {
  1561. var me = this;
  1562. me.stopUpdate();
  1563. me.callParent();
  1564. },
  1565.  
  1566. constructor: function(config) {
  1567. var me = this;
  1568.  
  1569. config = config || {};
  1570.  
  1571. if (!config.interval) {
  1572. config.interval = 3000;
  1573. }
  1574.  
  1575. if (!config.storeid) {
  1576. throw "no storeid specified";
  1577. }
  1578.  
  1579. var load_task = new Ext.util.DelayedTask();
  1580.  
  1581. var run_load_task = function() {
  1582. if (me.isStopped) {
  1583. return;
  1584. }
  1585.  
  1586. if (Proxmox.Utils.authOK()) {
  1587. var start = new Date();
  1588. me.load(function() {
  1589. var runtime = (new Date()) - start;
  1590. var interval = config.interval + runtime*2;
  1591. load_task.delay(interval, run_load_task);
  1592. });
  1593. } else {
  1594. load_task.delay(200, run_load_task);
  1595. }
  1596. };
  1597.  
  1598. Ext.apply(config, {
  1599. startUpdate: function() {
  1600. me.isStopped = false;
  1601. // run_load_task(); this makes problems with chrome
  1602. load_task.delay(1, run_load_task);
  1603. },
  1604. stopUpdate: function() {
  1605. me.isStopped = true;
  1606. load_task.cancel();
  1607. }
  1608. });
  1609.  
  1610. me.callParent([config]);
  1611.  
  1612. me.load_task = load_task;
  1613.  
  1614. if (me.autoStart) {
  1615. me.startUpdate();
  1616. }
  1617. }
  1618. });
  1619. /*
  1620. * The DiffStore is a in-memory store acting as proxy between a real store
  1621. * instance and a component.
  1622. * Its purpose is to redisplay the component *only* if the data has been changed
  1623. * inside the real store, to avoid the annoying visual flickering of using
  1624. * the real store directly.
  1625. *
  1626. * Implementation:
  1627. * The DiffStore monitors via mon() the 'load' events sent by the real store.
  1628. * On each 'load' event, the DiffStore compares its own content with the target
  1629. * store (call to cond_add_item()) and then fires a 'refresh' event.
  1630. * The 'refresh' event will automatically trigger a view refresh on the component
  1631. * who binds to this store.
  1632. */
  1633.  
  1634. /* Config properties:
  1635. * rstore: the realstore which will autorefresh its content from the API
  1636. * Only works if rstore has a model and use 'idProperty'
  1637. * sortAfterUpdate: sort the diffstore before rendering the view
  1638. */
  1639. Ext.define('Proxmox.data.DiffStore', {
  1640. extend: 'Ext.data.Store',
  1641. alias: 'store.diff',
  1642.  
  1643. sortAfterUpdate: false,
  1644.  
  1645. constructor: function(config) {
  1646. var me = this;
  1647.  
  1648. config = config || {};
  1649.  
  1650. if (!config.rstore) {
  1651. throw "no rstore specified";
  1652. }
  1653.  
  1654. if (!config.rstore.model) {
  1655. throw "no rstore model specified";
  1656. }
  1657.  
  1658. var rstore = config.rstore;
  1659.  
  1660. Ext.apply(config, {
  1661. model: rstore.model,
  1662. proxy: { type: 'memory' }
  1663. });
  1664.  
  1665. me.callParent([config]);
  1666.  
  1667. var first_load = true;
  1668.  
  1669. var cond_add_item = function(data, id) {
  1670. var olditem = me.getById(id);
  1671. if (olditem) {
  1672. olditem.beginEdit();
  1673. Ext.Array.each(me.model.prototype.fields, function(field) {
  1674. if (olditem.data[field.name] !== data[field.name]) {
  1675. olditem.set(field.name, data[field.name]);
  1676. }
  1677. });
  1678. olditem.endEdit(true);
  1679. olditem.commit();
  1680. } else {
  1681. var newrec = Ext.create(me.model, data);
  1682. var pos = (me.appendAtStart && !first_load) ? 0 : me.data.length;
  1683. me.insert(pos, newrec);
  1684. }
  1685. };
  1686.  
  1687. var loadFn = function(s, records, success) {
  1688.  
  1689. if (!success) {
  1690. return;
  1691. }
  1692.  
  1693. me.suspendEvents();
  1694.  
  1695. // getSource returns null if data is not filtered
  1696. // if it is filtered it returns all records
  1697. var allItems = me.getData().getSource() || me.getData();
  1698.  
  1699. // remove vanished items
  1700. allItems.each(function(olditem) {
  1701. var item = rstore.getById(olditem.getId());
  1702. if (!item) {
  1703. me.remove(olditem);
  1704. }
  1705. });
  1706.  
  1707. rstore.each(function(item) {
  1708. cond_add_item(item.data, item.getId());
  1709. });
  1710.  
  1711. me.filter();
  1712.  
  1713. if (me.sortAfterUpdate) {
  1714. me.sort();
  1715. }
  1716.  
  1717. first_load = false;
  1718.  
  1719. me.resumeEvents();
  1720. me.fireEvent('refresh', me);
  1721. me.fireEvent('datachanged', me);
  1722. };
  1723.  
  1724. if (rstore.isLoaded()) {
  1725. // if store is already loaded,
  1726. // insert items instantly
  1727. loadFn(rstore, [], true);
  1728. }
  1729.  
  1730. me.mon(rstore, 'load', loadFn);
  1731. }
  1732. });
  1733. /* This store encapsulates data items which are organized as an Array of key-values Objects
  1734. * ie data[0] contains something like {key: "keyboard", value: "da"}
  1735. *
  1736. * Designed to work with the KeyValue model and the JsonObject data reader
  1737. */
  1738. Ext.define('Proxmox.data.ObjectStore', {
  1739. extend: 'Proxmox.data.UpdateStore',
  1740.  
  1741. getRecord: function() {
  1742. var me = this;
  1743. var record = Ext.create('Ext.data.Model');
  1744. me.getData().each(function(item) {
  1745. record.set(item.data.key, item.data.value);
  1746. });
  1747. record.commit(true);
  1748. return record;
  1749. },
  1750.  
  1751. constructor: function(config) {
  1752. var me = this;
  1753.  
  1754. config = config || {};
  1755.  
  1756. if (!config.storeid) {
  1757. config.storeid = 'proxmox-store-' + (++Ext.idSeed);
  1758. }
  1759.  
  1760. Ext.applyIf(config, {
  1761. model: 'KeyValue',
  1762. proxy: {
  1763. type: 'proxmox',
  1764. url: config.url,
  1765. extraParams: config.extraParams,
  1766. reader: {
  1767. type: 'jsonobject',
  1768. rows: config.rows,
  1769. readArray: config.readArray,
  1770. rootProperty: config.root || 'data'
  1771. }
  1772. }
  1773. });
  1774.  
  1775. me.callParent([config]);
  1776. }
  1777. });
  1778. /* Extends the Proxmox.data.UpdateStore type
  1779. *
  1780. *
  1781. */
  1782. Ext.define('Proxmox.data.RRDStore', {
  1783. extend: 'Proxmox.data.UpdateStore',
  1784. alias: 'store.proxmoxRRDStore',
  1785.  
  1786. setRRDUrl: function(timeframe, cf) {
  1787. var me = this;
  1788. if (!timeframe) {
  1789. timeframe = me.timeframe;
  1790. }
  1791.  
  1792. if (!cf) {
  1793. cf = me.cf;
  1794. }
  1795.  
  1796. me.proxy.url = me.rrdurl + "?timeframe=" + timeframe + "&cf=" + cf;
  1797. },
  1798.  
  1799. proxy: {
  1800. type: 'proxmox'
  1801. },
  1802.  
  1803. timeframe: 'hour',
  1804.  
  1805. cf: 'AVERAGE',
  1806.  
  1807. constructor: function(config) {
  1808. var me = this;
  1809.  
  1810. config = config || {};
  1811.  
  1812. // set default interval to 30seconds
  1813. if (!config.interval) {
  1814. config.interval = 30000;
  1815. }
  1816.  
  1817. // set a new storeid
  1818. if (!config.storeid) {
  1819. config.storeid = 'rrdstore-' + (++Ext.idSeed);
  1820. }
  1821.  
  1822. // rrdurl is required
  1823. if (!config.rrdurl) {
  1824. throw "no rrdurl specified";
  1825. }
  1826.  
  1827. var stateid = 'proxmoxRRDTypeSelection';
  1828. var sp = Ext.state.Manager.getProvider();
  1829. var stateinit = sp.get(stateid);
  1830.  
  1831. if (stateinit) {
  1832. if(stateinit.timeframe !== me.timeframe || stateinit.cf !== me.rrdcffn){
  1833. me.timeframe = stateinit.timeframe;
  1834. me.rrdcffn = stateinit.cf;
  1835. }
  1836. }
  1837.  
  1838. me.callParent([config]);
  1839.  
  1840. me.setRRDUrl();
  1841. me.mon(sp, 'statechange', function(prov, key, state){
  1842. if (key === stateid) {
  1843. if (state && state.id) {
  1844. if (state.timeframe !== me.timeframe || state.cf !== me.cf) {
  1845. me.timeframe = state.timeframe;
  1846. me.cf = state.cf;
  1847. me.setRRDUrl();
  1848. me.reload();
  1849. }
  1850. }
  1851. }
  1852. });
  1853. }
  1854. });
  1855. Ext.define('Timezone', {
  1856. extend: 'Ext.data.Model',
  1857. fields: ['zone']
  1858. });
  1859.  
  1860. Ext.define('Proxmox.data.TimezoneStore', {
  1861. extend: 'Ext.data.Store',
  1862. model: 'Timezone',
  1863. data: [
  1864. ['Africa/Abidjan'],
  1865. ['Africa/Accra'],
  1866. ['Africa/Addis_Ababa'],
  1867. ['Africa/Algiers'],
  1868. ['Africa/Asmara'],
  1869. ['Africa/Bamako'],
  1870. ['Africa/Bangui'],
  1871. ['Africa/Banjul'],
  1872. ['Africa/Bissau'],
  1873. ['Africa/Blantyre'],
  1874. ['Africa/Brazzaville'],
  1875. ['Africa/Bujumbura'],
  1876. ['Africa/Cairo'],
  1877. ['Africa/Casablanca'],
  1878. ['Africa/Ceuta'],
  1879. ['Africa/Conakry'],
  1880. ['Africa/Dakar'],
  1881. ['Africa/Dar_es_Salaam'],
  1882. ['Africa/Djibouti'],
  1883. ['Africa/Douala'],
  1884. ['Africa/El_Aaiun'],
  1885. ['Africa/Freetown'],
  1886. ['Africa/Gaborone'],
  1887. ['Africa/Harare'],
  1888. ['Africa/Johannesburg'],
  1889. ['Africa/Kampala'],
  1890. ['Africa/Khartoum'],
  1891. ['Africa/Kigali'],
  1892. ['Africa/Kinshasa'],
  1893. ['Africa/Lagos'],
  1894. ['Africa/Libreville'],
  1895. ['Africa/Lome'],
  1896. ['Africa/Luanda'],
  1897. ['Africa/Lubumbashi'],
  1898. ['Africa/Lusaka'],
  1899. ['Africa/Malabo'],
  1900. ['Africa/Maputo'],
  1901. ['Africa/Maseru'],
  1902. ['Africa/Mbabane'],
  1903. ['Africa/Mogadishu'],
  1904. ['Africa/Monrovia'],
  1905. ['Africa/Nairobi'],
  1906. ['Africa/Ndjamena'],
  1907. ['Africa/Niamey'],
  1908. ['Africa/Nouakchott'],
  1909. ['Africa/Ouagadougou'],
  1910. ['Africa/Porto-Novo'],
  1911. ['Africa/Sao_Tome'],
  1912. ['Africa/Tripoli'],
  1913. ['Africa/Tunis'],
  1914. ['Africa/Windhoek'],
  1915. ['America/Adak'],
  1916. ['America/Anchorage'],
  1917. ['America/Anguilla'],
  1918. ['America/Antigua'],
  1919. ['America/Araguaina'],
  1920. ['America/Argentina/Buenos_Aires'],
  1921. ['America/Argentina/Catamarca'],
  1922. ['America/Argentina/Cordoba'],
  1923. ['America/Argentina/Jujuy'],
  1924. ['America/Argentina/La_Rioja'],
  1925. ['America/Argentina/Mendoza'],
  1926. ['America/Argentina/Rio_Gallegos'],
  1927. ['America/Argentina/Salta'],
  1928. ['America/Argentina/San_Juan'],
  1929. ['America/Argentina/San_Luis'],
  1930. ['America/Argentina/Tucuman'],
  1931. ['America/Argentina/Ushuaia'],
  1932. ['America/Aruba'],
  1933. ['America/Asuncion'],
  1934. ['America/Atikokan'],
  1935. ['America/Bahia'],
  1936. ['America/Bahia_Banderas'],
  1937. ['America/Barbados'],
  1938. ['America/Belem'],
  1939. ['America/Belize'],
  1940. ['America/Blanc-Sablon'],
  1941. ['America/Boa_Vista'],
  1942. ['America/Bogota'],
  1943. ['America/Boise'],
  1944. ['America/Cambridge_Bay'],
  1945. ['America/Campo_Grande'],
  1946. ['America/Cancun'],
  1947. ['America/Caracas'],
  1948. ['America/Cayenne'],
  1949. ['America/Cayman'],
  1950. ['America/Chicago'],
  1951. ['America/Chihuahua'],
  1952. ['America/Costa_Rica'],
  1953. ['America/Cuiaba'],
  1954. ['America/Curacao'],
  1955. ['America/Danmarkshavn'],
  1956. ['America/Dawson'],
  1957. ['America/Dawson_Creek'],
  1958. ['America/Denver'],
  1959. ['America/Detroit'],
  1960. ['America/Dominica'],
  1961. ['America/Edmonton'],
  1962. ['America/Eirunepe'],
  1963. ['America/El_Salvador'],
  1964. ['America/Fortaleza'],
  1965. ['America/Glace_Bay'],
  1966. ['America/Godthab'],
  1967. ['America/Goose_Bay'],
  1968. ['America/Grand_Turk'],
  1969. ['America/Grenada'],
  1970. ['America/Guadeloupe'],
  1971. ['America/Guatemala'],
  1972. ['America/Guayaquil'],
  1973. ['America/Guyana'],
  1974. ['America/Halifax'],
  1975. ['America/Havana'],
  1976. ['America/Hermosillo'],
  1977. ['America/Indiana/Indianapolis'],
  1978. ['America/Indiana/Knox'],
  1979. ['America/Indiana/Marengo'],
  1980. ['America/Indiana/Petersburg'],
  1981. ['America/Indiana/Tell_City'],
  1982. ['America/Indiana/Vevay'],
  1983. ['America/Indiana/Vincennes'],
  1984. ['America/Indiana/Winamac'],
  1985. ['America/Inuvik'],
  1986. ['America/Iqaluit'],
  1987. ['America/Jamaica'],
  1988. ['America/Juneau'],
  1989. ['America/Kentucky/Louisville'],
  1990. ['America/Kentucky/Monticello'],
  1991. ['America/La_Paz'],
  1992. ['America/Lima'],
  1993. ['America/Los_Angeles'],
  1994. ['America/Maceio'],
  1995. ['America/Managua'],
  1996. ['America/Manaus'],
  1997. ['America/Marigot'],
  1998. ['America/Martinique'],
  1999. ['America/Matamoros'],
  2000. ['America/Mazatlan'],
  2001. ['America/Menominee'],
  2002. ['America/Merida'],
  2003. ['America/Mexico_City'],
  2004. ['America/Miquelon'],
  2005. ['America/Moncton'],
  2006. ['America/Monterrey'],
  2007. ['America/Montevideo'],
  2008. ['America/Montreal'],
  2009. ['America/Montserrat'],
  2010. ['America/Nassau'],
  2011. ['America/New_York'],
  2012. ['America/Nipigon'],
  2013. ['America/Nome'],
  2014. ['America/Noronha'],
  2015. ['America/North_Dakota/Center'],
  2016. ['America/North_Dakota/New_Salem'],
  2017. ['America/Ojinaga'],
  2018. ['America/Panama'],
  2019. ['America/Pangnirtung'],
  2020. ['America/Paramaribo'],
  2021. ['America/Phoenix'],
  2022. ['America/Port-au-Prince'],
  2023. ['America/Port_of_Spain'],
  2024. ['America/Porto_Velho'],
  2025. ['America/Puerto_Rico'],
  2026. ['America/Rainy_River'],
  2027. ['America/Rankin_Inlet'],
  2028. ['America/Recife'],
  2029. ['America/Regina'],
  2030. ['America/Resolute'],
  2031. ['America/Rio_Branco'],
  2032. ['America/Santa_Isabel'],
  2033. ['America/Santarem'],
  2034. ['America/Santiago'],
  2035. ['America/Santo_Domingo'],
  2036. ['America/Sao_Paulo'],
  2037. ['America/Scoresbysund'],
  2038. ['America/Shiprock'],
  2039. ['America/St_Barthelemy'],
  2040. ['America/St_Johns'],
  2041. ['America/St_Kitts'],
  2042. ['America/St_Lucia'],
  2043. ['America/St_Thomas'],
  2044. ['America/St_Vincent'],
  2045. ['America/Swift_Current'],
  2046. ['America/Tegucigalpa'],
  2047. ['America/Thule'],
  2048. ['America/Thunder_Bay'],
  2049. ['America/Tijuana'],
  2050. ['America/Toronto'],
  2051. ['America/Tortola'],
  2052. ['America/Vancouver'],
  2053. ['America/Whitehorse'],
  2054. ['America/Winnipeg'],
  2055. ['America/Yakutat'],
  2056. ['America/Yellowknife'],
  2057. ['Antarctica/Casey'],
  2058. ['Antarctica/Davis'],
  2059. ['Antarctica/DumontDUrville'],
  2060. ['Antarctica/Macquarie'],
  2061. ['Antarctica/Mawson'],
  2062. ['Antarctica/McMurdo'],
  2063. ['Antarctica/Palmer'],
  2064. ['Antarctica/Rothera'],
  2065. ['Antarctica/South_Pole'],
  2066. ['Antarctica/Syowa'],
  2067. ['Antarctica/Vostok'],
  2068. ['Arctic/Longyearbyen'],
  2069. ['Asia/Aden'],
  2070. ['Asia/Almaty'],
  2071. ['Asia/Amman'],
  2072. ['Asia/Anadyr'],
  2073. ['Asia/Aqtau'],
  2074. ['Asia/Aqtobe'],
  2075. ['Asia/Ashgabat'],
  2076. ['Asia/Baghdad'],
  2077. ['Asia/Bahrain'],
  2078. ['Asia/Baku'],
  2079. ['Asia/Bangkok'],
  2080. ['Asia/Beirut'],
  2081. ['Asia/Bishkek'],
  2082. ['Asia/Brunei'],
  2083. ['Asia/Choibalsan'],
  2084. ['Asia/Chongqing'],
  2085. ['Asia/Colombo'],
  2086. ['Asia/Damascus'],
  2087. ['Asia/Dhaka'],
  2088. ['Asia/Dili'],
  2089. ['Asia/Dubai'],
  2090. ['Asia/Dushanbe'],
  2091. ['Asia/Gaza'],
  2092. ['Asia/Harbin'],
  2093. ['Asia/Ho_Chi_Minh'],
  2094. ['Asia/Hong_Kong'],
  2095. ['Asia/Hovd'],
  2096. ['Asia/Irkutsk'],
  2097. ['Asia/Jakarta'],
  2098. ['Asia/Jayapura'],
  2099. ['Asia/Jerusalem'],
  2100. ['Asia/Kabul'],
  2101. ['Asia/Kamchatka'],
  2102. ['Asia/Karachi'],
  2103. ['Asia/Kashgar'],
  2104. ['Asia/Kathmandu'],
  2105. ['Asia/Kolkata'],
  2106. ['Asia/Krasnoyarsk'],
  2107. ['Asia/Kuala_Lumpur'],
  2108. ['Asia/Kuching'],
  2109. ['Asia/Kuwait'],
  2110. ['Asia/Macau'],
  2111. ['Asia/Magadan'],
  2112. ['Asia/Makassar'],
  2113. ['Asia/Manila'],
  2114. ['Asia/Muscat'],
  2115. ['Asia/Nicosia'],
  2116. ['Asia/Novokuznetsk'],
  2117. ['Asia/Novosibirsk'],
  2118. ['Asia/Omsk'],
  2119. ['Asia/Oral'],
  2120. ['Asia/Phnom_Penh'],
  2121. ['Asia/Pontianak'],
  2122. ['Asia/Pyongyang'],
  2123. ['Asia/Qatar'],
  2124. ['Asia/Qyzylorda'],
  2125. ['Asia/Rangoon'],
  2126. ['Asia/Riyadh'],
  2127. ['Asia/Sakhalin'],
  2128. ['Asia/Samarkand'],
  2129. ['Asia/Seoul'],
  2130. ['Asia/Shanghai'],
  2131. ['Asia/Singapore'],
  2132. ['Asia/Taipei'],
  2133. ['Asia/Tashkent'],
  2134. ['Asia/Tbilisi'],
  2135. ['Asia/Tehran'],
  2136. ['Asia/Thimphu'],
  2137. ['Asia/Tokyo'],
  2138. ['Asia/Ulaanbaatar'],
  2139. ['Asia/Urumqi'],
  2140. ['Asia/Vientiane'],
  2141. ['Asia/Vladivostok'],
  2142. ['Asia/Yakutsk'],
  2143. ['Asia/Yekaterinburg'],
  2144. ['Asia/Yerevan'],
  2145. ['Atlantic/Azores'],
  2146. ['Atlantic/Bermuda'],
  2147. ['Atlantic/Canary'],
  2148. ['Atlantic/Cape_Verde'],
  2149. ['Atlantic/Faroe'],
  2150. ['Atlantic/Madeira'],
  2151. ['Atlantic/Reykjavik'],
  2152. ['Atlantic/South_Georgia'],
  2153. ['Atlantic/St_Helena'],
  2154. ['Atlantic/Stanley'],
  2155. ['Australia/Adelaide'],
  2156. ['Australia/Brisbane'],
  2157. ['Australia/Broken_Hill'],
  2158. ['Australia/Currie'],
  2159. ['Australia/Darwin'],
  2160. ['Australia/Eucla'],
  2161. ['Australia/Hobart'],
  2162. ['Australia/Lindeman'],
  2163. ['Australia/Lord_Howe'],
  2164. ['Australia/Melbourne'],
  2165. ['Australia/Perth'],
  2166. ['Australia/Sydney'],
  2167. ['Europe/Amsterdam'],
  2168. ['Europe/Andorra'],
  2169. ['Europe/Athens'],
  2170. ['Europe/Belgrade'],
  2171. ['Europe/Berlin'],
  2172. ['Europe/Bratislava'],
  2173. ['Europe/Brussels'],
  2174. ['Europe/Bucharest'],
  2175. ['Europe/Budapest'],
  2176. ['Europe/Chisinau'],
  2177. ['Europe/Copenhagen'],
  2178. ['Europe/Dublin'],
  2179. ['Europe/Gibraltar'],
  2180. ['Europe/Guernsey'],
  2181. ['Europe/Helsinki'],
  2182. ['Europe/Isle_of_Man'],
  2183. ['Europe/Istanbul'],
  2184. ['Europe/Jersey'],
  2185. ['Europe/Kaliningrad'],
  2186. ['Europe/Kiev'],
  2187. ['Europe/Lisbon'],
  2188. ['Europe/Ljubljana'],
  2189. ['Europe/London'],
  2190. ['Europe/Luxembourg'],
  2191. ['Europe/Madrid'],
  2192. ['Europe/Malta'],
  2193. ['Europe/Mariehamn'],
  2194. ['Europe/Minsk'],
  2195. ['Europe/Monaco'],
  2196. ['Europe/Moscow'],
  2197. ['Europe/Oslo'],
  2198. ['Europe/Paris'],
  2199. ['Europe/Podgorica'],
  2200. ['Europe/Prague'],
  2201. ['Europe/Riga'],
  2202. ['Europe/Rome'],
  2203. ['Europe/Samara'],
  2204. ['Europe/San_Marino'],
  2205. ['Europe/Sarajevo'],
  2206. ['Europe/Simferopol'],
  2207. ['Europe/Skopje'],
  2208. ['Europe/Sofia'],
  2209. ['Europe/Stockholm'],
  2210. ['Europe/Tallinn'],
  2211. ['Europe/Tirane'],
  2212. ['Europe/Uzhgorod'],
  2213. ['Europe/Vaduz'],
  2214. ['Europe/Vatican'],
  2215. ['Europe/Vienna'],
  2216. ['Europe/Vilnius'],
  2217. ['Europe/Volgograd'],
  2218. ['Europe/Warsaw'],
  2219. ['Europe/Zagreb'],
  2220. ['Europe/Zaporozhye'],
  2221. ['Europe/Zurich'],
  2222. ['Indian/Antananarivo'],
  2223. ['Indian/Chagos'],
  2224. ['Indian/Christmas'],
  2225. ['Indian/Cocos'],
  2226. ['Indian/Comoro'],
  2227. ['Indian/Kerguelen'],
  2228. ['Indian/Mahe'],
  2229. ['Indian/Maldives'],
  2230. ['Indian/Mauritius'],
  2231. ['Indian/Mayotte'],
  2232. ['Indian/Reunion'],
  2233. ['Pacific/Apia'],
  2234. ['Pacific/Auckland'],
  2235. ['Pacific/Chatham'],
  2236. ['Pacific/Chuuk'],
  2237. ['Pacific/Easter'],
  2238. ['Pacific/Efate'],
  2239. ['Pacific/Enderbury'],
  2240. ['Pacific/Fakaofo'],
  2241. ['Pacific/Fiji'],
  2242. ['Pacific/Funafuti'],
  2243. ['Pacific/Galapagos'],
  2244. ['Pacific/Gambier'],
  2245. ['Pacific/Guadalcanal'],
  2246. ['Pacific/Guam'],
  2247. ['Pacific/Honolulu'],
  2248. ['Pacific/Johnston'],
  2249. ['Pacific/Kiritimati'],
  2250. ['Pacific/Kosrae'],
  2251. ['Pacific/Kwajalein'],
  2252. ['Pacific/Majuro'],
  2253. ['Pacific/Marquesas'],
  2254. ['Pacific/Midway'],
  2255. ['Pacific/Nauru'],
  2256. ['Pacific/Niue'],
  2257. ['Pacific/Norfolk'],
  2258. ['Pacific/Noumea'],
  2259. ['Pacific/Pago_Pago'],
  2260. ['Pacific/Palau'],
  2261. ['Pacific/Pitcairn'],
  2262. ['Pacific/Pohnpei'],
  2263. ['Pacific/Port_Moresby'],
  2264. ['Pacific/Rarotonga'],
  2265. ['Pacific/Saipan'],
  2266. ['Pacific/Tahiti'],
  2267. ['Pacific/Tarawa'],
  2268. ['Pacific/Tongatapu'],
  2269. ['Pacific/Wake'],
  2270. ['Pacific/Wallis']
  2271. ]
  2272. });
  2273. Ext.define('Proxmox.form.field.Integer',{
  2274. extend: 'Ext.form.field.Number',
  2275. alias: 'widget.proxmoxintegerfield',
  2276.  
  2277. config: {
  2278. deleteEmpty: false
  2279. },
  2280.  
  2281. allowDecimals: false,
  2282. allowExponential: false,
  2283. step: 1,
  2284.  
  2285. getSubmitData: function() {
  2286. var me = this,
  2287. data = null,
  2288. val;
  2289. if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  2290. val = me.getSubmitValue();
  2291. if (val !== undefined && val !== null && val !== '') {
  2292. data = {};
  2293. data[me.getName()] = val;
  2294. } else if (me.getDeleteEmpty()) {
  2295. data = {};
  2296. data['delete'] = me.getName();
  2297. }
  2298. }
  2299. return data;
  2300. }
  2301.  
  2302. });
  2303. Ext.define('Proxmox.form.field.Textfield', {
  2304. extend: 'Ext.form.field.Text',
  2305. alias: ['widget.proxmoxtextfield'],
  2306.  
  2307. config: {
  2308. skipEmptyText: true,
  2309.  
  2310. deleteEmpty: false,
  2311. },
  2312.  
  2313. getSubmitData: function() {
  2314. var me = this,
  2315. data = null,
  2316. val;
  2317. if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  2318. val = me.getSubmitValue();
  2319. if (val !== null) {
  2320. data = {};
  2321. data[me.getName()] = val;
  2322. } else if (me.getDeleteEmpty()) {
  2323. data = {};
  2324. data['delete'] = me.getName();
  2325. }
  2326. }
  2327. return data;
  2328. },
  2329.  
  2330. getSubmitValue: function() {
  2331. var me = this;
  2332.  
  2333. var value = this.processRawValue(this.getRawValue());
  2334. if (value !== '') {
  2335. return value;
  2336. }
  2337.  
  2338. return me.getSkipEmptyText() ? null: value;
  2339. }
  2340. });
  2341. Ext.define('Proxmox.DateTimeField', {
  2342. extend: 'Ext.form.FieldContainer',
  2343. xtype: 'promxoxDateTimeField',
  2344.  
  2345. layout: 'hbox',
  2346.  
  2347. referenceHolder: true,
  2348.  
  2349. submitFormat: 'U',
  2350.  
  2351. getValue: function() {
  2352. var me = this;
  2353. var d = me.lookupReference('dateentry').getValue();
  2354.  
  2355. if (d === undefined || d === null) { return null; }
  2356.  
  2357. var t = me.lookupReference('timeentry').getValue();
  2358.  
  2359. if (t === undefined || t === null) { return null; }
  2360.  
  2361. var offset = (t.getHours()*3600+t.getMinutes()*60)*1000;
  2362.  
  2363. return new Date(d.getTime() + offset);
  2364. },
  2365.  
  2366. getSubmitValue: function() {
  2367. var me = this;
  2368. var format = me.submitFormat;
  2369. var value = me.getValue();
  2370.  
  2371. return value ? Ext.Date.format(value, format) : null;
  2372. },
  2373.  
  2374. items: [
  2375. {
  2376. xtype: 'datefield',
  2377. editable: false,
  2378. reference: 'dateentry',
  2379. flex: 1,
  2380. format: 'Y-m-d'
  2381. },
  2382. {
  2383. xtype: 'timefield',
  2384. reference: 'timeentry',
  2385. format: 'H:i',
  2386. width: 80,
  2387. value: '00:00',
  2388. increment: 60
  2389. }
  2390. ],
  2391.  
  2392. initComponent: function() {
  2393. var me = this;
  2394.  
  2395. me.callParent();
  2396.  
  2397. var value = me.value || new Date();
  2398.  
  2399. me.lookupReference('dateentry').setValue(value);
  2400. me.lookupReference('timeentry').setValue(value);
  2401.  
  2402. me.relayEvents(me.lookupReference('dateentry'), ['change']);
  2403. me.relayEvents(me.lookupReference('timeentry'), ['change']);
  2404. }
  2405. });
  2406. Ext.define('Proxmox.form.Checkbox', {
  2407. extend: 'Ext.form.field.Checkbox',
  2408. alias: ['widget.proxmoxcheckbox'],
  2409.  
  2410. config: {
  2411. defaultValue: undefined,
  2412. deleteDefaultValue: false,
  2413. deleteEmpty: false
  2414. },
  2415.  
  2416. inputValue: '1',
  2417.  
  2418. getSubmitData: function() {
  2419. var me = this,
  2420. data = null,
  2421. val;
  2422. if (!me.disabled && me.submitValue) {
  2423. val = me.getSubmitValue();
  2424. if (val !== null) {
  2425. data = {};
  2426. if ((val == me.getDefaultValue()) && me.getDeleteDefaultValue()) {
  2427. data['delete'] = me.getName();
  2428. } else {
  2429. data[me.getName()] = val;
  2430. }
  2431. } else if (me.getDeleteEmpty()) {
  2432. data = {};
  2433. data['delete'] = me.getName();
  2434. }
  2435. }
  2436. return data;
  2437. },
  2438.  
  2439. // also accept integer 1 as true
  2440. setRawValue: function(value) {
  2441. var me = this;
  2442.  
  2443. if (value === 1) {
  2444. me.callParent([true]);
  2445. } else {
  2446. me.callParent([value]);
  2447. }
  2448. }
  2449.  
  2450. });
  2451. /* Key-Value ComboBox
  2452. *
  2453. * config properties:
  2454. * comboItems: an array of Key - Value pairs
  2455. * deleteEmpty: if set to true (default), an empty value received from the
  2456. * comboBox will reset the property to its default value
  2457. */
  2458. Ext.define('Proxmox.form.KVComboBox', {
  2459. extend: 'Ext.form.field.ComboBox',
  2460. alias: 'widget.proxmoxKVComboBox',
  2461.  
  2462. config: {
  2463. deleteEmpty: true
  2464. },
  2465.  
  2466. comboItems: undefined,
  2467. displayField: 'value',
  2468. valueField: 'key',
  2469. queryMode: 'local',
  2470.  
  2471. // overide framework function to implement deleteEmpty behaviour
  2472. getSubmitData: function() {
  2473. var me = this,
  2474. data = null,
  2475. val;
  2476. if (!me.disabled && me.submitValue) {
  2477. val = me.getSubmitValue();
  2478. if (val !== null && val !== '' && val !== '__default__') {
  2479. data = {};
  2480. data[me.getName()] = val;
  2481. } else if (me.getDeleteEmpty()) {
  2482. data = {};
  2483. data['delete'] = me.getName();
  2484. }
  2485. }
  2486. return data;
  2487. },
  2488.  
  2489. validator: function(val) {
  2490. var me = this;
  2491.  
  2492. if (me.editable || val === null || val === '') {
  2493. return true;
  2494. }
  2495.  
  2496. if (me.store.getCount() > 0) {
  2497. var values = me.multiSelect ? val.split(me.delimiter) : [val];
  2498. var items = me.store.getData().collect('value', 'data');
  2499. if (Ext.Array.every(values, function(value) {
  2500. return Ext.Array.contains(items, value);
  2501. })) {
  2502. return true;
  2503. }
  2504. }
  2505.  
  2506. // returns a boolean or string
  2507. /*jslint confusion: true */
  2508. return "value '" + val + "' not allowed!";
  2509. },
  2510.  
  2511. initComponent: function() {
  2512. var me = this;
  2513.  
  2514. me.store = Ext.create('Ext.data.ArrayStore', {
  2515. model: 'KeyValue',
  2516. data : me.comboItems
  2517. });
  2518.  
  2519. if (me.initialConfig.editable === undefined) {
  2520. me.editable = false;
  2521. }
  2522.  
  2523. me.callParent();
  2524. }
  2525. });
  2526. Ext.define('Proxmox.form.LanguageSelector', {
  2527. extend: 'Proxmox.form.KVComboBox',
  2528. xtype: 'proxmoxLanguageSelector',
  2529.  
  2530. comboItems: Proxmox.Utils.language_array()
  2531. });
  2532. /*
  2533. * ComboGrid component: a ComboBox where the dropdown menu (the
  2534. * "Picker") is a Grid with Rows and Columns expects a listConfig
  2535. * object with a columns property roughly based on the GridPicker from
  2536. * https://www.sencha.com/forum/showthread.php?299909
  2537. *
  2538. */
  2539.  
  2540. Ext.define('Proxmox.form.ComboGrid', {
  2541. extend: 'Ext.form.field.ComboBox',
  2542. alias: ['widget.proxmoxComboGrid'],
  2543.  
  2544. // this value is used as default value after load()
  2545. preferredValue: undefined,
  2546.  
  2547. // hack: allow to select empty value
  2548. // seems extjs does not allow that when 'editable == false'
  2549. onKeyUp: function(e, t) {
  2550. var me = this;
  2551. var key = e.getKey();
  2552.  
  2553. if (!me.editable && me.allowBlank && !me.multiSelect &&
  2554. (key == e.BACKSPACE || key == e.DELETE)) {
  2555. me.setValue('');
  2556. }
  2557.  
  2558. me.callParent(arguments);
  2559. },
  2560.  
  2561. // needed to trigger onKeyUp etc.
  2562. enableKeyEvents: true,
  2563.  
  2564. editable: false,
  2565.  
  2566. // override ExtJS method
  2567. // if the field has multiSelect enabled, the store is not loaded, and
  2568. // the displayfield == valuefield, it saves the rawvalue as an array
  2569. // but the getRawValue method is only defined in the textfield class
  2570. // (which has not to deal with arrays) an returns the string in the
  2571. // field (not an array)
  2572. //
  2573. // so if we have multiselect enabled, return the rawValue (which
  2574. // should be an array) and else we do callParent so
  2575. // it should not impact any other use of the class
  2576. getRawValue: function() {
  2577. var me = this;
  2578. if (me.multiSelect) {
  2579. return me.rawValue;
  2580. } else {
  2581. return me.callParent();
  2582. }
  2583. },
  2584.  
  2585. // override ExtJS protected method
  2586. onBindStore: function(store, initial) {
  2587. var me = this,
  2588. picker = me.picker,
  2589. extraKeySpec,
  2590. valueCollectionConfig;
  2591.  
  2592. // We're being bound, not unbound...
  2593. if (store) {
  2594. // If store was created from a 2 dimensional array with generated field names 'field1' and 'field2'
  2595. if (store.autoCreated) {
  2596. me.queryMode = 'local';
  2597. me.valueField = me.displayField = 'field1';
  2598. if (!store.expanded) {
  2599. me.displayField = 'field2';
  2600. }
  2601.  
  2602. // displayTpl config will need regenerating with the autogenerated displayField name 'field1'
  2603. me.setDisplayTpl(null);
  2604. }
  2605. if (!Ext.isDefined(me.valueField)) {
  2606. me.valueField = me.displayField;
  2607. }
  2608.  
  2609. // Add a byValue index to the store so that we can efficiently look up records by the value field
  2610. // when setValue passes string value(s).
  2611. // The two indices (Ext.util.CollectionKeys) are configured unique: false, so that if duplicate keys
  2612. // are found, they are all returned by the get call.
  2613. // This is so that findByText and findByValue are able to return the *FIRST* matching value. By default,
  2614. // if unique is true, CollectionKey keeps the *last* matching value.
  2615. extraKeySpec = {
  2616. byValue: {
  2617. rootProperty: 'data',
  2618. unique: false
  2619. }
  2620. };
  2621. extraKeySpec.byValue.property = me.valueField;
  2622. store.setExtraKeys(extraKeySpec);
  2623.  
  2624. if (me.displayField === me.valueField) {
  2625. store.byText = store.byValue;
  2626. } else {
  2627. extraKeySpec.byText = {
  2628. rootProperty: 'data',
  2629. unique: false
  2630. };
  2631. extraKeySpec.byText.property = me.displayField;
  2632. store.setExtraKeys(extraKeySpec);
  2633. }
  2634.  
  2635. // We hold a collection of the values which have been selected, keyed by this field's valueField.
  2636. // This collection also functions as the selected items collection for the BoundList's selection model
  2637. valueCollectionConfig = {
  2638. rootProperty: 'data',
  2639. extraKeys: {
  2640. byInternalId: {
  2641. property: 'internalId'
  2642. },
  2643. byValue: {
  2644. property: me.valueField,
  2645. rootProperty: 'data'
  2646. }
  2647. },
  2648. // Whenever this collection is changed by anyone, whether by this field adding to it,
  2649. // or the BoundList operating, we must refresh our value.
  2650. listeners: {
  2651. beginupdate: me.onValueCollectionBeginUpdate,
  2652. endupdate: me.onValueCollectionEndUpdate,
  2653. scope: me
  2654. }
  2655. };
  2656.  
  2657. // This becomes our collection of selected records for the Field.
  2658. me.valueCollection = new Ext.util.Collection(valueCollectionConfig);
  2659.  
  2660. // We use the selected Collection as our value collection and the basis
  2661. // for rendering the tag list.
  2662.  
  2663. //proxmox override: since the picker is represented by a grid panel,
  2664. // we changed here the selection to RowModel
  2665. me.pickerSelectionModel = new Ext.selection.RowModel({
  2666. mode: me.multiSelect ? 'SIMPLE' : 'SINGLE',
  2667. // There are situations when a row is selected on mousedown but then the mouse is dragged to another row
  2668. // and released. In these situations, the event target for the click event won't be the row where the mouse
  2669. // was released but the boundview. The view will then determine that it should fire a container click, and
  2670. // the DataViewModel will then deselect all prior selections. Setting `deselectOnContainerClick` here will
  2671. // prevent the model from deselecting.
  2672. deselectOnContainerClick: false,
  2673. enableInitialSelection: false,
  2674. pruneRemoved: false,
  2675. selected: me.valueCollection,
  2676. store: store,
  2677. listeners: {
  2678. scope: me,
  2679. lastselectedchanged: me.updateBindSelection
  2680. }
  2681. });
  2682.  
  2683. if (!initial) {
  2684. me.resetToDefault();
  2685. }
  2686.  
  2687. if (picker) {
  2688. picker.setSelectionModel(me.pickerSelectionModel);
  2689. if (picker.getStore() !== store) {
  2690. picker.bindStore(store);
  2691. }
  2692. }
  2693. }
  2694. },
  2695.  
  2696. // copied from ComboBox
  2697. createPicker: function() {
  2698. var me = this;
  2699. var picker;
  2700.  
  2701. var pickerCfg = Ext.apply({
  2702. // proxmox overrides: display a grid for selection
  2703. xtype: 'gridpanel',
  2704. id: me.pickerId,
  2705. pickerField: me,
  2706. floating: true,
  2707. hidden: true,
  2708. store: me.store,
  2709. displayField: me.displayField,
  2710. preserveScrollOnRefresh: true,
  2711. pageSize: me.pageSize,
  2712. tpl: me.tpl,
  2713. selModel: me.pickerSelectionModel,
  2714. focusOnToFront: false
  2715. }, me.listConfig, me.defaultListConfig);
  2716.  
  2717. picker = me.picker || Ext.widget(pickerCfg);
  2718.  
  2719. if (picker.getStore() !== me.store) {
  2720. picker.bindStore(me.store);
  2721. }
  2722.  
  2723. if (me.pageSize) {
  2724. picker.pagingToolbar.on('beforechange', me.onPageChange, me);
  2725. }
  2726.  
  2727. // proxmox overrides: pass missing method in gridPanel to its view
  2728. picker.refresh = function() {
  2729. picker.getSelectionModel().select(me.valueCollection.getRange());
  2730. picker.getView().refresh();
  2731. };
  2732. picker.getNodeByRecord = function() {
  2733. picker.getView().getNodeByRecord(arguments);
  2734. };
  2735.  
  2736. // We limit the height of the picker to fit in the space above
  2737. // or below this field unless the picker has its own ideas about that.
  2738. if (!picker.initialConfig.maxHeight) {
  2739. picker.on({
  2740. beforeshow: me.onBeforePickerShow,
  2741. scope: me
  2742. });
  2743. }
  2744. picker.getSelectionModel().on({
  2745. beforeselect: me.onBeforeSelect,
  2746. beforedeselect: me.onBeforeDeselect,
  2747. focuschange: me.onFocusChange,
  2748. selectionChange: function (sm, selectedRecords) {
  2749. var me = this;
  2750. if (selectedRecords.length) {
  2751. me.setValue(selectedRecords);
  2752. me.fireEvent('select', me, selectedRecords);
  2753. }
  2754. },
  2755. scope: me
  2756. });
  2757.  
  2758. // hack for extjs6
  2759. // when the clicked item is the same as the previously selected,
  2760. // it does not select the item
  2761. // instead we hide the picker
  2762. if (!me.multiSelect) {
  2763. picker.on('itemclick', function (sm,record) {
  2764. if (picker.getSelection()[0] === record) {
  2765. picker.hide();
  2766. }
  2767. });
  2768. }
  2769.  
  2770. // when our store is not yet loaded, we increase
  2771. // the height of the gridpanel, so that we can see
  2772. // the loading mask
  2773. //
  2774. // we save the minheight to reset it after the load
  2775. picker.on('show', function() {
  2776. if (me.enableLoadMask) {
  2777. me.savedMinHeight = picker.getMinHeight();
  2778. picker.setMinHeight(100);
  2779. }
  2780. });
  2781.  
  2782. picker.getNavigationModel().navigateOnSpace = false;
  2783.  
  2784. return picker;
  2785. },
  2786.  
  2787. initComponent: function() {
  2788. var me = this;
  2789.  
  2790. Ext.apply(me, {
  2791. queryMode: 'local',
  2792. matchFieldWidth: false
  2793. });
  2794.  
  2795. Ext.applyIf(me, { value: ''}); // hack: avoid ExtJS validate() bug
  2796.  
  2797. Ext.applyIf(me.listConfig, { width: 400 });
  2798.  
  2799. me.callParent();
  2800.  
  2801. // Create the picker at an early stage, so it is available to store the previous selection
  2802. if (!me.picker) {
  2803. me.createPicker();
  2804. }
  2805.  
  2806. if (me.editable) {
  2807. // The trigger.picker causes first a focus event on the field then
  2808. // toggles the selection picker. Thus skip expanding in this case,
  2809. // else our focus listner expands and the picker.trigger then
  2810. // collapses it directly afterwards.
  2811. Ext.override(me.triggers.picker, {
  2812. onMouseDown : function (e) {
  2813. // copied "should we focus" check from Ext.form.trigger.Trigger
  2814. if (e.pointerType !== 'touch' && !this.field.owns(Ext.Element.getActiveElement())) {
  2815. me.skip_expand_on_focus = true;
  2816. }
  2817. this.callParent(arguments);
  2818. }
  2819. });
  2820.  
  2821. me.on("focus", function(me) {
  2822. if (!me.isExpanded && !me.skip_expand_on_focus) {
  2823. me.expand();
  2824. }
  2825. me.skip_expand_on_focus = false;
  2826. });
  2827. }
  2828.  
  2829. me.mon(me.store, 'beforeload', function() {
  2830. if (!me.isDisabled()) {
  2831. me.enableLoadMask = true;
  2832. }
  2833. });
  2834.  
  2835. // hack: autoSelect does not work
  2836. me.mon(me.store, 'load', function(store, r, success, o) {
  2837. if (success) {
  2838. me.clearInvalid();
  2839.  
  2840. if (me.enableLoadMask) {
  2841. delete me.enableLoadMask;
  2842.  
  2843. // if the picker exists,
  2844. // we reset its minheight to the saved var/0
  2845. // we have to update the layout, otherwise the height
  2846. // gets not recalculated
  2847. if (me.picker) {
  2848. me.picker.setMinHeight(me.savedMinHeight || 0);
  2849. delete me.savedMinHeight;
  2850. me.picker.updateLayout();
  2851. }
  2852. }
  2853.  
  2854. var def = me.getValue() || me.preferredValue;
  2855. if (def) {
  2856. me.setValue(def, true); // sync with grid
  2857. }
  2858. var found = false;
  2859. if (def) {
  2860. if (Ext.isArray(def)) {
  2861. Ext.Array.each(def, function(v) {
  2862. if (store.findRecord(me.valueField, v)) {
  2863. found = true;
  2864. return false; // break
  2865. }
  2866. });
  2867. } else {
  2868. found = store.findRecord(me.valueField, def);
  2869. }
  2870. }
  2871.  
  2872. if (!found) {
  2873. var rec = me.store.first();
  2874. if (me.autoSelect && rec && rec.data) {
  2875. def = rec.data[me.valueField];
  2876. me.setValue(def, true);
  2877. } else {
  2878. me.setValue(me.editable ? def : '', true);
  2879. }
  2880. }
  2881. }
  2882. });
  2883. }
  2884. });
  2885. Ext.define('Proxmox.form.RRDTypeSelector', {
  2886. extend: 'Ext.form.field.ComboBox',
  2887. alias: ['widget.proxmoxRRDTypeSelector'],
  2888.  
  2889. displayField: 'text',
  2890. valueField: 'id',
  2891. editable: false,
  2892. queryMode: 'local',
  2893. value: 'hour',
  2894. stateEvents: [ 'select' ],
  2895. stateful: true,
  2896. stateId: 'proxmoxRRDTypeSelection',
  2897. store: {
  2898. type: 'array',
  2899. fields: [ 'id', 'timeframe', 'cf', 'text' ],
  2900. data : [
  2901. [ 'hour', 'hour', 'AVERAGE',
  2902. gettext('Hour') + ' (' + gettext('average') +')' ],
  2903. [ 'hourmax', 'hour', 'MAX',
  2904. gettext('Hour') + ' (' + gettext('maximum') + ')' ],
  2905. [ 'day', 'day', 'AVERAGE',
  2906. gettext('Day') + ' (' + gettext('average') + ')' ],
  2907. [ 'daymax', 'day', 'MAX',
  2908. gettext('Day') + ' (' + gettext('maximum') + ')' ],
  2909. [ 'week', 'week', 'AVERAGE',
  2910. gettext('Week') + ' (' + gettext('average') + ')' ],
  2911. [ 'weekmax', 'week', 'MAX',
  2912. gettext('Week') + ' (' + gettext('maximum') + ')' ],
  2913. [ 'month', 'month', 'AVERAGE',
  2914. gettext('Month') + ' (' + gettext('average') + ')' ],
  2915. [ 'monthmax', 'month', 'MAX',
  2916. gettext('Month') + ' (' + gettext('maximum') + ')' ],
  2917. [ 'year', 'year', 'AVERAGE',
  2918. gettext('Year') + ' (' + gettext('average') + ')' ],
  2919. [ 'yearmax', 'year', 'MAX',
  2920. gettext('Year') + ' (' + gettext('maximum') + ')' ]
  2921. ]
  2922. },
  2923. // save current selection in the state Provider so RRDView can read it
  2924. getState: function() {
  2925. var ind = this.getStore().findExact('id', this.getValue());
  2926. var rec = this.getStore().getAt(ind);
  2927. if (!rec) {
  2928. return;
  2929. }
  2930. return {
  2931. id: rec.data.id,
  2932. timeframe: rec.data.timeframe,
  2933. cf: rec.data.cf
  2934. };
  2935. },
  2936. // set selection based on last saved state
  2937. applyState : function(state) {
  2938. if (state && state.id) {
  2939. this.setValue(state.id);
  2940. }
  2941. }
  2942. });
  2943. Ext.define('Proxmox.form.BondModeSelector', {
  2944. extend: 'Proxmox.form.KVComboBox',
  2945. alias: ['widget.bondModeSelector'],
  2946.  
  2947. openvswitch: false,
  2948.  
  2949. initComponent: function() {
  2950. var me = this;
  2951.  
  2952. if (me.openvswitch) {
  2953. me.comboItems = [
  2954. ['active-backup', 'active-backup'],
  2955. ['balance-slb', 'balance-slb'],
  2956. ['lacp-balance-slb', 'LACP (balance-slb)'],
  2957. ['lacp-balance-tcp', 'LACP (balance-tcp)']
  2958. ];
  2959. } else {
  2960. me.comboItems = [
  2961. ['balance-rr', 'balance-rr'],
  2962. ['active-backup', 'active-backup'],
  2963. ['balance-xor', 'balance-xor'],
  2964. ['broadcast', 'broadcast'],
  2965. ['802.3ad', 'LACP (802.3ad)'],
  2966. ['balance-tlb', 'balance-tlb'],
  2967. ['balance-alb', 'balance-alb']
  2968. ];
  2969. }
  2970.  
  2971. me.callParent();
  2972. }
  2973. });
  2974.  
  2975. Ext.define('Proxmox.form.BondPolicySelector', {
  2976. extend: 'Proxmox.form.KVComboBox',
  2977. alias: ['widget.bondPolicySelector'],
  2978. comboItems: [
  2979. ['layer2', 'layer2'],
  2980. ['layer2+3', 'layer2+3'],
  2981. ['layer3+4', 'layer3+4']
  2982. ]
  2983. });
  2984.  
  2985. /* Button features:
  2986. * - observe selection changes to enable/disable the button using enableFn()
  2987. * - pop up confirmation dialog using confirmMsg()
  2988. */
  2989. Ext.define('Proxmox.button.Button', {
  2990. extend: 'Ext.button.Button',
  2991. alias: 'widget.proxmoxButton',
  2992.  
  2993. // the selection model to observe
  2994. selModel: undefined,
  2995.  
  2996. // if 'false' handler will not be called (button disabled)
  2997. enableFn: function(record) { },
  2998.  
  2999. // function(record) or text
  3000. confirmMsg: false,
  3001.  
  3002. // take special care in confirm box (select no as default).
  3003. dangerous: false,
  3004.  
  3005. initComponent: function() {
  3006. /*jslint confusion: true */
  3007.  
  3008. var me = this;
  3009.  
  3010. if (me.handler) {
  3011.  
  3012. // Note: me.realHandler may be a string (see named scopes)
  3013. var realHandler = me.handler;
  3014.  
  3015. me.handler = function(button, event) {
  3016. var rec, msg;
  3017. if (me.selModel) {
  3018. rec = me.selModel.getSelection()[0];
  3019. if (!rec || (me.enableFn(rec) === false)) {
  3020. return;
  3021. }
  3022. }
  3023.  
  3024. if (me.confirmMsg) {
  3025. msg = me.confirmMsg;
  3026. if (Ext.isFunction(me.confirmMsg)) {
  3027. msg = me.confirmMsg(rec);
  3028. }
  3029. Ext.MessageBox.defaultButton = me.dangerous ? 2 : 1;
  3030. Ext.Msg.show({
  3031. title: gettext('Confirm'),
  3032. icon: me.dangerous ? Ext.Msg.WARNING : Ext.Msg.QUESTION,
  3033. msg: msg,
  3034. buttons: Ext.Msg.YESNO,
  3035. defaultFocus: me.dangerous ? 'no' : 'yes',
  3036. callback: function(btn) {
  3037. if (btn !== 'yes') {
  3038. return;
  3039. }
  3040. Ext.callback(realHandler, me.scope, [button, event, rec], 0, me);
  3041. }
  3042. });
  3043. } else {
  3044. Ext.callback(realHandler, me.scope, [button, event, rec], 0, me);
  3045. }
  3046. };
  3047. }
  3048.  
  3049. me.callParent();
  3050.  
  3051. var grid;
  3052. if (!me.selModel && me.selModel !== null) {
  3053. grid = me.up('grid');
  3054. if (grid && grid.selModel) {
  3055. me.selModel = grid.selModel;
  3056. }
  3057. }
  3058.  
  3059. if (me.waitMsgTarget === true) {
  3060. grid = me.up('grid');
  3061. if (grid) {
  3062. me.waitMsgTarget = grid;
  3063. } else {
  3064. throw "unable to find waitMsgTarget";
  3065. }
  3066. }
  3067.  
  3068. if (me.selModel) {
  3069.  
  3070. me.mon(me.selModel, "selectionchange", function() {
  3071. var rec = me.selModel.getSelection()[0];
  3072. if (!rec || (me.enableFn(rec) === false)) {
  3073. me.setDisabled(true);
  3074. } else {
  3075. me.setDisabled(false);
  3076. }
  3077. });
  3078. }
  3079. }
  3080. });
  3081.  
  3082.  
  3083. Ext.define('Proxmox.button.StdRemoveButton', {
  3084. extend: 'Proxmox.button.Button',
  3085. alias: 'widget.proxmoxStdRemoveButton',
  3086.  
  3087. text: gettext('Remove'),
  3088.  
  3089. disabled: true,
  3090.  
  3091. config: {
  3092. baseurl: undefined
  3093. },
  3094.  
  3095. getUrl: function(rec) {
  3096. var me = this;
  3097.  
  3098. return me.baseurl + '/' + rec.getId();
  3099. },
  3100.  
  3101. // also works with names scopes
  3102. callback: function(options, success, response) {},
  3103.  
  3104. getRecordName: function(rec) { return rec.getId() },
  3105.  
  3106. confirmMsg: function (rec) {
  3107. var me = this;
  3108.  
  3109. var name = me.getRecordName(rec);
  3110. return Ext.String.format(
  3111. gettext('Are you sure you want to remove entry {0}'),
  3112. "'" + name + "'");
  3113. },
  3114.  
  3115. handler: function(btn, event, rec) {
  3116. var me = this;
  3117.  
  3118. Proxmox.Utils.API2Request({
  3119. url: me.getUrl(rec),
  3120. method: 'DELETE',
  3121. waitMsgTarget: me.waitMsgTarget,
  3122. callback: function(options, success, response) {
  3123. Ext.callback(me.callback, me.scope, [options, success, response], 0, me);
  3124. },
  3125. failure: function (response, opts) {
  3126. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  3127. }
  3128. });
  3129. }
  3130. });
  3131. /* help button pointing to an online documentation
  3132. for components contained in a modal window
  3133. */
  3134. /*global
  3135. proxmoxOnlineHelpInfo
  3136. */
  3137. Ext.define('Proxmox.button.Help', {
  3138. extend: 'Ext.button.Button',
  3139. xtype: 'proxmoxHelpButton',
  3140.  
  3141. text: gettext('Help'),
  3142.  
  3143. // make help button less flashy by styling it like toolbar buttons
  3144. iconCls: ' x-btn-icon-el-default-toolbar-small fa fa-question-circle',
  3145. cls: 'x-btn-default-toolbar-small proxmox-inline-button',
  3146.  
  3147. hidden: true,
  3148.  
  3149. listenToGlobalEvent: true,
  3150.  
  3151. controller: {
  3152. xclass: 'Ext.app.ViewController',
  3153. listen: {
  3154. global: {
  3155. proxmoxShowHelp: 'onProxmoxShowHelp',
  3156. proxmoxHideHelp: 'onProxmoxHideHelp'
  3157. }
  3158. },
  3159. onProxmoxShowHelp: function(helpLink) {
  3160. var me = this.getView();
  3161. if (me.listenToGlobalEvent === true) {
  3162. me.setOnlineHelp(helpLink);
  3163. me.show();
  3164. }
  3165. },
  3166. onProxmoxHideHelp: function() {
  3167. var me = this.getView();
  3168. if (me.listenToGlobalEvent === true) {
  3169. me.hide();
  3170. }
  3171. }
  3172. },
  3173.  
  3174. getOnlineHelpInfo: function (ref) {
  3175. var helpMap;
  3176. if (typeof proxmoxOnlineHelpInfo !== 'undefined') {
  3177. helpMap = proxmoxOnlineHelpInfo;
  3178. } else if (typeof pveOnlineHelpInfo !== 'undefined') {
  3179. // be backward compatible with older pve-doc-generators
  3180. helpMap = pveOnlineHelpInfo;
  3181. } else {
  3182. throw "no global OnlineHelpInfo map declared";
  3183. }
  3184.  
  3185. return helpMap[ref];
  3186. },
  3187.  
  3188. // this sets the link and the tooltip text
  3189. setOnlineHelp:function(blockid) {
  3190. var me = this;
  3191.  
  3192. var info = me.getOnlineHelpInfo(blockid);
  3193. if (info) {
  3194. me.onlineHelp = blockid;
  3195. var title = info.title;
  3196. if (info.subtitle) {
  3197. title += ' - ' + info.subtitle;
  3198. }
  3199. me.setTooltip(title);
  3200. }
  3201. },
  3202.  
  3203. // helper to set the onlineHelp via a config object
  3204. setHelpConfig: function(config) {
  3205. var me = this;
  3206. me.setOnlineHelp(config.onlineHelp);
  3207. },
  3208.  
  3209. handler: function() {
  3210. var me = this;
  3211. var docsURI;
  3212.  
  3213. if (me.onlineHelp) {
  3214. var info = me.getOnlineHelpInfo(me.onlineHelp);
  3215. if (info) {
  3216. docsURI = window.location.origin + info.link;
  3217. }
  3218. }
  3219.  
  3220. if (docsURI) {
  3221. window.open(docsURI);
  3222. } else {
  3223. Ext.Msg.alert(gettext('Help'), gettext('No Help available'));
  3224. }
  3225. },
  3226.  
  3227. initComponent: function() {
  3228. /*jslint confusion: true */
  3229. var me = this;
  3230.  
  3231. me.callParent();
  3232.  
  3233. if (me.onlineHelp) {
  3234. me.setOnlineHelp(me.onlineHelp); // set tooltip
  3235. }
  3236. }
  3237. });
  3238. /* Renders a list of key values objets
  3239.  
  3240. mandatory config parameters:
  3241. rows: an object container where each propery is a key-value object we want to render
  3242. var rows = {
  3243. keyboard: {
  3244. header: gettext('Keyboard Layout'),
  3245. editor: 'Your.KeyboardEdit',
  3246. required: true
  3247. },
  3248.  
  3249. optional:
  3250. disabled: setting this parameter to true will disable selection and focus on the
  3251. proxmoxObjectGrid as well as greying out input elements.
  3252. Useful for a readonly tabular display
  3253.  
  3254. */
  3255.  
  3256. Ext.define('Proxmox.grid.ObjectGrid', {
  3257. extend: 'Ext.grid.GridPanel',
  3258. alias: ['widget.proxmoxObjectGrid'],
  3259. disabled: false,
  3260. hideHeaders: true,
  3261.  
  3262. monStoreErrors: false,
  3263.  
  3264. add_combobox_row: function(name, text, opts) {
  3265. var me = this;
  3266.  
  3267. opts = opts || {};
  3268. me.rows = me.rows || {};
  3269.  
  3270. me.rows[name] = {
  3271. required: true,
  3272. defaultValue: opts.defaultValue,
  3273. header: text,
  3274. renderer: opts.renderer,
  3275. editor: {
  3276. xtype: 'proxmoxWindowEdit',
  3277. subject: text,
  3278. fieldDefaults: {
  3279. labelWidth: opts.labelWidth || 100
  3280. },
  3281. items: {
  3282. xtype: 'proxmoxKVComboBox',
  3283. name: name,
  3284. comboItems: opts.comboItems,
  3285. value: opts.defaultValue,
  3286. deleteEmpty: opts.deleteEmpty ? true : false,
  3287. emptyText: opts.defaultValue,
  3288. labelWidth: Proxmox.Utils.compute_min_label_width(
  3289. text, opts.labelWidth),
  3290. fieldLabel: text
  3291. }
  3292. }
  3293. };
  3294. },
  3295.  
  3296. add_text_row: function(name, text, opts) {
  3297. var me = this;
  3298.  
  3299. opts = opts || {};
  3300. me.rows = me.rows || {};
  3301.  
  3302. me.rows[name] = {
  3303. required: true,
  3304. defaultValue: opts.defaultValue,
  3305. header: text,
  3306. renderer: opts.renderer,
  3307. editor: {
  3308. xtype: 'proxmoxWindowEdit',
  3309. subject: text,
  3310. fieldDefaults: {
  3311. labelWidth: opts.labelWidth || 100
  3312. },
  3313. items: {
  3314. xtype: 'proxmoxtextfield',
  3315. name: name,
  3316. deleteEmpty: opts.deleteEmpty ? true : false,
  3317. emptyText: opts.defaultValue,
  3318. labelWidth: Proxmox.Utils.compute_min_label_width(
  3319. text, opts.labelWidth),
  3320. vtype: opts.vtype,
  3321. fieldLabel: text
  3322. }
  3323. }
  3324. };
  3325. },
  3326.  
  3327. add_boolean_row: function(name, text, opts) {
  3328. var me = this;
  3329.  
  3330. opts = opts || {};
  3331. me.rows = me.rows || {};
  3332.  
  3333. me.rows[name] = {
  3334. required: true,
  3335. defaultValue: opts.defaultValue || 0,
  3336. header: text,
  3337. renderer: opts.renderer || Proxmox.Utils.format_boolean,
  3338. editor: {
  3339. xtype: 'proxmoxWindowEdit',
  3340. subject: text,
  3341. fieldDefaults: {
  3342. labelWidth: opts.labelWidth || 100
  3343. },
  3344. items: {
  3345. xtype: 'proxmoxcheckbox',
  3346. name: name,
  3347. uncheckedValue: 0,
  3348. defaultValue: opts.defaultValue || 0,
  3349. checked: opts.defaultValue ? true : false,
  3350. deleteDefaultValue: opts.deleteDefaultValue ? true : false,
  3351. labelWidth: Proxmox.Utils.compute_min_label_width(
  3352. text, opts.labelWidth),
  3353. fieldLabel: text
  3354. }
  3355. }
  3356. };
  3357. },
  3358.  
  3359. add_integer_row: function(name, text, opts) {
  3360. var me = this;
  3361.  
  3362. opts = opts || {}
  3363. me.rows = me.rows || {};
  3364.  
  3365. me.rows[name] = {
  3366. required: true,
  3367. defaultValue: opts.defaultValue,
  3368. header: text,
  3369. renderer: opts.renderer,
  3370. editor: {
  3371. xtype: 'proxmoxWindowEdit',
  3372. subject: text,
  3373. fieldDefaults: {
  3374. labelWidth: opts.labelWidth || 100
  3375. },
  3376. items: {
  3377. xtype: 'proxmoxintegerfield',
  3378. name: name,
  3379. minValue: opts.minValue,
  3380. maxValue: opts.maxValue,
  3381. emptyText: gettext('Default'),
  3382. deleteEmpty: opts.deleteEmpty ? true : false,
  3383. value: opts.defaultValue,
  3384. labelWidth: Proxmox.Utils.compute_min_label_width(
  3385. text, opts.labelWidth),
  3386. fieldLabel: text
  3387. }
  3388. }
  3389. };
  3390. },
  3391.  
  3392. editorConfig: {}, // default config passed to editor
  3393.  
  3394. run_editor: function() {
  3395. var me = this;
  3396.  
  3397. var sm = me.getSelectionModel();
  3398. var rec = sm.getSelection()[0];
  3399. if (!rec) {
  3400. return;
  3401. }
  3402.  
  3403. var rows = me.rows;
  3404. var rowdef = rows[rec.data.key];
  3405. if (!rowdef.editor) {
  3406. return;
  3407. }
  3408.  
  3409. var win;
  3410. var config;
  3411. if (Ext.isString(rowdef.editor)) {
  3412. config = Ext.apply({
  3413. confid: rec.data.key,
  3414. }, me.editorConfig);
  3415. win = Ext.create(rowdef.editor, config);
  3416. } else {
  3417. config = Ext.apply({
  3418. confid: rec.data.key,
  3419. }, me.editorConfig);
  3420. Ext.apply(config, rowdef.editor);
  3421. win = Ext.createWidget(rowdef.editor.xtype, config);
  3422. win.load();
  3423. }
  3424.  
  3425. win.show();
  3426. win.on('destroy', me.reload, me);
  3427. },
  3428.  
  3429. reload: function() {
  3430. var me = this;
  3431. me.rstore.load();
  3432. },
  3433.  
  3434. getObjectValue: function(key, defaultValue) {
  3435. var me = this;
  3436. var rec = me.store.getById(key);
  3437. if (rec) {
  3438. return rec.data.value;
  3439. }
  3440. return defaultValue;
  3441. },
  3442.  
  3443. renderKey: function(key, metaData, record, rowIndex, colIndex, store) {
  3444. var me = this;
  3445. var rows = me.rows;
  3446. var rowdef = (rows && rows[key]) ? rows[key] : {};
  3447. return rowdef.header || key;
  3448. },
  3449.  
  3450. renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
  3451. var me = this;
  3452. var rows = me.rows;
  3453. var key = record.data.key;
  3454. var rowdef = (rows && rows[key]) ? rows[key] : {};
  3455.  
  3456. var renderer = rowdef.renderer;
  3457. if (renderer) {
  3458. return renderer(value, metaData, record, rowIndex, colIndex, store);
  3459. }
  3460.  
  3461. return value;
  3462. },
  3463.  
  3464. listeners: {
  3465. itemkeydown: function(view, record, item, index, e) {
  3466. if (e.getKey() === e.ENTER) {
  3467. this.pressedIndex = index;
  3468. }
  3469. },
  3470. itemkeyup: function(view, record, item, index, e) {
  3471. if (e.getKey() === e.ENTER && index == this.pressedIndex) {
  3472. this.run_editor();
  3473. }
  3474.  
  3475. this.pressedIndex = undefined;
  3476. }
  3477. },
  3478.  
  3479. initComponent : function() {
  3480. var me = this;
  3481.  
  3482. var rows = me.rows;
  3483.  
  3484. if (!me.rstore) {
  3485. if (!me.url) {
  3486. throw "no url specified";
  3487. }
  3488.  
  3489. me.rstore = Ext.create('Proxmox.data.ObjectStore', {
  3490. url: me.url,
  3491. interval: me.interval,
  3492. extraParams: me.extraParams,
  3493. rows: me.rows
  3494. });
  3495. }
  3496.  
  3497. var rstore = me.rstore;
  3498.  
  3499. var store = Ext.create('Proxmox.data.DiffStore', { rstore: rstore,
  3500. sorters: [],
  3501. filters: []
  3502. });
  3503.  
  3504. if (rows) {
  3505. Ext.Object.each(rows, function(key, rowdef) {
  3506. if (Ext.isDefined(rowdef.defaultValue)) {
  3507. store.add({ key: key, value: rowdef.defaultValue });
  3508. } else if (rowdef.required) {
  3509. store.add({ key: key, value: undefined });
  3510. }
  3511. });
  3512. }
  3513.  
  3514. if (me.sorterFn) {
  3515. store.sorters.add(Ext.create('Ext.util.Sorter', {
  3516. sorterFn: me.sorterFn
  3517. }));
  3518. }
  3519.  
  3520. store.filters.add(Ext.create('Ext.util.Filter', {
  3521. filterFn: function(item) {
  3522. if (rows) {
  3523. var rowdef = rows[item.data.key];
  3524. if (!rowdef || (rowdef.visible === false)) {
  3525. return false;
  3526. }
  3527. }
  3528. return true;
  3529. }
  3530. }));
  3531.  
  3532. Proxmox.Utils.monStoreErrors(me, rstore);
  3533.  
  3534. Ext.applyIf(me, {
  3535. store: store,
  3536. stateful: false,
  3537. columns: [
  3538. {
  3539. header: gettext('Name'),
  3540. width: me.cwidth1 || 200,
  3541. dataIndex: 'key',
  3542. renderer: me.renderKey
  3543. },
  3544. {
  3545. flex: 1,
  3546. header: gettext('Value'),
  3547. dataIndex: 'value',
  3548. renderer: me.renderValue
  3549. }
  3550. ]
  3551. });
  3552.  
  3553. me.callParent();
  3554.  
  3555. if (me.monStoreErrors) {
  3556. Proxmox.Utils.monStoreErrors(me, me.store);
  3557. }
  3558. }
  3559. });
  3560. Ext.define('Proxmox.grid.PendingObjectGrid', {
  3561. extend: 'Proxmox.grid.ObjectGrid',
  3562. alias: ['widget.proxmoxPendingObjectGrid'],
  3563.  
  3564. getObjectValue: function(key, defaultValue, pending) {
  3565. var me = this;
  3566. var rec = me.store.getById(key);
  3567. if (rec) {
  3568. var value = rec.data.value;
  3569. if (pending) {
  3570. if (Ext.isDefined(rec.data.pending) && rec.data.pending !== '') {
  3571. value = rec.data.pending;
  3572. } else if (rec.data['delete'] === 1) {
  3573. value = defaultValue;
  3574. }
  3575. }
  3576.  
  3577. if (Ext.isDefined(value) && (value !== '')) {
  3578. return value;
  3579. } else {
  3580. return defaultValue;
  3581. }
  3582. }
  3583. return defaultValue;
  3584. },
  3585.  
  3586. hasPendingChanges: function(key) {
  3587. var me = this;
  3588. var rows = me.rows;
  3589. var rowdef = (rows && rows[key]) ? rows[key] : {};
  3590. var keys = rowdef.multiKey || [ key ];
  3591. var pending = false;
  3592.  
  3593. Ext.Array.each(keys, function(k) {
  3594. var rec = me.store.getById(k);
  3595. if (rec && rec.data && (
  3596. (Ext.isDefined(rec.data.pending) && rec.data.pending !== '') ||
  3597. rec.data['delete'] === 1
  3598. )) {
  3599. pending = true;
  3600. return false; // break
  3601. }
  3602. });
  3603.  
  3604. return pending;
  3605. },
  3606.  
  3607. renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
  3608. var me = this;
  3609. var rows = me.rows;
  3610. var key = record.data.key;
  3611. var rowdef = (rows && rows[key]) ? rows[key] : {};
  3612. var renderer = rowdef.renderer;
  3613. var current = '';
  3614. var pendingdelete = '';
  3615. var pending = '';
  3616.  
  3617. if (renderer) {
  3618. current = renderer(value, metaData, record, rowIndex, colIndex, store, false);
  3619. if (me.hasPendingChanges(key)) {
  3620. pending = renderer(record.data.pending, metaData, record, rowIndex, colIndex, store, true);
  3621. }
  3622. if (pending == current) {
  3623. pending = undefined;
  3624. }
  3625. } else {
  3626. current = value || '';
  3627. pending = record.data.pending;
  3628. }
  3629.  
  3630. if (record.data['delete']) {
  3631. var delete_all = true;
  3632. if (rowdef.multiKey) {
  3633. Ext.Array.each(rowdef.multiKey, function(k) {
  3634. var rec = me.store.getById(k);
  3635. if (rec && rec.data && rec.data['delete'] !== 1) {
  3636. delete_all = false;
  3637. return false; // break
  3638. }
  3639. });
  3640. }
  3641. if (delete_all) {
  3642. pending = '<div style="text-decoration: line-through;">'+ current +'</div>';
  3643. }
  3644. }
  3645.  
  3646. if (pending) {
  3647. return current + '<div style="color:red">' + pending + '</div>';
  3648. } else {
  3649. return current;
  3650. }
  3651. },
  3652.  
  3653. initComponent : function() {
  3654. var me = this;
  3655.  
  3656. var rows = me.rows;
  3657.  
  3658. if (!me.rstore) {
  3659. if (!me.url) {
  3660. throw "no url specified";
  3661. }
  3662.  
  3663. me.rstore = Ext.create('Proxmox.data.ObjectStore', {
  3664. model: 'KeyValuePendingDelete',
  3665. readArray: true,
  3666. url: me.url,
  3667. interval: me.interval,
  3668. extraParams: me.extraParams,
  3669. rows: me.rows
  3670. });
  3671. }
  3672.  
  3673. me.callParent();
  3674. }
  3675. });
  3676. Ext.define('Proxmox.panel.InputPanel', {
  3677. extend: 'Ext.panel.Panel',
  3678. alias: ['widget.inputpanel'],
  3679. listeners: {
  3680. activate: function() {
  3681. // notify owning container that it should display a help button
  3682. if (this.onlineHelp) {
  3683. Ext.GlobalEvents.fireEvent('proxmoxShowHelp', this.onlineHelp);
  3684. }
  3685. },
  3686. deactivate: function() {
  3687. if (this.onlineHelp) {
  3688. Ext.GlobalEvents.fireEvent('proxmoxHideHelp', this.onlineHelp);
  3689. }
  3690. }
  3691. },
  3692. border: false,
  3693.  
  3694. // override this with an URL to a relevant chapter of the pve manual
  3695. // setting this will display a help button in our parent panel
  3696. onlineHelp: undefined,
  3697.  
  3698. // will be set if the inputpanel has advanced items
  3699. hasAdvanced: false,
  3700.  
  3701. // if the panel has advanced items,
  3702. // this will determine if they are shown by default
  3703. showAdvanced: false,
  3704.  
  3705. // overwrite this to modify submit data
  3706. onGetValues: function(values) {
  3707. return values;
  3708. },
  3709.  
  3710. getValues: function(dirtyOnly) {
  3711. var me = this;
  3712.  
  3713. if (Ext.isFunction(me.onGetValues)) {
  3714. dirtyOnly = false;
  3715. }
  3716.  
  3717. var values = {};
  3718.  
  3719. Ext.Array.each(me.query('[isFormField]'), function(field) {
  3720. if (!dirtyOnly || field.isDirty()) {
  3721. Proxmox.Utils.assemble_field_data(values, field.getSubmitData());
  3722. }
  3723. });
  3724.  
  3725. return me.onGetValues(values);
  3726. },
  3727.  
  3728. setAdvancedVisible: function(visible) {
  3729. var me = this;
  3730. var advItems = me.getComponent('advancedContainer');
  3731. if (advItems) {
  3732. advItems.setVisible(visible);
  3733. }
  3734. },
  3735.  
  3736. setValues: function(values) {
  3737. var me = this;
  3738.  
  3739. var form = me.up('form');
  3740.  
  3741. Ext.iterate(values, function(fieldId, val) {
  3742. var field = me.query('[isFormField][name=' + fieldId + ']')[0];
  3743. if (field) {
  3744. field.setValue(val);
  3745. if (form.trackResetOnLoad) {
  3746. field.resetOriginalValue();
  3747. }
  3748. }
  3749. });
  3750. },
  3751.  
  3752. initComponent: function() {
  3753. var me = this;
  3754.  
  3755. var items;
  3756.  
  3757. if (me.items) {
  3758. me.columns = 1;
  3759. items = [
  3760. {
  3761. columnWidth: 1,
  3762. layout: 'anchor',
  3763. items: me.items
  3764. }
  3765. ];
  3766. me.items = undefined;
  3767. } else if (me.column4) {
  3768. me.columns = 4;
  3769. items = [
  3770. {
  3771. columnWidth: 0.25,
  3772. padding: '0 10 0 0',
  3773. layout: 'anchor',
  3774. items: me.column1
  3775. },
  3776. {
  3777. columnWidth: 0.25,
  3778. padding: '0 10 0 0',
  3779. layout: 'anchor',
  3780. items: me.column2
  3781. },
  3782. {
  3783. columnWidth: 0.25,
  3784. padding: '0 10 0 0',
  3785. layout: 'anchor',
  3786. items: me.column3
  3787. },
  3788. {
  3789. columnWidth: 0.25,
  3790. padding: '0 0 0 10',
  3791. layout: 'anchor',
  3792. items: me.column4
  3793. }
  3794. ];
  3795. if (me.columnB) {
  3796. items.push({
  3797. columnWidth: 1,
  3798. padding: '10 0 0 0',
  3799. layout: 'anchor',
  3800. items: me.columnB
  3801. });
  3802. }
  3803. } else if (me.column1) {
  3804. me.columns = 2;
  3805. items = [
  3806. {
  3807. columnWidth: 0.5,
  3808. padding: '0 10 0 0',
  3809. layout: 'anchor',
  3810. items: me.column1
  3811. },
  3812. {
  3813. columnWidth: 0.5,
  3814. padding: '0 0 0 10',
  3815. layout: 'anchor',
  3816. items: me.column2 || [] // allow empty column
  3817. }
  3818. ];
  3819. if (me.columnB) {
  3820. items.push({
  3821. columnWidth: 1,
  3822. padding: '10 0 0 0',
  3823. layout: 'anchor',
  3824. items: me.columnB
  3825. });
  3826. }
  3827. } else {
  3828. throw "unsupported config";
  3829. }
  3830.  
  3831. var advItems;
  3832. if (me.advancedItems) {
  3833. advItems = [
  3834. {
  3835. columnWidth: 1,
  3836. layout: 'anchor',
  3837. items: me.advancedItems
  3838. }
  3839. ];
  3840. me.advancedItems = undefined;
  3841. } else if (me.advancedColumn1) {
  3842. advItems = [
  3843. {
  3844. columnWidth: 0.5,
  3845. padding: '0 10 0 0',
  3846. layout: 'anchor',
  3847. items: me.advancedColumn1
  3848. },
  3849. {
  3850. columnWidth: 0.5,
  3851. padding: '0 0 0 10',
  3852. layout: 'anchor',
  3853. items: me.advancedColumn2 || [] // allow empty column
  3854. }
  3855. ];
  3856.  
  3857. me.advancedColumn1 = undefined;
  3858. me.advancedColumn2 = undefined;
  3859.  
  3860. if (me.advancedColumnB) {
  3861. advItems.push({
  3862. columnWidth: 1,
  3863. padding: '10 0 0 0',
  3864. layout: 'anchor',
  3865. items: me.advancedColumnB
  3866. });
  3867. me.advancedColumnB = undefined;
  3868. }
  3869. }
  3870.  
  3871. if (advItems) {
  3872. me.hasAdvanced = true;
  3873. advItems.unshift({
  3874. columnWidth: 1,
  3875. xtype: 'box',
  3876. hidden: false,
  3877. border: true,
  3878. autoEl: {
  3879. tag: 'hr'
  3880. }
  3881. });
  3882. items.push({
  3883. columnWidth: 1,
  3884. xtype: 'container',
  3885. itemId: 'advancedContainer',
  3886. hidden: !me.showAdvanced,
  3887. layout: 'column',
  3888. defaults: {
  3889. border: false
  3890. },
  3891. items: advItems
  3892. });
  3893. }
  3894.  
  3895. if (me.useFieldContainer) {
  3896. Ext.apply(me, {
  3897. layout: 'fit',
  3898. items: Ext.apply(me.useFieldContainer, {
  3899. layout: 'column',
  3900. defaultType: 'container',
  3901. items: items
  3902. })
  3903. });
  3904. } else {
  3905. Ext.apply(me, {
  3906. layout: 'column',
  3907. defaultType: 'container',
  3908. items: items
  3909. });
  3910. }
  3911.  
  3912. me.callParent();
  3913. }
  3914. });
  3915. /*
  3916. * Display log entries in a panel with scrollbar
  3917. * The log entries are automatically refreshed via a background task,
  3918. * with newest entries comming at the bottom
  3919. */
  3920. Ext.define('Proxmox.panel.LogView', {
  3921. extend: 'Ext.panel.Panel',
  3922.  
  3923. alias: ['widget.proxmoxLogView'],
  3924.  
  3925. pageSize: 500,
  3926.  
  3927. lineHeight: 16,
  3928.  
  3929. viewInfo: undefined,
  3930.  
  3931. scrollToEnd: true,
  3932.  
  3933. autoScroll: true,
  3934.  
  3935. layout: 'auto',
  3936.  
  3937. bodyPadding: 5,
  3938.  
  3939. getMaxDown: function(scrollToEnd) {
  3940. var me = this;
  3941.  
  3942. var target = me.getTargetEl();
  3943. var dom = target.dom;
  3944. if (scrollToEnd) {
  3945. dom.scrollTop = dom.scrollHeight - dom.clientHeight;
  3946. }
  3947.  
  3948. var maxDown = dom.scrollHeight - dom.clientHeight -
  3949. dom.scrollTop;
  3950.  
  3951. return maxDown;
  3952. },
  3953.  
  3954. updateView: function(start, end, total, text) {
  3955. var me = this;
  3956.  
  3957. if (me.destroyed) { // return if element is not there anymore
  3958. return;
  3959. }
  3960.  
  3961. var el = me.dataCmp.el;
  3962.  
  3963. if (me.viewInfo && me.viewInfo.start === start &&
  3964. me.viewInfo.end === end && me.viewInfo.total === total &&
  3965. me.viewInfo.textLength === text.length) {
  3966. return; // same content
  3967. }
  3968.  
  3969. var maxDown = me.getMaxDown();
  3970. var scrollToEnd = (maxDown <= 0) && me.scrollToEnd;
  3971.  
  3972. el.setStyle('padding-top', (start*me.lineHeight).toString() + 'px');
  3973. el.update(text);
  3974. me.dataCmp.setHeight(total*me.lineHeight);
  3975.  
  3976. if (scrollToEnd) {
  3977. me.getMaxDown(true);
  3978. }
  3979.  
  3980. me.viewInfo = {
  3981. start: start,
  3982. end: end,
  3983. total: total,
  3984. textLength: text.length
  3985. };
  3986. },
  3987.  
  3988. doAttemptLoad: function(start) {
  3989. var me = this;
  3990.  
  3991. var req_params = {
  3992. start: start,
  3993. limit: me.pageSize
  3994. };
  3995.  
  3996. if (me.log_select_timespan) {
  3997. // always show log until the end of the selected day
  3998. req_params.until = Ext.Date.format(me.until_date, 'Y-m-d') + ' 23:59:59';
  3999. req_params.since = Ext.Date.format(me.since_date, 'Y-m-d');
  4000. }
  4001.  
  4002. Proxmox.Utils.API2Request({
  4003. url: me.url,
  4004. params: req_params,
  4005. method: 'GET',
  4006. success: function(response) {
  4007. Proxmox.Utils.setErrorMask(me, false);
  4008. var list = response.result.data;
  4009. var total = response.result.total;
  4010. var first = 0, last = 0;
  4011. var text = '';
  4012. Ext.Array.each(list, function(item) {
  4013. if (!first|| item.n < first) {
  4014. first = item.n;
  4015. }
  4016. if (!last || item.n > last) {
  4017. last = item.n;
  4018. }
  4019. text = text + Ext.htmlEncode(item.t) + "<br>";
  4020. });
  4021.  
  4022. if (first && last && total) {
  4023. me.updateView(first -1 , last -1, total, text);
  4024. } else {
  4025. me.updateView(0, 0, 0, '');
  4026. }
  4027. },
  4028. failure: function(response) {
  4029. var msg = response.htmlStatus;
  4030. Proxmox.Utils.setErrorMask(me, msg);
  4031. }
  4032. });
  4033. },
  4034.  
  4035. attemptLoad: function(start) {
  4036. var me = this;
  4037. if (!me.loadTask) {
  4038. me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
  4039. }
  4040. me.loadTask.delay(200, me.doAttemptLoad, me, [start]);
  4041. },
  4042.  
  4043. requestUpdate: function(top, force) {
  4044. var me = this;
  4045.  
  4046. if (top === undefined) {
  4047. var target = me.getTargetEl();
  4048. top = target.dom.scrollTop;
  4049. }
  4050.  
  4051. var viewStart = parseInt((top / me.lineHeight) - 1, 10);
  4052. if (viewStart < 0) {
  4053. viewStart = 0;
  4054. }
  4055. var viewEnd = parseInt(((top + me.getHeight())/ me.lineHeight) + 1, 10);
  4056. var info = me.viewInfo;
  4057.  
  4058. if (info && !force) {
  4059. if (viewStart >= info.start && viewEnd <= info.end) {
  4060. return;
  4061. }
  4062. }
  4063.  
  4064. var line = parseInt((top / me.lineHeight) - (me.pageSize / 2) + 10, 10);
  4065. if (line < 0) {
  4066. line = 0;
  4067. }
  4068.  
  4069. me.attemptLoad(line);
  4070. },
  4071.  
  4072. afterRender: function() {
  4073. var me = this;
  4074.  
  4075. me.callParent(arguments);
  4076.  
  4077. Ext.Function.defer(function() {
  4078. var target = me.getTargetEl();
  4079. target.on('scroll', function(e) {
  4080. me.requestUpdate();
  4081. });
  4082. me.requestUpdate(0);
  4083. }, 20);
  4084. },
  4085.  
  4086. initComponent : function() {
  4087. /*jslint confusion: true */
  4088.  
  4089. var me = this;
  4090.  
  4091. if (!me.url) {
  4092. throw "no url specified";
  4093. }
  4094.  
  4095. // show logs from today back to 3 days ago per default
  4096. me.until_date = new Date();
  4097. me.since_date = new Date();
  4098. me.since_date.setDate(me.until_date.getDate() - 3);
  4099.  
  4100. me.dataCmp = Ext.create('Ext.Component', {
  4101. style: 'font:normal 11px tahoma, arial, verdana, sans-serif;' +
  4102. 'line-height: ' + me.lineHeight.toString() + 'px; white-space: pre;'
  4103. });
  4104.  
  4105. me.task = Ext.TaskManager.start({
  4106. run: function() {
  4107. if (!me.isVisible() || !me.scrollToEnd || !me.viewInfo) {
  4108. return;
  4109. }
  4110.  
  4111. var maxDown = me.getMaxDown();
  4112. if (maxDown > 0) {
  4113. return;
  4114. }
  4115.  
  4116. me.requestUpdate(undefined, true);
  4117. },
  4118. interval: 1000
  4119. });
  4120.  
  4121. Ext.apply(me, {
  4122. items: me.dataCmp,
  4123. listeners: {
  4124. destroy: function() {
  4125. Ext.TaskManager.stop(me.task);
  4126. }
  4127. }
  4128. });
  4129.  
  4130. if (me.log_select_timespan) {
  4131. me.tbar = ['->','Since: ',
  4132. {
  4133. xtype: 'datefield',
  4134. maxValue: me.until_date,
  4135. value: me.since_date,
  4136. name: 'since_date',
  4137. format: 'Y-m-d',
  4138. listeners: {
  4139. select: function(field, date) {
  4140. me.since_date_selected = date;
  4141. var until_field = field.up().down('field[name=until_date]');
  4142. if (date > until_field.getValue()) {
  4143. until_field.setValue(date);
  4144. }
  4145. }
  4146. }
  4147. },
  4148. 'Until: ',
  4149. {
  4150. xtype: 'datefield',
  4151. maxValue: me.until_date,
  4152. value: me.until_date,
  4153. name: 'until_date',
  4154. format: 'Y-m-d',
  4155. listeners: {
  4156. select: function(field, date) {
  4157. var since_field = field.up().down('field[name=since_date]');
  4158. if (date < since_field.getValue()) {
  4159. since_field.setValue(date);
  4160. }
  4161. }
  4162. }
  4163. },
  4164. {
  4165. xtype: 'button',
  4166. text: 'Update',
  4167. handler: function() {
  4168. var until_field = me.down('field[name=until_date]');
  4169. var since_field = me.down('field[name=since_date]');
  4170. if (until_field.getValue() < since_field.getValue()) {
  4171. Ext.Msg.alert('Error',
  4172. 'Since date must be less equal than Until date.');
  4173. until_field.setValue(me.until_date);
  4174. since_field.setValue(me.since_date);
  4175. } else {
  4176. me.until_date = until_field.getValue();
  4177. me.since_date = since_field.getValue();
  4178. me.requestUpdate();
  4179. }
  4180. }
  4181. }
  4182. ];
  4183. }
  4184.  
  4185.  
  4186. me.callParent();
  4187. }
  4188. });
  4189. Ext.define('Proxmox.widget.RRDChart', {
  4190. extend: 'Ext.chart.CartesianChart',
  4191. alias: 'widget.proxmoxRRDChart',
  4192.  
  4193. unit: undefined, // bytes, bytespersecond, percent
  4194.  
  4195. controller: {
  4196. xclass: 'Ext.app.ViewController',
  4197.  
  4198. convertToUnits: function(value) {
  4199. var units = ['', 'k','M','G','T', 'P'];
  4200. var si = 0;
  4201. while(value >= 1000 && si < (units.length -1)){
  4202. value = value / 1000;
  4203. si++;
  4204. }
  4205.  
  4206. // javascript floating point weirdness
  4207. value = Ext.Number.correctFloat(value);
  4208.  
  4209. // limit to 2 decimal points
  4210. value = Ext.util.Format.number(value, "0.##");
  4211.  
  4212. return value.toString() + " " + units[si];
  4213. },
  4214.  
  4215. leftAxisRenderer: function(axis, label, layoutContext) {
  4216. var me = this;
  4217.  
  4218. return me.convertToUnits(label);
  4219. },
  4220.  
  4221. onSeriesTooltipRender: function(tooltip, record, item) {
  4222. var me = this.getView();
  4223.  
  4224. var suffix = '';
  4225.  
  4226. if (me.unit === 'percent') {
  4227. suffix = '%';
  4228. } else if (me.unit === 'bytes') {
  4229. suffix = 'B';
  4230. } else if (me.unit === 'bytespersecond') {
  4231. suffix = 'B/s';
  4232. }
  4233.  
  4234. var prefix = item.field;
  4235. if (me.fieldTitles && me.fieldTitles[me.fields.indexOf(item.field)]) {
  4236. prefix = me.fieldTitles[me.fields.indexOf(item.field)];
  4237. }
  4238. tooltip.setHtml(prefix + ': ' + this.convertToUnits(record.get(item.field)) + suffix +
  4239. '<br>' + new Date(record.get('time')));
  4240. },
  4241.  
  4242. onAfterAnimation: function(chart, eopts) {
  4243. // if the undobuton is disabled,
  4244. // disable our tool
  4245.  
  4246. var ourUndoZoomButton = chart.tools[0];
  4247. var undoButton = chart.interactions[0].getUndoButton();
  4248. ourUndoZoomButton.setDisabled(undoButton.isDisabled());
  4249. }
  4250. },
  4251.  
  4252. width: 770,
  4253. height: 300,
  4254. animation: false,
  4255. interactions: [{
  4256. type: 'crosszoom'
  4257. }],
  4258. axes: [{
  4259. type: 'numeric',
  4260. position: 'left',
  4261. grid: true,
  4262. renderer: 'leftAxisRenderer',
  4263. //renderer: function(axis, label) { return label; },
  4264. minimum: 0
  4265. }, {
  4266. type: 'time',
  4267. position: 'bottom',
  4268. grid: true,
  4269. fields: ['time']
  4270. }],
  4271. legend: {
  4272. docked: 'bottom'
  4273. },
  4274. listeners: {
  4275. animationend: 'onAfterAnimation'
  4276. },
  4277.  
  4278.  
  4279. initComponent: function() {
  4280. var me = this;
  4281. var series = {};
  4282.  
  4283. if (!me.store) {
  4284. throw "cannot work without store";
  4285. }
  4286.  
  4287. if (!me.fields) {
  4288. throw "cannot work without fields";
  4289. }
  4290.  
  4291. me.callParent();
  4292.  
  4293. // add correct label for left axis
  4294. var axisTitle = "";
  4295. if (me.unit === 'percent') {
  4296. axisTitle = "%";
  4297. } else if (me.unit === 'bytes') {
  4298. axisTitle = "Bytes";
  4299. } else if (me.unit === 'bytespersecond') {
  4300. axisTitle = "Bytes/s";
  4301. } else if (me.fieldTitles && me.fieldTitles.length === 1) {
  4302. axisTitle = me.fieldTitles[0];
  4303. } else if (me.fields.length === 1) {
  4304. axisTitle = me.fields[0];
  4305. }
  4306.  
  4307. me.axes[0].setTitle(axisTitle);
  4308.  
  4309. if (!me.noTool) {
  4310. me.addTool([{
  4311. type: 'minus',
  4312. disabled: true,
  4313. tooltip: gettext('Undo Zoom'),
  4314. handler: function(){
  4315. var undoButton = me.interactions[0].getUndoButton();
  4316. if (undoButton.handler) {
  4317. undoButton.handler();
  4318. }
  4319. }
  4320. },{
  4321. type: 'restore',
  4322. tooltip: gettext('Toggle Legend'),
  4323. handler: function(){
  4324. if (me.legend) {
  4325. me.legend.setVisible(!me.legend.isVisible());
  4326. }
  4327. }
  4328. }]);
  4329. }
  4330.  
  4331. // add a series for each field we get
  4332. me.fields.forEach(function(item, index){
  4333. var title = item;
  4334. if (me.fieldTitles && me.fieldTitles[index]) {
  4335. title = me.fieldTitles[index];
  4336. }
  4337. me.addSeries(Ext.apply(
  4338. {
  4339. type: 'line',
  4340. xField: 'time',
  4341. yField: item,
  4342. title: title,
  4343. fill: true,
  4344. style: {
  4345. lineWidth: 1.5,
  4346. opacity: 0.60
  4347. },
  4348. marker: {
  4349. opacity: 0,
  4350. scaling: 0.01,
  4351. fx: {
  4352. duration: 200,
  4353. easing: 'easeOut'
  4354. }
  4355. },
  4356. highlightCfg: {
  4357. opacity: 1,
  4358. scaling: 1.5
  4359. },
  4360. tooltip: {
  4361. trackMouse: true,
  4362. renderer: 'onSeriesTooltipRender'
  4363. }
  4364. },
  4365. me.seriesConfig
  4366. ));
  4367. });
  4368.  
  4369. // enable animation after the store is loaded
  4370. me.store.onAfter('load', function() {
  4371. me.setAnimation(true);
  4372. }, this, {single: true});
  4373. }
  4374. });
  4375. Ext.define('Proxmox.panel.GaugeWidget', {
  4376. extend: 'Ext.panel.Panel',
  4377. alias: 'widget.proxmoxGauge',
  4378.  
  4379. defaults: {
  4380. style: {
  4381. 'text-align':'center'
  4382. }
  4383. },
  4384. items: [
  4385. {
  4386. xtype: 'box',
  4387. itemId: 'title',
  4388. data: {
  4389. title: ''
  4390. },
  4391. tpl: '<h3>{title}</h3>'
  4392. },
  4393. {
  4394. xtype: 'polar',
  4395. height: 120,
  4396. border: false,
  4397. itemId: 'chart',
  4398. series: [{
  4399. type: 'gauge',
  4400. value: 0,
  4401. colors: ['#f5f5f5'],
  4402. sectors: [0],
  4403. donut: 90,
  4404. needleLength: 100,
  4405. totalAngle: Math.PI
  4406. }],
  4407. sprites: [{
  4408. id: 'valueSprite',
  4409. type: 'text',
  4410. text: '',
  4411. textAlign: 'center',
  4412. textBaseline: 'bottom',
  4413. x: 125,
  4414. y: 110,
  4415. fontSize: 30
  4416. }]
  4417. },
  4418. {
  4419. xtype: 'box',
  4420. itemId: 'text'
  4421. }
  4422. ],
  4423.  
  4424. header: false,
  4425. border: false,
  4426.  
  4427. warningThreshold: 0.6,
  4428. criticalThreshold: 0.9,
  4429. warningColor: '#fc0',
  4430. criticalColor: '#FF6C59',
  4431. defaultColor: '#c2ddf2',
  4432. backgroundColor: '#f5f5f5',
  4433.  
  4434. initialValue: 0,
  4435.  
  4436.  
  4437. updateValue: function(value, text) {
  4438. var me = this;
  4439. var color = me.defaultColor;
  4440. var attr = {};
  4441.  
  4442. if (value >= me.criticalThreshold) {
  4443. color = me.criticalColor;
  4444. } else if (value >= me.warningThreshold) {
  4445. color = me.warningColor;
  4446. }
  4447.  
  4448. me.chart.series[0].setColors([color, me.backgroundColor]);
  4449. me.chart.series[0].setValue(value*100);
  4450.  
  4451. me.valueSprite.setText(' '+(value*100).toFixed(0) + '%');
  4452. attr.x = me.chart.getWidth()/2;
  4453. attr.y = me.chart.getHeight()-20;
  4454. if (me.spriteFontSize) {
  4455. attr.fontSize = me.spriteFontSize;
  4456. }
  4457. me.valueSprite.setAttributes(attr, true);
  4458.  
  4459. if (text !== undefined) {
  4460. me.text.setHtml(text);
  4461. }
  4462. },
  4463.  
  4464. initComponent: function() {
  4465. var me = this;
  4466.  
  4467. me.callParent();
  4468.  
  4469. if (me.title) {
  4470. me.getComponent('title').update({title: me.title});
  4471. }
  4472. me.text = me.getComponent('text');
  4473. me.chart = me.getComponent('chart');
  4474. me.valueSprite = me.chart.getSurface('chart').get('valueSprite');
  4475. }
  4476. });
  4477. // fixme: how can we avoid those lint errors?
  4478. /*jslint confusion: true */
  4479. Ext.define('Proxmox.window.Edit', {
  4480. extend: 'Ext.window.Window',
  4481. alias: 'widget.proxmoxWindowEdit',
  4482.  
  4483. // autoLoad trigger a load() after component creation
  4484. autoLoad: false,
  4485.  
  4486. resizable: false,
  4487.  
  4488. // use this tio atimatically generate a title like
  4489. // Create: <subject>
  4490. subject: undefined,
  4491.  
  4492. // set isCreate to true if you want a Create button (instead
  4493. // OK and RESET)
  4494. isCreate: false,
  4495.  
  4496. // set to true if you want an Add button (instead of Create)
  4497. isAdd: false,
  4498.  
  4499. // set to true if you want an Remove button (instead of Create)
  4500. isRemove: false,
  4501.  
  4502. // custom submitText
  4503. submitText: undefined,
  4504.  
  4505. backgroundDelay: 0,
  4506.  
  4507. // needed for finding the reference to submitbutton
  4508. // because we do not have a controller
  4509. referenceHolder: true,
  4510. defaultButton: 'submitbutton',
  4511.  
  4512. // finds the first form field
  4513. defaultFocus: 'field[disabled=false][hidden=false]',
  4514.  
  4515. showProgress: false,
  4516.  
  4517. showTaskViewer: false,
  4518.  
  4519. // gets called if we have a progress bar or taskview and it detected that
  4520. // the task finished. function(success)
  4521. taskDone: Ext.emptyFn,
  4522.  
  4523. // gets called when the api call is finished, right at the beginning
  4524. // function(success, response, options)
  4525. apiCallDone: Ext.emptyFn,
  4526.  
  4527. // assign a reference from docs, to add a help button docked to the
  4528. // bottom of the window. If undefined we magically fall back to the
  4529. // onlineHelp of our first item, if set.
  4530. onlineHelp: undefined,
  4531.  
  4532. isValid: function() {
  4533. var me = this;
  4534.  
  4535. var form = me.formPanel.getForm();
  4536. return form.isValid();
  4537. },
  4538.  
  4539. getValues: function(dirtyOnly) {
  4540. var me = this;
  4541.  
  4542. var values = {};
  4543.  
  4544. var form = me.formPanel.getForm();
  4545.  
  4546. form.getFields().each(function(field) {
  4547. if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
  4548. Proxmox.Utils.assemble_field_data(values, field.getSubmitData());
  4549. }
  4550. });
  4551.  
  4552. Ext.Array.each(me.query('inputpanel'), function(panel) {
  4553. Proxmox.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
  4554. });
  4555.  
  4556. return values;
  4557. },
  4558.  
  4559. setValues: function(values) {
  4560. var me = this;
  4561.  
  4562. var form = me.formPanel.getForm();
  4563.  
  4564. Ext.iterate(values, function(fieldId, val) {
  4565. var field = form.findField(fieldId);
  4566. if (field && !field.up('inputpanel')) {
  4567. field.setValue(val);
  4568. if (form.trackResetOnLoad) {
  4569. field.resetOriginalValue();
  4570. }
  4571. }
  4572. });
  4573.  
  4574. Ext.Array.each(me.query('inputpanel'), function(panel) {
  4575. panel.setValues(values);
  4576. });
  4577. },
  4578.  
  4579. submit: function() {
  4580. var me = this;
  4581.  
  4582. var form = me.formPanel.getForm();
  4583.  
  4584. var values = me.getValues();
  4585. Ext.Object.each(values, function(name, val) {
  4586. if (values.hasOwnProperty(name)) {
  4587. if (Ext.isArray(val) && !val.length) {
  4588. values[name] = '';
  4589. }
  4590. }
  4591. });
  4592.  
  4593. if (me.digest) {
  4594. values.digest = me.digest;
  4595. }
  4596.  
  4597. if (me.backgroundDelay) {
  4598. values.background_delay = me.backgroundDelay;
  4599. }
  4600.  
  4601. var url = me.url;
  4602. if (me.method === 'DELETE') {
  4603. url = url + "?" + Ext.Object.toQueryString(values);
  4604. values = undefined;
  4605. }
  4606.  
  4607. Proxmox.Utils.API2Request({
  4608. url: url,
  4609. waitMsgTarget: me,
  4610. method: me.method || (me.backgroundDelay ? 'POST' : 'PUT'),
  4611. params: values,
  4612. failure: function(response, options) {
  4613. me.apiCallDone(false, response, options);
  4614.  
  4615. if (response.result && response.result.errors) {
  4616. form.markInvalid(response.result.errors);
  4617. }
  4618. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  4619. },
  4620. success: function(response, options) {
  4621. var hasProgressBar = (me.backgroundDelay || me.showProgress || me.showTaskViewer) &&
  4622. response.result.data ? true : false;
  4623.  
  4624. me.apiCallDone(true, response, options);
  4625.  
  4626. if (hasProgressBar) {
  4627. // stay around so we can trigger our close events
  4628. // when background action is completed
  4629. me.hide();
  4630.  
  4631. var upid = response.result.data;
  4632. var viewerClass = me.showTaskViewer ? 'Viewer' : 'Progress';
  4633. var win = Ext.create('Proxmox.window.Task' + viewerClass, {
  4634. upid: upid,
  4635. taskDone: me.taskDone,
  4636. listeners: {
  4637. destroy: function () {
  4638. me.close();
  4639. }
  4640. }
  4641. });
  4642. win.show();
  4643. } else {
  4644. me.close();
  4645. }
  4646. }
  4647. });
  4648. },
  4649.  
  4650. load: function(options) {
  4651. var me = this;
  4652.  
  4653. var form = me.formPanel.getForm();
  4654.  
  4655. options = options || {};
  4656.  
  4657. var newopts = Ext.apply({
  4658. waitMsgTarget: me
  4659. }, options);
  4660.  
  4661. var createWrapper = function(successFn) {
  4662. Ext.apply(newopts, {
  4663. url: me.url,
  4664. method: 'GET',
  4665. success: function(response, opts) {
  4666. form.clearInvalid();
  4667. me.digest = response.result.data.digest;
  4668. if (successFn) {
  4669. successFn(response, opts);
  4670. } else {
  4671. me.setValues(response.result.data);
  4672. }
  4673. // hack: fix ExtJS bug
  4674. Ext.Array.each(me.query('radiofield'), function(f) {
  4675. f.resetOriginalValue();
  4676. });
  4677. },
  4678. failure: function(response, opts) {
  4679. Ext.Msg.alert(gettext('Error'), response.htmlStatus, function() {
  4680. me.close();
  4681. });
  4682. }
  4683. });
  4684. };
  4685.  
  4686. createWrapper(options.success);
  4687.  
  4688. Proxmox.Utils.API2Request(newopts);
  4689. },
  4690.  
  4691. initComponent : function() {
  4692. var me = this;
  4693.  
  4694. if (!me.url) {
  4695. throw "no url specified";
  4696. }
  4697.  
  4698. if (me.create) {throw "deprecated parameter, use isCreate";}
  4699.  
  4700. var items = Ext.isArray(me.items) ? me.items : [ me.items ];
  4701.  
  4702. me.items = undefined;
  4703.  
  4704. me.formPanel = Ext.create('Ext.form.Panel', {
  4705. url: me.url,
  4706. method: me.method || 'PUT',
  4707. trackResetOnLoad: true,
  4708. bodyPadding: 10,
  4709. border: false,
  4710. defaults: Ext.apply({}, me.defaults, {
  4711. border: false
  4712. }),
  4713. fieldDefaults: Ext.apply({}, me.fieldDefaults, {
  4714. labelWidth: 100,
  4715. anchor: '100%'
  4716. }),
  4717. items: items
  4718. });
  4719.  
  4720. var inputPanel = me.formPanel.down('inputpanel');
  4721.  
  4722. var form = me.formPanel.getForm();
  4723.  
  4724. var submitText;
  4725. if (me.isCreate) {
  4726. if (me.submitText) {
  4727. submitText = me.submitText;
  4728. } else if (me.isAdd) {
  4729. submitText = gettext('Add');
  4730. } else if (me.isRemove) {
  4731. submitText = gettext('Remove');
  4732. } else {
  4733. submitText = gettext('Create');
  4734. }
  4735. } else {
  4736. submitText = me.submitText || gettext('OK');
  4737. }
  4738.  
  4739. var submitBtn = Ext.create('Ext.Button', {
  4740. reference: 'submitbutton',
  4741. text: submitText,
  4742. disabled: !me.isCreate,
  4743. handler: function() {
  4744. me.submit();
  4745. }
  4746. });
  4747.  
  4748. var resetBtn = Ext.create('Ext.Button', {
  4749. text: 'Reset',
  4750. disabled: true,
  4751. handler: function(){
  4752. form.reset();
  4753. }
  4754. });
  4755.  
  4756. var set_button_status = function() {
  4757. var valid = form.isValid();
  4758. var dirty = form.isDirty();
  4759. submitBtn.setDisabled(!valid || !(dirty || me.isCreate));
  4760. resetBtn.setDisabled(!dirty);
  4761.  
  4762. if (inputPanel && inputPanel.hasAdvanced) {
  4763. // we want to show the advanced options
  4764. // as soon as some of it is not valid
  4765. var advancedItems = me.down('#advancedContainer').query('field');
  4766. var valid = true;
  4767. advancedItems.forEach(function(field) {
  4768. if (!field.isValid()) {
  4769. valid = false;
  4770. }
  4771. });
  4772.  
  4773. if (!valid) {
  4774. inputPanel.setAdvancedVisible(true);
  4775. me.down('#advancedcb').setValue(true);
  4776. }
  4777. }
  4778. };
  4779.  
  4780. form.on('dirtychange', set_button_status);
  4781. form.on('validitychange', set_button_status);
  4782.  
  4783. var colwidth = 300;
  4784. if (me.fieldDefaults && me.fieldDefaults.labelWidth) {
  4785. colwidth += me.fieldDefaults.labelWidth - 100;
  4786. }
  4787.  
  4788. var twoColumn = inputPanel &&
  4789. (inputPanel.column1 || inputPanel.column2);
  4790.  
  4791. if (me.subject && !me.title) {
  4792. me.title = Proxmox.Utils.dialog_title(me.subject, me.isCreate, me.isAdd);
  4793. }
  4794.  
  4795. if (me.isCreate) {
  4796. me.buttons = [ submitBtn ] ;
  4797. } else {
  4798. me.buttons = [ submitBtn, resetBtn ];
  4799. }
  4800.  
  4801. if (inputPanel && inputPanel.hasAdvanced) {
  4802. var sp = Ext.state.Manager.getProvider();
  4803. var advchecked = sp.get('proxmox-advanced-cb');
  4804. inputPanel.setAdvancedVisible(advchecked);
  4805. me.buttons.unshift(
  4806. {
  4807. xtype: 'proxmoxcheckbox',
  4808. itemId: 'advancedcb',
  4809. boxLabelAlign: 'before',
  4810. boxLabel: gettext('Advanced'),
  4811. stateId: 'proxmox-advanced-cb',
  4812. value: advchecked,
  4813. listeners: {
  4814. change: function(cb, val) {
  4815. inputPanel.setAdvancedVisible(val);
  4816. sp.set('proxmox-advanced-cb', val);
  4817. }
  4818. }
  4819. }
  4820. );
  4821. }
  4822.  
  4823. var onlineHelp = me.onlineHelp;
  4824. if (!onlineHelp && inputPanel && inputPanel.onlineHelp) {
  4825. onlineHelp = inputPanel.onlineHelp;
  4826. }
  4827.  
  4828. if (onlineHelp) {
  4829. var helpButton = Ext.create('Proxmox.button.Help');
  4830. me.buttons.unshift(helpButton, '->');
  4831. Ext.GlobalEvents.fireEvent('proxmoxShowHelp', onlineHelp);
  4832. }
  4833.  
  4834. Ext.applyIf(me, {
  4835. modal: true,
  4836. width: twoColumn ? colwidth*2 : colwidth,
  4837. border: false,
  4838. items: [ me.formPanel ]
  4839. });
  4840.  
  4841. me.callParent();
  4842.  
  4843. // always mark invalid fields
  4844. me.on('afterlayout', function() {
  4845. // on touch devices, the isValid function
  4846. // triggers a layout, which triggers an isValid
  4847. // and so on
  4848. // to prevent this we disable the layouting here
  4849. // and enable it afterwards
  4850. me.suspendLayout = true;
  4851. me.isValid();
  4852. me.suspendLayout = false;
  4853. });
  4854.  
  4855. if (me.autoLoad) {
  4856. me.load();
  4857. }
  4858. }
  4859. });
  4860. Ext.define('Proxmox.window.PasswordEdit', {
  4861. extend: 'Proxmox.window.Edit',
  4862. alias: 'proxmoxWindowPasswordEdit',
  4863.  
  4864. subject: gettext('Password'),
  4865.  
  4866. url: '/api2/extjs/access/password',
  4867.  
  4868. fieldDefaults: {
  4869. labelWidth: 120
  4870. },
  4871.  
  4872. items: [
  4873. {
  4874. xtype: 'textfield',
  4875. inputType: 'password',
  4876. fieldLabel: gettext('Password'),
  4877. minLength: 5,
  4878. allowBlank: false,
  4879. name: 'password',
  4880. listeners: {
  4881. change: function(field){
  4882. field.next().validate();
  4883. },
  4884. blur: function(field){
  4885. field.next().validate();
  4886. }
  4887. }
  4888. },
  4889. {
  4890. xtype: 'textfield',
  4891. inputType: 'password',
  4892. fieldLabel: gettext('Confirm password'),
  4893. name: 'verifypassword',
  4894. allowBlank: false,
  4895. vtype: 'password',
  4896. initialPassField: 'password',
  4897. submitValue: false
  4898. },
  4899. {
  4900. xtype: 'hiddenfield',
  4901. name: 'userid'
  4902. }
  4903. ],
  4904.  
  4905. initComponent : function() {
  4906. var me = this;
  4907.  
  4908. if (!me.userid) {
  4909. throw "no userid specified";
  4910. }
  4911.  
  4912. me.callParent();
  4913. me.down('[name=userid]').setValue(me.userid);
  4914. }
  4915. });
  4916. Ext.define('Proxmox.window.TaskProgress', {
  4917. extend: 'Ext.window.Window',
  4918. alias: 'widget.proxmoxTaskProgress',
  4919.  
  4920. taskDone: Ext.emptyFn,
  4921.  
  4922. initComponent: function() {
  4923. var me = this;
  4924.  
  4925. if (!me.upid) {
  4926. throw "no task specified";
  4927. }
  4928.  
  4929. var task = Proxmox.Utils.parse_task_upid(me.upid);
  4930.  
  4931. var statstore = Ext.create('Proxmox.data.ObjectStore', {
  4932. url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
  4933. interval: 1000,
  4934. rows: {
  4935. status: { defaultValue: 'unknown' },
  4936. exitstatus: { defaultValue: 'unknown' }
  4937. }
  4938. });
  4939.  
  4940. me.on('destroy', statstore.stopUpdate);
  4941.  
  4942. var getObjectValue = function(key, defaultValue) {
  4943. var rec = statstore.getById(key);
  4944. if (rec) {
  4945. return rec.data.value;
  4946. }
  4947. return defaultValue;
  4948. };
  4949.  
  4950. var pbar = Ext.create('Ext.ProgressBar', { text: 'running...' });
  4951.  
  4952. me.mon(statstore, 'load', function() {
  4953. var status = getObjectValue('status');
  4954. if (status === 'stopped') {
  4955. var exitstatus = getObjectValue('exitstatus');
  4956. if (exitstatus == 'OK') {
  4957. pbar.reset();
  4958. pbar.updateText("Done!");
  4959. Ext.Function.defer(me.close, 1000, me);
  4960. } else {
  4961. me.close();
  4962. Ext.Msg.alert('Task failed', exitstatus);
  4963. }
  4964. me.taskDone(exitstatus == 'OK');
  4965. }
  4966. });
  4967.  
  4968. var descr = Proxmox.Utils.format_task_description(task.type, task.id);
  4969.  
  4970. Ext.apply(me, {
  4971. title: gettext('Task') + ': ' + descr,
  4972. width: 300,
  4973. layout: 'auto',
  4974. modal: true,
  4975. bodyPadding: 5,
  4976. items: pbar,
  4977. buttons: [
  4978. {
  4979. text: gettext('Details'),
  4980. handler: function() {
  4981. var win = Ext.create('Proxmox.window.TaskViewer', {
  4982. taskDone: me.taskDone,
  4983. upid: me.upid
  4984. });
  4985. win.show();
  4986. me.close();
  4987. }
  4988. }
  4989. ]
  4990. });
  4991.  
  4992. me.callParent();
  4993.  
  4994. statstore.startUpdate();
  4995.  
  4996. pbar.wait();
  4997. }
  4998. });
  4999.  
  5000. // fixme: how can we avoid those lint errors?
  5001. /*jslint confusion: true */
  5002.  
  5003. Ext.define('Proxmox.window.TaskViewer', {
  5004. extend: 'Ext.window.Window',
  5005. alias: 'widget.proxmoxTaskViewer',
  5006.  
  5007. extraTitle: '', // string to prepend after the generic task title
  5008.  
  5009. taskDone: Ext.emptyFn,
  5010.  
  5011. initComponent: function() {
  5012. var me = this;
  5013.  
  5014. if (!me.upid) {
  5015. throw "no task specified";
  5016. }
  5017.  
  5018. var task = Proxmox.Utils.parse_task_upid(me.upid);
  5019.  
  5020. var statgrid;
  5021.  
  5022. var rows = {
  5023. status: {
  5024. header: gettext('Status'),
  5025. defaultValue: 'unknown',
  5026. renderer: function(value) {
  5027. if (value != 'stopped') {
  5028. return value;
  5029. }
  5030. var es = statgrid.getObjectValue('exitstatus');
  5031. if (es) {
  5032. return value + ': ' + es;
  5033. }
  5034. }
  5035. },
  5036. exitstatus: {
  5037. visible: false
  5038. },
  5039. type: {
  5040. header: gettext('Task type'),
  5041. required: true
  5042. },
  5043. user: {
  5044. header: gettext('User name'),
  5045. required: true
  5046. },
  5047. node: {
  5048. header: gettext('Node'),
  5049. required: true
  5050. },
  5051. pid: {
  5052. header: gettext('Process ID'),
  5053. required: true
  5054. },
  5055. starttime: {
  5056. header: gettext('Start Time'),
  5057. required: true,
  5058. renderer: Proxmox.Utils.render_timestamp
  5059. },
  5060. upid: {
  5061. header: gettext('Unique task ID')
  5062. }
  5063. };
  5064.  
  5065. var statstore = Ext.create('Proxmox.data.ObjectStore', {
  5066. url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
  5067. interval: 1000,
  5068. rows: rows
  5069. });
  5070.  
  5071. me.on('destroy', statstore.stopUpdate);
  5072.  
  5073. var stop_task = function() {
  5074. Proxmox.Utils.API2Request({
  5075. url: "/nodes/" + task.node + "/tasks/" + me.upid,
  5076. waitMsgTarget: me,
  5077. method: 'DELETE',
  5078. failure: function(response, opts) {
  5079. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  5080. }
  5081. });
  5082. };
  5083.  
  5084. var stop_btn1 = new Ext.Button({
  5085. text: gettext('Stop'),
  5086. disabled: true,
  5087. handler: stop_task
  5088. });
  5089.  
  5090. var stop_btn2 = new Ext.Button({
  5091. text: gettext('Stop'),
  5092. disabled: true,
  5093. handler: stop_task
  5094. });
  5095.  
  5096. statgrid = Ext.create('Proxmox.grid.ObjectGrid', {
  5097. title: gettext('Status'),
  5098. layout: 'fit',
  5099. tbar: [ stop_btn1 ],
  5100. rstore: statstore,
  5101. rows: rows,
  5102. border: false
  5103. });
  5104.  
  5105. var logView = Ext.create('Proxmox.panel.LogView', {
  5106. title: gettext('Output'),
  5107. tbar: [ stop_btn2 ],
  5108. border: false,
  5109. url: "/api2/extjs/nodes/" + task.node + "/tasks/" + me.upid + "/log"
  5110. });
  5111.  
  5112. me.mon(statstore, 'load', function() {
  5113. var status = statgrid.getObjectValue('status');
  5114.  
  5115. if (status === 'stopped') {
  5116. logView.requestUpdate(undefined, true);
  5117. logView.scrollToEnd = false;
  5118. statstore.stopUpdate();
  5119. me.taskDone(statgrid.getObjectValue('exitstatus') == 'OK');
  5120. }
  5121.  
  5122. stop_btn1.setDisabled(status !== 'running');
  5123. stop_btn2.setDisabled(status !== 'running');
  5124. });
  5125.  
  5126. statstore.startUpdate();
  5127.  
  5128. Ext.apply(me, {
  5129. title: "Task viewer: " + task.desc + me.extraTitle,
  5130. width: 800,
  5131. height: 400,
  5132. layout: 'fit',
  5133. modal: true,
  5134. items: [{
  5135. xtype: 'tabpanel',
  5136. region: 'center',
  5137. items: [ logView, statgrid ]
  5138. }]
  5139. });
  5140.  
  5141. me.callParent();
  5142.  
  5143. logView.fireEvent('show', logView);
  5144. }
  5145. });
  5146.  
  5147. Ext.define('apt-pkglist', {
  5148. extend: 'Ext.data.Model',
  5149. fields: [ 'Package', 'Title', 'Description', 'Section', 'Arch',
  5150. 'Priority', 'Version', 'OldVersion', 'ChangeLogUrl', 'Origin' ],
  5151. idProperty: 'Package'
  5152. });
  5153.  
  5154. Ext.define('Proxmox.node.APT', {
  5155. extend: 'Ext.grid.GridPanel',
  5156.  
  5157. xtype: 'proxmoxNodeAPT',
  5158.  
  5159. upgradeBtn: undefined,
  5160.  
  5161. columns: [
  5162. {
  5163. header: gettext('Package'),
  5164. width: 200,
  5165. sortable: true,
  5166. dataIndex: 'Package'
  5167. },
  5168. {
  5169. text: gettext('Version'),
  5170. columns: [
  5171. {
  5172. header: gettext('current'),
  5173. width: 100,
  5174. sortable: false,
  5175. dataIndex: 'OldVersion'
  5176. },
  5177. {
  5178. header: gettext('new'),
  5179. width: 100,
  5180. sortable: false,
  5181. dataIndex: 'Version'
  5182. }
  5183. ]
  5184. },
  5185. {
  5186. header: gettext('Description'),
  5187. sortable: false,
  5188. dataIndex: 'Title',
  5189. flex: 1
  5190. }
  5191. ],
  5192.  
  5193. initComponent : function() {
  5194. var me = this;
  5195.  
  5196. if (!me.nodename) {
  5197. throw "no node name specified";
  5198. }
  5199.  
  5200. var store = Ext.create('Ext.data.Store', {
  5201. model: 'apt-pkglist',
  5202. groupField: 'Origin',
  5203. proxy: {
  5204. type: 'proxmox',
  5205. url: "/api2/json/nodes/" + me.nodename + "/apt/update"
  5206. },
  5207. sorters: [
  5208. {
  5209. property : 'Package',
  5210. direction: 'ASC'
  5211. }
  5212. ]
  5213. });
  5214.  
  5215. var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
  5216. groupHeaderTpl: '{[ "Origin: " + values.name ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})',
  5217. enableGroupingMenu: false
  5218. });
  5219.  
  5220. var rowBodyFeature = Ext.create('Ext.grid.feature.RowBody', {
  5221. getAdditionalData: function (data, rowIndex, record, orig) {
  5222. var headerCt = this.view.headerCt;
  5223. var colspan = headerCt.getColumnCount();
  5224. // Usually you would style the my-body-class in CSS file
  5225. return {
  5226. rowBody: '<div style="padding: 1em">' +
  5227. Ext.String.htmlEncode(data.Description) +
  5228. '</div>',
  5229. rowBodyColspan: colspan
  5230. };
  5231. }
  5232. });
  5233.  
  5234. var reload = function() {
  5235. store.load();
  5236. };
  5237.  
  5238. Proxmox.Utils.monStoreErrors(me, store, true);
  5239.  
  5240. var apt_command = function(cmd){
  5241. Proxmox.Utils.API2Request({
  5242. url: "/nodes/" + me.nodename + "/apt/" + cmd,
  5243. method: 'POST',
  5244. failure: function(response, opts) {
  5245. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  5246. },
  5247. success: function(response, opts) {
  5248. var upid = response.result.data;
  5249.  
  5250. var win = Ext.create('Proxmox.window.TaskViewer', {
  5251. upid: upid
  5252. });
  5253. win.show();
  5254. me.mon(win, 'close', reload);
  5255. }
  5256. });
  5257. };
  5258.  
  5259. var sm = Ext.create('Ext.selection.RowModel', {});
  5260.  
  5261. var update_btn = new Ext.Button({
  5262. text: gettext('Refresh'),
  5263. handler: function(){
  5264. Proxmox.Utils.checked_command(function() { apt_command('update'); });
  5265. }
  5266. });
  5267.  
  5268. var show_changelog = function(rec) {
  5269. if (!rec || !rec.data || !(rec.data.ChangeLogUrl && rec.data.Package)) {
  5270. return;
  5271. }
  5272.  
  5273. var view = Ext.createWidget('component', {
  5274. autoScroll: true,
  5275. style: {
  5276. 'background-color': 'white',
  5277. 'white-space': 'pre',
  5278. 'font-family': 'monospace',
  5279. padding: '5px'
  5280. }
  5281. });
  5282.  
  5283. var win = Ext.create('Ext.window.Window', {
  5284. title: gettext('Changelog') + ": " + rec.data.Package,
  5285. width: 800,
  5286. height: 400,
  5287. layout: 'fit',
  5288. modal: true,
  5289. items: [ view ]
  5290. });
  5291.  
  5292. Proxmox.Utils.API2Request({
  5293. waitMsgTarget: me,
  5294. url: "/nodes/" + me.nodename + "/apt/changelog",
  5295. params: {
  5296. name: rec.data.Package,
  5297. version: rec.data.Version
  5298. },
  5299. method: 'GET',
  5300. failure: function(response, opts) {
  5301. win.close();
  5302. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  5303. },
  5304. success: function(response, opts) {
  5305. win.show();
  5306. view.update(Ext.htmlEncode(response.result.data));
  5307. }
  5308. });
  5309.  
  5310. };
  5311.  
  5312. var changelog_btn = new Proxmox.button.Button({
  5313. text: gettext('Changelog'),
  5314. selModel: sm,
  5315. disabled: true,
  5316. enableFn: function(rec) {
  5317. if (!rec || !rec.data || !(rec.data.ChangeLogUrl && rec.data.Package)) {
  5318. return false;
  5319. }
  5320. return true;
  5321. },
  5322. handler: function(b, e, rec) {
  5323. show_changelog(rec);
  5324. }
  5325. });
  5326.  
  5327. if (me.upgradeBtn) {
  5328. me.tbar = [ update_btn, me.upgradeBtn, changelog_btn ];
  5329. } else {
  5330. me.tbar = [ update_btn, changelog_btn ];
  5331. }
  5332.  
  5333. Ext.apply(me, {
  5334. store: store,
  5335. stateful: true,
  5336. stateId: 'grid-update',
  5337. selModel: sm,
  5338. viewConfig: {
  5339. stripeRows: false,
  5340. emptyText: '<div style="display:table; width:100%; height:100%;"><div style="display:table-cell; vertical-align: middle; text-align:center;"><b>' + gettext('No updates available.') + '</div></div>'
  5341. },
  5342. features: [ groupingFeature, rowBodyFeature ],
  5343. listeners: {
  5344. activate: reload,
  5345. itemdblclick: function(v, rec) {
  5346. show_changelog(rec);
  5347. }
  5348. }
  5349. });
  5350.  
  5351. me.callParent();
  5352. }
  5353. });
  5354. Ext.define('Proxmox.node.NetworkEdit', {
  5355. extend: 'Proxmox.window.Edit',
  5356. alias: ['widget.proxmoxNodeNetworkEdit'],
  5357.  
  5358. initComponent : function() {
  5359. var me = this;
  5360.  
  5361. if (!me.nodename) {
  5362. throw "no node name specified";
  5363. }
  5364.  
  5365. if (!me.iftype) {
  5366. throw "no network device type specified";
  5367. }
  5368.  
  5369. me.isCreate = !me.iface;
  5370.  
  5371. var iface_vtype;
  5372.  
  5373. if (me.iftype === 'bridge') {
  5374. iface_vtype = 'BridgeName';
  5375. } else if (me.iftype === 'bond') {
  5376. iface_vtype = 'BondName';
  5377. } else if (me.iftype === 'eth' && !me.isCreate) {
  5378. iface_vtype = 'InterfaceName';
  5379. } else if (me.iftype === 'vlan' && !me.isCreate) {
  5380. iface_vtype = 'InterfaceName';
  5381. } else if (me.iftype === 'OVSBridge') {
  5382. iface_vtype = 'BridgeName';
  5383. } else if (me.iftype === 'OVSBond') {
  5384. iface_vtype = 'BondName';
  5385. } else if (me.iftype === 'OVSIntPort') {
  5386. iface_vtype = 'InterfaceName';
  5387. } else if (me.iftype === 'OVSPort') {
  5388. iface_vtype = 'InterfaceName';
  5389. } else {
  5390. console.log(me.iftype);
  5391. throw "unknown network device type specified";
  5392. }
  5393.  
  5394. me.subject = Proxmox.Utils.render_network_iface_type(me.iftype);
  5395.  
  5396. var column2 = [];
  5397.  
  5398. if (!(me.iftype === 'OVSIntPort' || me.iftype === 'OVSPort' ||
  5399. me.iftype === 'OVSBond')) {
  5400. column2.push({
  5401. xtype: 'proxmoxcheckbox',
  5402. fieldLabel: gettext('Autostart'),
  5403. name: 'autostart',
  5404. uncheckedValue: 0,
  5405. checked: me.isCreate ? true : undefined
  5406. });
  5407. }
  5408.  
  5409. if (me.iftype === 'bridge') {
  5410. column2.push({
  5411. xtype: 'proxmoxcheckbox',
  5412. fieldLabel: gettext('VLAN aware'),
  5413. name: 'bridge_vlan_aware',
  5414. deleteEmpty: !me.isCreate
  5415. });
  5416. column2.push({
  5417. xtype: 'textfield',
  5418. fieldLabel: gettext('Bridge ports'),
  5419. name: 'bridge_ports'
  5420. });
  5421. } else if (me.iftype === 'OVSBridge') {
  5422. column2.push({
  5423. xtype: 'textfield',
  5424. fieldLabel: gettext('Bridge ports'),
  5425. name: 'ovs_ports'
  5426. });
  5427. column2.push({
  5428. xtype: 'textfield',
  5429. fieldLabel: gettext('OVS options'),
  5430. name: 'ovs_options'
  5431. });
  5432. } else if (me.iftype === 'OVSPort' || me.iftype === 'OVSIntPort') {
  5433. column2.push({
  5434. xtype: me.isCreate ? 'PVE.form.BridgeSelector' : 'displayfield',
  5435. fieldLabel: Proxmox.Utils.render_network_iface_type('OVSBridge'),
  5436. allowBlank: false,
  5437. nodename: me.nodename,
  5438. bridgeType: 'OVSBridge',
  5439. name: 'ovs_bridge'
  5440. });
  5441. column2.push({
  5442. xtype: 'pveVlanField',
  5443. deleteEmpty: !me.isCreate,
  5444. name: 'ovs_tag',
  5445. value: ''
  5446. });
  5447. column2.push({
  5448. xtype: 'textfield',
  5449. fieldLabel: gettext('OVS options'),
  5450. name: 'ovs_options'
  5451. });
  5452. } else if (me.iftype === 'bond') {
  5453. column2.push({
  5454. xtype: 'textfield',
  5455. fieldLabel: gettext('Slaves'),
  5456. name: 'slaves'
  5457. });
  5458.  
  5459. var policySelector = Ext.createWidget('bondPolicySelector', {
  5460. fieldLabel: gettext('Hash policy'),
  5461. name: 'bond_xmit_hash_policy',
  5462. deleteEmpty: !me.isCreate,
  5463. disabled: true
  5464. });
  5465.  
  5466. column2.push({
  5467. xtype: 'bondModeSelector',
  5468. fieldLabel: gettext('Mode'),
  5469. name: 'bond_mode',
  5470. value: me.isCreate ? 'balance-rr' : undefined,
  5471. listeners: {
  5472. change: function(f, value) {
  5473. if (value === 'balance-xor' ||
  5474. value === '802.3ad') {
  5475. policySelector.setDisabled(false);
  5476. } else {
  5477. policySelector.setDisabled(true);
  5478. policySelector.setValue('');
  5479. }
  5480. }
  5481. },
  5482. allowBlank: false
  5483. });
  5484.  
  5485. column2.push(policySelector);
  5486.  
  5487. } else if (me.iftype === 'OVSBond') {
  5488. column2.push({
  5489. xtype: me.isCreate ? 'PVE.form.BridgeSelector' : 'displayfield',
  5490. fieldLabel: Proxmox.Utils.render_network_iface_type('OVSBridge'),
  5491. allowBlank: false,
  5492. nodename: me.nodename,
  5493. bridgeType: 'OVSBridge',
  5494. name: 'ovs_bridge'
  5495. });
  5496. column2.push({
  5497. xtype: 'pveVlanField',
  5498. deleteEmpty: !me.isCreate,
  5499. name: 'ovs_tag',
  5500. value: ''
  5501. });
  5502. column2.push({
  5503. xtype: 'textfield',
  5504. fieldLabel: gettext('OVS options'),
  5505. name: 'ovs_options'
  5506. });
  5507. }
  5508.  
  5509. column2.push({
  5510. xtype: 'textfield',
  5511. fieldLabel: gettext('Comment'),
  5512. allowBlank: true,
  5513. nodename: me.nodename,
  5514. name: 'comments'
  5515. });
  5516.  
  5517. var url;
  5518. var method;
  5519.  
  5520. if (me.isCreate) {
  5521. url = "/api2/extjs/nodes/" + me.nodename + "/network";
  5522. method = 'POST';
  5523. } else {
  5524. url = "/api2/extjs/nodes/" + me.nodename + "/network/" + me.iface;
  5525. method = 'PUT';
  5526. }
  5527.  
  5528. var column1 = [
  5529. {
  5530. xtype: 'hiddenfield',
  5531. name: 'type',
  5532. value: me.iftype
  5533. },
  5534. {
  5535. xtype: me.isCreate ? 'textfield' : 'displayfield',
  5536. fieldLabel: gettext('Name'),
  5537. name: 'iface',
  5538. value: me.iface,
  5539. vtype: iface_vtype,
  5540. allowBlank: false
  5541. }
  5542. ];
  5543.  
  5544. if (me.iftype === 'OVSBond') {
  5545. column1.push(
  5546. {
  5547. xtype: 'bondModeSelector',
  5548. fieldLabel: gettext('Mode'),
  5549. name: 'bond_mode',
  5550. openvswitch: true,
  5551. value: me.isCreate ? 'active-backup' : undefined,
  5552. allowBlank: false
  5553. },
  5554. {
  5555. xtype: 'textfield',
  5556. fieldLabel: gettext('Slaves'),
  5557. name: 'ovs_bonds'
  5558. }
  5559. );
  5560. } else {
  5561.  
  5562. column1.push(
  5563. {
  5564. xtype: 'proxmoxtextfield',
  5565. deleteEmpty: !me.isCreate,
  5566. fieldLabel: gettext('IP address'),
  5567. vtype: 'IPAddress',
  5568. name: 'address'
  5569. },
  5570. {
  5571. xtype: 'proxmoxtextfield',
  5572. deleteEmpty: !me.isCreate,
  5573. fieldLabel: gettext('Subnet mask'),
  5574. vtype: 'IPAddress',
  5575. name: 'netmask',
  5576. validator: function(value) {
  5577. /*jslint confusion: true */
  5578. if (!me.items) {
  5579. return true;
  5580. }
  5581. var address = me.down('field[name=address]').getValue();
  5582. if (value !== '') {
  5583. if (address === '') {
  5584. return "Subnet mask requires option 'IP address'";
  5585. }
  5586. } else {
  5587. if (address !== '') {
  5588. return "Option 'IP address' requires a subnet mask";
  5589. }
  5590. }
  5591.  
  5592. return true;
  5593. }
  5594. },
  5595. {
  5596. xtype: 'proxmoxtextfield',
  5597. deleteEmpty: !me.isCreate,
  5598. fieldLabel: gettext('Gateway'),
  5599. vtype: 'IPAddress',
  5600. name: 'gateway'
  5601. },
  5602. {
  5603. xtype: 'proxmoxtextfield',
  5604. deleteEmpty: !me.isCreate,
  5605. fieldLabel: gettext('IPv6 address'),
  5606. vtype: 'IP6Address',
  5607. name: 'address6'
  5608. },
  5609. {
  5610. xtype: 'proxmoxtextfield',
  5611. deleteEmpty: !me.isCreate,
  5612. fieldLabel: gettext('Prefix length'),
  5613. vtype: 'IP6PrefixLength',
  5614. name: 'netmask6',
  5615. value: '',
  5616. allowBlank: true,
  5617. validator: function(value) {
  5618. /*jslint confusion: true */
  5619. if (!me.items) {
  5620. return true;
  5621. }
  5622. var address = me.down('field[name=address6]').getValue();
  5623. if (value !== '') {
  5624. if (address === '') {
  5625. return "IPv6 prefix length requires option 'IPv6 address'";
  5626. }
  5627. } else {
  5628. if (address !== '') {
  5629. return "Option 'IPv6 address' requires an IPv6 prefix length";
  5630. }
  5631. }
  5632.  
  5633. return true;
  5634. }
  5635. },
  5636. {
  5637. xtype: 'proxmoxtextfield',
  5638. deleteEmpty: !me.isCreate,
  5639. fieldLabel: gettext('Gateway'),
  5640. vtype: 'IP6Address',
  5641. name: 'gateway6'
  5642. }
  5643. );
  5644. }
  5645.  
  5646. Ext.applyIf(me, {
  5647. url: url,
  5648. method: method,
  5649. items: {
  5650. xtype: 'inputpanel',
  5651. column1: column1,
  5652. column2: column2
  5653. }
  5654. });
  5655.  
  5656. me.callParent();
  5657.  
  5658. if (me.isCreate) {
  5659. me.down('field[name=iface]').setValue(me.iface_default);
  5660. } else {
  5661. me.load({
  5662. success: function(response, options) {
  5663. var data = response.result.data;
  5664. if (data.type !== me.iftype) {
  5665. var msg = "Got unexpected device type";
  5666. Ext.Msg.alert(gettext('Error'), msg, function() {
  5667. me.close();
  5668. });
  5669. return;
  5670. }
  5671. me.setValues(data);
  5672. me.isValid(); // trigger validation
  5673. }
  5674. });
  5675. }
  5676. }
  5677. });
  5678. Ext.define('proxmox-networks', {
  5679. extend: 'Ext.data.Model',
  5680. fields: [
  5681. 'iface', 'type', 'active', 'autostart',
  5682. 'bridge_ports', 'slaves',
  5683. 'address', 'netmask', 'gateway',
  5684. 'address6', 'netmask6', 'gateway6',
  5685. 'comments'
  5686. ],
  5687. idProperty: 'iface'
  5688. });
  5689.  
  5690. Ext.define('Proxmox.node.NetworkView', {
  5691. extend: 'Ext.panel.Panel',
  5692.  
  5693. alias: ['widget.proxmoxNodeNetworkView'],
  5694.  
  5695. // defines what types of network devices we want to create
  5696. // order is always the same
  5697. types: ['bridge', 'bond', 'ovs'],
  5698.  
  5699. initComponent : function() {
  5700. var me = this;
  5701.  
  5702. if (!me.nodename) {
  5703. throw "no node name specified";
  5704. }
  5705.  
  5706. var baseUrl = '/nodes/' + me.nodename + '/network';
  5707.  
  5708. var store = Ext.create('Ext.data.Store', {
  5709. model: 'proxmox-networks',
  5710. proxy: {
  5711. type: 'proxmox',
  5712. url: '/api2/json' + baseUrl
  5713. },
  5714. sorters: [
  5715. {
  5716. property : 'iface',
  5717. direction: 'ASC'
  5718. }
  5719. ]
  5720. });
  5721.  
  5722. var reload = function() {
  5723. var changeitem = me.down('#changes');
  5724. Proxmox.Utils.API2Request({
  5725. url: baseUrl,
  5726. failure: function(response, opts) {
  5727. store.loadData({});
  5728. Proxmox.Utils.setErrorMask(me, response.htmlStatus);
  5729. changeitem.update('');
  5730. changeitem.setHidden(true);
  5731. },
  5732. success: function(response, opts) {
  5733. var result = Ext.decode(response.responseText);
  5734. store.loadData(result.data);
  5735. var changes = result.changes;
  5736. if (changes === undefined || changes === '') {
  5737. changes = gettext("No changes");
  5738. changeitem.setHidden(true);
  5739. } else {
  5740. changeitem.update("<pre>" + Ext.htmlEncode(changes) + "</pre>");
  5741. changeitem.setHidden(false);
  5742. }
  5743. }
  5744. });
  5745. };
  5746.  
  5747. var run_editor = function() {
  5748. var grid = me.down('gridpanel');
  5749. var sm = grid.getSelectionModel();
  5750. var rec = sm.getSelection()[0];
  5751. if (!rec) {
  5752. return;
  5753. }
  5754.  
  5755. var win = Ext.create('Proxmox.node.NetworkEdit', {
  5756. nodename: me.nodename,
  5757. iface: rec.data.iface,
  5758. iftype: rec.data.type
  5759. });
  5760. win.show();
  5761. win.on('destroy', reload);
  5762. };
  5763.  
  5764. var edit_btn = new Ext.Button({
  5765. text: gettext('Edit'),
  5766. disabled: true,
  5767. handler: run_editor
  5768. });
  5769.  
  5770. var del_btn = new Ext.Button({
  5771. text: gettext('Remove'),
  5772. disabled: true,
  5773. handler: function(){
  5774. var grid = me.down('gridpanel');
  5775. var sm = grid.getSelectionModel();
  5776. var rec = sm.getSelection()[0];
  5777. if (!rec) {
  5778. return;
  5779. }
  5780.  
  5781. var iface = rec.data.iface;
  5782.  
  5783. Proxmox.Utils.API2Request({
  5784. url: baseUrl + '/' + iface,
  5785. method: 'DELETE',
  5786. waitMsgTarget: me,
  5787. callback: function() {
  5788. reload();
  5789. },
  5790. failure: function(response, opts) {
  5791. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  5792. }
  5793. });
  5794. }
  5795. });
  5796.  
  5797. var set_button_status = function() {
  5798. var grid = me.down('gridpanel');
  5799. var sm = grid.getSelectionModel();
  5800. var rec = sm.getSelection()[0];
  5801.  
  5802. edit_btn.setDisabled(!rec);
  5803. del_btn.setDisabled(!rec);
  5804. };
  5805.  
  5806. var render_ports = function(value, metaData, record) {
  5807. if (value === 'bridge') {
  5808. return record.data.bridge_ports;
  5809. } else if (value === 'bond') {
  5810. return record.data.slaves;
  5811. } else if (value === 'OVSBridge') {
  5812. return record.data.ovs_ports;
  5813. } else if (value === 'OVSBond') {
  5814. return record.data.ovs_bonds;
  5815. }
  5816. };
  5817.  
  5818. var find_next_iface_id = function(prefix) {
  5819. var next;
  5820. for (next = 0; next <= 9999; next++) {
  5821. if (!store.getById(prefix + next.toString())) {
  5822. break;
  5823. }
  5824. }
  5825. return prefix + next.toString();
  5826. };
  5827.  
  5828. var menu_items = [];
  5829.  
  5830. if (me.types.indexOf('bridge') !== -1) {
  5831. menu_items.push({
  5832. text: Proxmox.Utils.render_network_iface_type('bridge'),
  5833. handler: function() {
  5834. var win = Ext.create('Proxmox.node.NetworkEdit', {
  5835. nodename: me.nodename,
  5836. iftype: 'bridge',
  5837. iface_default: find_next_iface_id('vmbr')
  5838. });
  5839. win.on('destroy', reload);
  5840. win.show();
  5841. }
  5842. });
  5843. }
  5844.  
  5845. if (me.types.indexOf('bond') !== -1) {
  5846. menu_items.push({
  5847. text: Proxmox.Utils.render_network_iface_type('bond'),
  5848. handler: function() {
  5849. var win = Ext.create('Proxmox.node.NetworkEdit', {
  5850. nodename: me.nodename,
  5851. iftype: 'bond',
  5852. iface_default: find_next_iface_id('bond')
  5853. });
  5854. win.on('destroy', reload);
  5855. win.show();
  5856. }
  5857. });
  5858. }
  5859.  
  5860. if (me.types.indexOf('ovs') !== -1) {
  5861. if (menu_items.length > 0) {
  5862. menu_items.push({ xtype: 'menuseparator' });
  5863. }
  5864.  
  5865. menu_items.push(
  5866. {
  5867. text: Proxmox.Utils.render_network_iface_type('OVSBridge'),
  5868. handler: function() {
  5869. var win = Ext.create('Proxmox.node.NetworkEdit', {
  5870. nodename: me.nodename,
  5871. iftype: 'OVSBridge',
  5872. iface_default: find_next_iface_id('vmbr')
  5873. });
  5874. win.on('destroy', reload);
  5875. win.show();
  5876. }
  5877. },
  5878. {
  5879. text: Proxmox.Utils.render_network_iface_type('OVSBond'),
  5880. handler: function() {
  5881. var win = Ext.create('Proxmox.node.NetworkEdit', {
  5882. nodename: me.nodename,
  5883. iftype: 'OVSBond',
  5884. iface_default: find_next_iface_id('bond')
  5885. });
  5886. win.on('destroy', reload);
  5887. win.show();
  5888. }
  5889. },
  5890. {
  5891. text: Proxmox.Utils.render_network_iface_type('OVSIntPort'),
  5892. handler: function() {
  5893. var win = Ext.create('Proxmox.node.NetworkEdit', {
  5894. nodename: me.nodename,
  5895. iftype: 'OVSIntPort'
  5896. });
  5897. win.on('destroy', reload);
  5898. win.show();
  5899. }
  5900. }
  5901. );
  5902. }
  5903.  
  5904. Ext.apply(me, {
  5905. layout: 'border',
  5906. tbar: [
  5907. {
  5908. text: gettext('Create'),
  5909. menu: {
  5910. plain: true,
  5911. items: menu_items
  5912. }
  5913. }, ' ',
  5914. {
  5915. text: gettext('Revert'),
  5916. handler: function() {
  5917. Proxmox.Utils.API2Request({
  5918. url: baseUrl,
  5919. method: 'DELETE',
  5920. waitMsgTarget: me,
  5921. callback: function() {
  5922. reload();
  5923. },
  5924. failure: function(response, opts) {
  5925. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  5926. }
  5927. });
  5928. }
  5929. },
  5930. edit_btn,
  5931. del_btn
  5932. ],
  5933. items: [
  5934. {
  5935. xtype: 'gridpanel',
  5936. stateful: true,
  5937. stateId: 'grid-node-network',
  5938. store: store,
  5939. region: 'center',
  5940. border: false,
  5941. columns: [
  5942. {
  5943. header: gettext('Name'),
  5944. sortable: true,
  5945. dataIndex: 'iface'
  5946. },
  5947. {
  5948. header: gettext('Type'),
  5949. sortable: true,
  5950. width: 120,
  5951. renderer: Proxmox.Utils.render_network_iface_type,
  5952. dataIndex: 'type'
  5953. },
  5954. {
  5955. xtype: 'booleancolumn',
  5956. header: gettext('Active'),
  5957. width: 80,
  5958. sortable: true,
  5959. dataIndex: 'active',
  5960. trueText: Proxmox.Utils.yesText,
  5961. falseText: Proxmox.Utils.noText,
  5962. undefinedText: Proxmox.Utils.noText,
  5963. },
  5964. {
  5965. xtype: 'booleancolumn',
  5966. header: gettext('Autostart'),
  5967. width: 80,
  5968. sortable: true,
  5969. dataIndex: 'autostart',
  5970. trueText: Proxmox.Utils.yesText,
  5971. falseText: Proxmox.Utils.noText,
  5972. undefinedText: Proxmox.Utils.noText
  5973. },
  5974. {
  5975. xtype: 'booleancolumn',
  5976. header: gettext('VLAN aware'),
  5977. width: 80,
  5978. sortable: true,
  5979. dataIndex: 'bridge_vlan_aware',
  5980. trueText: Proxmox.Utils.yesText,
  5981. falseText: Proxmox.Utils.noText,
  5982. undefinedText: Proxmox.Utils.noText
  5983. },
  5984. {
  5985. header: gettext('Ports/Slaves'),
  5986. dataIndex: 'type',
  5987. renderer: render_ports
  5988. },
  5989. {
  5990. header: gettext('IP address'),
  5991. sortable: true,
  5992. width: 120,
  5993. dataIndex: 'address',
  5994. renderer: function(value, metaData, rec) {
  5995. if (rec.data.address && rec.data.address6) {
  5996. return rec.data.address + "<br>"
  5997. + rec.data.address6 + '/' + rec.data.netmask6;
  5998. } else if (rec.data.address6) {
  5999. return rec.data.address6 + '/' + rec.data.netmask6;
  6000. } else {
  6001. return rec.data.address;
  6002. }
  6003. }
  6004. },
  6005. {
  6006. header: gettext('Subnet mask'),
  6007. width: 120,
  6008. sortable: true,
  6009. dataIndex: 'netmask'
  6010. },
  6011. {
  6012. header: gettext('Gateway'),
  6013. width: 120,
  6014. sortable: true,
  6015. dataIndex: 'gateway',
  6016. renderer: function(value, metaData, rec) {
  6017. if (rec.data.gateway && rec.data.gateway6) {
  6018. return rec.data.gateway + "<br>" + rec.data.gateway6;
  6019. } else if (rec.data.gateway6) {
  6020. return rec.data.gateway6;
  6021. } else {
  6022. return rec.data.gateway;
  6023. }
  6024. }
  6025. },
  6026. {
  6027. header: gettext('Comment'),
  6028. dataIndex: 'comments',
  6029. flex: 1,
  6030. renderer: Ext.String.htmlEncode
  6031. }
  6032. ],
  6033. listeners: {
  6034. selectionchange: set_button_status,
  6035. itemdblclick: run_editor
  6036. }
  6037. },
  6038. {
  6039. border: false,
  6040. region: 'south',
  6041. autoScroll: true,
  6042. hidden: true,
  6043. itemId: 'changes',
  6044. tbar: [
  6045. gettext('Pending changes') + ' (' +
  6046. gettext('Please reboot to activate changes') + ')'
  6047. ],
  6048. split: true,
  6049. bodyPadding: 5,
  6050. flex: 0.6,
  6051. html: gettext("No changes")
  6052. }
  6053. ],
  6054. });
  6055.  
  6056. me.callParent();
  6057. reload();
  6058. }
  6059. });
  6060. Ext.define('Proxmox.node.DNSEdit', {
  6061. extend: 'Proxmox.window.Edit',
  6062. alias: ['widget.proxmoxNodeDNSEdit'],
  6063.  
  6064. initComponent : function() {
  6065. var me = this;
  6066.  
  6067. if (!me.nodename) {
  6068. throw "no node name specified";
  6069. }
  6070.  
  6071. me.items = [
  6072. {
  6073. xtype: 'textfield',
  6074. fieldLabel: gettext('Search domain'),
  6075. name: 'search',
  6076. allowBlank: false
  6077. },
  6078. {
  6079. xtype: 'proxmoxtextfield',
  6080. fieldLabel: gettext('DNS server') + " 1",
  6081. vtype: 'IP64Address',
  6082. skipEmptyText: true,
  6083. name: 'dns1'
  6084. },
  6085. {
  6086. xtype: 'proxmoxtextfield',
  6087. fieldLabel: gettext('DNS server') + " 2",
  6088. vtype: 'IP64Address',
  6089. skipEmptyText: true,
  6090. name: 'dns2'
  6091. },
  6092. {
  6093. xtype: 'proxmoxtextfield',
  6094. fieldLabel: gettext('DNS server') + " 3",
  6095. vtype: 'IP64Address',
  6096. skipEmptyText: true,
  6097. name: 'dns3'
  6098. }
  6099. ];
  6100.  
  6101. Ext.applyIf(me, {
  6102. subject: gettext('DNS'),
  6103. url: "/api2/extjs/nodes/" + me.nodename + "/dns",
  6104. fieldDefaults: {
  6105. labelWidth: 120
  6106. }
  6107. });
  6108.  
  6109. me.callParent();
  6110.  
  6111. me.load();
  6112. }
  6113. });
  6114. Ext.define('Proxmox.node.DNSView', {
  6115. extend: 'Proxmox.grid.ObjectGrid',
  6116. alias: ['widget.proxmoxNodeDNSView'],
  6117.  
  6118. initComponent : function() {
  6119. var me = this;
  6120.  
  6121. if (!me.nodename) {
  6122. throw "no node name specified";
  6123. }
  6124.  
  6125. var run_editor = function() {
  6126. var win = Ext.create('Proxmox.node.DNSEdit', {
  6127. nodename: me.nodename
  6128. });
  6129. win.show();
  6130. };
  6131.  
  6132. Ext.apply(me, {
  6133. url: "/api2/json/nodes/" + me.nodename + "/dns",
  6134. cwidth1: 130,
  6135. interval: 1000,
  6136. run_editor: run_editor,
  6137. rows: {
  6138. search: { header: 'Search domain', required: true },
  6139. dns1: { header: gettext('DNS server') + " 1", required: true },
  6140. dns2: { header: gettext('DNS server') + " 2" },
  6141. dns3: { header: gettext('DNS server') + " 3" }
  6142. },
  6143. tbar: [
  6144. {
  6145. text: gettext("Edit"),
  6146. handler: run_editor
  6147. }
  6148. ],
  6149. listeners: {
  6150. itemdblclick: run_editor
  6151. }
  6152. });
  6153.  
  6154. me.callParent();
  6155.  
  6156. me.on('activate', me.rstore.startUpdate);
  6157. me.on('deactivate', me.rstore.stopUpdate);
  6158. me.on('destroy', me.rstore.stopUpdate);
  6159. }
  6160. });
  6161. Ext.define('Proxmox.node.Tasks', {
  6162. extend: 'Ext.grid.GridPanel',
  6163.  
  6164. alias: ['widget.proxmoxNodeTasks'],
  6165. stateful: true,
  6166. stateId: 'grid-node-tasks',
  6167. loadMask: true,
  6168. sortableColumns: false,
  6169. vmidFilter: 0,
  6170.  
  6171. initComponent : function() {
  6172. var me = this;
  6173.  
  6174. if (!me.nodename) {
  6175. throw "no node name specified";
  6176. }
  6177.  
  6178. var store = Ext.create('Ext.data.BufferedStore', {
  6179. pageSize: 500,
  6180. autoLoad: true,
  6181. remoteFilter: true,
  6182. model: 'proxmox-tasks',
  6183. proxy: {
  6184. type: 'proxmox',
  6185. startParam: 'start',
  6186. limitParam: 'limit',
  6187. url: "/api2/json/nodes/" + me.nodename + "/tasks"
  6188. }
  6189. });
  6190.  
  6191. var userfilter = '';
  6192. var filter_errors = 0;
  6193.  
  6194. var updateProxyParams = function() {
  6195. var params = {
  6196. errors: filter_errors
  6197. };
  6198. if (userfilter) {
  6199. params.userfilter = userfilter;
  6200. }
  6201. if (me.vmidFilter) {
  6202. params.vmid = me.vmidFilter;
  6203. }
  6204. store.proxy.extraParams = params;
  6205. };
  6206.  
  6207. updateProxyParams();
  6208.  
  6209. var reload_task = Ext.create('Ext.util.DelayedTask',function() {
  6210. updateProxyParams();
  6211. store.reload();
  6212. });
  6213.  
  6214. var run_task_viewer = function() {
  6215. var sm = me.getSelectionModel();
  6216. var rec = sm.getSelection()[0];
  6217. if (!rec) {
  6218. return;
  6219. }
  6220.  
  6221. var win = Ext.create('Proxmox.window.TaskViewer', {
  6222. upid: rec.data.upid
  6223. });
  6224. win.show();
  6225. };
  6226.  
  6227. var view_btn = new Ext.Button({
  6228. text: gettext('View'),
  6229. disabled: true,
  6230. handler: run_task_viewer
  6231. });
  6232.  
  6233. Proxmox.Utils.monStoreErrors(me, store, true);
  6234.  
  6235. Ext.apply(me, {
  6236. store: store,
  6237. viewConfig: {
  6238. trackOver: false,
  6239. stripeRows: false, // does not work with getRowClass()
  6240.  
  6241. getRowClass: function(record, index) {
  6242. var status = record.get('status');
  6243.  
  6244. if (status && status != 'OK') {
  6245. return "proxmox-invalid-row";
  6246. }
  6247. }
  6248. },
  6249. tbar: [
  6250. view_btn, '->', gettext('User name') +':', ' ',
  6251. {
  6252. xtype: 'textfield',
  6253. width: 200,
  6254. value: userfilter,
  6255. enableKeyEvents: true,
  6256. listeners: {
  6257. keyup: function(field, e) {
  6258. userfilter = field.getValue();
  6259. reload_task.delay(500);
  6260. }
  6261. }
  6262. }, ' ', gettext('Only Errors') + ':', ' ',
  6263. {
  6264. xtype: 'checkbox',
  6265. hideLabel: true,
  6266. checked: filter_errors,
  6267. listeners: {
  6268. change: function(field, checked) {
  6269. filter_errors = checked ? 1 : 0;
  6270. reload_task.delay(10);
  6271. }
  6272. }
  6273. }, ' '
  6274. ],
  6275. columns: [
  6276. {
  6277. header: gettext("Start Time"),
  6278. dataIndex: 'starttime',
  6279. width: 100,
  6280. renderer: function(value) {
  6281. return Ext.Date.format(value, "M d H:i:s");
  6282. }
  6283. },
  6284. {
  6285. header: gettext("End Time"),
  6286. dataIndex: 'endtime',
  6287. width: 100,
  6288. renderer: function(value, metaData, record) {
  6289. return Ext.Date.format(value,"M d H:i:s");
  6290. }
  6291. },
  6292. {
  6293. header: gettext("Node"),
  6294. dataIndex: 'node',
  6295. width: 100
  6296. },
  6297. {
  6298. header: gettext("User name"),
  6299. dataIndex: 'user',
  6300. width: 150
  6301. },
  6302. {
  6303. header: gettext("Description"),
  6304. dataIndex: 'upid',
  6305. flex: 1,
  6306. renderer: Proxmox.Utils.render_upid
  6307. },
  6308. {
  6309. header: gettext("Status"),
  6310. dataIndex: 'status',
  6311. width: 200,
  6312. renderer: function(value, metaData, record) {
  6313. if (value == 'OK') {
  6314. return 'OK';
  6315. }
  6316. // metaData.attr = 'style="color:red;"';
  6317. return "ERROR: " + value;
  6318. }
  6319. }
  6320. ],
  6321. listeners: {
  6322. itemdblclick: run_task_viewer,
  6323. selectionchange: function(v, selections) {
  6324. view_btn.setDisabled(!(selections && selections[0]));
  6325. },
  6326. show: function() { reload_task.delay(10); },
  6327. destroy: function() { reload_task.cancel(); }
  6328. }
  6329. });
  6330.  
  6331. me.callParent();
  6332.  
  6333. }
  6334. });
  6335. Ext.define('proxmox-services', {
  6336. extend: 'Ext.data.Model',
  6337. fields: [ 'service', 'name', 'desc', 'state' ],
  6338. idProperty: 'service'
  6339. });
  6340.  
  6341. Ext.define('Proxmox.node.ServiceView', {
  6342. extend: 'Ext.grid.GridPanel',
  6343.  
  6344. alias: ['widget.proxmoxNodeServiceView'],
  6345.  
  6346. startOnlyServices: {},
  6347.  
  6348. initComponent : function() {
  6349. var me = this;
  6350.  
  6351. if (!me.nodename) {
  6352. throw "no node name specified";
  6353. }
  6354.  
  6355. var rstore = Ext.create('Proxmox.data.UpdateStore', {
  6356. interval: 1000,
  6357. storeid: 'proxmox-services' + me.nodename,
  6358. model: 'proxmox-services',
  6359. proxy: {
  6360. type: 'proxmox',
  6361. url: "/api2/json/nodes/" + me.nodename + "/services"
  6362. }
  6363. });
  6364.  
  6365. var store = Ext.create('Proxmox.data.DiffStore', {
  6366. rstore: rstore,
  6367. sortAfterUpdate: true,
  6368. sorters: [
  6369. {
  6370. property : 'name',
  6371. direction: 'ASC'
  6372. }
  6373. ]
  6374. });
  6375.  
  6376. var view_service_log = function() {
  6377. var sm = me.getSelectionModel();
  6378. var rec = sm.getSelection()[0];
  6379. var win = Ext.create('Ext.window.Window', {
  6380. title: gettext('Syslog') + ': ' + rec.data.service,
  6381. modal: true,
  6382. items: {
  6383. xtype: 'proxmoxLogView',
  6384. width: 800,
  6385. height: 400,
  6386. url: "/api2/extjs/nodes/" + me.nodename + "/syslog?service=" +
  6387. rec.data.service,
  6388. log_select_timespan: 1
  6389. }
  6390. });
  6391. win.show();
  6392. };
  6393.  
  6394. var service_cmd = function(cmd) {
  6395. var sm = me.getSelectionModel();
  6396. var rec = sm.getSelection()[0];
  6397. Proxmox.Utils.API2Request({
  6398. url: "/nodes/" + me.nodename + "/services/" + rec.data.service + "/" + cmd,
  6399. method: 'POST',
  6400. failure: function(response, opts) {
  6401. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  6402. me.loading = true;
  6403. },
  6404. success: function(response, opts) {
  6405. rstore.startUpdate();
  6406. var upid = response.result.data;
  6407.  
  6408. var win = Ext.create('Proxmox.window.TaskProgress', {
  6409. upid: upid
  6410. });
  6411. win.show();
  6412. }
  6413. });
  6414. };
  6415.  
  6416. var start_btn = new Ext.Button({
  6417. text: gettext('Start'),
  6418. disabled: true,
  6419. handler: function(){
  6420. service_cmd("start");
  6421. }
  6422. });
  6423.  
  6424. var stop_btn = new Ext.Button({
  6425. text: gettext('Stop'),
  6426. disabled: true,
  6427. handler: function(){
  6428. service_cmd("stop");
  6429. }
  6430. });
  6431.  
  6432. var restart_btn = new Ext.Button({
  6433. text: gettext('Restart'),
  6434. disabled: true,
  6435. handler: function(){
  6436. service_cmd("restart");
  6437. }
  6438. });
  6439.  
  6440. var syslog_btn = new Ext.Button({
  6441. text: gettext('Syslog'),
  6442. disabled: true,
  6443. handler: view_service_log
  6444. });
  6445.  
  6446. var set_button_status = function() {
  6447. var sm = me.getSelectionModel();
  6448. var rec = sm.getSelection()[0];
  6449.  
  6450. if (!rec) {
  6451. start_btn.disable();
  6452. stop_btn.disable();
  6453. restart_btn.disable();
  6454. syslog_btn.disable();
  6455. return;
  6456. }
  6457. var service = rec.data.service;
  6458. var state = rec.data.state;
  6459.  
  6460. syslog_btn.enable();
  6461.  
  6462. if (me.startOnlyServices[service]) {
  6463. if (state == 'running') {
  6464. start_btn.disable();
  6465. restart_btn.enable();
  6466. } else {
  6467. start_btn.enable();
  6468. restart_btn.disable();
  6469. }
  6470. stop_btn.disable();
  6471. } else {
  6472. if (state == 'running') {
  6473. start_btn.disable();
  6474. restart_btn.enable();
  6475. stop_btn.enable();
  6476. } else {
  6477. start_btn.enable();
  6478. restart_btn.disable();
  6479. stop_btn.disable();
  6480. }
  6481. }
  6482. };
  6483.  
  6484. me.mon(store, 'refresh', set_button_status);
  6485.  
  6486. Proxmox.Utils.monStoreErrors(me, rstore);
  6487.  
  6488. Ext.apply(me, {
  6489. store: store,
  6490. stateful: false,
  6491. tbar: [ start_btn, stop_btn, restart_btn, syslog_btn ],
  6492. columns: [
  6493. {
  6494. header: gettext('Name'),
  6495. flex: 1,
  6496. sortable: true,
  6497. dataIndex: 'name'
  6498. },
  6499. {
  6500. header: gettext('Status'),
  6501. width: 100,
  6502. sortable: true,
  6503. dataIndex: 'state'
  6504. },
  6505. {
  6506. header: gettext('Description'),
  6507. renderer: Ext.String.htmlEncode,
  6508. dataIndex: 'desc',
  6509. flex: 2
  6510. }
  6511. ],
  6512. listeners: {
  6513. selectionchange: set_button_status,
  6514. itemdblclick: view_service_log,
  6515. activate: rstore.startUpdate,
  6516. destroy: rstore.stopUpdate
  6517. }
  6518. });
  6519.  
  6520. me.callParent();
  6521. }
  6522. });
  6523. Ext.define('Proxmox.node.TimeEdit', {
  6524. extend: 'Proxmox.window.Edit',
  6525. alias: ['widget.proxmoxNodeTimeEdit'],
  6526.  
  6527. subject: gettext('Time zone'),
  6528.  
  6529. width: 400,
  6530.  
  6531. autoLoad: true,
  6532.  
  6533. fieldDefaults: {
  6534. labelWidth: 70
  6535. },
  6536.  
  6537. items: {
  6538. xtype: 'combo',
  6539. fieldLabel: gettext('Time zone'),
  6540. name: 'timezone',
  6541. queryMode: 'local',
  6542. store: Ext.create('Proxmox.data.TimezoneStore'),
  6543. displayField: 'zone',
  6544. forceSelection: true,
  6545. editable: false,
  6546. allowBlank: false
  6547. },
  6548.  
  6549. initComponent : function() {
  6550. var me = this;
  6551.  
  6552. if (!me.nodename) {
  6553. throw "no node name specified";
  6554. }
  6555. me.url = "/api2/extjs/nodes/" + me.nodename + "/time";
  6556.  
  6557. me.callParent();
  6558. }
  6559. });
  6560. Ext.define('Proxmox.node.TimeView', {
  6561. extend: 'Proxmox.grid.ObjectGrid',
  6562. alias: ['widget.proxmoxNodeTimeView'],
  6563.  
  6564. initComponent : function() {
  6565. var me = this;
  6566.  
  6567. if (!me.nodename) {
  6568. throw "no node name specified";
  6569. }
  6570.  
  6571. var tzoffset = (new Date()).getTimezoneOffset()*60000;
  6572. var renderlocaltime = function(value) {
  6573. var servertime = new Date((value * 1000) + tzoffset);
  6574. return Ext.Date.format(servertime, 'Y-m-d H:i:s');
  6575. };
  6576.  
  6577. var run_editor = function() {
  6578. var win = Ext.create('Proxmox.node.TimeEdit', {
  6579. nodename: me.nodename
  6580. });
  6581. win.show();
  6582. };
  6583.  
  6584. Ext.apply(me, {
  6585. url: "/api2/json/nodes/" + me.nodename + "/time",
  6586. cwidth1: 150,
  6587. interval: 1000,
  6588. run_editor: run_editor,
  6589. rows: {
  6590. timezone: {
  6591. header: gettext('Time zone'),
  6592. required: true
  6593. },
  6594. localtime: {
  6595. header: gettext('Server time'),
  6596. required: true,
  6597. renderer: renderlocaltime
  6598. }
  6599. },
  6600. tbar: [
  6601. {
  6602. text: gettext("Edit"),
  6603. handler: run_editor
  6604. }
  6605. ],
  6606. listeners: {
  6607. itemdblclick: run_editor
  6608. }
  6609. });
  6610.  
  6611. me.callParent();
  6612.  
  6613. me.on('activate', me.rstore.startUpdate);
  6614. me.on('deactivate', me.rstore.stopUpdate);
  6615. me.on('destroy', me.rstore.stopUpdate);
  6616. }
  6617. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement