Guest User

Untitled

a guest
Apr 5th, 2012
1,465
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 386.03 KB | None | 0 0
  1. Ext.ns('PVE');
  2.  
  3. // avoid errors when running without development tools
  4. if (!Ext.isDefined(Ext.global.console)) {
  5. var console = {
  6. dir: function() {},
  7. log: function() {}
  8. };
  9. }
  10. console.log("Starting PVE Manager");
  11.  
  12. Ext.Ajax.defaultHeaders = {
  13. 'Accept': 'application/json'
  14. };
  15.  
  16. // do not send '_dc' parameter
  17. Ext.Ajax.disableCaching = false;
  18.  
  19. Ext.Ajax.on('beforerequest', function(conn, options) {
  20. if (PVE.CSRFPreventionToken) {
  21. if (!options.headers) {
  22. options.headers = {};
  23. }
  24. options.headers.CSRFPreventionToken = PVE.CSRFPreventionToken;
  25. }
  26. });
  27.  
  28. // custom Vtypes
  29. Ext.apply(Ext.form.field.VTypes, {
  30. IPAddress: function(v) {
  31. return (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/).test(v);
  32. },
  33. IPAddressText: gettext('Example') + ': 192.168.1.1',
  34. IPAddressMask: /[\d\.]/i,
  35.  
  36. MacAddress: function(v) {
  37. return (/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/).test(v);
  38. },
  39. MacAddressMask: /[a-fA-F0-9:]/,
  40. MacAddressText: gettext('Example') + ': 01:23:45:67:89:ab',
  41.  
  42. BridgeName: function(v) {
  43. return (/^vmbr\d{1,4}$/).test(v);
  44. },
  45. BridgeNameText: gettext('Format') + ': vmbr<b>N</b>, where 0 <= <b>N</b> <= 9999',
  46.  
  47. BondName: function(v) {
  48. return (/^bond\d{1,4}$/).test(v);
  49. },
  50. BondNameText: gettext('Format') + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999',
  51.  
  52. QemuStartDate: function(v) {
  53. return (/^(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)$/).test(v);
  54. },
  55. QemuStartDateText: gettext('Format') + ': "now" or "2006-06-17T16:01:21" or "2006-06-17"',
  56.  
  57. StorageId: function(v) {
  58. return (/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i).test(v);
  59. },
  60. StorageIdText: gettext("Allowed characters") + ": 'a-z', '0-9', '-', '_', '.'",
  61.  
  62. HttpProxy: function(v) {
  63. return (/^http:\/\/.*$/).test(v);
  64. },
  65. HttpProxyText: gettext('Example') + ": http://username:password&#64;host:port/",
  66.  
  67. DnsName: function(v) {
  68. return (/^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/).test(v);
  69. },
  70. DnsNameText: gettext('This is not a valid DNS name')
  71. });
  72.  
  73. // we dont want that a displayfield set the form dirty flag!
  74. Ext.override(Ext.form.field.Display, {
  75. isDirty: function() { return false; }
  76. });
  77.  
  78. // hack: ExtJS does not display the correct value if we
  79. // call setValue while the store is loading, so we need
  80. // to call it again after loading
  81. Ext.override(Ext.form.field.ComboBox, {
  82. onLoad: function() {
  83. this.setValue(this.value, false);
  84. this.callOverridden(arguments);
  85. }
  86. });
  87.  
  88. Ext.define('PVE.Utils', { statics: {
  89.  
  90. // this class only contains static functions
  91.  
  92. log_severity_hash: {
  93. 0: "panic",
  94. 1: "alert",
  95. 2: "critical",
  96. 3: "error",
  97. 4: "warning",
  98. 5: "notice",
  99. 6: "info",
  100. 7: "debug"
  101. },
  102.  
  103. support_level_hash: {
  104. 'c': gettext('Community'),
  105. 'b': gettext('Basic'),
  106. 's': gettext('Standard'),
  107. 'p': gettext('Premium')
  108. },
  109.  
  110. kvm_ostypes: {
  111. other: gettext('Other OS types'),
  112. wxp: 'Microsoft Windows XP/2003',
  113. w2k: 'Microsoft Windows 2000',
  114. w2k8: 'Microsoft Windows Vista/2008',
  115. win7: 'Microsoft Windows 7/2008r2',
  116. l24: 'Linux 2.4 Kernel',
  117. l26: 'Linux 3.X/2.6 Kernel'
  118. },
  119.  
  120. render_kvm_ostype: function (value) {
  121. if (!value) {
  122. return gettext('Other OS types');
  123. }
  124. var text = PVE.Utils.kvm_ostypes[value];
  125. if (text) {
  126. return text + ' (' + value + ')';
  127. }
  128. return value;
  129. },
  130.  
  131. // fixme: auto-generate this
  132. // for now, please keep in sync with PVE::Tools::kvmkeymaps
  133. kvm_keymaps: {
  134. //ar: 'Arabic',
  135. da: 'Danish',
  136. de: 'German',
  137. 'de-ch': 'German (Swiss)',
  138. 'en-gb': 'English (UK)',
  139. 'en-us': 'English (USA',
  140. es: 'Spanish',
  141. //et: 'Estonia',
  142. fi: 'Finnish',
  143. //fo: 'Faroe Islands',
  144. fr: 'French',
  145. 'fr-be': 'French (Belgium)',
  146. 'fr-ca': 'French (Canada)',
  147. 'fr-ch': 'French (Swiss)',
  148. //hr: 'Croatia',
  149. hu: 'Hungarian',
  150. is: 'Icelandic',
  151. it: 'Italian',
  152. ja: 'Japanese',
  153. lt: 'Lithuanian',
  154. //lv: 'Latvian',
  155. mk: 'Macedonian',
  156. nl: 'Dutch',
  157. //'nl-be': 'Dutch (Belgium)',
  158. no: 'Norwegian',
  159. pl: 'Polish',
  160. pt: 'Portuguese',
  161. 'pt-br': 'Portuguese (Brazil)',
  162. //ru: 'Russian',
  163. si: 'Slovenian',
  164. sv: 'Swedish'
  165. //th: 'Thai',
  166. //tr: 'Turkish'
  167. },
  168.  
  169. kvm_vga_drivers: {
  170. std: 'Standard VGA',
  171. vmware: 'VMWare compatible',
  172. cirrus: 'Cirrus Logic GD5446'
  173. },
  174.  
  175. render_kvm_language: function (value) {
  176. if (!value) {
  177. return PVE.Utils.defaultText;
  178. }
  179. var text = PVE.Utils.kvm_keymaps[value];
  180. if (text) {
  181. return text + ' (' + value + ')';
  182. }
  183. return value;
  184. },
  185.  
  186. kvm_keymap_array: function() {
  187. var data = [['', PVE.Utils.render_kvm_language('')]];
  188. Ext.Object.each(PVE.Utils.kvm_keymaps, function(key, value) {
  189. data.push([key, PVE.Utils.render_kvm_language(value)]);
  190. });
  191.  
  192. return data;
  193. },
  194.  
  195. language_map: {
  196. zh_CN: 'Chinese',
  197. ja: 'Japanese',
  198. en: 'English',
  199. de: 'German',
  200. es: 'Spanish',
  201. fr: 'French',
  202. sv: 'Swedish'
  203. },
  204.  
  205. render_language: function (value) {
  206. if (!value) {
  207. return PVE.Utils.defaultText + ' (English)';
  208. }
  209. var text = PVE.Utils.language_map[value];
  210. if (text) {
  211. return text + ' (' + value + ')';
  212. }
  213. return value;
  214. },
  215.  
  216. language_array: function() {
  217. var data = [['', PVE.Utils.render_language('')]];
  218. Ext.Object.each(PVE.Utils.language_map, function(key, value) {
  219. data.push([key, PVE.Utils.render_language(value)]);
  220. });
  221.  
  222. return data;
  223. },
  224.  
  225. render_kvm_vga_driver: function (value) {
  226. if (!value) {
  227. return PVE.Utils.defaultText;
  228. }
  229. var text = PVE.Utils.kvm_vga_drivers[value];
  230. if (text) {
  231. return text + ' (' + value + ')';
  232. }
  233. return value;
  234. },
  235.  
  236. kvm_vga_driver_array: function() {
  237. var data = [['', PVE.Utils.render_kvm_vga_driver('')]];
  238. Ext.Object.each(PVE.Utils.kvm_vga_drivers, function(key, value) {
  239. data.push([key, PVE.Utils.render_kvm_vga_driver(value)]);
  240. });
  241.  
  242. return data;
  243. },
  244.  
  245. authOK: function() {
  246. return Ext.util.Cookies.get('PVEAuthCookie');
  247. },
  248.  
  249. authClear: function() {
  250. Ext.util.Cookies.clear("PVEAuthCookie");
  251. },
  252.  
  253. // fixme: remove - not needed?
  254. gridLineHeigh: function() {
  255. return 21;
  256.  
  257. //if (Ext.isGecko)
  258. //return 23;
  259. //return 21;
  260. },
  261.  
  262. extractRequestError: function(result, verbose) {
  263. var msg = gettext('Successful');
  264.  
  265. if (!result.success) {
  266. msg = gettext("Unknown error");
  267. if (result.message) {
  268. msg = result.message;
  269. if (result.status) {
  270. msg += ' (' + result.status + ')';
  271. }
  272. }
  273. if (verbose && Ext.isObject(result.errors)) {
  274. msg += "<br>";
  275. Ext.Object.each(result.errors, function(prop, desc) {
  276. msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
  277. Ext.htmlEncode(desc);
  278. });
  279. }
  280. }
  281.  
  282. return msg;
  283. },
  284.  
  285. extractFormActionError: function(action) {
  286. var msg;
  287. switch (action.failureType) {
  288. case Ext.form.action.Action.CLIENT_INVALID:
  289. msg = gettext('Form fields may not be submitted with invalid values');
  290. break;
  291. case Ext.form.action.Action.CONNECT_FAILURE:
  292. msg = gettext('Connection error');
  293. var resp = action.response;
  294. if (resp.status && resp.statusText) {
  295. msg += " " + resp.status + ": " + resp.statusText;
  296. }
  297. break;
  298. case Ext.form.action.Action.LOAD_FAILURE:
  299. case Ext.form.action.Action.SERVER_INVALID:
  300. msg = PVE.Utils.extractRequestError(action.result, true);
  301. break;
  302. }
  303. return msg;
  304. },
  305.  
  306. // Ext.Ajax.request
  307. API2Request: function(reqOpts) {
  308.  
  309. var newopts = Ext.apply({
  310. waitMsg: gettext('Please wait...')
  311. }, reqOpts);
  312.  
  313. if (!newopts.url.match(/^\/api2/)) {
  314. newopts.url = '/api2/extjs' + newopts.url;
  315. }
  316. delete newopts.callback;
  317.  
  318. var createWrapper = function(successFn, callbackFn, failureFn) {
  319. Ext.apply(newopts, {
  320. success: function(response, options) {
  321. if (options.waitMsgTarget) {
  322. options.waitMsgTarget.setLoading(false);
  323. }
  324. var result = Ext.decode(response.responseText);
  325. response.result = result;
  326. if (!result.success) {
  327. response.htmlStatus = PVE.Utils.extractRequestError(result, true);
  328. Ext.callback(callbackFn, options.scope, [options, false, response]);
  329. Ext.callback(failureFn, options.scope, [response, options]);
  330. return;
  331. }
  332. Ext.callback(callbackFn, options.scope, [options, true, response]);
  333. Ext.callback(successFn, options.scope, [response, options]);
  334. },
  335. failure: function(response, options) {
  336. if (options.waitMsgTarget) {
  337. options.waitMsgTarget.setLoading(false);
  338. }
  339. response.result = {};
  340. try {
  341. response.result = Ext.decode(response.responseText);
  342. } catch(e) {}
  343. var msg = gettext('Connection error') + ' - server offline?';
  344. if (response.aborted) {
  345. msg = gettext('Connection error') + ' - aborted.';
  346. } else if (response.timedout) {
  347. msg = gettext('Connection error') + ' - Timeout.';
  348. } else if (response.status && response.statusText) {
  349. msg = gettext('Connection error') + ' ' + response.status + ': ' + response.statusText;
  350. }
  351. response.htmlStatus = msg;
  352. Ext.callback(callbackFn, options.scope, [options, false, response]);
  353. Ext.callback(failureFn, options.scope, [response, options]);
  354. }
  355. });
  356. };
  357.  
  358. createWrapper(reqOpts.success, reqOpts.callback, reqOpts.failure);
  359.  
  360. var target = newopts.waitMsgTarget;
  361. if (target) {
  362. // Note: ExtJS bug - this does not work when component is not rendered
  363. target.setLoading(newopts.waitMsg);
  364. }
  365. Ext.Ajax.request(newopts);
  366. },
  367.  
  368. assemble_field_data: function(values, data) {
  369. if (Ext.isObject(data)) {
  370. Ext.Object.each(data, function(name, val) {
  371. if (values.hasOwnProperty(name)) {
  372. var bucket = values[name];
  373. if (!Ext.isArray(bucket)) {
  374. bucket = values[name] = [bucket];
  375. }
  376. if (Ext.isArray(val)) {
  377. values[name] = bucket.concat(val);
  378. } else {
  379. bucket.push(val);
  380. }
  381. } else {
  382. values[name] = val;
  383. }
  384. });
  385. }
  386. },
  387.  
  388. task_desc_table: {
  389. vncproxy: [ 'VM/CT', gettext('Console') ],
  390. vncshell: [ '', gettext('Shell') ],
  391. qmcreate: [ 'VM', gettext('Create') ],
  392. qmrestore: [ 'VM', gettext('Restore') ],
  393. qmdestroy: [ 'VM', gettext('Destroy') ],
  394. qmigrate: [ 'VM', gettext('Migrate') ],
  395. qmstart: [ 'VM', gettext('Start') ],
  396. qmstop: [ 'VM', gettext('Stop') ],
  397. qmreset: [ 'VM', gettext('Reset') ],
  398. qmshutdown: [ 'VM', gettext('Shutdown') ],
  399. qmsuspend: [ 'VM', gettext('Suspend') ],
  400. qmresume: [ 'VM', gettext('Resume') ],
  401. vzcreate: ['CT', gettext('Create') ],
  402. vzrestore: ['CT', gettext('Restore') ],
  403. vzdestroy: ['CT', gettext('Destroy') ],
  404. vzmigrate: [ 'CT', gettext('Migrate') ],
  405. vzstart: ['CT', gettext('Start') ],
  406. vzstop: ['CT', gettext('Stop') ],
  407. vzmount: ['CT', gettext('Mount') ],
  408. vzumount: ['CT', gettext('Unmount') ],
  409. vzshutdown: ['CT', gettext('Shutdown') ],
  410. hamigrate: [ 'HA', gettext('Migrate') ],
  411. hastart: [ 'HA', gettext('Start') ],
  412. hastop: [ 'HA', gettext('Stop') ],
  413. srvstart: ['SRV', gettext('Start') ],
  414. srvstop: ['SRV', gettext('Stop') ],
  415. srvrestart: ['SRV', gettext('Restart') ],
  416. srvreload: ['SRV', gettext('Reload') ],
  417. imgcopy: ['', gettext('Copy data') ],
  418. imgdel: ['', gettext('Erase data') ],
  419. download: ['', gettext('Download') ],
  420. vzdump: ['', gettext('Backup') ]
  421. },
  422.  
  423. format_task_description: function(type, id) {
  424. var farray = PVE.Utils.task_desc_table[type];
  425. if (!farray) {
  426. return type;
  427. }
  428. var prefix = farray[0];
  429. var text = farray[1];
  430. if (prefix) {
  431. return prefix + ' ' + id + ' - ' + text;
  432. }
  433. return text;
  434. },
  435.  
  436. parse_task_upid: function(upid) {
  437. var task = {};
  438.  
  439. var res = upid.match(/^UPID:(\S+):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/);
  440. if (!res) {
  441. throw "unable to parse upid '" + upid + "'";
  442. }
  443. task.node = res[1];
  444. task.pid = parseInt(res[2], 16);
  445. task.pstart = parseInt(res[3], 16);
  446. task.starttime = parseInt(res[4], 16);
  447. task.type = res[5];
  448. task.id = res[6];
  449. task.user = res[7];
  450.  
  451. task.desc = PVE.Utils.format_task_description(task.type, task.id);
  452.  
  453. return task;
  454. },
  455.  
  456. format_size: function(size) {
  457. /*jslint confusion: true */
  458.  
  459. if (size < 1024) {
  460. return size;
  461. }
  462.  
  463. var kb = size / 1024;
  464.  
  465. if (kb < 1024) {
  466. return kb.toFixed(0) + "KB";
  467. }
  468.  
  469. var mb = size / (1024*1024);
  470.  
  471. if (mb < 1024) {
  472. return mb.toFixed(0) + "MB";
  473. }
  474.  
  475. var gb = mb / 1024;
  476.  
  477. if (gb < 1024) {
  478. return gb.toFixed(2) + "GB";
  479. }
  480.  
  481. var tb = gb / 1024;
  482.  
  483. return tb.toFixed(2) + "TB";
  484.  
  485. },
  486.  
  487. format_html_bar: function(per, text) {
  488.  
  489. return "<div class='pve-bar-wrap'>" + text + "<div class='pve-bar-border'>" +
  490. "<div class='pve-bar-inner' style='width:" + per + "%;'></div>" +
  491. "</div></div>";
  492.  
  493. },
  494.  
  495. format_cpu_bar: function(per1, per2, text) {
  496.  
  497. return "<div class='pve-bar-border'>" +
  498. "<div class='pve-bar-inner' style='width:" + per1 + "%;'></div>" +
  499. "<div class='pve-bar-inner2' style='width:" + per2 + "%;'></div>" +
  500. "<div class='pve-bar-text'>" + text + "</div>" +
  501. "</div>";
  502. },
  503.  
  504. format_large_bar: function(per, text) {
  505.  
  506. if (!text) {
  507. text = per.toFixed(1) + "%";
  508. }
  509.  
  510. return "<div class='pve-largebar-border'>" +
  511. "<div class='pve-largebar-inner' style='width:" + per + "%;'></div>" +
  512. "<div class='pve-largebar-text'>" + text + "</div>" +
  513. "</div>";
  514. },
  515.  
  516. format_duration_long: function(ut) {
  517.  
  518. var days = Math.floor(ut / 86400);
  519. ut -= days*86400;
  520. var hours = Math.floor(ut / 3600);
  521. ut -= hours*3600;
  522. var mins = Math.floor(ut / 60);
  523. ut -= mins*60;
  524.  
  525. var hours_str = '00' + hours.toString();
  526. hours_str = hours_str.substr(hours_str.length - 2);
  527. var mins_str = "00" + mins.toString();
  528. mins_str = mins_str.substr(mins_str.length - 2);
  529. var ut_str = "00" + ut.toString();
  530. ut_str = ut_str.substr(ut_str.length - 2);
  531.  
  532. if (days) {
  533. var ds = days > 1 ? PVE.Utils.daysText : PVE.Utils.dayText;
  534. return days.toString() + ' ' + ds + ' ' +
  535. hours_str + ':' + mins_str + ':' + ut_str;
  536. } else {
  537. return hours_str + ':' + mins_str + ':' + ut_str;
  538. }
  539. },
  540.  
  541. format_duration_short: function(ut) {
  542.  
  543. if (ut < 60) {
  544. return ut.toString() + 's';
  545. }
  546.  
  547. if (ut < 3600) {
  548. var mins = ut / 60;
  549. return mins.toFixed(0) + 'm';
  550. }
  551.  
  552. if (ut < 86400) {
  553. var hours = ut / 3600;
  554. return hours.toFixed(0) + 'h';
  555. }
  556.  
  557. var days = ut / 86400;
  558. return days.toFixed(0) + 'd';
  559. },
  560.  
  561. yesText: gettext('Yes'),
  562. noText: gettext('No'),
  563. errorText: gettext('Error'),
  564. unknownText: gettext('Unknown'),
  565. defaultText: gettext('Default'),
  566. daysText: gettext('days'),
  567. dayText: gettext('day'),
  568. runningText: gettext('running'),
  569. stoppedText: gettext('stopped'),
  570. neverText: gettext('never'),
  571.  
  572. format_expire: function(date) {
  573. if (!date) {
  574. return PVE.Utils.neverText;
  575. }
  576. return Ext.Date.format(date, "Y-m-d");
  577. },
  578.  
  579. format_storage_type: function(value) {
  580. if (value === 'dir') {
  581. return 'Directory';
  582. } else if (value === 'nfs') {
  583. return 'NFS';
  584. } else if (value === 'lvm') {
  585. return 'LVM';
  586. } else if (value === 'iscsi') {
  587. return 'iSCSI';
  588. } else {
  589. return PVE.Utils.unknownText;
  590. }
  591. },
  592.  
  593. format_boolean_with_default: function(value) {
  594. if (Ext.isDefined(value) && value !== '') {
  595. return value ? PVE.Utils.yesText : PVE.Utils.noText;
  596. }
  597. return PVE.Utils.defaultText;
  598. },
  599.  
  600. format_boolean: function(value) {
  601. return value ? PVE.Utils.yesText : PVE.Utils.noText;
  602. },
  603.  
  604. format_neg_boolean: function(value) {
  605. return !value ? PVE.Utils.yesText : PVE.Utils.noText;
  606. },
  607.  
  608. format_content_types: function(value) {
  609. var cta = [];
  610.  
  611. Ext.each(value.split(',').sort(), function(ct) {
  612. if (ct === 'images') {
  613. cta.push('Images');
  614. } else if (ct === 'backup') {
  615. cta.push('Backups');
  616. } else if (ct === 'vztmpl') {
  617. cta.push('Templates');
  618. } else if (ct === 'iso') {
  619. cta.push('ISO');
  620. } else if (ct === 'rootdir') {
  621. cta.push('Containers');
  622. }
  623. });
  624.  
  625. return cta.join(', ');
  626. },
  627.  
  628. render_storage_content: function(value, metaData, record) {
  629. var data = record.data;
  630. if (Ext.isNumber(data.channel) &&
  631. Ext.isNumber(data.id) &&
  632. Ext.isNumber(data.lun)) {
  633. return "CH " +
  634. Ext.String.leftPad(data.channel,2, '0') +
  635. " ID " + data.id + " LUN " + data.lun;
  636. }
  637. return data.volid.replace(/^.*:(.*\/)?/,'');
  638. },
  639.  
  640. render_serverity: function (value) {
  641. return PVE.Utils.log_severity_hash[value] || value;
  642. },
  643.  
  644. render_cpu: function(value, metaData, record, rowIndex, colIndex, store) {
  645.  
  646. if (!(record.data.uptime && Ext.isNumeric(value))) {
  647. return '';
  648. }
  649.  
  650. var maxcpu = record.data.maxcpu || 1;
  651.  
  652. if (!Ext.isNumeric(maxcpu) && (maxcpu >= 1)) {
  653. return '';
  654. }
  655.  
  656. var per = value * 100;
  657.  
  658. return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
  659. },
  660.  
  661. render_size: function(value, metaData, record, rowIndex, colIndex, store) {
  662. /*jslint confusion: true */
  663.  
  664. if (!Ext.isNumeric(value)) {
  665. return '';
  666. }
  667.  
  668. return PVE.Utils.format_size(value);
  669. },
  670.  
  671. render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
  672. var servertime = new Date(value * 1000);
  673. return Ext.Date.format(servertime, 'Y-m-d H:i:s');
  674. },
  675.  
  676. render_mem_usage: function(value, metaData, record, rowIndex, colIndex, store) {
  677.  
  678. var mem = value;
  679. var maxmem = record.data.maxmem;
  680.  
  681. if (!record.data.uptime) {
  682. return '';
  683. }
  684.  
  685. if (!(Ext.isNumeric(mem) && maxmem)) {
  686. return '';
  687. }
  688.  
  689. var per = (mem * 100) / maxmem;
  690.  
  691. return per.toFixed(1) + '%';
  692. },
  693.  
  694. render_disk_usage: function(value, metaData, record, rowIndex, colIndex, store) {
  695.  
  696. var disk = value;
  697. var maxdisk = record.data.maxdisk;
  698.  
  699. if (!(Ext.isNumeric(disk) && maxdisk)) {
  700. return '';
  701. }
  702.  
  703. var per = (disk * 100) / maxdisk;
  704.  
  705. return per.toFixed(1) + '%';
  706. },
  707.  
  708. render_resource_type: function(value, metaData, record, rowIndex, colIndex, store) {
  709.  
  710. var cls = 'pve-itype-icon-' + value;
  711.  
  712. if (record.data.running) {
  713. metaData.tdCls = cls + "-running";
  714. } else {
  715. metaData.tdCls = cls;
  716. }
  717.  
  718. return value;
  719. },
  720.  
  721. render_uptime: function(value, metaData, record, rowIndex, colIndex, store) {
  722.  
  723. var uptime = value;
  724.  
  725. if (uptime === undefined) {
  726. return '';
  727. }
  728.  
  729. if (uptime <= 0) {
  730. return '-';
  731. }
  732.  
  733. return PVE.Utils.format_duration_long(uptime);
  734. },
  735.  
  736. render_support_level: function(value, metaData, record) {
  737. return PVE.Utils.support_level_hash[value] || '-';
  738. },
  739.  
  740. render_upid: function(value, metaData, record) {
  741. var type = record.data.type;
  742. var id = record.data.id;
  743.  
  744. return PVE.Utils.format_task_description(type, id);
  745. },
  746.  
  747. dialog_title: function(subject, create, isAdd) {
  748. if (create) {
  749. if (isAdd) {
  750. return gettext('Add') + ': ' + subject;
  751. } else {
  752. return gettext('Create') + ': ' + subject;
  753. }
  754. } else {
  755. return gettext('Edit') + ': ' + subject;
  756. }
  757. },
  758.  
  759. openConoleWindow: function(vmtype, vmid, nodename, vmname) {
  760. var url = Ext.urlEncode({
  761. console: vmtype, // kvm, openvz or shell
  762. vmid: vmid,
  763. vmname: vmname,
  764. node: nodename
  765. });
  766. var nw = window.open("?" + url, '_blank',
  767. "innerWidth=745,innerheight=427");
  768. nw.focus();
  769. },
  770.  
  771. // comp.setLoading() is buggy in ExtJS 4.0.7, so we
  772. // use el.mask() instead
  773. setErrorMask: function(comp, msg) {
  774. var el = comp.el;
  775. if (!el) {
  776. return;
  777. }
  778. if (!msg) {
  779. el.unmask();
  780. } else {
  781. if (msg === true) {
  782. el.mask(gettext("Loading..."));
  783. } else {
  784. el.mask(msg);
  785. }
  786. }
  787. },
  788.  
  789. monStoreErrors: function(me, store) {
  790. me.mon(store, 'beforeload', function(s, operation, eOpts) {
  791. if (!me.loadCount) {
  792. me.loadCount = 0; // make sure it is numeric
  793. PVE.Utils.setErrorMask(me, true);
  794. }
  795. });
  796.  
  797. // only works with 'pve' proxy
  798. me.mon(store.proxy, 'afterload', function(proxy, request, success) {
  799. me.loadCount++;
  800.  
  801. if (success) {
  802. PVE.Utils.setErrorMask(me, false);
  803. return;
  804. }
  805.  
  806. var msg;
  807. var operation = request.operation;
  808. var error = operation.getError();
  809. if (error.statusText) {
  810. msg = error.statusText + ' (' + error.status + ')';
  811. } else {
  812. msg = gettext('Connection error');
  813. }
  814. PVE.Utils.setErrorMask(me, msg);
  815. });
  816. }
  817.  
  818. }});
  819.  
  820. // Some configuration values are complex strings -
  821. // so we need parsers/generators for them.
  822.  
  823. Ext.define('PVE.Parser', { statics: {
  824.  
  825. // this class only contains static functions
  826.  
  827. parseQemuNetwork: function(key, value) {
  828. if (!(key && value)) {
  829. return;
  830. }
  831.  
  832. var res = {};
  833.  
  834. var errors = false;
  835. Ext.Array.each(value.split(','), function(p) {
  836. if (!p || p.match(/^\s*$/)) {
  837. return; // continue
  838. }
  839.  
  840. var match_res;
  841.  
  842. if ((match_res = p.match(/^(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i)) !== null) {
  843. res.model = match_res[1].toLowerCase();
  844. if (match_res[3]) {
  845. res.macaddr = match_res[3];
  846. }
  847. } else if ((match_res = p.match(/^bridge=(\S+)$/)) !== null) {
  848. res.bridge = match_res[1];
  849. } else if ((match_res = p.match(/^rate=(\d+(\.\d+)?)$/)) !== null) {
  850. res.rate = match_res[1];
  851. } else if ((match_res = p.match(/^tag=(\d+(\.\d+)?)$/)) !== null) {
  852. res.tag = match_res[1];
  853. } else {
  854. errors = true;
  855. return false; // break
  856. }
  857. });
  858.  
  859. if (errors || !res.model) {
  860. return;
  861. }
  862.  
  863. return res;
  864. },
  865.  
  866. printQemuNetwork: function(net) {
  867.  
  868. var netstr = net.model;
  869. if (net.macaddr) {
  870. netstr += "=" + net.macaddr;
  871. }
  872. if (net.bridge) {
  873. netstr += ",bridge=" + net.bridge;
  874. if (net.tag) {
  875. netstr += ",tag=" + net.tag;
  876. }
  877. }
  878. if (net.rate) {
  879. netstr += ",rate=" + net.rate;
  880. }
  881. return netstr;
  882. },
  883.  
  884. parseQemuDrive: function(key, value) {
  885. if (!(key && value)) {
  886. return;
  887. }
  888.  
  889. var res = {};
  890.  
  891. var match_res = key.match(/^([a-z]+)(\d+)$/);
  892. if (!match_res) {
  893. return;
  894. }
  895. res['interface'] = match_res[1];
  896. res.index = match_res[2];
  897.  
  898. var errors = false;
  899. Ext.Array.each(value.split(','), function(p) {
  900. if (!p || p.match(/^\s*$/)) {
  901. return; // continue
  902. }
  903. var match_res = p.match(/^([a-z]+)=(\S+)$/);
  904. if (!match_res) {
  905. if (!p.match(/\=/)) {
  906. res.file = p;
  907. return; // continue
  908. }
  909. errors = true;
  910. return false; // break
  911. }
  912. var k = match_res[1];
  913. if (k === 'volume') {
  914. k = 'file';
  915. }
  916.  
  917. if (Ext.isDefined(res[k])) {
  918. errors = true;
  919. return false; // break
  920. }
  921.  
  922. var v = match_res[2];
  923.  
  924. if (k === 'cache' && v === 'off') {
  925. v = 'none';
  926. }
  927.  
  928. res[k] = v;
  929. });
  930.  
  931. if (errors || !res.file) {
  932. return;
  933. }
  934.  
  935. return res;
  936. },
  937.  
  938. printQemuDrive: function(drive) {
  939.  
  940. var drivestr = drive.file;
  941.  
  942. Ext.Object.each(drive, function(key, value) {
  943. if (!Ext.isDefined(value) || key === 'file' ||
  944. key === 'index' || key === 'interface') {
  945. return; // continue
  946. }
  947. drivestr += ',' + key + '=' + value;
  948. });
  949.  
  950. return drivestr;
  951. },
  952.  
  953. parseOpenVZNetIf: function(value) {
  954. if (!value) {
  955. return;
  956. }
  957.  
  958. var res = {};
  959.  
  960. var errors = false;
  961. Ext.Array.each(value.split(';'), function(item) {
  962. if (!item || item.match(/^\s*$/)) {
  963. return; // continue
  964. }
  965.  
  966. var data = {};
  967. Ext.Array.each(item.split(','), function(p) {
  968. if (!p || p.match(/^\s*$/)) {
  969. return; // continue
  970. }
  971. var match_res = p.match(/^(ifname|mac|bridge|host_ifname|host_mac)=(\S+)$/);
  972. if (!match_res) {
  973. errors = true;
  974. return false; // break
  975. }
  976. data[match_res[1]] = match_res[2];
  977. });
  978.  
  979. if (errors || !data.ifname) {
  980. errors = true;
  981. return false; // break
  982. }
  983.  
  984. data.raw = item;
  985.  
  986. res[data.ifname] = data;
  987. });
  988.  
  989. return errors ? undefined: res;
  990. },
  991.  
  992. printOpenVZNetIf: function(netif) {
  993. var netarray = [];
  994.  
  995. Ext.Object.each(netif, function(iface, data) {
  996. var tmparray = [];
  997. Ext.Array.each(['ifname', 'mac', 'bridge', 'host_ifname' , 'host_mac'], function(key) {
  998. var value = data[key];
  999. if (value) {
  1000. tmparray.push(key + '=' + value);
  1001. }
  1002. });
  1003. netarray.push(tmparray.join(','));
  1004. });
  1005.  
  1006. return netarray.join(';');
  1007. }
  1008. }});
  1009. /* This state provider keeps part of the state inside
  1010. * the browser history.
  1011. *
  1012. * We compress (shorten) url using dictionary based compression
  1013. * i.e. use column separated list instead of url encoded hash:
  1014. * #v\d* version/format
  1015. * := indicates string values
  1016. * :\d+ lookup value in dictionary hash
  1017. * #v1:=value1:5:=value2:=value3:...
  1018. */
  1019.  
  1020. Ext.define('PVE.StateProvider', {
  1021. extend: 'Ext.state.LocalStorageProvider',
  1022.  
  1023. // private
  1024. setHV: function(name, newvalue, fireEvents) {
  1025. var me = this;
  1026.  
  1027. var changes = false;
  1028. var oldtext = Ext.encode(me.UIState[name]);
  1029. var newtext = Ext.encode(newvalue);
  1030. if (newtext != oldtext) {
  1031. changes = true;
  1032. me.UIState[name] = newvalue;
  1033. //console.log("changed old " + name + " " + oldtext);
  1034. //console.log("changed new " + name + " " + newtext);
  1035. if (fireEvents) {
  1036. me.fireEvent("statechange", me, name, { value: newvalue });
  1037. }
  1038. }
  1039. return changes;
  1040. },
  1041.  
  1042. // private
  1043. hslist: [
  1044. // order is important for notifications
  1045. // [ name, default ]
  1046. ['view', 'server'],
  1047. ['rid', 'root'],
  1048. ['ltab', 'tasks'],
  1049. ['nodetab', ''],
  1050. ['storagetab', ''],
  1051. ['pooltab', ''],
  1052. ['kvmtab', ''],
  1053. ['ovztab', ''],
  1054. ['dctab', '']
  1055. ],
  1056.  
  1057. hprefix: 'v1',
  1058.  
  1059. compDict: {
  1060. pool: 26,
  1061. syslog: 25,
  1062. ubc: 24,
  1063. initlog: 23,
  1064. openvz: 22,
  1065. backup: 21,
  1066. ressources: 20,
  1067. content: 19,
  1068. root: 18,
  1069. domains: 17,
  1070. roles: 16,
  1071. groups: 15,
  1072. users: 14,
  1073. time: 13,
  1074. dns: 12,
  1075. network: 11,
  1076. services: 10,
  1077. options: 9,
  1078. console: 8,
  1079. hardware: 7,
  1080. permissions: 6,
  1081. summary: 5,
  1082. tasks: 4,
  1083. clog: 3,
  1084. storage: 2,
  1085. folder: 1,
  1086. server: 0
  1087. },
  1088.  
  1089. decodeHToken: function(token) {
  1090. var me = this;
  1091.  
  1092. var state = {};
  1093. if (!token) {
  1094. Ext.Array.each(me.hslist, function(rec) {
  1095. state[rec[0]] = rec[1];
  1096. });
  1097. return state;
  1098. }
  1099.  
  1100. // return Ext.urlDecode(token);
  1101.  
  1102. var items = token.split(':');
  1103. var prefix = items.shift();
  1104.  
  1105. if (prefix != me.hprefix) {
  1106. return me.decodeHToken();
  1107. }
  1108.  
  1109. Ext.Array.each(me.hslist, function(rec) {
  1110. var value = items.shift();
  1111. if (value) {
  1112. if (value[0] === '=') {
  1113. value = decodeURIComponent(value.slice(1));
  1114. } else {
  1115. Ext.Object.each(me.compDict, function(key, cv) {
  1116. if (value == cv) {
  1117. value = key;
  1118. return false;
  1119. }
  1120. });
  1121. }
  1122. }
  1123. state[rec[0]] = value;
  1124. });
  1125.  
  1126. return state;
  1127. },
  1128.  
  1129. encodeHToken: function(state) {
  1130. var me = this;
  1131.  
  1132. // return Ext.urlEncode(state);
  1133.  
  1134. var ctoken = me.hprefix;
  1135. Ext.Array.each(me.hslist, function(rec) {
  1136. var value = state[rec[0]];
  1137. if (!Ext.isDefined(value)) {
  1138. value = rec[1];
  1139. }
  1140. value = encodeURIComponent(value);
  1141. if (!value) {
  1142. ctoken += ':';
  1143. } else {
  1144. var comp = me.compDict[value];
  1145. if (Ext.isDefined(comp)) {
  1146. ctoken += ":" + comp;
  1147. } else {
  1148. ctoken += ":=" + value;
  1149. }
  1150. }
  1151. });
  1152.  
  1153. return ctoken;
  1154. },
  1155.  
  1156. constructor: function(config){
  1157. var me = this;
  1158.  
  1159. me.callParent([config]);
  1160.  
  1161. me.UIState = me.decodeHToken(); // set default
  1162.  
  1163. var history_change_cb = function(token) {
  1164. //console.log("HC " + token);
  1165. if (!token) {
  1166. var res = window.confirm('Are you sure you want to navigate away from this page?');
  1167. if (res){
  1168. // process text value and close...
  1169. Ext.History.back();
  1170. } else {
  1171. Ext.History.forward();
  1172. }
  1173. return;
  1174. }
  1175.  
  1176. var newstate = me.decodeHToken(token);
  1177. Ext.Array.each(me.hslist, function(rec) {
  1178. if (typeof newstate[rec[0]] == "undefined") {
  1179. return;
  1180. }
  1181. me.setHV(rec[0], newstate[rec[0]], true);
  1182. });
  1183. };
  1184.  
  1185. var start_token = Ext.History.getToken();
  1186. if (start_token) {
  1187. history_change_cb(start_token);
  1188. } else {
  1189. var htext = me.encodeHToken(me.UIState);
  1190. Ext.History.add(htext);
  1191. }
  1192.  
  1193. Ext.History.on('change', history_change_cb);
  1194. },
  1195.  
  1196. get: function(name, defaultValue){
  1197. var me = this;
  1198. var data;
  1199.  
  1200. if (typeof me.UIState[name] != "undefined") {
  1201. data = { value: me.UIState[name] };
  1202. } else {
  1203. data = me.callParent(arguments);
  1204. }
  1205.  
  1206. //console.log("GET " + name + " " + Ext.encode(data));
  1207. return data;
  1208. },
  1209.  
  1210. clear: function(name){
  1211. var me = this;
  1212.  
  1213. if (typeof me.UIState[name] != "undefined") {
  1214. me.UIState[name] = null;
  1215. }
  1216.  
  1217. me.callParent(arguments);
  1218. },
  1219.  
  1220. set: function(name, value){
  1221. var me = this;
  1222.  
  1223. //console.log("SET " + name + " " + Ext.encode(value));
  1224. if (typeof me.UIState[name] != "undefined") {
  1225. var newvalue = value ? value.value : null;
  1226. if (me.setHV(name, newvalue, false)) {
  1227. var htext = me.encodeHToken(me.UIState);
  1228. Ext.History.add(htext);
  1229. }
  1230. } else {
  1231. me.callParent(arguments);
  1232. }
  1233. }
  1234. });/* Button features:
  1235. * - observe selection changes to enable/disable the button using enableFn()
  1236. * - pop up confirmation dialog using confirmMsg()
  1237. */
  1238. Ext.define('PVE.button.Button', {
  1239. extend: 'Ext.button.Button',
  1240. alias: 'widget.pveButton',
  1241.  
  1242. // the selection model to observe
  1243. selModel: undefined,
  1244.  
  1245. // if 'false' handler will not be called (button disabled)
  1246. enableFn: function(record) { },
  1247.  
  1248. // function(record) or text
  1249. confirmMsg: false,
  1250.  
  1251. initComponent: function() {
  1252. /*jslint confusion: true */
  1253.  
  1254. var me = this;
  1255.  
  1256. if (me.handler) {
  1257. me.realHandler = me.handler;
  1258.  
  1259. me.handler = function(button, event) {
  1260. var rec, msg;
  1261. if (me.selModel) {
  1262. rec = me.selModel.getSelection()[0];
  1263. if (!rec || (me.enableFn(rec) === false)) {
  1264. return;
  1265. }
  1266. }
  1267.  
  1268. if (me.confirmMsg) {
  1269. msg = me.confirmMsg;
  1270. if (Ext.isFunction(me.confirmMsg)) {
  1271. msg = me.confirmMsg(rec);
  1272. }
  1273. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1274. if (btn !== 'yes') {
  1275. return;
  1276. }
  1277. me.realHandler(button, event, rec);
  1278. });
  1279. } else {
  1280. me.realHandler(button, event, rec);
  1281. }
  1282. };
  1283. }
  1284.  
  1285. me.callParent();
  1286.  
  1287. if (me.selModel) {
  1288.  
  1289. me.mon(me.selModel, "selectionchange", function() {
  1290. var rec = me.selModel.getSelection()[0];
  1291. if (!rec || (me.enableFn(rec) === false)) {
  1292. me.setDisabled(true);
  1293. } else {
  1294. me.setDisabled(false);
  1295. }
  1296. });
  1297. }
  1298. }
  1299. });
  1300. Ext.define('PVE.qemu.SendKeyMenu', {
  1301. extend: 'Ext.button.Button',
  1302. alias: ['widget.pveQemuSendKeyMenu'],
  1303.  
  1304. initComponent : function() {
  1305. var me = this;
  1306.  
  1307. if (!me.nodename) {
  1308. throw "no node name specified";
  1309. }
  1310.  
  1311. if (!me.vmid) {
  1312. throw "no VM ID specified";
  1313. }
  1314.  
  1315. var sendKey = function(key) {
  1316. PVE.Utils.API2Request({
  1317. params: { key: key },
  1318. url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/sendkey",
  1319. method: 'PUT',
  1320. waitMsgTarget: me,
  1321. failure: function(response, opts) {
  1322. Ext.Msg.alert('Error', response.htmlStatus);
  1323. }
  1324. });
  1325. };
  1326.  
  1327. Ext.apply(me, {
  1328. text: 'SendKey',
  1329. menu: new Ext.menu.Menu({
  1330. height: 200,
  1331. items: [
  1332. {
  1333. text: 'Ctrl-Alt-Delete', handler: function() {
  1334. sendKey('ctrl-alt-delete');
  1335. }
  1336. },
  1337. {
  1338. text: 'Ctrl-Alt-Backspace', handler: function() {
  1339. sendKey('ctrl-alt-backspace');
  1340. }
  1341. },
  1342. {
  1343. text: 'Ctrl-Alt-F1', handler: function() {
  1344. sendKey('ctrl-alt-f1');
  1345. }
  1346. },
  1347. {
  1348. text: 'Ctrl-Alt-F2', handler: function() {
  1349. sendKey('ctrl-alt-f2');
  1350. }
  1351. },
  1352. {
  1353. text: 'Ctrl-Alt-F3', handler: function() {
  1354. sendKey('ctrl-alt-f3');
  1355. }
  1356. },
  1357. {
  1358. text: 'Ctrl-Alt-F4', handler: function() {
  1359. sendKey('ctrl-alt-f4');
  1360. }
  1361. },
  1362. {
  1363. text: 'Ctrl-Alt-F5', handler: function() {
  1364. sendKey('ctrl-alt-f5');
  1365. }
  1366. },
  1367. {
  1368. text: 'Ctrl-Alt-F6', handler: function() {
  1369. sendKey('ctrl-alt-f6');
  1370. }
  1371. },
  1372. {
  1373. text: 'Ctrl-Alt-F7', handler: function() {
  1374. sendKey('ctrl-alt-f7');
  1375. }
  1376. },
  1377. {
  1378. text: 'Ctrl-Alt-F8', handler: function() {
  1379. sendKey('ctrl-alt-f8');
  1380. }
  1381. },
  1382. {
  1383. text: 'Ctrl-Alt-F9', handler: function() {
  1384. sendKey('ctrl-alt-f9');
  1385. }
  1386. },
  1387. {
  1388. text: 'Ctrl-Alt-F10', handler: function() {
  1389. sendKey('ctrl-alt-f10');
  1390. }
  1391. },
  1392. {
  1393. text: 'Ctrl-Alt-F11', handler: function() {
  1394. sendKey('ctrl-alt-f11');
  1395. }
  1396. },
  1397. {
  1398. text: 'Ctrl-Alt-F12', handler: function() {
  1399. sendKey('ctrl-alt-f12');
  1400. }
  1401. }
  1402. ]
  1403. })
  1404. });
  1405.  
  1406. me.callParent();
  1407. }
  1408. });
  1409. Ext.define('PVE.qemu.CmdMenu', {
  1410. extend: 'Ext.menu.Menu',
  1411.  
  1412. initComponent: function() {
  1413. var me = this;
  1414.  
  1415. var nodename = me.pveSelNode.data.node;
  1416. if (!nodename) {
  1417. throw "no node name specified";
  1418. }
  1419.  
  1420. var vmid = me.pveSelNode.data.vmid;
  1421. if (!vmid) {
  1422. throw "no VM ID specified";
  1423. }
  1424.  
  1425. var vmname = me.pveSelNode.data.name;
  1426.  
  1427. var vm_command = function(cmd, params) {
  1428. PVE.Utils.API2Request({
  1429. params: params,
  1430. url: '/nodes/' + nodename + '/qemu/' + vmid + "/status/" + cmd,
  1431. method: 'POST',
  1432. failure: function(response, opts) {
  1433. Ext.Msg.alert('Error', response.htmlStatus);
  1434. }
  1435. });
  1436. };
  1437.  
  1438. me.title = "VM " + vmid;
  1439.  
  1440. me.items = [
  1441. {
  1442. text: gettext('Start'),
  1443. icon: '/pve2/images/start.png',
  1444. handler: function() {
  1445. vm_command('start');
  1446. }
  1447. },
  1448. {
  1449. text: gettext('Migrate'),
  1450. icon: '/pve2/images/forward.png',
  1451. handler: function() {
  1452. var win = Ext.create('PVE.window.Migrate', {
  1453. vmtype: 'qemu',
  1454. nodename: nodename,
  1455. vmid: vmid
  1456. });
  1457. win.show();
  1458. }
  1459. },
  1460. {
  1461. text: gettext('Shutdown'),
  1462. icon: '/pve2/images/stop.png',
  1463. handler: function() {
  1464. var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid);
  1465. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1466. if (btn !== 'yes') {
  1467. return;
  1468. }
  1469.  
  1470. vm_command('shutdown', { timeout: 30 });
  1471. });
  1472. }
  1473. },
  1474. {
  1475. text: gettext('Stop'),
  1476. icon: '/pve2/images/gtk-stop.png',
  1477. handler: function() {
  1478. var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid);
  1479. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1480. if (btn !== 'yes') {
  1481. return;
  1482. }
  1483.  
  1484. vm_command("stop", { timeout: 30 });
  1485. });
  1486. }
  1487. },
  1488. {
  1489. text: gettext('Console'),
  1490. icon: '/pve2/images/display.png',
  1491. handler: function() {
  1492. PVE.Utils.openConoleWindow('kvm', vmid, nodename, vmname);
  1493. }
  1494. }
  1495. ];
  1496.  
  1497. me.callParent();
  1498. }
  1499. });
  1500. Ext.define('PVE.openvz.CmdMenu', {
  1501. extend: 'Ext.menu.Menu',
  1502.  
  1503. initComponent: function() {
  1504. var me = this;
  1505.  
  1506. var nodename = me.pveSelNode.data.node;
  1507. if (!nodename) {
  1508. throw "no node name specified";
  1509. }
  1510.  
  1511. var vmid = me.pveSelNode.data.vmid;
  1512. if (!vmid) {
  1513. throw "no VM ID specified";
  1514. }
  1515.  
  1516. var vmname = me.pveSelNode.data.name;
  1517.  
  1518. var vm_command = function(cmd, params) {
  1519. PVE.Utils.API2Request({
  1520. params: params,
  1521. url: '/nodes/' + nodename + '/openvz/' + vmid + "/status/" + cmd,
  1522. method: 'POST',
  1523. failure: function(response, opts) {
  1524. Ext.Msg.alert('Error', response.htmlStatus);
  1525. }
  1526. });
  1527. };
  1528.  
  1529. me.title = "CT " + vmid;
  1530.  
  1531. me.items = [
  1532. {
  1533. text: gettext('Start'),
  1534. icon: '/pve2/images/start.png',
  1535. handler: function() {
  1536. vm_command('start');
  1537. }
  1538. },
  1539. {
  1540. text: gettext('Migrate'),
  1541. icon: '/pve2/images/forward.png',
  1542. handler: function() {
  1543. var win = Ext.create('PVE.window.Migrate', {
  1544. vmtype: 'openvz',
  1545. nodename: nodename,
  1546. vmid: vmid
  1547. });
  1548. win.show();
  1549. }
  1550. },
  1551. {
  1552. text: gettext('Shutdown'),
  1553. icon: '/pve2/images/stop.png',
  1554. handler: function() {
  1555. var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid);
  1556. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1557. if (btn !== 'yes') {
  1558. return;
  1559. }
  1560.  
  1561. vm_command('shutdown');
  1562. });
  1563. }
  1564. },
  1565. {
  1566. text: gettext('Stop'),
  1567. icon: '/pve2/images/gtk-stop.png',
  1568. handler: function() {
  1569. var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid);
  1570. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1571. if (btn !== 'yes') {
  1572. return;
  1573. }
  1574.  
  1575. vm_command("stop");
  1576. });
  1577. }
  1578. },
  1579. {
  1580. text: gettext('Console'),
  1581. icon: '/pve2/images/display.png',
  1582. handler: function() {
  1583. PVE.Utils.openConoleWindow('openvz', vmid, nodename, vmname);
  1584. }
  1585. }
  1586. ];
  1587.  
  1588. me.callParent();
  1589. }
  1590. });
  1591. PVE_vnc_console_event = function(appletid, action, err) {
  1592. //console.log("TESTINIT param1 " + appletid + " action " + action);
  1593.  
  1594. if (action === "error") {
  1595. var compid = appletid.replace("-vncapp", "");
  1596. var comp = Ext.getCmp(compid);
  1597.  
  1598. if (!comp || !comp.vmid || !comp.toplevel) {
  1599. return;
  1600. }
  1601.  
  1602. // try to detect migrated VM
  1603. PVE.Utils.API2Request({
  1604. url: '/cluster/resources',
  1605. method: 'GET',
  1606. success: function(response) {
  1607. var list = response.result.data;
  1608. Ext.Array.each(list, function(item) {
  1609. if (item.type === 'qemu' && item.vmid == comp.vmid) {
  1610. if (item.node !== comp.nodename) {
  1611. //console.log("MOVED VM to node " + item.node);
  1612. comp.nodename = item.node;
  1613. comp.url = "/nodes/" + comp.nodename + "/" + item.type + "/" + comp.vmid + "/vncproxy";
  1614. //console.log("NEW URL " + comp.url);
  1615. comp.reloadApplet();
  1616. }
  1617. return false; // break
  1618. }
  1619. });
  1620. }
  1621. });
  1622. }
  1623.  
  1624. return;
  1625. /*
  1626. var el = Ext.get(appletid);
  1627. if (!el)
  1628. return;
  1629.  
  1630. if (action === "close") {
  1631. // el.remove();
  1632. } else if (action === "error") {
  1633. // console.log("TESTERROR: " + err);
  1634. // var compid = appletid.replace("-vncapp", "");
  1635. // var comp = Ext.getCmp(compid);
  1636. }
  1637.  
  1638. //Ext.get('mytestid').remove();
  1639. */
  1640.  
  1641. };
  1642.  
  1643. Ext.define('PVE.VNCConsole', {
  1644. extend: 'Ext.panel.Panel',
  1645. alias: ['widget.pveVNCConsole'],
  1646.  
  1647. initComponent : function() {
  1648. var me = this;
  1649.  
  1650. if (!me.url) {
  1651. throw "no url specified";
  1652. }
  1653.  
  1654. var myid = me.id + "-vncapp";
  1655.  
  1656. me.appletID = myid;
  1657.  
  1658. var box = Ext.create('Ext.Component', {
  1659. border: false,
  1660. html: ""
  1661. });
  1662.  
  1663. var resize_window = function() {
  1664. //console.log("resize");
  1665.  
  1666. var applet = Ext.getDom(myid);
  1667. //console.log("resize " + myid + " " + applet);
  1668.  
  1669. // try again when dom element is available
  1670. if (!(applet && Ext.isFunction(applet.getPreferredSize))) {
  1671. return Ext.Function.defer(resize_window, 1000);
  1672. }
  1673.  
  1674. var tbar = me.getDockedItems("[dock=top]")[0];
  1675. var tbh = tbar ? tbar.getHeight() : 0;
  1676. var ps = applet.getPreferredSize();
  1677. var aw = ps.width;
  1678. var ah = ps.height;
  1679.  
  1680. if (aw < 640) { aw = 640; }
  1681. if (ah < 400) { ah = 400; }
  1682.  
  1683. var oh;
  1684. var ow;
  1685.  
  1686. //console.log("size0 " + aw + " " + ah + " tbh " + tbh);
  1687.  
  1688. if (window.innerHeight) {
  1689. oh = window.innerHeight;
  1690. ow = window.innerWidth;
  1691. } else if (document.documentElement &&
  1692. document.documentElement.clientHeight) {
  1693. oh = document.documentElement.clientHeight;
  1694. ow = document.documentElement.clientWidth;
  1695. } else if (document.body) {
  1696. oh = document.body.clientHeight;
  1697. ow = document.body.clientWidth;
  1698. } else {
  1699. throw "can't get window size";
  1700. }
  1701.  
  1702. Ext.fly(applet).setSize(aw, ah + tbh);
  1703.  
  1704. var offsetw = aw - ow;
  1705. var offseth = ah + tbh - oh;
  1706.  
  1707. if (offsetw !== 0 || offseth !== 0) {
  1708. //console.log("try resize by " + offsetw + " " + offseth);
  1709. try { window.resizeBy(offsetw, offseth); } catch (e) {}
  1710. }
  1711.  
  1712. Ext.Function.defer(resize_window, 1000);
  1713. };
  1714.  
  1715. var resize_box = function() {
  1716. var applet = Ext.getDom(myid);
  1717.  
  1718. if ((applet && Ext.isFunction(applet.getPreferredSize))) {
  1719. var ps = applet.getPreferredSize();
  1720. Ext.fly(applet).setSize(ps.width, ps.height);
  1721. }
  1722.  
  1723. Ext.Function.defer(resize_box, 1000);
  1724. };
  1725.  
  1726. var start_vnc_viewer = function(param) {
  1727. var cert = param.cert;
  1728. cert = cert.replace(/\n/g, "|");
  1729.  
  1730. box.update({
  1731. id: myid,
  1732. border: false,
  1733. tag: 'applet',
  1734. code: 'com.tigervnc.vncviewer.VncViewer',
  1735. archive: '/vncterm/VncViewer.jar',
  1736. // NOTE: set size to '100%' - else resize does not work
  1737. width: "100%",
  1738. height: "100%",
  1739. cn: [
  1740. {tag: 'param', name: 'id', value: myid},
  1741. {tag: 'param', name: 'PORT', value: param.port},
  1742. {tag: 'param', name: 'PASSWORD', value: param.ticket},
  1743. {tag: 'param', name: 'USERNAME', value: param.user},
  1744. {tag: 'param', name: 'Show Controls', value: 'No'},
  1745. {tag: 'param', name: 'Offer Relogin', value: 'No'},
  1746. {tag: 'param', name: 'PVECert', value: cert}
  1747. ]
  1748. });
  1749. if (me.toplevel) {
  1750. Ext.Function.defer(resize_window, 1000);
  1751. } else {
  1752. Ext.Function.defer(resize_box, 1000);
  1753. }
  1754. };
  1755.  
  1756. Ext.apply(me, {
  1757. layout: 'fit',
  1758. border: false,
  1759. autoScroll: me.toplevel ? false : true,
  1760. items: box,
  1761. reloadApplet: function() {
  1762. PVE.Utils.API2Request({
  1763. url: me.url,
  1764. params: me.params,
  1765. method: me.method || 'POST',
  1766. failure: function(response, opts) {
  1767. box.update(gettext('Error') + ' ' + response.htmlStatus);
  1768. },
  1769. success: function(response, opts) {
  1770. start_vnc_viewer(response.result.data);
  1771. }
  1772. });
  1773. }
  1774. });
  1775.  
  1776. me.callParent();
  1777.  
  1778. if (me.toplevel) {
  1779. me.on("render", function() { me.reloadApplet();});
  1780. } else {
  1781. me.on("show", function() { me.reloadApplet();});
  1782. me.on("hide", function() { box.update(""); });
  1783. }
  1784. }
  1785. });
  1786.  
  1787. Ext.define('PVE.KVMConsole', {
  1788. extend: 'PVE.VNCConsole',
  1789. alias: ['widget.pveKVMConsole'],
  1790.  
  1791. initComponent : function() {
  1792. var me = this;
  1793.  
  1794. if (!me.nodename) {
  1795. throw "no node name specified";
  1796. }
  1797.  
  1798. if (!me.vmid) {
  1799. throw "no VM ID specified";
  1800. }
  1801.  
  1802. var vm_command = function(cmd, params, reload_applet) {
  1803. PVE.Utils.API2Request({
  1804. params: params,
  1805. url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/status/" + cmd,
  1806. method: 'POST',
  1807. waitMsgTarget: me,
  1808. failure: function(response, opts) {
  1809. Ext.Msg.alert('Error', response.htmlStatus);
  1810. },
  1811. success: function() {
  1812. if (reload_applet) {
  1813. Ext.Function.defer(me.reloadApplet, 1000, me);
  1814. }
  1815. }
  1816. });
  1817. };
  1818.  
  1819. var tbar = [
  1820. {
  1821. text: gettext('Start'),
  1822. handler: function() {
  1823. vm_command("start", {}, 1);
  1824. }
  1825. },
  1826. {
  1827. text: gettext('Shutdown'),
  1828. handler: function() {
  1829. var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), me.vmid);
  1830. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1831. if (btn !== 'yes') {
  1832. return;
  1833. }
  1834. vm_command('shutdown', {timeout: 30});
  1835. });
  1836. }
  1837. },
  1838. {
  1839. text: gettext('Stop'),
  1840. handler: function() {
  1841. var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), me.vmid);
  1842. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1843. if (btn !== 'yes') {
  1844. return;
  1845. }
  1846. vm_command("stop", { timeout: 30});
  1847. });
  1848. }
  1849. },
  1850. {
  1851. xtype: 'pveQemuSendKeyMenu',
  1852. nodename: me.nodename,
  1853. vmid: me.vmid
  1854. },
  1855. {
  1856. text: gettext('Reset'),
  1857. handler: function() {
  1858. var msg = Ext.String.format(gettext("Do you really want to reset VM {0}?"), me.vmid);
  1859. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1860. if (btn !== 'yes') {
  1861. return;
  1862. }
  1863. vm_command("reset");
  1864. });
  1865. }
  1866. },
  1867. {
  1868. text: gettext('Suspend'),
  1869. handler: function() {
  1870. var msg = Ext.String.format(gettext("Do you really want to suspend VM {0}?"), me.vmid);
  1871. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1872. if (btn !== 'yes') {
  1873. return;
  1874. }
  1875. vm_command("suspend");
  1876. });
  1877. }
  1878. },
  1879. {
  1880. text: gettext('Resume'),
  1881. handler: function() {
  1882. vm_command("resume");
  1883. }
  1884. },
  1885. // Note: no migrate here, because we can't display migrate log
  1886. {
  1887. text: gettext('Console'),
  1888. handler: function() {
  1889. PVE.Utils.openConoleWindow('kvm', me.vmid, me.nodename, me.vmname);
  1890. }
  1891. },
  1892. '->',
  1893. {
  1894. text: gettext('Refresh'),
  1895. handler: function() {
  1896. var applet = Ext.getDom(me.appletID);
  1897. applet.sendRefreshRequest();
  1898. }
  1899. },
  1900. {
  1901. text: gettext('Reload'),
  1902. handler: function () {
  1903. me.reloadApplet();
  1904. }
  1905. }
  1906. ];
  1907.  
  1908. Ext.apply(me, {
  1909. tbar: tbar,
  1910. url: "/nodes/" + me.nodename + "/qemu/" + me.vmid + "/vncproxy"
  1911. });
  1912.  
  1913. me.callParent();
  1914. }
  1915. });
  1916.  
  1917. Ext.define('PVE.OpenVZConsole', {
  1918. extend: 'PVE.VNCConsole',
  1919. alias: ['widget.pveOpenVZConsole'],
  1920.  
  1921. initComponent : function() {
  1922. var me = this;
  1923.  
  1924. if (!me.nodename) {
  1925. throw "no node name specified";
  1926. }
  1927.  
  1928. if (!me.vmid) {
  1929. throw "no VM ID specified";
  1930. }
  1931.  
  1932. var vm_command = function(cmd, params, reload_applet) {
  1933. PVE.Utils.API2Request({
  1934. params: params,
  1935. url: '/nodes/' + me.nodename + '/openvz/' + me.vmid + "/status/" + cmd,
  1936. waitMsgTarget: me,
  1937. method: 'POST',
  1938. failure: function(response, opts) {
  1939. Ext.Msg.alert('Error', response.htmlStatus);
  1940. },
  1941. success: function() {
  1942. if (reload_applet) {
  1943. Ext.Function.defer(me.reloadApplet, 1000, me);
  1944. }
  1945. }
  1946. });
  1947. };
  1948.  
  1949. var tbar = [
  1950. {
  1951. text: gettext('Start'),
  1952. handler: function() {
  1953. vm_command("start", {}, 1);
  1954. }
  1955. },
  1956. {
  1957. text: gettext('Shutdown'),
  1958. handler: function() {
  1959. var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), me.vmid);
  1960. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1961. if (btn !== 'yes') {
  1962. return;
  1963. }
  1964. vm_command("shutdown");
  1965. });
  1966. }
  1967. },
  1968. {
  1969. text: gettext('Stop'),
  1970. handler: function() {
  1971. var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), me.vmid);
  1972. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1973. if (btn !== 'yes') {
  1974. return;
  1975. }
  1976. vm_command("stop");
  1977. });
  1978. }
  1979. },
  1980. // Note: no migrate here, because we can't display migrate log
  1981. // and openvz migrate does not work if console is open
  1982. {
  1983. text: gettext('Console'),
  1984. handler: function() {
  1985. PVE.Utils.openConoleWindow('openvz', me.vmid, me.nodename, me.vmname);
  1986. }
  1987. },
  1988. '->',
  1989. {
  1990. text: gettext('Refresh'),
  1991. handler: function() {
  1992. var applet = Ext.getDom(me.appletID);
  1993. applet.sendRefreshRequest();
  1994. }
  1995. },
  1996. {
  1997. text: gettext('Reload'),
  1998. handler: function () {
  1999. me.reloadApplet();
  2000. }
  2001. }
  2002. ];
  2003.  
  2004. Ext.apply(me, {
  2005. tbar: tbar,
  2006. url: "/nodes/" + me.nodename + "/openvz/" + me.vmid + "/vncproxy"
  2007. });
  2008.  
  2009. me.callParent();
  2010. }
  2011. });
  2012.  
  2013. Ext.define('PVE.Shell', {
  2014. extend: 'PVE.VNCConsole',
  2015. alias: ['widget.pveShell'],
  2016.  
  2017. initComponent : function() {
  2018. var me = this;
  2019.  
  2020. if (!me.nodename) {
  2021. throw "no node name specified";
  2022. }
  2023.  
  2024. var tbar = [
  2025. '->',
  2026. {
  2027. text: gettext('Refresh'),
  2028. handler: function() {
  2029. var applet = Ext.getDom(me.appletID);
  2030. applet.sendRefreshRequest();
  2031. }
  2032. },
  2033. {
  2034. text: gettext('Reload'),
  2035. handler: function () { me.reloadApplet(); }
  2036. },
  2037. {
  2038. text: gettext('Shell'),
  2039. handler: function() {
  2040. PVE.Utils.openConoleWindow('shell', undefined, me.nodename);
  2041. }
  2042. }
  2043. ];
  2044.  
  2045. Ext.apply(me, {
  2046. tbar: tbar,
  2047. url: "/nodes/" + me.nodename + "/vncshell"
  2048. });
  2049.  
  2050. me.callParent();
  2051. }
  2052. });Ext.define('PVE.data.TimezoneStore', {
  2053. extend: 'Ext.data.Store',
  2054.  
  2055. statics: {
  2056. timezones: [
  2057. ['Africa/Abidjan'],
  2058. ['Africa/Accra'],
  2059. ['Africa/Addis_Ababa'],
  2060. ['Africa/Algiers'],
  2061. ['Africa/Asmara'],
  2062. ['Africa/Bamako'],
  2063. ['Africa/Bangui'],
  2064. ['Africa/Banjul'],
  2065. ['Africa/Bissau'],
  2066. ['Africa/Blantyre'],
  2067. ['Africa/Brazzaville'],
  2068. ['Africa/Bujumbura'],
  2069. ['Africa/Cairo'],
  2070. ['Africa/Casablanca'],
  2071. ['Africa/Ceuta'],
  2072. ['Africa/Conakry'],
  2073. ['Africa/Dakar'],
  2074. ['Africa/Dar_es_Salaam'],
  2075. ['Africa/Djibouti'],
  2076. ['Africa/Douala'],
  2077. ['Africa/El_Aaiun'],
  2078. ['Africa/Freetown'],
  2079. ['Africa/Gaborone'],
  2080. ['Africa/Harare'],
  2081. ['Africa/Johannesburg'],
  2082. ['Africa/Kampala'],
  2083. ['Africa/Khartoum'],
  2084. ['Africa/Kigali'],
  2085. ['Africa/Kinshasa'],
  2086. ['Africa/Lagos'],
  2087. ['Africa/Libreville'],
  2088. ['Africa/Lome'],
  2089. ['Africa/Luanda'],
  2090. ['Africa/Lubumbashi'],
  2091. ['Africa/Lusaka'],
  2092. ['Africa/Malabo'],
  2093. ['Africa/Maputo'],
  2094. ['Africa/Maseru'],
  2095. ['Africa/Mbabane'],
  2096. ['Africa/Mogadishu'],
  2097. ['Africa/Monrovia'],
  2098. ['Africa/Nairobi'],
  2099. ['Africa/Ndjamena'],
  2100. ['Africa/Niamey'],
  2101. ['Africa/Nouakchott'],
  2102. ['Africa/Ouagadougou'],
  2103. ['Africa/Porto-Novo'],
  2104. ['Africa/Sao_Tome'],
  2105. ['Africa/Tripoli'],
  2106. ['Africa/Tunis'],
  2107. ['Africa/Windhoek'],
  2108. ['America/Adak'],
  2109. ['America/Anchorage'],
  2110. ['America/Anguilla'],
  2111. ['America/Antigua'],
  2112. ['America/Araguaina'],
  2113. ['America/Argentina/Buenos_Aires'],
  2114. ['America/Argentina/Catamarca'],
  2115. ['America/Argentina/Cordoba'],
  2116. ['America/Argentina/Jujuy'],
  2117. ['America/Argentina/La_Rioja'],
  2118. ['America/Argentina/Mendoza'],
  2119. ['America/Argentina/Rio_Gallegos'],
  2120. ['America/Argentina/Salta'],
  2121. ['America/Argentina/San_Juan'],
  2122. ['America/Argentina/San_Luis'],
  2123. ['America/Argentina/Tucuman'],
  2124. ['America/Argentina/Ushuaia'],
  2125. ['America/Aruba'],
  2126. ['America/Asuncion'],
  2127. ['America/Atikokan'],
  2128. ['America/Bahia'],
  2129. ['America/Bahia_Banderas'],
  2130. ['America/Barbados'],
  2131. ['America/Belem'],
  2132. ['America/Belize'],
  2133. ['America/Blanc-Sablon'],
  2134. ['America/Boa_Vista'],
  2135. ['America/Bogota'],
  2136. ['America/Boise'],
  2137. ['America/Cambridge_Bay'],
  2138. ['America/Campo_Grande'],
  2139. ['America/Cancun'],
  2140. ['America/Caracas'],
  2141. ['America/Cayenne'],
  2142. ['America/Cayman'],
  2143. ['America/Chicago'],
  2144. ['America/Chihuahua'],
  2145. ['America/Costa_Rica'],
  2146. ['America/Cuiaba'],
  2147. ['America/Curacao'],
  2148. ['America/Danmarkshavn'],
  2149. ['America/Dawson'],
  2150. ['America/Dawson_Creek'],
  2151. ['America/Denver'],
  2152. ['America/Detroit'],
  2153. ['America/Dominica'],
  2154. ['America/Edmonton'],
  2155. ['America/Eirunepe'],
  2156. ['America/El_Salvador'],
  2157. ['America/Fortaleza'],
  2158. ['America/Glace_Bay'],
  2159. ['America/Godthab'],
  2160. ['America/Goose_Bay'],
  2161. ['America/Grand_Turk'],
  2162. ['America/Grenada'],
  2163. ['America/Guadeloupe'],
  2164. ['America/Guatemala'],
  2165. ['America/Guayaquil'],
  2166. ['America/Guyana'],
  2167. ['America/Halifax'],
  2168. ['America/Havana'],
  2169. ['America/Hermosillo'],
  2170. ['America/Indiana/Indianapolis'],
  2171. ['America/Indiana/Knox'],
  2172. ['America/Indiana/Marengo'],
  2173. ['America/Indiana/Petersburg'],
  2174. ['America/Indiana/Tell_City'],
  2175. ['America/Indiana/Vevay'],
  2176. ['America/Indiana/Vincennes'],
  2177. ['America/Indiana/Winamac'],
  2178. ['America/Inuvik'],
  2179. ['America/Iqaluit'],
  2180. ['America/Jamaica'],
  2181. ['America/Juneau'],
  2182. ['America/Kentucky/Louisville'],
  2183. ['America/Kentucky/Monticello'],
  2184. ['America/La_Paz'],
  2185. ['America/Lima'],
  2186. ['America/Los_Angeles'],
  2187. ['America/Maceio'],
  2188. ['America/Managua'],
  2189. ['America/Manaus'],
  2190. ['America/Marigot'],
  2191. ['America/Martinique'],
  2192. ['America/Matamoros'],
  2193. ['America/Mazatlan'],
  2194. ['America/Menominee'],
  2195. ['America/Merida'],
  2196. ['America/Mexico_City'],
  2197. ['America/Miquelon'],
  2198. ['America/Moncton'],
  2199. ['America/Monterrey'],
  2200. ['America/Montevideo'],
  2201. ['America/Montreal'],
  2202. ['America/Montserrat'],
  2203. ['America/Nassau'],
  2204. ['America/New_York'],
  2205. ['America/Nipigon'],
  2206. ['America/Nome'],
  2207. ['America/Noronha'],
  2208. ['America/North_Dakota/Center'],
  2209. ['America/North_Dakota/New_Salem'],
  2210. ['America/Ojinaga'],
  2211. ['America/Panama'],
  2212. ['America/Pangnirtung'],
  2213. ['America/Paramaribo'],
  2214. ['America/Phoenix'],
  2215. ['America/Port-au-Prince'],
  2216. ['America/Port_of_Spain'],
  2217. ['America/Porto_Velho'],
  2218. ['America/Puerto_Rico'],
  2219. ['America/Rainy_River'],
  2220. ['America/Rankin_Inlet'],
  2221. ['America/Recife'],
  2222. ['America/Regina'],
  2223. ['America/Resolute'],
  2224. ['America/Rio_Branco'],
  2225. ['America/Santa_Isabel'],
  2226. ['America/Santarem'],
  2227. ['America/Santiago'],
  2228. ['America/Santo_Domingo'],
  2229. ['America/Sao_Paulo'],
  2230. ['America/Scoresbysund'],
  2231. ['America/Shiprock'],
  2232. ['America/St_Barthelemy'],
  2233. ['America/St_Johns'],
  2234. ['America/St_Kitts'],
  2235. ['America/St_Lucia'],
  2236. ['America/St_Thomas'],
  2237. ['America/St_Vincent'],
  2238. ['America/Swift_Current'],
  2239. ['America/Tegucigalpa'],
  2240. ['America/Thule'],
  2241. ['America/Thunder_Bay'],
  2242. ['America/Tijuana'],
  2243. ['America/Toronto'],
  2244. ['America/Tortola'],
  2245. ['America/Vancouver'],
  2246. ['America/Whitehorse'],
  2247. ['America/Winnipeg'],
  2248. ['America/Yakutat'],
  2249. ['America/Yellowknife'],
  2250. ['Antarctica/Casey'],
  2251. ['Antarctica/Davis'],
  2252. ['Antarctica/DumontDUrville'],
  2253. ['Antarctica/Macquarie'],
  2254. ['Antarctica/Mawson'],
  2255. ['Antarctica/McMurdo'],
  2256. ['Antarctica/Palmer'],
  2257. ['Antarctica/Rothera'],
  2258. ['Antarctica/South_Pole'],
  2259. ['Antarctica/Syowa'],
  2260. ['Antarctica/Vostok'],
  2261. ['Arctic/Longyearbyen'],
  2262. ['Asia/Aden'],
  2263. ['Asia/Almaty'],
  2264. ['Asia/Amman'],
  2265. ['Asia/Anadyr'],
  2266. ['Asia/Aqtau'],
  2267. ['Asia/Aqtobe'],
  2268. ['Asia/Ashgabat'],
  2269. ['Asia/Baghdad'],
  2270. ['Asia/Bahrain'],
  2271. ['Asia/Baku'],
  2272. ['Asia/Bangkok'],
  2273. ['Asia/Beirut'],
  2274. ['Asia/Bishkek'],
  2275. ['Asia/Brunei'],
  2276. ['Asia/Choibalsan'],
  2277. ['Asia/Chongqing'],
  2278. ['Asia/Colombo'],
  2279. ['Asia/Damascus'],
  2280. ['Asia/Dhaka'],
  2281. ['Asia/Dili'],
  2282. ['Asia/Dubai'],
  2283. ['Asia/Dushanbe'],
  2284. ['Asia/Gaza'],
  2285. ['Asia/Harbin'],
  2286. ['Asia/Ho_Chi_Minh'],
  2287. ['Asia/Hong_Kong'],
  2288. ['Asia/Hovd'],
  2289. ['Asia/Irkutsk'],
  2290. ['Asia/Jakarta'],
  2291. ['Asia/Jayapura'],
  2292. ['Asia/Jerusalem'],
  2293. ['Asia/Kabul'],
  2294. ['Asia/Kamchatka'],
  2295. ['Asia/Karachi'],
  2296. ['Asia/Kashgar'],
  2297. ['Asia/Kathmandu'],
  2298. ['Asia/Kolkata'],
  2299. ['Asia/Krasnoyarsk'],
  2300. ['Asia/Kuala_Lumpur'],
  2301. ['Asia/Kuching'],
  2302. ['Asia/Kuwait'],
  2303. ['Asia/Macau'],
  2304. ['Asia/Magadan'],
  2305. ['Asia/Makassar'],
  2306. ['Asia/Manila'],
  2307. ['Asia/Muscat'],
  2308. ['Asia/Nicosia'],
  2309. ['Asia/Novokuznetsk'],
  2310. ['Asia/Novosibirsk'],
  2311. ['Asia/Omsk'],
  2312. ['Asia/Oral'],
  2313. ['Asia/Phnom_Penh'],
  2314. ['Asia/Pontianak'],
  2315. ['Asia/Pyongyang'],
  2316. ['Asia/Qatar'],
  2317. ['Asia/Qyzylorda'],
  2318. ['Asia/Rangoon'],
  2319. ['Asia/Riyadh'],
  2320. ['Asia/Sakhalin'],
  2321. ['Asia/Samarkand'],
  2322. ['Asia/Seoul'],
  2323. ['Asia/Shanghai'],
  2324. ['Asia/Singapore'],
  2325. ['Asia/Taipei'],
  2326. ['Asia/Tashkent'],
  2327. ['Asia/Tbilisi'],
  2328. ['Asia/Tehran'],
  2329. ['Asia/Thimphu'],
  2330. ['Asia/Tokyo'],
  2331. ['Asia/Ulaanbaatar'],
  2332. ['Asia/Urumqi'],
  2333. ['Asia/Vientiane'],
  2334. ['Asia/Vladivostok'],
  2335. ['Asia/Yakutsk'],
  2336. ['Asia/Yekaterinburg'],
  2337. ['Asia/Yerevan'],
  2338. ['Atlantic/Azores'],
  2339. ['Atlantic/Bermuda'],
  2340. ['Atlantic/Canary'],
  2341. ['Atlantic/Cape_Verde'],
  2342. ['Atlantic/Faroe'],
  2343. ['Atlantic/Madeira'],
  2344. ['Atlantic/Reykjavik'],
  2345. ['Atlantic/South_Georgia'],
  2346. ['Atlantic/St_Helena'],
  2347. ['Atlantic/Stanley'],
  2348. ['Australia/Adelaide'],
  2349. ['Australia/Brisbane'],
  2350. ['Australia/Broken_Hill'],
  2351. ['Australia/Currie'],
  2352. ['Australia/Darwin'],
  2353. ['Australia/Eucla'],
  2354. ['Australia/Hobart'],
  2355. ['Australia/Lindeman'],
  2356. ['Australia/Lord_Howe'],
  2357. ['Australia/Melbourne'],
  2358. ['Australia/Perth'],
  2359. ['Australia/Sydney'],
  2360. ['Europe/Amsterdam'],
  2361. ['Europe/Andorra'],
  2362. ['Europe/Athens'],
  2363. ['Europe/Belgrade'],
  2364. ['Europe/Berlin'],
  2365. ['Europe/Bratislava'],
  2366. ['Europe/Brussels'],
  2367. ['Europe/Bucharest'],
  2368. ['Europe/Budapest'],
  2369. ['Europe/Chisinau'],
  2370. ['Europe/Copenhagen'],
  2371. ['Europe/Dublin'],
  2372. ['Europe/Gibraltar'],
  2373. ['Europe/Guernsey'],
  2374. ['Europe/Helsinki'],
  2375. ['Europe/Isle_of_Man'],
  2376. ['Europe/Istanbul'],
  2377. ['Europe/Jersey'],
  2378. ['Europe/Kaliningrad'],
  2379. ['Europe/Kiev'],
  2380. ['Europe/Lisbon'],
  2381. ['Europe/Ljubljana'],
  2382. ['Europe/London'],
  2383. ['Europe/Luxembourg'],
  2384. ['Europe/Madrid'],
  2385. ['Europe/Malta'],
  2386. ['Europe/Mariehamn'],
  2387. ['Europe/Minsk'],
  2388. ['Europe/Monaco'],
  2389. ['Europe/Moscow'],
  2390. ['Europe/Oslo'],
  2391. ['Europe/Paris'],
  2392. ['Europe/Podgorica'],
  2393. ['Europe/Prague'],
  2394. ['Europe/Riga'],
  2395. ['Europe/Rome'],
  2396. ['Europe/Samara'],
  2397. ['Europe/San_Marino'],
  2398. ['Europe/Sarajevo'],
  2399. ['Europe/Simferopol'],
  2400. ['Europe/Skopje'],
  2401. ['Europe/Sofia'],
  2402. ['Europe/Stockholm'],
  2403. ['Europe/Tallinn'],
  2404. ['Europe/Tirane'],
  2405. ['Europe/Uzhgorod'],
  2406. ['Europe/Vaduz'],
  2407. ['Europe/Vatican'],
  2408. ['Europe/Vienna'],
  2409. ['Europe/Vilnius'],
  2410. ['Europe/Volgograd'],
  2411. ['Europe/Warsaw'],
  2412. ['Europe/Zagreb'],
  2413. ['Europe/Zaporozhye'],
  2414. ['Europe/Zurich'],
  2415. ['Indian/Antananarivo'],
  2416. ['Indian/Chagos'],
  2417. ['Indian/Christmas'],
  2418. ['Indian/Cocos'],
  2419. ['Indian/Comoro'],
  2420. ['Indian/Kerguelen'],
  2421. ['Indian/Mahe'],
  2422. ['Indian/Maldives'],
  2423. ['Indian/Mauritius'],
  2424. ['Indian/Mayotte'],
  2425. ['Indian/Reunion'],
  2426. ['Pacific/Apia'],
  2427. ['Pacific/Auckland'],
  2428. ['Pacific/Chatham'],
  2429. ['Pacific/Chuuk'],
  2430. ['Pacific/Easter'],
  2431. ['Pacific/Efate'],
  2432. ['Pacific/Enderbury'],
  2433. ['Pacific/Fakaofo'],
  2434. ['Pacific/Fiji'],
  2435. ['Pacific/Funafuti'],
  2436. ['Pacific/Galapagos'],
  2437. ['Pacific/Gambier'],
  2438. ['Pacific/Guadalcanal'],
  2439. ['Pacific/Guam'],
  2440. ['Pacific/Honolulu'],
  2441. ['Pacific/Johnston'],
  2442. ['Pacific/Kiritimati'],
  2443. ['Pacific/Kosrae'],
  2444. ['Pacific/Kwajalein'],
  2445. ['Pacific/Majuro'],
  2446. ['Pacific/Marquesas'],
  2447. ['Pacific/Midway'],
  2448. ['Pacific/Nauru'],
  2449. ['Pacific/Niue'],
  2450. ['Pacific/Norfolk'],
  2451. ['Pacific/Noumea'],
  2452. ['Pacific/Pago_Pago'],
  2453. ['Pacific/Palau'],
  2454. ['Pacific/Pitcairn'],
  2455. ['Pacific/Pohnpei'],
  2456. ['Pacific/Port_Moresby'],
  2457. ['Pacific/Rarotonga'],
  2458. ['Pacific/Saipan'],
  2459. ['Pacific/Tahiti'],
  2460. ['Pacific/Tarawa'],
  2461. ['Pacific/Tongatapu'],
  2462. ['Pacific/Wake'],
  2463. ['Pacific/Wallis']
  2464. ]
  2465. },
  2466.  
  2467. constructor: function(config) {
  2468. var me = this;
  2469.  
  2470. config = config || {};
  2471.  
  2472. Ext.regModel('Timezone', {
  2473. fields: ['zone'],
  2474. proxy: {
  2475. type: 'memory',
  2476. reader: 'array'
  2477. }
  2478. });
  2479.  
  2480. Ext.apply(config, {
  2481. model: 'Timezone',
  2482. data: PVE.data.TimezoneStore.timezones
  2483. });
  2484.  
  2485. me.callParent([config]);
  2486. }
  2487. });/* A reader to store a single JSON Object (hash) into a storage.
  2488. * Also accepts an array containing a single hash.
  2489. * So it can read:
  2490. *
  2491. * example1: { data: "xyz" }
  2492. * example2: [ { data: "xyz" } ]
  2493. */
  2494.  
  2495. Ext.define('PVE.data.reader.JsonObject', {
  2496. extend: 'Ext.data.reader.Json',
  2497. alias : 'reader.jsonobject',
  2498.  
  2499. root: 'data',
  2500.  
  2501. constructor: function(config) {
  2502. var me = this;
  2503.  
  2504. Ext.apply(me, config || {});
  2505.  
  2506. me.callParent([config]);
  2507. },
  2508.  
  2509. getResponseData: function(response) {
  2510. var me = this;
  2511.  
  2512. var data = [];
  2513. try {
  2514. var result = Ext.decode(response.responseText);
  2515. var root = me.getRoot(result);
  2516. var org_root = root;
  2517.  
  2518. if (Ext.isArray(org_root)) {
  2519. if (org_root.length == 1) {
  2520. root = org_root[0];
  2521. } else {
  2522. root = {};
  2523. }
  2524. }
  2525.  
  2526. if (me.rows) {
  2527. Ext.Object.each(me.rows, function(key, rowdef) {
  2528. if (Ext.isDefined(root[key])) {
  2529. data.push({key: key, value: root[key]});
  2530. } else if (Ext.isDefined(rowdef.defaultValue)) {
  2531. data.push({key: key, value: rowdef.defaultValue});
  2532. } else if (rowdef.required) {
  2533. data.push({key: key, value: undefined});
  2534. }
  2535. });
  2536. } else {
  2537. Ext.Object.each(root, function(key, value) {
  2538. data.push({key: key, value: value });
  2539. });
  2540. }
  2541. }
  2542. catch (ex) {
  2543. Ext.Error.raise({
  2544. response: response,
  2545. json: response.responseText,
  2546. parseError: ex,
  2547. msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
  2548. });
  2549. }
  2550.  
  2551. return data;
  2552. }
  2553. });
  2554.  
  2555. Ext.define('PVE.RestProxy', {
  2556. extend: 'Ext.data.RestProxy',
  2557. alias : 'proxy.pve',
  2558.  
  2559. constructor: function(config) {
  2560. var me = this;
  2561.  
  2562. config = config || {};
  2563.  
  2564. Ext.applyIf(config, {
  2565. pageParam : null,
  2566. startParam: null,
  2567. limitParam: null,
  2568. groupParam: null,
  2569. sortParam: null,
  2570. filterParam: null,
  2571. noCache : false,
  2572. reader: {
  2573. type: 'json',
  2574. root: config.root || 'data'
  2575. },
  2576. afterRequest: function(request, success) {
  2577. me.fireEvent('afterload', me, request, success);
  2578. return;
  2579. }
  2580. });
  2581.  
  2582. me.callParent([config]);
  2583. }
  2584.  
  2585. }, function() {
  2586.  
  2587. Ext.define('pve-domains', {
  2588. extend: "Ext.data.Model",
  2589. fields: [ 'realm', 'type', 'comment', 'default',
  2590. {
  2591. name: 'descr',
  2592. // Note: We use this in the RealmComboBox.js
  2593. // (see Bug #125)
  2594. convert: function(value, record) {
  2595. var info = record.data;
  2596. var text;
  2597.  
  2598. if (value) {
  2599. return value;
  2600. }
  2601. // return realm if there is no comment
  2602. return info.comment || info.realm;
  2603. }
  2604. }
  2605. ],
  2606. proxy: {
  2607. type: 'pve',
  2608. url: "/api2/json/access/domains"
  2609. }
  2610. });
  2611.  
  2612. Ext.define('KeyValue', {
  2613. extend: "Ext.data.Model",
  2614. fields: [ 'key', 'value' ],
  2615. idProperty: 'key'
  2616. });
  2617.  
  2618. Ext.define('pve-string-list', {
  2619. extend: 'Ext.data.Model',
  2620. fields: [ 'n', 't' ],
  2621. idProperty: 'n'
  2622. });
  2623.  
  2624. Ext.define('pve-tasks', {
  2625. extend: 'Ext.data.Model',
  2626. fields: [
  2627. { name: 'starttime', type : 'date', dateFormat: 'timestamp' },
  2628. { name: 'endtime', type : 'date', dateFormat: 'timestamp' },
  2629. { name: 'pid', type: 'int' },
  2630. 'node', 'upid', 'user', 'status', 'type', 'id'
  2631. ],
  2632. idProperty: 'upid'
  2633. });
  2634.  
  2635. Ext.define('pve-cluster-log', {
  2636. extend: 'Ext.data.Model',
  2637. fields: [
  2638. { name: 'uid' , type: 'int' },
  2639. { name: 'time', type : 'date', dateFormat: 'timestamp' },
  2640. { name: 'pri', type: 'int' },
  2641. { name: 'pid', type: 'int' },
  2642. 'node', 'user', 'tag', 'msg',
  2643. {
  2644. name: 'id',
  2645. convert: function(value, record) {
  2646. var info = record.data;
  2647. var text;
  2648.  
  2649. if (value) {
  2650. return value;
  2651. }
  2652. // compute unique ID
  2653. return info.uid + ':' + info.node;
  2654. }
  2655. }
  2656. ],
  2657. idProperty: 'id'
  2658. });
  2659. });
  2660. // Serialize load (avoid too many parallel connections)
  2661. Ext.define('PVE.data.UpdateQueue', {
  2662. singleton: true,
  2663.  
  2664. constructor : function(){
  2665. var me = this;
  2666.  
  2667. var queue = [];
  2668. var queue_idx = {};
  2669.  
  2670. var idle = true;
  2671.  
  2672. var start_update = function() {
  2673. if (!idle) {
  2674. return;
  2675. }
  2676.  
  2677. var store = queue.shift();
  2678. if (!store) {
  2679. return;
  2680. }
  2681.  
  2682. queue_idx[store.storeid] = null;
  2683.  
  2684. idle = false;
  2685. store.load({
  2686. callback: function(records, operation, success) {
  2687. idle = true;
  2688. start_update();
  2689. }
  2690. });
  2691. };
  2692.  
  2693. Ext.apply(me, {
  2694. queue: function(store) {
  2695. if (!store.storeid) {
  2696. throw "unable to queue store without storeid";
  2697. }
  2698. if (!queue_idx[store.storeid]) {
  2699. queue_idx[store.storeid] = store;
  2700. queue.push(store);
  2701. }
  2702. start_update();
  2703. }
  2704. });
  2705. }
  2706. });
  2707. Ext.define('PVE.data.UpdateStore', {
  2708. extend: 'Ext.data.Store',
  2709.  
  2710. constructor: function(config) {
  2711. var me = this;
  2712.  
  2713. config = config || {};
  2714.  
  2715. if (!config.interval) {
  2716. config.interval = 3000;
  2717. }
  2718.  
  2719. if (!config.storeid) {
  2720. throw "no storeid specified";
  2721. }
  2722.  
  2723. var load_task = new Ext.util.DelayedTask();
  2724.  
  2725. var run_load_task = function() {
  2726. if (PVE.Utils.authOK()) {
  2727. PVE.data.UpdateQueue.queue(me);
  2728. load_task.delay(config.interval, run_load_task);
  2729. } else {
  2730. load_task.delay(200, run_load_task);
  2731. }
  2732. };
  2733.  
  2734. Ext.apply(config, {
  2735. startUpdate: function() {
  2736. run_load_task();
  2737. },
  2738. stopUpdate: function() {
  2739. load_task.cancel();
  2740. }
  2741. });
  2742.  
  2743. me.callParent([config]);
  2744.  
  2745. me.on('destroy', function() {
  2746. load_task.cancel();
  2747. });
  2748. }
  2749. });
  2750. /* Config properties:
  2751. * rstore: A storage to track changes
  2752. * Only works if rstore has a model and use 'idProperty'
  2753. */
  2754. Ext.define('PVE.data.DiffStore', {
  2755. extend: 'Ext.data.Store',
  2756.  
  2757. constructor: function(config) {
  2758. var me = this;
  2759.  
  2760. config = config || {};
  2761.  
  2762. if (!config.rstore) {
  2763. throw "no rstore specified";
  2764. }
  2765.  
  2766. if (!config.rstore.model) {
  2767. throw "no rstore model specified";
  2768. }
  2769.  
  2770. var rstore = config.rstore;
  2771.  
  2772. Ext.apply(config, {
  2773. model: rstore.model,
  2774. proxy: { type: 'memory' }
  2775. });
  2776.  
  2777. me.callParent([config]);
  2778.  
  2779. var first_load = true;
  2780.  
  2781. var cond_add_item = function(data, id) {
  2782. var olditem = me.getById(id);
  2783. if (olditem) {
  2784. olditem.beginEdit();
  2785. me.model.prototype.fields.eachKey(function(field) {
  2786. if (olditem.data[field] !== data[field]) {
  2787. olditem.set(field, data[field]);
  2788. }
  2789. });
  2790. olditem.endEdit(true);
  2791. olditem.commit();
  2792. } else {
  2793. var newrec = Ext.ModelMgr.create(data, me.model, id);
  2794. var pos = (me.appendAtStart && !first_load) ? 0 : me.data.length;
  2795. me.insert(pos, newrec);
  2796. }
  2797. };
  2798.  
  2799. me.mon(rstore, 'load', function(s, records, success) {
  2800.  
  2801. if (!success) {
  2802. return;
  2803. }
  2804.  
  2805. me.suspendEvents();
  2806.  
  2807. // remove vanished items
  2808. (me.snapshot || me.data).each(function(olditem) {
  2809. var item = rstore.getById(olditem.getId());
  2810. if (!item) {
  2811. me.remove(olditem);
  2812. }
  2813. });
  2814.  
  2815. rstore.each(function(item) {
  2816. cond_add_item(item.data, item.getId());
  2817. });
  2818.  
  2819. me.filter();
  2820.  
  2821. first_load = false;
  2822.  
  2823. me.resumeEvents();
  2824. me.fireEvent('datachanged', me);
  2825. });
  2826. }
  2827. });
  2828. Ext.define('PVE.data.ObjectStore', {
  2829. extend: 'PVE.data.UpdateStore',
  2830.  
  2831. constructor: function(config) {
  2832. var me = this;
  2833.  
  2834. config = config || {};
  2835.  
  2836. if (!config.storeid) {
  2837. config.storeid = 'pve-store-' + (++Ext.idSeed);
  2838. }
  2839.  
  2840. Ext.applyIf(config, {
  2841. model: 'KeyValue',
  2842. proxy: {
  2843. type: 'pve',
  2844. url: config.url,
  2845. extraParams: config.extraParams,
  2846. reader: {
  2847. type: 'jsonobject',
  2848. rows: config.rows
  2849. }
  2850. }
  2851. });
  2852.  
  2853. me.callParent([config]);
  2854. }
  2855. });
  2856. Ext.define('PVE.data.ResourceStore', {
  2857. extend: 'PVE.data.UpdateStore',
  2858. singleton: true,
  2859.  
  2860. findNextVMID: function() {
  2861. var me = this, i;
  2862.  
  2863. for (i = 100; i < 10000; i++) {
  2864. if (me.findExact('vmid', i) < 0) {
  2865. return i;
  2866. }
  2867. }
  2868. },
  2869.  
  2870. findVMID: function(vmid) {
  2871. var me = this, i;
  2872.  
  2873. return (me.findExact('vmid', parseInt(vmid, 10)) >= 0);
  2874. },
  2875.  
  2876.  
  2877. constructor: function(config) {
  2878. // fixme: how to avoid those warnings
  2879. /*jslint confusion: true */
  2880.  
  2881. var me = this;
  2882.  
  2883. config = config || {};
  2884.  
  2885. var field_defaults = {
  2886. type: {
  2887. header: gettext('Type'),
  2888. type: 'text',
  2889. renderer: PVE.Utils.render_resource_type,
  2890. sortable: true,
  2891. hideable: false,
  2892. width: 80
  2893. },
  2894. id: {
  2895. header: 'ID',
  2896. type: 'text',
  2897. hidden: true,
  2898. sortable: true,
  2899. width: 80
  2900. },
  2901. running: {
  2902. header: gettext('Online'),
  2903. type: 'boolean',
  2904. hidden: true,
  2905. convert: function(value, record) {
  2906. var info = record.data;
  2907. if (info.type === 'qemu' || info.type === 'openvz' || info.type === 'node') {
  2908. return (Ext.isNumeric(info.uptime) && (info.uptime > 0));
  2909. } else {
  2910. return false;
  2911. }
  2912. }
  2913. },
  2914. text: {
  2915. header: gettext('Description'),
  2916. type: 'text',
  2917. sortable: true,
  2918. width: 200,
  2919. convert: function(value, record) {
  2920. var info = record.data;
  2921. var text;
  2922.  
  2923. if (value) {
  2924. return value;
  2925. }
  2926.  
  2927. if (info.type === 'node') {
  2928. text = info.node;
  2929. } else if (info.type === 'pool') {
  2930. text = info.pool;
  2931. } else if (info.type === 'storage') {
  2932. text = info.storage + ' (' + info.node + ')';
  2933. } else if (info.type === 'qemu' || info.type === 'openvz') {
  2934. text = String(info.vmid);
  2935. if (info.name) {
  2936. text += " (" + info.name + ')';
  2937. }
  2938. } else {
  2939. text = info.id;
  2940. }
  2941. return text;
  2942. }
  2943. },
  2944. vmid: {
  2945. header: 'VMID',
  2946. type: 'integer',
  2947. hidden: true,
  2948. sortable: true,
  2949. width: 80
  2950. },
  2951. name: {
  2952. header: gettext('Name'),
  2953. hidden: true,
  2954. sortable: true,
  2955. type: 'text'
  2956. },
  2957. disk: {
  2958. header: gettext('Disk usage'),
  2959. type: 'integer',
  2960. renderer: PVE.Utils.render_disk_usage,
  2961. sortable: true,
  2962. width: 100
  2963. },
  2964. maxdisk: {
  2965. header: gettext('Disk size'),
  2966. type: 'integer',
  2967. renderer: PVE.Utils.render_size,
  2968. sortable: true,
  2969. hidden: true,
  2970. width: 100
  2971. },
  2972. mem: {
  2973. header: gettext('Memory usage'),
  2974. type: 'integer',
  2975. renderer: PVE.Utils.render_mem_usage,
  2976. sortable: true,
  2977. width: 100
  2978. },
  2979. maxmem: {
  2980. header: gettext('Memory size'),
  2981. type: 'integer',
  2982. renderer: PVE.Utils.render_size,
  2983. hidden: true,
  2984. sortable: true,
  2985. width: 100
  2986. },
  2987. cpu: {
  2988. header: gettext('CPU usage'),
  2989. type: 'float',
  2990. renderer: PVE.Utils.render_cpu,
  2991. sortable: true,
  2992. width: 100
  2993. },
  2994. maxcpu: {
  2995. header: 'maxcpu',
  2996. type: 'integer',
  2997. hidden: true,
  2998. sortable: true,
  2999. width: 60
  3000. },
  3001. uptime: {
  3002. header: gettext('Uptime'),
  3003. type: 'integer',
  3004. renderer: PVE.Utils.render_uptime,
  3005. sortable: true,
  3006. width: 110
  3007. },
  3008. node: {
  3009. header: gettext('Node'),
  3010. type: 'text',
  3011. hidden: true,
  3012. sortable: true,
  3013. width: 110
  3014. },
  3015. storage: {
  3016. header: gettext('Storage'),
  3017. type: 'text',
  3018. hidden: true,
  3019. sortable: true,
  3020. width: 110
  3021. },
  3022. pool: {
  3023. header: gettext('Pool'),
  3024. type: 'text',
  3025. hidden: true,
  3026. sortable: true,
  3027. width: 110
  3028. }
  3029. };
  3030.  
  3031. var fields = [];
  3032. var fieldNames = [];
  3033. Ext.Object.each(field_defaults, function(key, value) {
  3034. if (!Ext.isDefined(value.convert)) {
  3035. fields.push({name: key, type: value.type});
  3036. fieldNames.push(key);
  3037. } else if (key === 'text' || key === 'running') {
  3038. fields.push({name: key, type: value.type, convert: value.convert});
  3039. fieldNames.push(key);
  3040. }
  3041. });
  3042.  
  3043. Ext.define('PVEResources', {
  3044. extend: "Ext.data.Model",
  3045. fields: fields,
  3046. proxy: {
  3047. type: 'pve',
  3048. url: '/api2/json/cluster/resources'
  3049. }
  3050. });
  3051.  
  3052. Ext.define('PVETree', {
  3053. extend: "Ext.data.Model",
  3054. fields: fields,
  3055. proxy: { type: 'memory' }
  3056. });
  3057.  
  3058. Ext.apply(config, {
  3059. storeid: 'PVEResources',
  3060. model: 'PVEResources',
  3061. autoDestory: false,
  3062. defaultColums: function() {
  3063. var res = [];
  3064. Ext.Object.each(field_defaults, function(field, info) {
  3065. var fi = Ext.apply({ dataIndex: field }, info);
  3066. res.push(fi);
  3067. });
  3068. return res;
  3069. },
  3070. fieldNames: fieldNames
  3071. });
  3072.  
  3073. me.callParent([config]);
  3074. }
  3075. });
  3076. Ext.define('PVE.form.Checkbox', {
  3077. extend: 'Ext.form.field.Checkbox',
  3078. alias: ['widget.pvecheckbox'],
  3079.  
  3080. defaultValue: undefined,
  3081.  
  3082. deleteDefaultValue: false,
  3083. deleteEmpty: false,
  3084.  
  3085. inputValue: '1',
  3086.  
  3087. height: 22, // hack: set same height as text fields
  3088.  
  3089. getSubmitData: function() {
  3090. var me = this,
  3091. data = null,
  3092. val;
  3093. if (!me.disabled && me.submitValue) {
  3094. val = me.getSubmitValue();
  3095. if (val !== null) {
  3096. data = {};
  3097. if ((val == me.defaultValue) && me.deleteDefaultValue) {
  3098. data['delete'] = me.getName();
  3099. } else {
  3100. data[me.getName()] = val;
  3101. }
  3102. } else if (me.deleteEmpty) {
  3103. data = {};
  3104. data['delete'] = me.getName();
  3105. }
  3106. }
  3107. return data;
  3108. },
  3109.  
  3110. // also accept integer 1 as true
  3111. setRawValue: function(value) {
  3112. var me = this;
  3113.  
  3114. if (value === 1) {
  3115. me.callParent([true]);
  3116. } else {
  3117. me.callParent([value]);
  3118. }
  3119. }
  3120.  
  3121. });Ext.define('PVE.form.Textfield', {
  3122. extend: 'Ext.form.field.Text',
  3123. alias: ['widget.pvetextfield'],
  3124.  
  3125. skipEmptyText: true,
  3126.  
  3127. deleteEmpty: false,
  3128.  
  3129. getSubmitData: function() {
  3130. var me = this,
  3131. data = null,
  3132. val;
  3133. if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  3134. val = me.getSubmitValue();
  3135. if (val !== null) {
  3136. data = {};
  3137. data[me.getName()] = val;
  3138. } else if (me.deleteEmpty) {
  3139. data = {};
  3140. data['delete'] = me.getName();
  3141. }
  3142. }
  3143. return data;
  3144. },
  3145.  
  3146. getSubmitValue: function() {
  3147. var me = this;
  3148.  
  3149. var value = this.processRawValue(this.getRawValue());
  3150. if (value !== '') {
  3151. return value;
  3152. }
  3153.  
  3154. return me.skipEmptyText ? null: value;
  3155. }
  3156. });Ext.define('PVE.form.RRDTypeSelector', {
  3157. extend: 'Ext.form.field.ComboBox',
  3158. alias: ['widget.pveRRDTypeSelector'],
  3159.  
  3160. initComponent: function() {
  3161. var me = this;
  3162.  
  3163. var store = new Ext.data.ArrayStore({
  3164. fields: [ 'id', 'timeframe', 'cf', 'text' ],
  3165. data : [
  3166. [ 'hour', 'hour', 'AVERAGE', "Hour (average)" ],
  3167. [ 'hourmax', 'hour', 'MAX', "Hour (max)" ],
  3168. [ 'day', 'day', 'AVERAGE', "Day (average)" ],
  3169. [ 'daymax', 'day', 'MAX', "Day (max)" ],
  3170. [ 'week', 'week', 'AVERAGE', "Week (average)" ],
  3171. [ 'weekmax', 'week', 'MAX', "Week (max)" ],
  3172. [ 'month', 'month', 'AVERAGE', "Month (average)" ],
  3173. [ 'monthmax', 'month', 'MAX', "Month (max)" ],
  3174. [ 'year', 'year', 'AVERAGE', "Year (average)" ],
  3175. [ 'yearmax', 'year', 'MAX', "Year (max)" ]
  3176. ]
  3177. });
  3178.  
  3179. Ext.apply(me, {
  3180. store: store,
  3181. displayField: 'text',
  3182. valueField: 'id',
  3183. editable: false,
  3184. queryMode: 'local',
  3185. value: 'hour',
  3186. getState: function() {
  3187. var ind = store.findExact('id', me.getValue());
  3188. var rec = store.getAt(ind);
  3189. if (!rec) {
  3190. return;
  3191. }
  3192. return {
  3193. id: rec.data.id,
  3194. timeframe: rec.data.timeframe,
  3195. cf: rec.data.cf
  3196. };
  3197. },
  3198. applyState : function(state) {
  3199. if (state && state.id) {
  3200. me.setValue(state.id);
  3201. }
  3202. },
  3203. stateEvents: [ 'select' ],
  3204. stateful: true,
  3205. id: 'pveRRDTypeSelection'
  3206. });
  3207.  
  3208. me.callParent();
  3209.  
  3210. var statechange = function(sp, key, value) {
  3211. if (key === me.id) {
  3212. me.applyState(value);
  3213. }
  3214. };
  3215.  
  3216. var sp = Ext.state.Manager.getProvider();
  3217. me.mon(sp, 'statechange', statechange, me);
  3218. }
  3219. });
  3220.  
  3221. Ext.define('PVE.form.ComboGrid', {
  3222. extend: 'Ext.form.field.ComboBox',
  3223. alias: ['widget.PVE.form.ComboGrid'],
  3224.  
  3225. computeHeight: function() {
  3226. var me = this;
  3227. var lh = PVE.Utils.gridLineHeigh();
  3228. var count = me.store.getCount();
  3229. return (count > 10) ? 10*lh : 26+count*lh;
  3230. },
  3231.  
  3232. // hack: allow to select empty value
  3233. // seems extjs does not allow that when 'editable == false'
  3234. onKeyUp: function(e, t) {
  3235. var me = this;
  3236. var key = e.getKey();
  3237.  
  3238. if (!me.editable && me.allowBlank && !me.multiSelect &&
  3239. (key == e.BACKSPACE || key == e.DELETE)) {
  3240. me.setValue('');
  3241. }
  3242.  
  3243. me.callParent(arguments);
  3244. },
  3245.  
  3246. // copied from ComboBox
  3247. createPicker: function() {
  3248. var me = this,
  3249. picker,
  3250. menuCls = Ext.baseCSSPrefix + 'menu',
  3251.  
  3252. opts = Ext.apply({
  3253. selModel: {
  3254. mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
  3255. },
  3256. floating: true,
  3257. hidden: true,
  3258. ownerCt: me.ownerCt,
  3259. cls: me.el.up('.' + menuCls) ? menuCls : '',
  3260. store: me.store,
  3261. displayField: me.displayField,
  3262. focusOnToFront: false,
  3263. height: me.computeHeight(),
  3264. pageSize: me.pageSize
  3265. }, me.listConfig, me.defaultListConfig);
  3266.  
  3267. // NOTE: we simply use a grid panel
  3268. //picker = me.picker = Ext.create('Ext.view.BoundList', opts);
  3269. picker = me.picker = Ext.create('Ext.grid.Panel', opts);
  3270.  
  3271. // pass getNode() to the view
  3272. picker.getNode = function() {
  3273. picker.getView().getNode(arguments);
  3274. };
  3275.  
  3276. me.mon(picker, {
  3277. itemclick: me.onItemClick,
  3278. refresh: me.onListRefresh,
  3279. show: function() {
  3280. picker.setHeight(me.computeHeight());
  3281. me.syncSelection();
  3282. },
  3283. scope: me
  3284. });
  3285.  
  3286. me.mon(picker.getSelectionModel(), 'selectionchange', me.onListSelectionChange, me);
  3287.  
  3288. return picker;
  3289. },
  3290.  
  3291. initComponent: function() {
  3292. var me = this;
  3293.  
  3294. Ext.apply(me, {
  3295. queryMode: 'local',
  3296. editable: false,
  3297. matchFieldWidth: false
  3298. });
  3299.  
  3300. Ext.applyIf(me, { value: ''}); // hack: avoid ExtJS validate() bug
  3301.  
  3302. Ext.applyIf(me.listConfig, { width: 400 });
  3303.  
  3304. me.callParent();
  3305.  
  3306. me.store.on('beforeload', function() {
  3307. if (!me.isDisabled()) {
  3308. me.setDisabled(true);
  3309. me.enableAfterLoad = true;
  3310. }
  3311. });
  3312.  
  3313. // hack: autoSelect does not work
  3314. me.store.on('load', function(store, r, success, o) {
  3315. if (success) {
  3316. me.clearInvalid();
  3317.  
  3318. if (me.enableAfterLoad) {
  3319. delete me.enableAfterLoad;
  3320. me.setDisabled(false);
  3321. }
  3322.  
  3323. var def = me.getValue();
  3324. if (def) {
  3325. me.setValue(def, true); // sync with grid
  3326. }
  3327. var found = false;
  3328. if (def) {
  3329. if (Ext.isArray(def)) {
  3330. Ext.Array.each(def, function(v) {
  3331. if (store.findRecord(me.valueField, v)) {
  3332. found = true;
  3333. return false; // break
  3334. }
  3335. });
  3336. } else {
  3337. found = store.findRecord(me.valueField, def);
  3338. }
  3339. }
  3340.  
  3341. if (!found) {
  3342. var rec = me.store.first();
  3343. if (me.autoSelect && rec && rec.data) {
  3344. def = rec.data[me.valueField];
  3345. me.setValue(def, true);
  3346. } else {
  3347. me.setValue('', true);
  3348. }
  3349. }
  3350. }
  3351. });
  3352. }
  3353. });
  3354. Ext.define('PVE.form.KVComboBox', {
  3355. extend: 'Ext.form.field.ComboBox',
  3356. alias: 'widget.pveKVComboBox',
  3357.  
  3358. deleteEmpty: true,
  3359.  
  3360. getSubmitData: function() {
  3361. var me = this,
  3362. data = null,
  3363. val;
  3364. if (!me.disabled && me.submitValue) {
  3365. val = me.getSubmitValue();
  3366. if (val !== null && val !== '') {
  3367. data = {};
  3368. data[me.getName()] = val;
  3369. } else if (me.deleteEmpty) {
  3370. data = {};
  3371. data['delete'] = me.getName();
  3372. }
  3373. }
  3374. return data;
  3375. },
  3376.  
  3377. initComponent: function() {
  3378. var me = this;
  3379.  
  3380. me.store = Ext.create('Ext.data.ArrayStore', {
  3381. model: 'KeyValue',
  3382. data : me.data
  3383. });
  3384.  
  3385. Ext.apply(me, {
  3386. displayField: 'value',
  3387. valueField: 'key',
  3388. queryMode: 'local',
  3389. editable: false
  3390. });
  3391.  
  3392. me.callParent();
  3393. }
  3394. });
  3395. // boolean type including 'Default' (delete property from file)
  3396. Ext.define('PVE.form.Boolean', {
  3397. extend: 'PVE.form.KVComboBox',
  3398. alias: ['widget.booleanfield'],
  3399.  
  3400. initComponent: function() {
  3401. var me = this;
  3402.  
  3403. me.data = [
  3404. ['', gettext('Default')],
  3405. [1, gettext('Yes')],
  3406. [0, gettext('No')]
  3407. ];
  3408.  
  3409. me.callParent();
  3410. }
  3411. });
  3412. Ext.define('PVE.form.CompressionSelector', {
  3413. extend: 'PVE.form.KVComboBox',
  3414. alias: ['widget.pveCompressionSelector'],
  3415.  
  3416. initComponent: function() {
  3417. var me = this;
  3418.  
  3419. me.data = [
  3420. ['', gettext('none')],
  3421. ['lzo', 'LZO (' + gettext('fast') + ')'],
  3422. ['gzip', 'GZIP (' + gettext('good') + ')']
  3423. ];
  3424.  
  3425. me.callParent();
  3426. }
  3427. });
  3428. Ext.define('PVE.form.PoolSelector', {
  3429. extend: 'PVE.form.ComboGrid',
  3430. alias: ['widget.pvePoolSelector'],
  3431.  
  3432. allowBlank: false,
  3433.  
  3434. initComponent: function() {
  3435. var me = this;
  3436.  
  3437. var store = new Ext.data.Store({
  3438. model: 'pve-pools'
  3439. });
  3440.  
  3441. Ext.apply(me, {
  3442. store: store,
  3443. autoSelect: false,
  3444. valueField: 'poolid',
  3445. displayField: 'poolid',
  3446. listConfig: {
  3447. columns: [
  3448. {
  3449. header: gettext('Pool'),
  3450. sortable: true,
  3451. dataIndex: 'poolid',
  3452. flex: 1
  3453. },
  3454. {
  3455. id: 'comment',
  3456. header: 'Comment',
  3457. sortable: false,
  3458. dataIndex: 'comment',
  3459. flex: 1
  3460. }
  3461. ]
  3462. }
  3463. });
  3464.  
  3465. me.callParent();
  3466.  
  3467. store.load();
  3468. }
  3469.  
  3470. }, function() {
  3471.  
  3472. Ext.define('pve-pools', {
  3473. extend: 'Ext.data.Model',
  3474. fields: [ 'poolid', 'comment' ],
  3475. proxy: {
  3476. type: 'pve',
  3477. url: "/api2/json/pools"
  3478. },
  3479. idProperty: 'poolid'
  3480. });
  3481.  
  3482. });
  3483. Ext.define('PVE.form.GroupSelector', {
  3484. extend: 'PVE.form.ComboGrid',
  3485. alias: ['widget.pveGroupSelector'],
  3486.  
  3487. allowBlank: false,
  3488.  
  3489. initComponent: function() {
  3490. var me = this;
  3491.  
  3492. var store = new Ext.data.Store({
  3493. model: 'pve-groups'
  3494. });
  3495.  
  3496. Ext.apply(me, {
  3497. store: store,
  3498. autoSelect: false,
  3499. valueField: 'groupid',
  3500. displayField: 'groupid',
  3501. listConfig: {
  3502. columns: [
  3503. {
  3504. header: gettext('Group'),
  3505. sortable: true,
  3506. dataIndex: 'groupid',
  3507. flex: 1
  3508. },
  3509. {
  3510. id: 'comment',
  3511. header: 'Comment',
  3512. sortable: false,
  3513. dataIndex: 'comment',
  3514. flex: 1
  3515. }
  3516. ]
  3517. }
  3518. });
  3519.  
  3520. me.callParent();
  3521.  
  3522. store.load();
  3523. }
  3524.  
  3525. }, function() {
  3526.  
  3527. Ext.define('pve-groups', {
  3528. extend: 'Ext.data.Model',
  3529. fields: [ 'groupid', 'comment' ],
  3530. proxy: {
  3531. type: 'pve',
  3532. url: "/api2/json/access/groups"
  3533. },
  3534. idProperty: 'groupid'
  3535. });
  3536.  
  3537. });
  3538. Ext.define('PVE.form.UserSelector', {
  3539. extend: 'PVE.form.ComboGrid',
  3540. alias: ['widget.pveUserSelector'],
  3541.  
  3542. initComponent: function() {
  3543. var me = this;
  3544.  
  3545. var store = new Ext.data.Store({
  3546. model: 'pve-users'
  3547. });
  3548.  
  3549. var render_full_name = function(firstname, metaData, record) {
  3550.  
  3551. var first = firstname || '';
  3552. var last = record.data.lastname || '';
  3553. return first + " " + last;
  3554. };
  3555.  
  3556. Ext.apply(me, {
  3557. store: store,
  3558. allowBlank: false,
  3559. autoSelect: false,
  3560. valueField: 'userid',
  3561. displayField: 'userid',
  3562. listConfig: {
  3563. columns: [
  3564. {
  3565. header: gettext('User'),
  3566. sortable: true,
  3567. dataIndex: 'userid',
  3568. flex: 1
  3569. },
  3570. {
  3571. header: 'Name',
  3572. sortable: true,
  3573. renderer: render_full_name,
  3574. dataIndex: 'firstname',
  3575. flex: 1
  3576. },
  3577. {
  3578. id: 'comment',
  3579. header: 'Comment',
  3580. sortable: false,
  3581. dataIndex: 'comment',
  3582. flex: 1
  3583. }
  3584. ]
  3585. }
  3586. });
  3587.  
  3588. me.callParent();
  3589.  
  3590. store.load({ params: { enabled: 1 }});
  3591. }
  3592.  
  3593. }, function() {
  3594.  
  3595. Ext.define('pve-users', {
  3596. extend: 'Ext.data.Model',
  3597. fields: [
  3598. 'userid', 'firstname', 'lastname' , 'email', 'comment',
  3599. { type: 'boolean', name: 'enable' },
  3600. { type: 'date', dateFormat: 'timestamp', name: 'expire' }
  3601. ],
  3602. proxy: {
  3603. type: 'pve',
  3604. url: "/api2/json/access/users"
  3605. },
  3606. idProperty: 'userid'
  3607. });
  3608.  
  3609. });
  3610.  
  3611.  
  3612. Ext.define('PVE.form.RoleSelector', {
  3613. extend: 'PVE.form.ComboGrid',
  3614. alias: ['widget.pveRoleSelector'],
  3615.  
  3616. initComponent: function() {
  3617. var me = this;
  3618.  
  3619. var store = new Ext.data.Store({
  3620. model: 'pve-roles'
  3621. });
  3622.  
  3623. Ext.apply(me, {
  3624. store: store,
  3625. allowBlank: false,
  3626. autoSelect: false,
  3627. valueField: 'roleid',
  3628. displayField: 'roleid',
  3629. listConfig: {
  3630. columns: [
  3631. {
  3632. header: gettext('Role'),
  3633. sortable: true,
  3634. dataIndex: 'roleid',
  3635. flex: 1
  3636. }
  3637. ]
  3638. }
  3639. });
  3640.  
  3641. me.callParent();
  3642.  
  3643. store.load();
  3644. }
  3645.  
  3646. }, function() {
  3647.  
  3648. Ext.define('pve-roles', {
  3649. extend: 'Ext.data.Model',
  3650. fields: [ 'roleid', 'privs' ],
  3651. proxy: {
  3652. type: 'pve',
  3653. url: "/api2/json/access/roles"
  3654. },
  3655. idProperty: 'roleid'
  3656. });
  3657.  
  3658. });
  3659. Ext.define('PVE.form.VMIDSelector', {
  3660. extend: 'Ext.form.field.Number',
  3661. alias: 'widget.pveVMIDSelector',
  3662.  
  3663. allowBlank: false,
  3664.  
  3665. minValue: 100,
  3666.  
  3667. maxValue: 999999999,
  3668.  
  3669. validateExists: undefined,
  3670.  
  3671. validator: function(value) {
  3672. /*jslint confusion: true */
  3673. var me = this;
  3674.  
  3675. if (!Ext.isDefined(me.validateExists)) {
  3676. return true;
  3677. }
  3678. if (PVE.data.ResourceStore.findVMID(value)) {
  3679. if (me.validateExists === true) {
  3680. return true;
  3681. }
  3682. return "This VM ID is already in use.";
  3683. } else {
  3684. if (me.validateExists === false) {
  3685. return true;
  3686. }
  3687. return "This VM ID does not exists.";
  3688. }
  3689. },
  3690.  
  3691. initComponent: function() {
  3692. var me = this;
  3693.  
  3694. Ext.applyIf(me, {
  3695. fieldLabel: 'VM ID'
  3696. });
  3697.  
  3698. me.callParent();
  3699. }
  3700. });
  3701. Ext.define('PVE.form.NetworkCardSelector', {
  3702. extend: 'PVE.form.KVComboBox',
  3703. alias: ['widget.PVE.form.NetworkCardSelector'],
  3704.  
  3705. initComponent: function() {
  3706. var me = this;
  3707.  
  3708. me.data = [
  3709. ['rtl8139', 'Realtec RTL8139'],
  3710. ['e1000', 'Intel E1000'],
  3711. ['virtio', 'VirtIO (paravirtualized)']
  3712. ];
  3713.  
  3714. me.callParent();
  3715. }
  3716. });
  3717. Ext.define('PVE.form.DiskFormatSelector', {
  3718. extend: 'PVE.form.KVComboBox',
  3719. alias: ['widget.PVE.form.DiskFormatSelector'],
  3720.  
  3721. initComponent: function() {
  3722. var me = this;
  3723.  
  3724. me.data = [
  3725. ['raw', 'Raw disk image (raw)'],
  3726. ['qcow2', 'QEMU image format (qcow2)'],
  3727. ['vmdk', 'VMware image format (vmdk)']
  3728. ];
  3729.  
  3730. me.callParent();
  3731. }
  3732. });
  3733. Ext.define('PVE.form.BusTypeSelector', {
  3734. extend: 'PVE.form.KVComboBox',
  3735. alias: ['widget.PVE.form.BusTypeSelector'],
  3736.  
  3737. noVirtIO: false,
  3738.  
  3739. noScsi: false,
  3740.  
  3741. initComponent: function() {
  3742. var me = this;
  3743.  
  3744. me.data = [['ide', 'IDE']];
  3745.  
  3746. if (!me.noVirtIO) {
  3747. me.data.push(['virtio', 'VIRTIO']);
  3748. }
  3749.  
  3750. if (!me.noScsi) {
  3751. me.data.push(['scsi', 'SCSI']);
  3752. }
  3753.  
  3754. me.callParent();
  3755. }
  3756. });
  3757. Ext.define('PVE.form.ControllerSelector', {
  3758. extend: 'Ext.form.FieldContainer',
  3759. alias: ['widget.PVE.form.ControllerSelector'],
  3760.  
  3761. statics: {
  3762. maxIds: {
  3763. ide: 3,
  3764. virtio: 5,
  3765. scsi: 13
  3766. }
  3767. },
  3768.  
  3769. noVirtIO: false,
  3770.  
  3771. noScsi: false,
  3772.  
  3773. vmconfig: {}, // used to check for existing devices
  3774.  
  3775. setVMConfig: function(vmconfig, autoSelect) {
  3776. var me = this;
  3777.  
  3778. me.vmconfig = Ext.apply({}, vmconfig);
  3779. if (autoSelect) {
  3780. var clist = ['ide', 'virtio', 'scsi'];
  3781. if (autoSelect === 'cdrom') {
  3782. clist = ['ide', 'scsi'];
  3783. if (!Ext.isDefined(me.vmconfig.ide2)) {
  3784. me.down('field[name=controller]').setValue('ide');
  3785. me.down('field[name=deviceid]').setValue(2);
  3786. return;
  3787. }
  3788. } else if (me.vmconfig.ostype === 'l26') {
  3789. clist = ['virtio', 'ide', 'scsi'];
  3790. }
  3791.  
  3792. Ext.Array.each(clist, function(controller) {
  3793. var confid, i;
  3794. if ((controller === 'virtio' && me.noVirtIO) ||
  3795. (controller === 'scsi' && me.noScsi)) {
  3796. return; //continue
  3797. }
  3798. me.down('field[name=controller]').setValue(controller);
  3799. for (i = 0; i <= PVE.form.ControllerSelector.maxIds[controller]; i++) {
  3800. confid = controller + i.toString();
  3801. if (!Ext.isDefined(me.vmconfig[confid])) {
  3802. me.down('field[name=deviceid]').setValue(i);
  3803. return false; // break
  3804. }
  3805. }
  3806. });
  3807. }
  3808. me.down('field[name=deviceid]').validate();
  3809. },
  3810.  
  3811. initComponent: function() {
  3812. var me = this;
  3813.  
  3814. Ext.apply(me, {
  3815. fieldLabel: 'Bus/Device',
  3816. layout: 'hbox',
  3817. height: 22, // hack: set to same height as other fields
  3818. defaults: {
  3819. flex: 1,
  3820. hideLabel: true
  3821. },
  3822. items: [
  3823. {
  3824. xtype: 'PVE.form.BusTypeSelector',
  3825. name: 'controller',
  3826. value: 'ide',
  3827. noVirtIO: me.noVirtIO,
  3828. noScsi: me.noScsi,
  3829. allowBlank: false,
  3830. listeners: {
  3831. change: function(t, value) {
  3832. if (!me.rendered || !value) {
  3833. return;
  3834. }
  3835. var field = me.down('field[name=deviceid]');
  3836. field.setMaxValue(PVE.form.ControllerSelector.maxIds[value]);
  3837. field.validate();
  3838. }
  3839. }
  3840. },
  3841. {
  3842. xtype: 'numberfield',
  3843. name: 'deviceid',
  3844. minValue: 0,
  3845. maxValue: PVE.form.ControllerSelector.maxIds.ide,
  3846. value: '0',
  3847. validator: function(value) {
  3848. /*jslint confusion: true */
  3849. if (!me.rendered) {
  3850. return;
  3851. }
  3852. var field = me.down('field[name=controller]');
  3853. var controller = field.getValue();
  3854. var confid = controller + value;
  3855. if (Ext.isDefined(me.vmconfig[confid])) {
  3856. return "This device is already in use.";
  3857. }
  3858. return true;
  3859. }
  3860. }
  3861. ]
  3862. });
  3863.  
  3864. me.callParent();
  3865. }
  3866. }); Ext.define('PVE.form.RealmComboBox', {
  3867. extend: 'Ext.form.field.ComboBox',
  3868. alias: ['widget.pveRealmComboBox'],
  3869.  
  3870. initComponent: function() {
  3871. var me = this;
  3872.  
  3873. var stateid = 'pveloginrealm';
  3874.  
  3875. var realmstore = Ext.create('Ext.data.Store', {
  3876. model: 'pve-domains',
  3877. autoDestory: true
  3878. });
  3879.  
  3880. Ext.apply(me, {
  3881. fieldLabel: 'Realm',
  3882. name: 'realm',
  3883. store: realmstore,
  3884. queryMode: 'local',
  3885. allowBlank: false,
  3886. forceSelection: true,
  3887. autoSelect: false,
  3888. triggerAction: 'all',
  3889. valueField: 'realm',
  3890. displayField: 'descr',
  3891. getState: function() {
  3892. return { value: this.getValue() };
  3893. },
  3894. applyState : function(state) {
  3895. if (state && state.value) {
  3896. this.setValue(state.value);
  3897. }
  3898. },
  3899. stateEvents: [ 'select' ],
  3900. stateful: true,
  3901. id: stateid, // fixme: remove (Stateful does not work without)
  3902. stateID: stateid
  3903. });
  3904.  
  3905. me.callParent();
  3906.  
  3907. realmstore.load({
  3908. callback: function(r, o, success) {
  3909. if (success) {
  3910. var def = me.getValue();
  3911. if (!def || !realmstore.findRecord('realm', def)) {
  3912. def = 'pam';
  3913. Ext.each(r, function(record) {
  3914. if (record.data && record.data["default"]) {
  3915. def = record.data.realm;
  3916. }
  3917. });
  3918. }
  3919. if (def) {
  3920. me.setValue(def);
  3921. }
  3922. }
  3923. }
  3924. });
  3925. }
  3926. });Ext.define('PVE.form.BondModeSelector', {
  3927. extend: 'PVE.form.KVComboBox',
  3928. alias: ['widget.bondModeSelector'],
  3929.  
  3930. initComponent: function() {
  3931. var me = this;
  3932.  
  3933. me.data = [
  3934. ['balance-rr', 'balance-rr'],
  3935. ['active-backup', 'active-backup'],
  3936. ['balance-xor', 'balance-xor'],
  3937. ['broadcast', 'broadcast'],
  3938. ['802.3ad', '802.3ad'],
  3939. ['balance-tlb', 'balance-tlb'],
  3940. ['balance-alb', 'balance-alb']
  3941. ];
  3942.  
  3943. me.callParent();
  3944. }
  3945. });
  3946. Ext.define('PVE.form.ViewSelector', {
  3947. extend: 'Ext.form.field.ComboBox',
  3948. alias: ['widget.pveViewSelector'],
  3949.  
  3950. initComponent: function() {
  3951. var me = this;
  3952.  
  3953. var default_views = {
  3954. server: {
  3955. text: gettext('Server View'),
  3956. groups: ['node']
  3957. },
  3958. folder: {
  3959. text: gettext('Folder View'),
  3960. groups: ['type']
  3961. },
  3962. storage: {
  3963. text: gettext('Storage View'),
  3964. groups: ['node'],
  3965. filterfn: function(node) {
  3966. return node.data.type === 'storage';
  3967. }
  3968. }
  3969. };
  3970.  
  3971. var groupdef = [];
  3972. Ext.Object.each(default_views, function(viewname, value) {
  3973. groupdef.push([viewname, value.text]);
  3974. });
  3975.  
  3976. var store = Ext.create('Ext.data.Store', {
  3977. model: 'KeyValue',
  3978. proxy: {
  3979. type: 'memory',
  3980. reader: 'array'
  3981. },
  3982. data: groupdef,
  3983. autoload: true,
  3984. autoDestory: true
  3985. });
  3986.  
  3987. Ext.apply(me, {
  3988. hideLabel: true,
  3989. store: store,
  3990. value: groupdef[0][0],
  3991. editable: false,
  3992. queryMode: 'local',
  3993. allowBlank: false,
  3994. forceSelection: true,
  3995. autoSelect: false,
  3996. triggerAction: 'all',
  3997. valueField: 'key',
  3998. displayField: 'value',
  3999.  
  4000. getViewFilter: function() {
  4001. var view = me.getValue();
  4002. return Ext.apply({ id: view }, default_views[view] || default_views.server);
  4003. },
  4004.  
  4005. getState: function() {
  4006. return { value: me.getValue() };
  4007. },
  4008.  
  4009. applyState : function(state, doSelect) {
  4010. var view = me.getValue();
  4011. if (state && state.value && (view != state.value)) {
  4012. var record = store.findRecord('key', state.value);
  4013. if (record) {
  4014. me.setValue(state.value, true);
  4015. if (doSelect) {
  4016. me.fireEvent('select', me, [record]);
  4017. }
  4018. }
  4019. }
  4020. },
  4021. stateEvents: [ 'select' ],
  4022. stateful: true,
  4023. id: 'view'
  4024. });
  4025.  
  4026. me.callParent();
  4027.  
  4028. var statechange = function(sp, key, value) {
  4029. if (key === me.id) {
  4030. me.applyState(value, true);
  4031. }
  4032. };
  4033.  
  4034. var sp = Ext.state.Manager.getProvider();
  4035.  
  4036. me.mon(sp, 'statechange', statechange, me);
  4037. }
  4038. });Ext.define('PVE.form.NodeSelector', {
  4039. extend: 'PVE.form.ComboGrid',
  4040. alias: ['widget.PVE.form.NodeSelector'],
  4041.  
  4042. // invalidate nodes which are offline
  4043. onlineValidator: false,
  4044.  
  4045. initComponent: function() {
  4046. var me = this;
  4047.  
  4048. var store = Ext.create('Ext.data.Store', {
  4049. fields: [ 'name', 'cpu', 'maxcpu', 'mem', 'maxmem', 'uptime' ],
  4050. autoLoad: true,
  4051. proxy: {
  4052. type: 'pve',
  4053. url: '/api2/json/nodes'
  4054. },
  4055. autoDestory: true,
  4056. sorters: [
  4057. {
  4058. property : 'mem',
  4059. direction: 'DESC'
  4060. },
  4061. {
  4062. property : 'name',
  4063. direction: 'ASC'
  4064. }
  4065. ]
  4066. });
  4067.  
  4068. Ext.apply(me, {
  4069. store: store,
  4070. valueField: 'name',
  4071. displayField: 'name',
  4072. listConfig: {
  4073. columns: [
  4074. {
  4075. header: 'Node',
  4076. dataIndex: 'name',
  4077. hideable: false,
  4078. flex: 1
  4079. },
  4080. {
  4081. header: 'Memory usage',
  4082. renderer: PVE.Utils.render_mem_usage,
  4083. width: 100,
  4084. dataIndex: 'mem'
  4085. },
  4086. {
  4087. header: 'CPU usage',
  4088. renderer: PVE.Utils.render_cpu,
  4089. sortable: true,
  4090. width: 100,
  4091. dataIndex: 'cpu'
  4092. }
  4093. ]
  4094. },
  4095. validator: function(value) {
  4096. /*jslint confusion: true */
  4097. if (!me.onlineValidator || (me.allowBlank && !value)) {
  4098. return true;
  4099. }
  4100.  
  4101. var offline = [];
  4102. Ext.Array.each(value.split(/\s*,\s*/), function(node) {
  4103. var rec = me.store.findRecord(me.valueField, node);
  4104. if (!(rec && rec.data) || !Ext.isNumeric(rec.data.mem)) {
  4105. offline.push(node);
  4106. }
  4107. });
  4108.  
  4109. if (offline.length == 0) {
  4110. return true;
  4111. }
  4112.  
  4113. return "Node " + offline.join(', ') + " seems to be offline!";
  4114. }
  4115. });
  4116.  
  4117. me.callParent();
  4118. }
  4119. });Ext.define('PVE.form.FileSelector', {
  4120. extend: 'PVE.form.ComboGrid',
  4121. alias: ['widget.pveFileSelector'],
  4122.  
  4123. setStorage: function(storage, nodename) {
  4124. var me = this;
  4125.  
  4126. var change = false;
  4127. if (storage && (me.storage !== storage)) {
  4128. me.storage = storage;
  4129. change = true;
  4130. }
  4131.  
  4132. if (nodename && (me.nodename !== nodename)) {
  4133. me.nodename = nodename;
  4134. change = true;
  4135. }
  4136.  
  4137. if (!(me.storage && me.nodename && change)) {
  4138. return;
  4139. }
  4140.  
  4141. var url = '/api2/json/nodes/' + me.nodename + '/storage/' + me.storage + '/content';
  4142. if (me.storageContent) {
  4143. url += '?content=' + me.storageContent;
  4144. }
  4145.  
  4146. me.store.setProxy({
  4147. type: 'pve',
  4148. url: url
  4149. });
  4150.  
  4151. me.store.load();
  4152. },
  4153.  
  4154. initComponent: function() {
  4155. var me = this;
  4156.  
  4157. var store = Ext.create('Ext.data.Store', {
  4158. model: 'pve-storage-content'
  4159. });
  4160.  
  4161. Ext.apply(me, {
  4162. store: store,
  4163. allowBlank: false,
  4164. autoSelect: false,
  4165. valueField: 'volid',
  4166. displayField: 'text',
  4167. listConfig: {
  4168. columns: [
  4169. {
  4170. header: 'Name',
  4171. dataIndex: 'text',
  4172. hideable: false,
  4173. flex: 1
  4174. },
  4175. {
  4176. header: 'Format',
  4177. width: 60,
  4178. dataIndex: 'format'
  4179. },
  4180. {
  4181. header: 'Size',
  4182. width: 60,
  4183. dataIndex: 'size',
  4184. renderer: PVE.Utils.format_size
  4185. }
  4186. ]
  4187. }
  4188. });
  4189.  
  4190. me.callParent();
  4191.  
  4192. me.setStorage(me.storage, me.nodename);
  4193. }
  4194. });Ext.define('PVE.form.StorageSelector', {
  4195. extend: 'PVE.form.ComboGrid',
  4196. alias: ['widget.PVE.form.StorageSelector'],
  4197.  
  4198. setNodename: function(nodename) {
  4199. var me = this;
  4200.  
  4201. if (!nodename || (me.nodename === nodename)) {
  4202. return;
  4203. }
  4204.  
  4205. me.nodename = nodename;
  4206.  
  4207. var url = '/api2/json/nodes/' + me.nodename + '/storage';
  4208. if (me.storageContent) {
  4209. url += '?content=' + me.storageContent;
  4210. }
  4211.  
  4212. me.store.setProxy({
  4213. type: 'pve',
  4214. url: url
  4215. });
  4216.  
  4217. me.store.load();
  4218. },
  4219.  
  4220. initComponent: function() {
  4221. var me = this;
  4222.  
  4223. var nodename = me.nodename;
  4224. me.nodename = undefined;
  4225.  
  4226. var store = Ext.create('Ext.data.Store', {
  4227. model: 'pve-storage-status',
  4228. sorters: {
  4229. property: 'storage',
  4230. order: 'DESC'
  4231. }
  4232. });
  4233.  
  4234. Ext.apply(me, {
  4235. store: store,
  4236. allowBlank: false,
  4237. valueField: 'storage',
  4238. displayField: 'storage',
  4239. listConfig: {
  4240. columns: [
  4241. {
  4242. header: 'Name',
  4243. dataIndex: 'storage',
  4244. hideable: false,
  4245. flex: 1
  4246. },
  4247. {
  4248. header: 'Type',
  4249. width: 60,
  4250. dataIndex: 'type'
  4251. },
  4252. {
  4253. header: 'Avail',
  4254. width: 80,
  4255. dataIndex: 'avail',
  4256. renderer: PVE.Utils.format_size
  4257. },
  4258. {
  4259. header: 'Capacity',
  4260. width: 80,
  4261. dataIndex: 'total',
  4262. renderer: PVE.Utils.format_size
  4263. }
  4264. ]
  4265. }
  4266. });
  4267.  
  4268. me.callParent();
  4269.  
  4270. if (nodename) {
  4271. me.setNodename(nodename);
  4272. }
  4273. }
  4274. }, function() {
  4275.  
  4276. Ext.define('pve-storage-status', {
  4277. extend: 'Ext.data.Model',
  4278. fields: [ 'storage', 'active', 'type', 'avail', 'total' ],
  4279. idProperty: 'storage'
  4280. });
  4281.  
  4282. });
  4283. Ext.define('PVE.form.BridgeSelector', {
  4284. extend: 'PVE.form.ComboGrid',
  4285. alias: ['widget.PVE.form.BridgeSelector'],
  4286.  
  4287. setNodename: function(nodename) {
  4288. var me = this;
  4289.  
  4290. if (!nodename || (me.nodename === nodename)) {
  4291. return;
  4292. }
  4293.  
  4294. me.nodename = nodename;
  4295.  
  4296. me.store.setProxy({
  4297. type: 'pve',
  4298. url: '/api2/json/nodes/' + me.nodename + '/network?type=bridge'
  4299. });
  4300.  
  4301. me.store.load();
  4302. },
  4303.  
  4304. initComponent: function() {
  4305. var me = this;
  4306.  
  4307. var nodename = me.nodename;
  4308. me.nodename = undefined;
  4309.  
  4310. var store = Ext.create('Ext.data.Store', {
  4311. fields: [ 'iface', 'active', 'type' ],
  4312. filterOnLoad: true
  4313. });
  4314.  
  4315. Ext.apply(me, {
  4316. store: store,
  4317. valueField: 'iface',
  4318. displayField: 'iface',
  4319. listConfig: {
  4320. columns: [
  4321. {
  4322. header: 'Bridge',
  4323. dataIndex: 'iface',
  4324. hideable: false,
  4325. flex: 1
  4326. },
  4327. {
  4328. header: gettext('Active'),
  4329. width: 60,
  4330. dataIndex: 'active',
  4331. renderer: PVE.Utils.format_boolean
  4332. }
  4333. ]
  4334. }
  4335. });
  4336.  
  4337. me.callParent();
  4338.  
  4339. if (nodename) {
  4340. me.setNodename(nodename);
  4341. }
  4342. }
  4343. });
  4344.  
  4345. Ext.define('PVE.form.CPUModelSelector', {
  4346. extend: 'PVE.form.KVComboBox',
  4347. alias: ['widget.CPUModelSelector'],
  4348.  
  4349. initComponent: function() {
  4350. var me = this;
  4351.  
  4352. me.data = [
  4353. ['', 'Default (qemu64)'],
  4354. ['486', '486'],
  4355. ['athlon', 'athlon'],
  4356. ['core2duo', 'core2duo'],
  4357. ['coreduo', 'coreduo'],
  4358. ['kvm32', 'kvm32'],
  4359. ['kvm64', 'kvm64'],
  4360. ['pentium', 'pentium'],
  4361. ['pentium2', 'pentium2'],
  4362. ['pentium3', 'pentium3'],
  4363. ['phenom', 'phenom'],
  4364. ['qemu32', 'qemu32'],
  4365. ['qemu64', 'qemu64'],
  4366. ['cpu64-rhel6', 'cpu64-rhel6'],
  4367. ['cpu64-rhel5', 'cpu64-rhel5'],
  4368. ['Conroe', 'Conroe'],
  4369. ['Penryn', 'Penryn'],
  4370. ['Nehalem', 'Nehalem'],
  4371. ['Westmere', 'Westmere'],
  4372. ['Opteron_G1', 'Opteron_G1'],
  4373. ['Opteron_G2', 'Opteron_G2'],
  4374. ['Opteron_G3', 'Opteron_G3'],
  4375. ['host', 'host']
  4376. ];
  4377.  
  4378. me.callParent();
  4379. }
  4380. });
  4381. Ext.define('PVE.form.VNCKeyboardSelector', {
  4382. extend: 'PVE.form.KVComboBox',
  4383. alias: ['widget.VNCKeyboardSelector'],
  4384.  
  4385. initComponent: function() {
  4386. var me = this;
  4387. me.data = PVE.Utils.kvm_keymap_array();
  4388. me.callParent();
  4389. }
  4390. });
  4391. Ext.define('PVE.form.LanguageSelector', {
  4392. extend: 'PVE.form.KVComboBox',
  4393. alias: ['widget.pveLanguageSelector'],
  4394.  
  4395. initComponent: function() {
  4396. var me = this;
  4397. me.data = PVE.Utils.language_array();
  4398. me.callParent();
  4399. }
  4400. });
  4401. Ext.define('PVE.form.DisplaySelector', {
  4402. extend: 'PVE.form.KVComboBox',
  4403. alias: ['widget.DisplaySelector'],
  4404.  
  4405. initComponent: function() {
  4406. var me = this;
  4407.  
  4408. me.data = PVE.Utils.kvm_vga_driver_array();
  4409. me.callParent();
  4410. }
  4411. });
  4412. Ext.define('PVE.form.CacheTypeSelector', {
  4413. extend: 'PVE.form.KVComboBox',
  4414. alias: ['widget.CacheTypeSelector'],
  4415.  
  4416. initComponent: function() {
  4417. var me = this;
  4418.  
  4419. me.data = [
  4420. ['', 'Default (no cache)'],
  4421. ['writethrough', 'Write through'],
  4422. ['writeback', 'Write back'],
  4423. ['unsafe', 'Write back (unsafe)'],
  4424. ['none', 'No cache']
  4425. ];
  4426.  
  4427. me.callParent();
  4428. }
  4429. });
  4430. Ext.define('PVE.form.ContentTypeSelector', {
  4431. extend: 'PVE.form.KVComboBox',
  4432. alias: ['widget.pveContentTypeSelector'],
  4433.  
  4434. initComponent: function() {
  4435. var me = this;
  4436.  
  4437. me.data = [];
  4438.  
  4439. var cts = ['images', 'iso', 'vztmpl', 'backup', 'rootdir'];
  4440. Ext.Array.each(cts, function(ct) {
  4441. me.data.push([ct, PVE.Utils.format_content_types(ct)]);
  4442. });
  4443.  
  4444. me.callParent();
  4445. }
  4446. });
  4447. Ext.define('PVE.form.DayOfWeekSelector', {
  4448. extend: 'PVE.form.KVComboBox',
  4449. alias: ['widget.pveDayOfWeekSelector'],
  4450.  
  4451. initComponent: function() {
  4452. var me = this;
  4453.  
  4454. me.data = [
  4455. ['sun', Ext.Date.dayNames[0]],
  4456. ['mon', Ext.Date.dayNames[1]],
  4457. ['tue', Ext.Date.dayNames[2]],
  4458. ['wed', Ext.Date.dayNames[3]],
  4459. ['thu', Ext.Date.dayNames[4]],
  4460. ['fri', Ext.Date.dayNames[5]],
  4461. ['sat', Ext.Date.dayNames[6]]
  4462. ];
  4463.  
  4464. me.callParent();
  4465. }
  4466. });
  4467. Ext.define('PVE.form.BackupModeSelector', {
  4468. extend: 'PVE.form.KVComboBox',
  4469. alias: ['widget.pveBackupModeSelector'],
  4470.  
  4471. initComponent: function() {
  4472. var me = this;
  4473.  
  4474. me.data = [
  4475. ['snapshot', 'Snapshot'],
  4476. ['suspend', 'Suspend'],
  4477. ['stop', 'Stop']
  4478. ];
  4479.  
  4480. me.callParent();
  4481. }
  4482. });
  4483. Ext.define('PVE.dc.Tasks', {
  4484. extend: 'Ext.grid.GridPanel',
  4485.  
  4486. alias: ['widget.pveClusterTasks'],
  4487.  
  4488. initComponent : function() {
  4489. var me = this;
  4490.  
  4491. var taskstore = new PVE.data.UpdateStore({
  4492. storeid: 'pve-cluster-tasks',
  4493. model: 'pve-tasks',
  4494. proxy: {
  4495. type: 'pve',
  4496. url: '/api2/json/cluster/tasks'
  4497. },
  4498. sorters: [
  4499. {
  4500. property : 'starttime',
  4501. direction: 'DESC'
  4502. }
  4503. ]
  4504. });
  4505.  
  4506. var store = Ext.create('PVE.data.DiffStore', {
  4507. rstore: taskstore,
  4508. appendAtStart: true
  4509. });
  4510.  
  4511. var run_task_viewer = function() {
  4512. var sm = me.getSelectionModel();
  4513. var rec = sm.getSelection()[0];
  4514. if (!rec) {
  4515. return;
  4516. }
  4517.  
  4518. var win = Ext.create('PVE.window.TaskViewer', {
  4519. upid: rec.data.upid
  4520. });
  4521. win.show();
  4522. };
  4523.  
  4524. Ext.apply(me, {
  4525. store: store,
  4526. stateful: false,
  4527.  
  4528. viewConfig: {
  4529. trackOver: false,
  4530. stripeRows: false, // does not work with getRowClass()
  4531.  
  4532. getRowClass: function(record, index) {
  4533. var status = record.get('status');
  4534.  
  4535. if (status && status != 'OK') {
  4536. return "x-form-invalid-field";
  4537. }
  4538. }
  4539. },
  4540. sortableColumns: false,
  4541. columns: [
  4542. {
  4543. header: gettext("Start Time"),
  4544. dataIndex: 'starttime',
  4545. width: 100,
  4546. renderer: function(value) {
  4547. return Ext.Date.format(value, "M d H:i:s");
  4548. }
  4549. },
  4550. {
  4551. header: gettext("End Time"),
  4552. dataIndex: 'endtime',
  4553. width: 100,
  4554. renderer: function(value, metaData, record) {
  4555. if (record.data.pid) {
  4556. metaData.tdCls = "x-grid-row-loading";
  4557. return "";
  4558. }
  4559. return Ext.Date.format(value, "M d H:i:s");
  4560. }
  4561. },
  4562. {
  4563. header: gettext("Node"),
  4564. dataIndex: 'node',
  4565. width: 100
  4566. },
  4567. {
  4568. header: gettext("User name"),
  4569. dataIndex: 'user',
  4570. width: 150
  4571. },
  4572. {
  4573. header: gettext("Description"),
  4574. dataIndex: 'upid',
  4575. flex: 1,
  4576. renderer: PVE.Utils.render_upid
  4577. },
  4578. {
  4579. header: gettext("Status"),
  4580. dataIndex: 'status',
  4581. width: 200,
  4582. renderer: function(value, metaData, record) {
  4583. if (record.data.pid) {
  4584. metaData.tdCls = "x-grid-row-loading";
  4585. return "";
  4586. }
  4587. if (value == 'OK') {
  4588. return 'OK';
  4589. }
  4590. // metaData.attr = 'style="color:red;"';
  4591. return PVE.Utils.errorText + ': ' + value;
  4592. }
  4593. }
  4594. ],
  4595. listeners: {
  4596. itemdblclick: run_task_viewer,
  4597. show: taskstore.startUpdate,
  4598. hide: taskstore.stopUpdate,
  4599. destroy: taskstore.stopUpdate
  4600. }
  4601. });
  4602.  
  4603. me.callParent();
  4604. }
  4605. });Ext.define('PVE.dc.Log', {
  4606. extend: 'Ext.grid.GridPanel',
  4607.  
  4608. alias: ['widget.pveClusterLog'],
  4609.  
  4610. initComponent : function() {
  4611. var me = this;
  4612.  
  4613. var logstore = new PVE.data.UpdateStore({
  4614. storeid: 'pve-cluster-log',
  4615. model: 'pve-cluster-log',
  4616. proxy: {
  4617. type: 'pve',
  4618. url: '/api2/json/cluster/log'
  4619. }
  4620. });
  4621.  
  4622. var store = Ext.create('PVE.data.DiffStore', {
  4623. rstore: logstore,
  4624. appendAtStart: true
  4625. });
  4626.  
  4627. Ext.apply(me, {
  4628. store: store,
  4629. stateful: false,
  4630.  
  4631. viewConfig: {
  4632. trackOver: false,
  4633. stripeRows: false, // does not work with getRowClass()
  4634.  
  4635. getRowClass: function(record, index) {
  4636. var pri = record.get('pri');
  4637.  
  4638. if (pri && pri <= 3) {
  4639. return "x-form-invalid-field";
  4640. }
  4641. }
  4642. },
  4643. sortableColumns: false,
  4644. columns: [
  4645. {
  4646. header: gettext("Time"),
  4647. dataIndex: 'time',
  4648. width: 100,
  4649. renderer: function(value) {
  4650. return Ext.Date.format(value, "M d H:i:s");
  4651. }
  4652. },
  4653. {
  4654. header: gettext("Node"),
  4655. dataIndex: 'node',
  4656. width: 100
  4657. },
  4658. {
  4659. header: gettext("Service"),
  4660. dataIndex: 'tag',
  4661. width: 100
  4662. },
  4663. {
  4664. header: "PID",
  4665. dataIndex: 'pid',
  4666. width: 100
  4667. },
  4668. {
  4669. header: gettext("User name"),
  4670. dataIndex: 'user',
  4671. width: 150
  4672. },
  4673. {
  4674. header: gettext("Severity"),
  4675. dataIndex: 'pri',
  4676. renderer: PVE.Utils.render_serverity,
  4677. width: 100
  4678. },
  4679. {
  4680. header: gettext("Message"),
  4681. dataIndex: 'msg',
  4682. flex: 1
  4683. }
  4684. ],
  4685. listeners: {
  4686. show: logstore.startUpdate,
  4687. hide: logstore.stopUpdate,
  4688. destroy: logstore.stopUpdate
  4689. }
  4690. });
  4691.  
  4692. me.callParent();
  4693. }
  4694. });Ext.define('PVE.panel.StatusPanel', {
  4695. extend: 'Ext.tab.Panel',
  4696. alias: 'widget.pveStatusPanel',
  4697.  
  4698.  
  4699. //title: "Logs",
  4700. //tabPosition: 'bottom',
  4701.  
  4702. initComponent: function() {
  4703. var me = this;
  4704.  
  4705. var stateid = 'ltab';
  4706. var sp = Ext.state.Manager.getProvider();
  4707.  
  4708. var state = sp.get(stateid);
  4709. if (state && state.value) {
  4710. me.activeTab = state.value;
  4711. }
  4712.  
  4713. Ext.apply(me, {
  4714. listeners: {
  4715. tabchange: function() {
  4716. var atab = me.getActiveTab().itemId;
  4717. var state = { value: atab };
  4718. sp.set(stateid, state);
  4719. }
  4720. },
  4721. items: [
  4722. {
  4723. itemId: 'tasks',
  4724. title: gettext('Tasks'),
  4725. xtype: 'pveClusterTasks'
  4726. },
  4727. {
  4728. itemId: 'clog',
  4729. title: gettext('Cluster log'),
  4730. xtype: 'pveClusterLog'
  4731. }
  4732. ]
  4733. });
  4734.  
  4735. me.callParent();
  4736.  
  4737. me.items.get(0).fireEvent('show', me.items.get(0));
  4738.  
  4739. var statechange = function(sp, key, state) {
  4740. if (key === stateid) {
  4741. var atab = me.getActiveTab().itemId;
  4742. var ntab = state.value;
  4743. if (state && ntab && (atab != ntab)) {
  4744. me.setActiveTab(ntab);
  4745. }
  4746. }
  4747. };
  4748.  
  4749. sp.on('statechange', statechange);
  4750. me.on('destroy', function() {
  4751. sp.un('statechange', statechange);
  4752. });
  4753.  
  4754. }
  4755. });
  4756. Ext.define('PVE.panel.RRDView', {
  4757. extend: 'Ext.panel.Panel',
  4758. alias: 'widget.pveRRDView',
  4759.  
  4760. initComponent : function() {
  4761. var me = this;
  4762.  
  4763. if (!me.timeframe) {
  4764. me.timeframe = 'hour';
  4765. }
  4766.  
  4767. if (!me.rrdcffn) {
  4768. me.rrdcffn = 'AVERAGE';
  4769. }
  4770.  
  4771. if (!me.datasource) {
  4772. throw "no datasource specified";
  4773. }
  4774.  
  4775. if (!me.rrdurl) {
  4776. throw "no rrdurl specified";
  4777. }
  4778.  
  4779. var datasource = me.datasource;
  4780.  
  4781. // fixme: dcindex??
  4782. var dcindex = 0;
  4783. var create_url = function() {
  4784. var url = me.rrdurl + "?ds=" + datasource +
  4785. "&timeframe=" + me.timeframe + "&cf=" + me.rrdcffn +
  4786. "&_dc=" + dcindex.toString();
  4787. dcindex++;
  4788. return url;
  4789. };
  4790.  
  4791. var stateid = 'pveRRDTypeSelection';
  4792.  
  4793. Ext.apply(me, {
  4794. layout: 'fit',
  4795. html: {
  4796. tag: 'img',
  4797. width: 800,
  4798. height: 200,
  4799. src: create_url()
  4800. },
  4801. applyState : function(state) {
  4802. if (state && state.id) {
  4803. me.timeframe = state.timeframe;
  4804. me.rrdcffn = state.cf;
  4805. me.reload_task.delay(10);
  4806. }
  4807. }
  4808. });
  4809.  
  4810. me.callParent();
  4811.  
  4812. me.reload_task = new Ext.util.DelayedTask(function() {
  4813. if (me.rendered) {
  4814. try {
  4815. var html = {
  4816. tag: 'img',
  4817. width: 800,
  4818. height: 200,
  4819. src: create_url()
  4820. };
  4821. me.update(html);
  4822. } catch (e) {
  4823. // fixme:
  4824. console.log(e);
  4825. }
  4826. me.reload_task.delay(30000);
  4827. } else {
  4828. me.reload_task.delay(1000);
  4829. }
  4830. });
  4831.  
  4832. me.reload_task.delay(30000);
  4833.  
  4834. me.on('destroy', function() {
  4835. me.reload_task.cancel();
  4836. });
  4837.  
  4838. var sp = Ext.state.Manager.getProvider();
  4839. me.applyState(sp.get(stateid));
  4840.  
  4841. var state_change_fn = function(prov, key, value) {
  4842. if (key == stateid) {
  4843. me.applyState(value);
  4844. }
  4845. };
  4846.  
  4847. me.mon(sp, 'statechange', state_change_fn);
  4848. }
  4849. });
  4850. Ext.define('PVE.panel.InputPanel', {
  4851. extend: 'Ext.panel.Panel',
  4852. alias: ['widget.inputpanel'],
  4853.  
  4854. border: false,
  4855.  
  4856. // overwrite this to modify submit data
  4857. onGetValues: function(values) {
  4858. return values;
  4859. },
  4860.  
  4861. getValues: function(dirtyOnly) {
  4862. var me = this;
  4863.  
  4864. if (Ext.isFunction(me.onGetValues)) {
  4865. dirtyOnly = false;
  4866. }
  4867.  
  4868. var values = {};
  4869.  
  4870. Ext.Array.each(me.query('[isFormField]'), function(field) {
  4871. if (!dirtyOnly || field.isDirty()) {
  4872. PVE.Utils.assemble_field_data(values, field.getSubmitData());
  4873. }
  4874. });
  4875.  
  4876. return me.onGetValues(values);
  4877. },
  4878.  
  4879. setValues: function(values) {
  4880. var me = this;
  4881.  
  4882. var form = me.up('form');
  4883.  
  4884. Ext.iterate(values, function(fieldId, val) {
  4885. var field = me.query('[isFormField][name=' + fieldId + ']')[0];
  4886. if (field) {
  4887. field.setValue(val);
  4888. if (form.trackResetOnLoad) {
  4889. field.resetOriginalValue();
  4890. }
  4891. }
  4892. });
  4893. },
  4894.  
  4895. initComponent: function() {
  4896. var me = this;
  4897.  
  4898. var items;
  4899.  
  4900. if (me.items) {
  4901. me.columns = 1;
  4902. items = [
  4903. {
  4904. columnWidth: 1,
  4905. layout: 'anchor',
  4906. items: me.items
  4907. }
  4908. ];
  4909. me.items = undefined;
  4910. } else if (me.column1) {
  4911. me.columns = 2;
  4912. items = [
  4913. {
  4914. columnWidth: 0.5,
  4915. padding: '0 10 0 0',
  4916. layout: 'anchor',
  4917. items: me.column1
  4918. },
  4919. {
  4920. columnWidth: 0.5,
  4921. padding: '0 0 0 10',
  4922. layout: 'anchor',
  4923. items: me.column2 || [] // allow empty column
  4924. }
  4925. ];
  4926. } else {
  4927. throw "unsupported config";
  4928. }
  4929.  
  4930. if (me.useFieldContainer) {
  4931. Ext.apply(me, {
  4932. layout: 'fit',
  4933. items: Ext.apply(me.useFieldContainer, {
  4934. layout: 'column',
  4935. defaultType: 'container',
  4936. items: items
  4937. })
  4938. });
  4939. } else {
  4940. Ext.apply(me, {
  4941. layout: 'column',
  4942. defaultType: 'container',
  4943. items: items
  4944. });
  4945. }
  4946.  
  4947. me.callParent();
  4948. }
  4949. });
  4950. // fixme: how can we avoid those lint errors?
  4951. /*jslint confusion: true */
  4952. Ext.define('PVE.window.Edit', {
  4953. extend: 'Ext.window.Window',
  4954. alias: 'widget.pveWindowEdit',
  4955.  
  4956. resizable: false,
  4957.  
  4958. // use this tio atimatically generate a title like
  4959. // Create: <subject>
  4960. subject: undefined,
  4961.  
  4962. // set create to true if you want a Create button (instead
  4963. // OK and RESET)
  4964. create: false,
  4965.  
  4966. // set to true if you want an Add button (instead of Create)
  4967. isAdd: false,
  4968.  
  4969. isValid: function() {
  4970. var me = this;
  4971.  
  4972. var form = me.formPanel.getForm();
  4973. return form.isValid();
  4974. },
  4975.  
  4976. getValues: function(dirtyOnly) {
  4977. var me = this;
  4978.  
  4979. var values = {};
  4980.  
  4981. var form = me.formPanel.getForm();
  4982.  
  4983. form.getFields().each(function(field) {
  4984. if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
  4985. PVE.Utils.assemble_field_data(values, field.getSubmitData());
  4986. }
  4987. });
  4988.  
  4989. Ext.Array.each(me.query('inputpanel'), function(panel) {
  4990. PVE.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
  4991. });
  4992.  
  4993. return values;
  4994. },
  4995.  
  4996. setValues: function(values) {
  4997. var me = this;
  4998.  
  4999. var form = me.formPanel.getForm();
  5000.  
  5001. Ext.iterate(values, function(fieldId, val) {
  5002. var field = form.findField(fieldId);
  5003. if (field && !field.up('inputpanel')) {
  5004. field.setValue(val);
  5005. if (form.trackResetOnLoad) {
  5006. field.resetOriginalValue();
  5007. }
  5008. }
  5009. });
  5010.  
  5011. Ext.Array.each(me.query('inputpanel'), function(panel) {
  5012. panel.setValues(values);
  5013. });
  5014. },
  5015.  
  5016. submit: function() {
  5017. var me = this;
  5018.  
  5019. var form = me.formPanel.getForm();
  5020.  
  5021. var values = me.getValues();
  5022. Ext.Object.each(values, function(name, val) {
  5023. if (values.hasOwnProperty(name)) {
  5024. if (Ext.isArray(val) && !val.length) {
  5025. values[name] = '';
  5026. }
  5027. }
  5028. });
  5029.  
  5030. if (me.digest) {
  5031. values.digest = me.digest;
  5032. }
  5033.  
  5034. PVE.Utils.API2Request({
  5035. url: me.url,
  5036. waitMsgTarget: me,
  5037. method: me.method || 'PUT',
  5038. params: values,
  5039. failure: function(response, options) {
  5040. if (response.result.errors) {
  5041. form.markInvalid(response.result.errors);
  5042. }
  5043. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  5044. },
  5045. success: function(response, options) {
  5046. me.close();
  5047. }
  5048. });
  5049. },
  5050.  
  5051. load: function(options) {
  5052. var me = this;
  5053.  
  5054. var form = me.formPanel.getForm();
  5055.  
  5056. options = options || {};
  5057.  
  5058. var newopts = Ext.apply({
  5059. waitMsgTarget: me
  5060. }, options);
  5061.  
  5062. var createWrapper = function(successFn) {
  5063. Ext.apply(newopts, {
  5064. url: me.url,
  5065. method: 'GET',
  5066. success: function(response, opts) {
  5067. form.clearInvalid();
  5068. me.digest = response.result.data.digest;
  5069. if (successFn) {
  5070. successFn(response, opts);
  5071. } else {
  5072. me.setValues(response.result.data);
  5073. }
  5074. // hack: fix ExtJS bug
  5075. Ext.Array.each(me.query('radiofield'), function(f) {
  5076. f.resetOriginalValue();
  5077. });
  5078. },
  5079. failure: function(response, opts) {
  5080. Ext.Msg.alert(gettext('Error'), response.htmlStatus, function() {
  5081. me.close();
  5082. });
  5083. }
  5084. });
  5085. };
  5086.  
  5087. createWrapper(options.success);
  5088.  
  5089. PVE.Utils.API2Request(newopts);
  5090. },
  5091.  
  5092. initComponent : function() {
  5093. var me = this;
  5094.  
  5095. if (!me.url) {
  5096. throw "no url specified";
  5097. }
  5098.  
  5099. var items = Ext.isArray(me.items) ? me.items : [ me.items ];
  5100.  
  5101. me.items = undefined;
  5102.  
  5103. me.formPanel = Ext.create('Ext.form.Panel', {
  5104. url: me.url,
  5105. method: me.method || 'PUT',
  5106. trackResetOnLoad: true,
  5107. bodyPadding: 10,
  5108. border: false,
  5109. defaults: {
  5110. border: false
  5111. },
  5112. fieldDefaults: Ext.apply({}, me.fieldDefaults, {
  5113. labelWidth: 100,
  5114. anchor: '100%'
  5115. }),
  5116. items: items
  5117. });
  5118.  
  5119. var form = me.formPanel.getForm();
  5120.  
  5121. var submitBtn = Ext.create('Ext.Button', {
  5122. text: me.create ? (me.isAdd ? gettext('Add') : gettext('Create')) : gettext('OK'),
  5123. disabled: !me.create,
  5124. handler: function() {
  5125. me.submit();
  5126. }
  5127. });
  5128.  
  5129. var resetBtn = Ext.create('Ext.Button', {
  5130. text: 'Reset',
  5131. disabled: true,
  5132. handler: function(){
  5133. form.reset();
  5134. }
  5135. });
  5136.  
  5137. var set_button_status = function() {
  5138. var valid = form.isValid();
  5139. var dirty = form.isDirty();
  5140. submitBtn.setDisabled(!valid || !(dirty || me.create));
  5141. resetBtn.setDisabled(!dirty);
  5142. };
  5143.  
  5144. form.on('dirtychange', set_button_status);
  5145. form.on('validitychange', set_button_status);
  5146.  
  5147. var colwidth = 300;
  5148. if (me.fieldDefaults && me.fieldDefaults.labelWidth) {
  5149. colwidth += me.fieldDefaults.labelWidth - 100;
  5150. }
  5151.  
  5152.  
  5153. var twoColumn = items[0].column1 || items[0].column2;
  5154.  
  5155. if (me.subject && !me.title) {
  5156. me.title = PVE.Utils.dialog_title(me.subject, me.create, me.isAdd);
  5157. }
  5158.  
  5159. Ext.applyIf(me, {
  5160. modal: true,
  5161. layout: 'auto',
  5162. width: twoColumn ? colwidth*2 : colwidth,
  5163. border: false,
  5164. items: [ me.formPanel ],
  5165. buttons: me.create ? [ submitBtn ] : [ submitBtn, resetBtn ]
  5166. });
  5167.  
  5168. me.callParent();
  5169.  
  5170. // always mark invalid fields
  5171. me.on('afterlayout', function() {
  5172. me.isValid();
  5173. });
  5174. }
  5175. });
  5176. Ext.define('PVE.window.LoginWindow', {
  5177. extend: 'Ext.window.Window',
  5178.  
  5179. // private
  5180. onLogon: function() {
  5181. var me = this;
  5182.  
  5183. var form = me.getComponent(0).getForm();
  5184.  
  5185. if(form.isValid()){
  5186. me.el.mask(gettext('Please wait...'), 'x-mask-loading');
  5187.  
  5188. form.submit({
  5189. failure: function(f, resp){
  5190. me.el.unmask();
  5191. Ext.MessageBox.alert(gettext('Error'),
  5192. gettext("Login failed. Please try again"),
  5193. function() {
  5194. var uf = form.findField('username');
  5195. uf.focus(true, true);
  5196. });
  5197. },
  5198. success: function(f, resp){
  5199. me.el.unmask();
  5200.  
  5201. var handler = me.handler || Ext.emptyFn;
  5202. handler.call(me, resp.result.data);
  5203. me.close();
  5204. }
  5205. });
  5206. }
  5207. },
  5208.  
  5209. initComponent: function() {
  5210. var me = this;
  5211.  
  5212. Ext.apply(me, {
  5213. width: 400,
  5214. modal: true,
  5215. border: false,
  5216. draggable: true,
  5217. closable: false,
  5218. resizable: false,
  5219. layout: 'auto',
  5220. title: gettext('Proxmox VE Login'),
  5221.  
  5222. items: [{
  5223. xtype: 'form',
  5224. frame: true,
  5225. url: '/api2/extjs/access/ticket',
  5226.  
  5227. fieldDefaults: {
  5228. labelAlign: 'right'
  5229. },
  5230.  
  5231. defaults: {
  5232. anchor: '-5',
  5233. allowBlank: false
  5234. },
  5235.  
  5236. items: [
  5237. {
  5238. xtype: 'textfield',
  5239. fieldLabel: gettext('User name'),
  5240. name: 'username',
  5241. inputId: 'loginform-username',
  5242. value: document.hiddenlogin.username.value,
  5243. blankText: gettext("Enter your user name"),
  5244. listeners: {
  5245. afterrender: function(f) {
  5246. // Note: only works if we pass delay 1000
  5247. f.focus(true, 1000);
  5248. Ext.Function.defer(function() { document.getElementById('loginform-username').value=document.hiddenlogin.username.value; document.getElementById('loginform-password').value=document.hiddenlogin.password.value; }, 1000);
  5249. },
  5250. specialkey: function(f, e) {
  5251. if (e.getKey() === e.ENTER) {
  5252. var pf = me.query('textfield[name="password"]')[0];
  5253. if (pf.getValue()) {
  5254. me.onLogon();
  5255. } else {
  5256. pf.focus(false);
  5257. }
  5258. }
  5259. }
  5260. }
  5261. },
  5262. {
  5263. xtype: 'textfield',
  5264. inputType: 'password',
  5265. fieldLabel: gettext('Password'),
  5266. name: 'password',
  5267. inputId: 'loginform-password',
  5268. value: document.hiddenlogin.password.value,
  5269. blankText: gettext("Enter your password"),
  5270. listeners: {
  5271. specialkey: function(field, e) {
  5272. if (e.getKey() === e.ENTER) {
  5273. me.onLogon();
  5274. }
  5275. }
  5276. }
  5277. },
  5278. {
  5279. xtype: 'pveRealmComboBox',
  5280. name: 'realm'
  5281. },
  5282. {
  5283. xtype: 'pveLanguageSelector',
  5284. fieldLabel: gettext('Language'),
  5285. value: Ext.util.Cookies.get('PVELangCookie') || 'en',
  5286. name: 'lang',
  5287. submitValue: false,
  5288. listeners: {
  5289. change: function(t, value) {
  5290. var dt = Ext.Date.add(new Date(), Ext.Date.YEAR, 10);
  5291. Ext.util.Cookies.set('PVELangCookie', value, dt);
  5292. me.el.mask(gettext('Please wait...'), 'x-mask-loading');
  5293. window.location.reload();
  5294. }
  5295. }
  5296. }
  5297. ],
  5298. buttons: [
  5299. {
  5300. text: gettext('Login'),
  5301. handler: function(){
  5302. document.hiddenlogin.username.value=document.getElementById('loginform-username').value;
  5303. document.hiddenlogin.password.value=document.getElementById('loginform-password').value;
  5304. document.hiddenlogin.submit();
  5305. me.onLogon();
  5306. }
  5307. }
  5308. ]
  5309. }]
  5310.  
  5311. });
  5312.  
  5313. me.callParent();
  5314.  
  5315. }
  5316.  
  5317. });
  5318. // fixme: how can we avoid those lint errors?
  5319. /*jslint confusion: true */
  5320.  
  5321. Ext.define('PVE.window.TaskViewer', {
  5322. extend: 'Ext.window.Window',
  5323. alias: 'widget.pveTaskViewer',
  5324.  
  5325. initComponent: function() {
  5326. var me = this;
  5327.  
  5328. if (!me.upid) {
  5329. throw "no task specified";
  5330. }
  5331.  
  5332. var task = PVE.Utils.parse_task_upid(me.upid);
  5333.  
  5334. var rows = {
  5335. status: {
  5336. header: gettext('Status'),
  5337. defaultValue: 'unknown'
  5338. },
  5339. type: {
  5340. header: 'Task type',
  5341. required: true
  5342. },
  5343. user: {
  5344. header: gettext('User name'),
  5345. required: true
  5346. },
  5347. node: {
  5348. header: gettext('Node'),
  5349. required: true
  5350. },
  5351. pid: {
  5352. header: 'Process ID',
  5353. required: true
  5354. },
  5355. starttime: {
  5356. header: gettext('Start Time'),
  5357. required: true,
  5358. renderer: PVE.Utils.render_timestamp
  5359. },
  5360. upid: {
  5361. header: 'Unique task ID'
  5362. }
  5363. };
  5364.  
  5365. var statstore = Ext.create('PVE.data.ObjectStore', {
  5366. url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
  5367. interval: 1000,
  5368. rows: rows
  5369. });
  5370.  
  5371. me.on('destroy', statstore.stopUpdate);
  5372.  
  5373. var stop_task = function() {
  5374. PVE.Utils.API2Request({
  5375. url: "/nodes/" + task.node + "/tasks/" + me.upid,
  5376. waitMsgTarget: me,
  5377. method: 'DELETE',
  5378. failure: function(response, opts) {
  5379. Ext.Msg.alert('Error', response.htmlStatus);
  5380. }
  5381. });
  5382. };
  5383.  
  5384. var stop_btn1 = new Ext.Button({
  5385. text: gettext('Stop'),
  5386. disabled: true,
  5387. handler: stop_task
  5388. });
  5389.  
  5390. var stop_btn2 = new Ext.Button({
  5391. text: gettext('Stop'),
  5392. disabled: true,
  5393. handler: stop_task
  5394. });
  5395.  
  5396. var statgrid = Ext.create('PVE.grid.ObjectGrid', {
  5397. title: gettext('Status'),
  5398. layout: 'fit',
  5399. tbar: [ stop_btn1 ],
  5400. rstore: statstore,
  5401. rows: rows,
  5402. border: false
  5403. });
  5404.  
  5405. var logView = Ext.create('PVE.panel.LogView', {
  5406. title: gettext('Output'),
  5407. tbar: [ stop_btn2 ],
  5408. border: false,
  5409. url: "/api2/extjs/nodes/" + task.node + "/tasks/" + me.upid + "/log"
  5410. });
  5411.  
  5412. me.mon(statstore, 'load', function() {
  5413. var status = statgrid.getObjectValue('status');
  5414.  
  5415. if (status === 'stopped') {
  5416. logView.requestUpdate(undefined, true);
  5417. logView.scrollToEnd = false;
  5418. statstore.stopUpdate();
  5419. }
  5420.  
  5421. stop_btn1.setDisabled(status !== 'running');
  5422. stop_btn2.setDisabled(status !== 'running');
  5423. });
  5424.  
  5425. statstore.startUpdate();
  5426.  
  5427. Ext.applyIf(me, {
  5428. title: "Task viewer: " + task.desc,
  5429. width: 800,
  5430. height: 400,
  5431. layout: 'fit',
  5432. modal: true,
  5433. bodyPadding: 5,
  5434. items: [{
  5435. xtype: 'tabpanel',
  5436. region: 'center',
  5437. items: [ logView, statgrid ]
  5438. }]
  5439. });
  5440.  
  5441. me.callParent();
  5442.  
  5443. logView.fireEvent('show', logView);
  5444. }
  5445. });
  5446.  
  5447. Ext.define('PVE.window.Wizard', {
  5448. extend: 'Ext.window.Window',
  5449.  
  5450. getValues: function(dirtyOnly) {
  5451. var me = this;
  5452.  
  5453. var values = {};
  5454.  
  5455. var form = me.down('form').getForm();
  5456.  
  5457. form.getFields().each(function(field) {
  5458. if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
  5459. PVE.Utils.assemble_field_data(values, field.getSubmitData());
  5460. }
  5461. });
  5462.  
  5463. Ext.Array.each(me.query('inputpanel'), function(panel) {
  5464. PVE.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
  5465. });
  5466.  
  5467. return values;
  5468. },
  5469.  
  5470. initComponent: function() {
  5471. var me = this;
  5472.  
  5473. var tabs = me.items || [];
  5474. delete me.items;
  5475.  
  5476. /*
  5477. * Items may have the following functions:
  5478. * validator(): per tab custom validation
  5479. * onSubmit(): submit handler
  5480. * onGetValues(): overwrite getValues results
  5481. */
  5482.  
  5483. Ext.Array.each(tabs, function(tab) {
  5484. tab.disabled = true;
  5485. });
  5486. tabs[0].disabled = false;
  5487.  
  5488. var check_card = function(card) {
  5489. var valid = true;
  5490. var fields = card.query('field, fieldcontainer');
  5491. if (card.isXType('fieldcontainer')) {
  5492. fields.unshift(card);
  5493. }
  5494. Ext.Array.each(fields, function(field) {
  5495. // Note: not all fielcontainer have isValid()
  5496. if (Ext.isFunction(field.isValid) && !field.isValid()) {
  5497. valid = false;
  5498. }
  5499. });
  5500.  
  5501. if (Ext.isFunction(card.validator)) {
  5502. return card.validator();
  5503. }
  5504.  
  5505. return valid;
  5506. };
  5507.  
  5508.  
  5509. var tbar = Ext.create('Ext.toolbar.Toolbar', {
  5510. ui: 'footer',
  5511. region: 'south',
  5512. margins: '0 5 5 5',
  5513. items: [
  5514. '->',
  5515. {
  5516. text: gettext('Back'),
  5517. disabled: true,
  5518. itemId: 'back',
  5519. minWidth: 60,
  5520. handler: function() {
  5521. var tp = me.down('#wizcontent');
  5522. var atab = tp.getActiveTab();
  5523. var prev = tp.items.indexOf(atab) - 1;
  5524. if (prev < 0) {
  5525. return;
  5526. }
  5527. var ntab = tp.items.getAt(prev);
  5528. if (ntab) {
  5529. tp.setActiveTab(ntab);
  5530. }
  5531.  
  5532.  
  5533. }
  5534. },
  5535. {
  5536. text: gettext('Next'),
  5537. disabled: true,
  5538. itemId: 'next',
  5539. minWidth: 60,
  5540. handler: function() {
  5541.  
  5542. var form = me.down('form').getForm();
  5543.  
  5544. var tp = me.down('#wizcontent');
  5545. var atab = tp.getActiveTab();
  5546. if (!check_card(atab)) {
  5547. return;
  5548. }
  5549.  
  5550. var next = tp.items.indexOf(atab) + 1;
  5551. var ntab = tp.items.getAt(next);
  5552. if (ntab) {
  5553. ntab.enable();
  5554. tp.setActiveTab(ntab);
  5555. }
  5556.  
  5557. }
  5558. },
  5559. {
  5560. text: gettext('Finish'),
  5561. minWidth: 60,
  5562. hidden: true,
  5563. itemId: 'submit',
  5564. handler: function() {
  5565. var tp = me.down('#wizcontent');
  5566. var atab = tp.getActiveTab();
  5567. atab.onSubmit();
  5568. }
  5569. }
  5570. ]
  5571. });
  5572.  
  5573. var display_header = function(newcard) {
  5574. var html = '<h1>' + newcard.title + '</h1>';
  5575. if (newcard.descr) {
  5576. html += newcard.descr;
  5577. }
  5578. me.down('#header').update(html);
  5579. };
  5580.  
  5581. var disable_at = function(card) {
  5582. var tp = me.down('#wizcontent');
  5583. var idx = tp.items.indexOf(card);
  5584. for(;idx < tp.items.getCount();idx++) {
  5585. var nc = tp.items.getAt(idx);
  5586. if (nc) {
  5587. nc.disable();
  5588. }
  5589. }
  5590. };
  5591.  
  5592. var tabchange = function(tp, newcard, oldcard) {
  5593. if (newcard.onSubmit) {
  5594. me.down('#next').setVisible(false);
  5595. me.down('#submit').setVisible(true);
  5596. } else {
  5597. me.down('#next').setVisible(true);
  5598. me.down('#submit').setVisible(false);
  5599. }
  5600. var valid = check_card(newcard);
  5601. me.down('#next').setDisabled(!valid);
  5602. me.down('#submit').setDisabled(!valid);
  5603. me.down('#back').setDisabled(tp.items.indexOf(newcard) == 0);
  5604.  
  5605. if (oldcard && !check_card(oldcard)) {
  5606. disable_at(oldcard);
  5607. }
  5608.  
  5609. var next = tp.items.indexOf(newcard) + 1;
  5610. var ntab = tp.items.getAt(next);
  5611. if (valid && ntab && !newcard.onSubmit) {
  5612. ntab.enable();
  5613. }
  5614. };
  5615.  
  5616. if (me.subject && !me.title) {
  5617. me.title = PVE.Utils.dialog_title(me.subject, true, false);
  5618. }
  5619.  
  5620. Ext.applyIf(me, {
  5621. width: 620,
  5622. height: 400,
  5623. modal: true,
  5624. border: false,
  5625. draggable: true,
  5626. closable: true,
  5627. resizable: false,
  5628. layout: 'border',
  5629. items: [
  5630. {
  5631. // disabled for now - not really needed
  5632. hidden: true,
  5633. region: 'north',
  5634. itemId: 'header',
  5635. layout: 'fit',
  5636. margins: '5 5 0 5',
  5637. bodyPadding: 10,
  5638. html: ''
  5639. },
  5640. {
  5641. xtype: 'form',
  5642. region: 'center',
  5643. layout: 'fit',
  5644. border: false,
  5645. margins: '5 5 0 5',
  5646. fieldDefaults: {
  5647. labelWidth: 100,
  5648. anchor: '100%'
  5649. },
  5650. items: [{
  5651. itemId: 'wizcontent',
  5652. xtype: 'tabpanel',
  5653. activeItem: 0,
  5654. bodyPadding: 10,
  5655. listeners: {
  5656. afterrender: function(tp) {
  5657. var atab = this.getActiveTab();
  5658. tabchange(tp, atab);
  5659. },
  5660. tabchange: function(tp, newcard, oldcard) {
  5661. display_header(newcard);
  5662. tabchange(tp, newcard, oldcard);
  5663. }
  5664. },
  5665. items: tabs
  5666. }]
  5667. },
  5668. tbar
  5669. ]
  5670. });
  5671. me.callParent();
  5672. display_header(tabs[0]);
  5673.  
  5674. Ext.Array.each(me.query('field'), function(field) {
  5675. field.on('validitychange', function(f) {
  5676. var tp = me.down('#wizcontent');
  5677. var atab = tp.getActiveTab();
  5678. var valid = check_card(atab);
  5679. me.down('#next').setDisabled(!valid);
  5680. me.down('#submit').setDisabled(!valid);
  5681. var next = tp.items.indexOf(atab) + 1;
  5682. var ntab = tp.items.getAt(next);
  5683. if (!valid) {
  5684. disable_at(ntab);
  5685. } else if (ntab && !atab.onSubmit) {
  5686. ntab.enable();
  5687. }
  5688. });
  5689. });
  5690. }
  5691. });
  5692. Ext.define('PVE.window.NotesEdit', {
  5693. extend: 'PVE.window.Edit',
  5694.  
  5695. initComponent : function() {
  5696. var me = this;
  5697.  
  5698. Ext.apply(me, {
  5699. title: gettext('Notes'),
  5700. width: 600,
  5701. layout: 'fit',
  5702. items: {
  5703. xtype: 'textarea',
  5704. name: 'description',
  5705. rows: 7,
  5706. value: '',
  5707. hideLabel: true
  5708. }
  5709. });
  5710.  
  5711. me.callParent();
  5712.  
  5713. me.load();
  5714. }
  5715. });
  5716. Ext.define('PVE.window.Backup', {
  5717. extend: 'Ext.window.Window',
  5718.  
  5719. resizable: false,
  5720.  
  5721. initComponent : function() {
  5722. var me = this;
  5723.  
  5724. if (!me.nodename) {
  5725. throw "no node name specified";
  5726. }
  5727.  
  5728. if (!me.vmid) {
  5729. throw "no VM ID specified";
  5730. }
  5731.  
  5732. if (!me.vmtype) {
  5733. throw "no VM type specified";
  5734. }
  5735.  
  5736. var storagesel = Ext.create('PVE.form.StorageSelector', {
  5737. nodename: me.nodename,
  5738. name: 'storage',
  5739. value: me.storage,
  5740. fieldLabel: gettext('Storage'),
  5741. storageContent: 'backup',
  5742. allowBlank: false
  5743. });
  5744.  
  5745. me.formPanel = Ext.create('Ext.form.Panel', {
  5746. bodyPadding: 10,
  5747. border: false,
  5748. fieldDefaults: {
  5749. labelWidth: 100,
  5750. anchor: '100%'
  5751. },
  5752. items: [
  5753. storagesel,
  5754. {
  5755. xtype: 'pveBackupModeSelector',
  5756. fieldLabel: gettext('Mode'),
  5757. value: 'snapshot',
  5758. name: 'mode'
  5759. },
  5760. {
  5761. xtype: 'pveCompressionSelector',
  5762. name: 'compress',
  5763. value: 'lzo',
  5764. fieldLabel: gettext('Compression')
  5765. }
  5766. ]
  5767. });
  5768.  
  5769. var form = me.formPanel.getForm();
  5770.  
  5771. var submitBtn = Ext.create('Ext.Button', {
  5772. text: gettext('Backup'),
  5773. handler: function(){
  5774. var storage = storagesel.getValue();
  5775. var values = form.getValues();
  5776. var params = {
  5777. storage: storage,
  5778. vmid: me.vmid,
  5779. mode: values.mode,
  5780. remove: 0
  5781. };
  5782. if (values.compress) {
  5783. params.compress = values.compress;
  5784. }
  5785.  
  5786. PVE.Utils.API2Request({
  5787. url: '/nodes/' + me.nodename + '/vzdump',
  5788. params: params,
  5789. method: 'POST',
  5790. failure: function (response, opts) {
  5791. Ext.Msg.alert('Error',response.htmlStatus);
  5792. },
  5793. success: function(response, options) {
  5794. var upid = response.result.data;
  5795.  
  5796. var win = Ext.create('PVE.window.TaskViewer', {
  5797. upid: upid
  5798. });
  5799. win.show();
  5800. me.close();
  5801. }
  5802. });
  5803. }
  5804. });
  5805.  
  5806. var title = gettext('Backup') + " " +
  5807. ((me.vmtype === 'openvz') ? "CT" : "VM") +
  5808. " " + me.vmid;
  5809.  
  5810. Ext.apply(me, {
  5811. title: title,
  5812. width: 350,
  5813. modal: true,
  5814. layout: 'auto',
  5815. border: false,
  5816. items: [ me.formPanel ],
  5817. buttons: [ submitBtn ]
  5818. });
  5819.  
  5820. me.callParent();
  5821. }
  5822. });
  5823. Ext.define('PVE.window.Restore', {
  5824. extend: 'Ext.window.Window', // fixme: PVE.window.Edit?
  5825.  
  5826. resizable: false,
  5827.  
  5828. initComponent : function() {
  5829. var me = this;
  5830.  
  5831. if (!me.nodename) {
  5832. throw "no node name specified";
  5833. }
  5834.  
  5835. if (!me.volid) {
  5836. throw "no volume ID specified";
  5837. }
  5838.  
  5839. if (!me.vmtype) {
  5840. throw "no vmtype specified";
  5841. }
  5842.  
  5843. var storagesel = Ext.create('PVE.form.StorageSelector', {
  5844. nodename: me.nodename,
  5845. name: 'storage',
  5846. value: '',
  5847. fieldLabel: gettext('Storage'),
  5848. storageContent: (me.vmtype === 'openvz') ? 'rootdir' : 'images',
  5849. allowBlank: true
  5850. });
  5851.  
  5852. me.formPanel = Ext.create('Ext.form.Panel', {
  5853. bodyPadding: 10,
  5854. border: false,
  5855. fieldDefaults: {
  5856. labelWidth: 60,
  5857. anchor: '100%'
  5858. },
  5859. items: [
  5860. {
  5861. xtype: 'displayfield',
  5862. value: me.volidText || me.volid,
  5863. fieldLabel: gettext('Source')
  5864. },
  5865. storagesel,
  5866. {
  5867. xtype: me.vmid ? 'displayfield' : 'pveVMIDSelector',
  5868. name: 'vmid',
  5869. fieldLabel: 'VM ID',
  5870. value: me.vmid || PVE.data.ResourceStore.findNextVMID(),
  5871. validateExists: false
  5872. }
  5873. ]
  5874. });
  5875.  
  5876. var form = me.formPanel.getForm();
  5877.  
  5878. var doRestore = function(url, params) {
  5879. PVE.Utils.API2Request({
  5880. url: url,
  5881. params: params,
  5882. method: 'POST',
  5883. waitMsgTarget: me,
  5884. failure: function (response, opts) {
  5885. Ext.Msg.alert('Error', response.htmlStatus);
  5886. },
  5887. success: function(response, options) {
  5888. var upid = response.result.data;
  5889.  
  5890. var win = Ext.create('PVE.window.TaskViewer', {
  5891. upid: upid
  5892. });
  5893. win.show();
  5894. me.close();
  5895. }
  5896. });
  5897. };
  5898.  
  5899. var submitBtn = Ext.create('Ext.Button', {
  5900. text: gettext('Restore'),
  5901. handler: function(){
  5902. var storage = storagesel.getValue();
  5903. var values = form.getValues();
  5904.  
  5905. var params = {
  5906. storage: storage,
  5907. vmid: me.vmid || values.vmid,
  5908. force: me.vmid ? 1 : 0
  5909. };
  5910.  
  5911. var url;
  5912. if (me.vmtype === 'openvz') {
  5913. url = '/nodes/' + me.nodename + '/openvz';
  5914. params.ostemplate = me.volid;
  5915. params.restore = 1;
  5916. } else if (me.vmtype === 'qemu') {
  5917. url = '/nodes/' + me.nodename + '/qemu';
  5918. params.archive = me.volid;
  5919. } else {
  5920. throw 'unknown VM type';
  5921. }
  5922.  
  5923. if (me.vmid) {
  5924. var msg = gettext('Are you sure you want to restore this VM?') + ' ' +
  5925. gettext('This will permanently erase current VM data.');
  5926. Ext.Msg.confirm('Confirmation', msg, function(btn) {
  5927. if (btn !== 'yes') {
  5928. return;
  5929. }
  5930. doRestore(url, params);
  5931. });
  5932. } else {
  5933. doRestore(url, params);
  5934. }
  5935. }
  5936. });
  5937.  
  5938. form.on('validitychange', function(f, valid) {
  5939. submitBtn.setDisabled(!valid);
  5940. });
  5941.  
  5942. var title = (me.vmtype === 'openvz') ? "Restore CT" : "Restore VM";
  5943.  
  5944. Ext.apply(me, {
  5945. title: title,
  5946. width: 450,
  5947. modal: true,
  5948. layout: 'auto',
  5949. border: false,
  5950. items: [ me.formPanel ],
  5951. buttons: [ submitBtn ]
  5952. });
  5953.  
  5954. me.callParent();
  5955. }
  5956. });
  5957. Ext.define('PVE.panel.NotesView', {
  5958. extend: 'Ext.panel.Panel',
  5959.  
  5960. load: function() {
  5961. var me = this;
  5962.  
  5963. PVE.Utils.API2Request({
  5964. url: me.url,
  5965. waitMsgTarget: me,
  5966. failure: function(response, opts) {
  5967. me.update("Error " + response.htmlStatus);
  5968. },
  5969. success: function(response, opts) {
  5970. var data = response.result.data.description || '';
  5971. me.update(Ext.htmlEncode(data));
  5972. }
  5973. });
  5974. },
  5975.  
  5976. initComponent : function() {
  5977. var me = this;
  5978.  
  5979. var nodename = me.pveSelNode.data.node;
  5980. if (!nodename) {
  5981. throw "no node name specified";
  5982. }
  5983.  
  5984. var vmid = me.pveSelNode.data.vmid;
  5985. if (!vmid) {
  5986. throw "no VM ID specified";
  5987. }
  5988.  
  5989. var vmtype = me.pveSelNode.data.type;
  5990. var url;
  5991.  
  5992. if (vmtype === 'qemu') {
  5993. me.url = '/api2/extjs/nodes/' + nodename + '/qemu/' + vmid + '/config';
  5994. } else if (vmtype === 'openvz') {
  5995. me.url = '/api2/extjs/nodes/' + nodename + '/openvz/' + vmid + '/config';
  5996. } else {
  5997. throw "unknown vm type '" + vmtype + "'";
  5998. }
  5999.  
  6000. Ext.apply(me, {
  6001. title: gettext("Notes"),
  6002. style: 'padding-left:10px',
  6003. bodyStyle: 'white-space:pre',
  6004. bodyPadding: 10,
  6005. autoScroll: true,
  6006. listeners: {
  6007. render: function(c) {
  6008. c.el.on('dblclick', function() {
  6009. var win = Ext.create('PVE.window.NotesEdit', {
  6010. pveSelNode: me.pveSelNode,
  6011. url: me.url
  6012. });
  6013. win.show();
  6014. win.on('destroy', me.load, me);
  6015. });
  6016. }
  6017. }
  6018. });
  6019.  
  6020. me.callParent();
  6021. }
  6022. });
  6023. Ext.override(Ext.view.Table, {
  6024. afterRender: function() {
  6025. var me = this;
  6026.  
  6027. me.callParent();
  6028. me.mon(me.el, {
  6029. scroll: me.fireBodyScroll,
  6030. scope: me
  6031. });
  6032. if (!me.featuresMC ||
  6033. (me.featuresMC.findIndex('ftype', 'selectable') < 0)) {
  6034. me.el.unselectable();
  6035. }
  6036.  
  6037. me.attachEventsForFeatures();
  6038. }
  6039. });
  6040.  
  6041. Ext.define('PVE.grid.SelectFeature', {
  6042. extend: 'Ext.grid.feature.Feature',
  6043. alias: 'feature.selectable',
  6044.  
  6045. mutateMetaRowTpl: function(metaRowTpl) {
  6046. var tpl, i,
  6047. ln = metaRowTpl.length;
  6048.  
  6049. for (i = 0; i < ln; i++) {
  6050. tpl = metaRowTpl[i];
  6051. tpl = tpl.replace(/x-grid-row/, 'x-grid-row x-selectable');
  6052. tpl = tpl.replace(/x-grid-cell-inner x-unselectable/g, 'x-grid-cell-inner');
  6053. tpl = tpl.replace(/unselectable="on"/g, '');
  6054. metaRowTpl[i] = tpl;
  6055. }
  6056. }
  6057. });
  6058. Ext.define('PVE.grid.ObjectGrid', {
  6059. extend: 'Ext.grid.GridPanel',
  6060. alias: ['widget.pveObjectGrid'],
  6061.  
  6062. getObjectValue: function(key, defaultValue) {
  6063. var me = this;
  6064. var rec = me.store.getById(key);
  6065. if (rec) {
  6066. return rec.data.value;
  6067. }
  6068. return defaultValue;
  6069. },
  6070.  
  6071. renderKey: function(key, metaData, record, rowIndex, colIndex, store) {
  6072. var me = this;
  6073. var rows = me.rows;
  6074. var rowdef = (rows && rows[key]) ? rows[key] : {};
  6075. return rowdef.header || key;
  6076. },
  6077.  
  6078. renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
  6079. var me = this;
  6080. var rows = me.rows;
  6081. var key = record.data.key;
  6082. var rowdef = (rows && rows[key]) ? rows[key] : {};
  6083.  
  6084. var renderer = rowdef.renderer;
  6085. if (renderer) {
  6086. return renderer(value, metaData, record, rowIndex, colIndex, store);
  6087. }
  6088.  
  6089. return value;
  6090. },
  6091.  
  6092. initComponent : function() {
  6093. var me = this;
  6094.  
  6095. var rows = me.rows;
  6096.  
  6097. if (!me.rstore) {
  6098. if (!me.url) {
  6099. throw "no url specified";
  6100. }
  6101.  
  6102. me.rstore = Ext.create('PVE.data.ObjectStore', {
  6103. url: me.url,
  6104. interval: me.interval,
  6105. extraParams: me.extraParams,
  6106. rows: me.rows
  6107. });
  6108. }
  6109.  
  6110. var rstore = me.rstore;
  6111.  
  6112. var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  6113.  
  6114. if (rows) {
  6115. Ext.Object.each(rows, function(key, rowdef) {
  6116. if (Ext.isDefined(rowdef.defaultValue)) {
  6117. store.add({ key: key, value: rowdef.defaultValue });
  6118. } else if (rowdef.required) {
  6119. store.add({ key: key, value: undefined });
  6120. }
  6121. });
  6122. }
  6123.  
  6124. if (me.sorterFn) {
  6125. store.sorters.add(new Ext.util.Sorter({
  6126. sorterFn: me.sorterFn
  6127. }));
  6128. }
  6129.  
  6130. store.filters.add(new Ext.util.Filter({
  6131. filterFn: function(item) {
  6132. if (rows) {
  6133. var rowdef = rows[item.data.key];
  6134. if (!rowdef || (rowdef.visible === false)) {
  6135. return false;
  6136. }
  6137. }
  6138. return true;
  6139. }
  6140. }));
  6141.  
  6142. PVE.Utils.monStoreErrors(me, rstore);
  6143.  
  6144. Ext.applyIf(me, {
  6145. store: store,
  6146. hideHeaders: true,
  6147. stateful: false,
  6148. columns: [
  6149. {
  6150. header: 'Name',
  6151. width: me.cwidth1 || 100,
  6152. dataIndex: 'key',
  6153. renderer: me.renderKey
  6154. },
  6155. {
  6156. flex: 1,
  6157. header: 'Value',
  6158. dataIndex: 'value',
  6159. renderer: me.renderValue
  6160. }
  6161. ]
  6162. });
  6163.  
  6164. me.callParent();
  6165. }
  6166. });
  6167. // fixme: remove this fix
  6168. // this hack is required for ExtJS 4.0.0
  6169. Ext.override(Ext.grid.feature.Chunking, {
  6170. attachEvents: function() {
  6171. var grid = this.view.up('gridpanel'),
  6172. scroller = grid.down('gridscroller[dock=right]');
  6173. if (scroller === null ) {
  6174. grid.on("afterlayout", this.attachEvents, this);
  6175. return;
  6176. }
  6177. scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
  6178. },
  6179. rowHeight: PVE.Utils.gridLineHeigh()
  6180. });
  6181.  
  6182. Ext.define('PVE.grid.ResourceGrid', {
  6183. extend: 'Ext.grid.GridPanel',
  6184. alias: ['widget.pveResourceGrid'],
  6185.  
  6186. //fixme: this makes still problems with the scrollbar
  6187. //features: [ {ftype: 'chunking'}],
  6188.  
  6189. initComponent : function() {
  6190. var me = this;
  6191.  
  6192. var rstore = PVE.data.ResourceStore;
  6193. var sp = Ext.state.Manager.getProvider();
  6194.  
  6195. var coldef = rstore.defaultColums();
  6196.  
  6197. var store = Ext.create('Ext.data.Store', {
  6198. model: 'PVEResources',
  6199. sorters: [
  6200. {
  6201. property : 'type',
  6202. direction: 'ASC'
  6203. }
  6204. ],
  6205. proxy: { type: 'memory' }
  6206. });
  6207.  
  6208. var textfilter = '';
  6209.  
  6210. var textfilter_match = function(item) {
  6211. var match = false;
  6212. Ext.each(['name', 'storage', 'node', 'type', 'text'], function(field) {
  6213. var v = item.data[field];
  6214. if (v !== undefined) {
  6215. v = v.toLowerCase();
  6216. if (v.indexOf(textfilter) >= 0) {
  6217. match = true;
  6218. return false;
  6219. }
  6220. }
  6221. });
  6222. return match;
  6223. };
  6224.  
  6225. var updateGrid = function() {
  6226.  
  6227. var filterfn = me.viewFilter ? me.viewFilter.filterfn : null;
  6228.  
  6229. //console.log("START GRID UPDATE " + me.viewFilter);
  6230.  
  6231. store.suspendEvents();
  6232.  
  6233. var nodeidx = {};
  6234. var gather_child_nodes = function(cn) {
  6235. if (!cn) {
  6236. return;
  6237. }
  6238. var cs = cn.childNodes;
  6239. if (!cs) {
  6240. return;
  6241. }
  6242. var len = cs.length, i = 0, n, res;
  6243.  
  6244. for (; i < len; i++) {
  6245. var child = cs[i];
  6246. var orgnode = rstore.data.get(child.data.id);
  6247. if (orgnode) {
  6248. if ((!filterfn || filterfn(child)) &&
  6249. (!textfilter || textfilter_match(child))) {
  6250. nodeidx[child.data.id] = orgnode;
  6251. }
  6252. }
  6253. gather_child_nodes(child);
  6254. }
  6255. };
  6256. gather_child_nodes(me.pveSelNode);
  6257.  
  6258. // remove vanished items
  6259. var rmlist = [];
  6260. store.each(function(olditem) {
  6261. var item = nodeidx[olditem.data.id];
  6262. if (!item) {
  6263. //console.log("GRID REM UID: " + olditem.data.id);
  6264. rmlist.push(olditem);
  6265. }
  6266. });
  6267.  
  6268. if (rmlist.length) {
  6269. store.remove(rmlist);
  6270. }
  6271.  
  6272. // add new items
  6273. var addlist = [];
  6274. var key;
  6275. for (key in nodeidx) {
  6276. if (nodeidx.hasOwnProperty(key)) {
  6277. var item = nodeidx[key];
  6278.  
  6279. // getById() use find(), which is slow (ExtJS4 DP5)
  6280. //var olditem = store.getById(item.data.id);
  6281. var olditem = store.data.get(item.data.id);
  6282.  
  6283. if (!olditem) {
  6284. //console.log("GRID ADD UID: " + item.data.id);
  6285. var info = Ext.apply({}, item.data);
  6286. var child = Ext.ModelMgr.create(info, store.model, info.id);
  6287. addlist.push(item);
  6288. continue;
  6289. }
  6290. // try to detect changes
  6291. var changes = false;
  6292. var fieldkeys = PVE.data.ResourceStore.fieldNames;
  6293. var fieldcount = fieldkeys.length;
  6294. var fieldind;
  6295. for (fieldind = 0; fieldind < fieldcount; fieldind++) {
  6296. var field = fieldkeys[fieldind];
  6297. if (field != 'id' && item.data[field] != olditem.data[field]) {
  6298. changes = true;
  6299. //console.log("changed item " + item.id + " " + field + " " + item.data[field] + " != " + olditem.data[field]);
  6300. olditem.beginEdit();
  6301. olditem.set(field, item.data[field]);
  6302. }
  6303. }
  6304. if (changes) {
  6305. olditem.endEdit(true);
  6306. olditem.commit(true);
  6307. }
  6308. }
  6309. }
  6310.  
  6311. if (addlist.length) {
  6312. store.add(addlist);
  6313. }
  6314.  
  6315. store.sort();
  6316.  
  6317. store.resumeEvents();
  6318.  
  6319. store.fireEvent('datachanged', store);
  6320.  
  6321. //console.log("END GRID UPDATE");
  6322. };
  6323.  
  6324. var filter_task = new Ext.util.DelayedTask(function(){
  6325. updateGrid();
  6326. });
  6327.  
  6328. var load_cb = function() {
  6329. updateGrid();
  6330. };
  6331.  
  6332. Ext.applyIf(me, {
  6333. title: gettext('Search')
  6334. });
  6335.  
  6336. Ext.apply(me, {
  6337. store: store,
  6338. tbar: [
  6339. '->',
  6340. gettext('Search') + ':', ' ',
  6341. {
  6342. xtype: 'textfield',
  6343. width: 200,
  6344. value: textfilter,
  6345. enableKeyEvents: true,
  6346. listeners: {
  6347. keyup: function(field, e) {
  6348. var v = field.getValue();
  6349. textfilter = v;
  6350. filter_task.delay(500);
  6351. }
  6352. }
  6353. }
  6354. ],
  6355. viewConfig: {
  6356. stripeRows: true
  6357. },
  6358. listeners: {
  6359. itemcontextmenu: function(v, record, item, index, event) {
  6360. event.stopEvent();
  6361. v.select(record);
  6362. var menu;
  6363.  
  6364. if (record.data.type === 'qemu') {
  6365. menu = Ext.create('PVE.qemu.CmdMenu', {
  6366. pveSelNode: record
  6367. });
  6368. } else if (record.data.type === 'openvz') {
  6369. menu = Ext.create('PVE.openvz.CmdMenu', {
  6370. pveSelNode: record
  6371. });
  6372. } else {
  6373. return;
  6374. }
  6375.  
  6376. menu.showAt(event.getXY());
  6377. },
  6378. itemdblclick: function(v, record) {
  6379. var ws = me.up('pveStdWorkspace');
  6380. ws.selectById(record.data.id);
  6381. },
  6382. destroy: function() {
  6383. rstore.un("load", load_cb);
  6384. }
  6385. },
  6386. columns: coldef
  6387. });
  6388.  
  6389. me.callParent();
  6390.  
  6391. updateGrid();
  6392. rstore.on("load", load_cb);
  6393. }
  6394. });Ext.define('PVE.pool.AddVM', {
  6395. extend: 'PVE.window.Edit',
  6396.  
  6397. initComponent : function() {
  6398. /*jslint confusion: true */
  6399. var me = this;
  6400.  
  6401. if (!me.pool) {
  6402. throw "no pool specified";
  6403. }
  6404.  
  6405. me.create = true;
  6406. me.isAdd = true;
  6407. me.url = "/pools/" + me.pool;
  6408. me.method = 'PUT';
  6409.  
  6410. Ext.apply(me, {
  6411. subject: gettext('Virtual Machine'),
  6412. width: 350,
  6413. items: [
  6414. {
  6415. xtype: 'pveVMIDSelector',
  6416. name: 'vms',
  6417. validateExists: true,
  6418. value: '',
  6419. fieldLabel: "VM ID"
  6420. }
  6421. ]
  6422. });
  6423.  
  6424. me.callParent();
  6425. }
  6426. });
  6427.  
  6428. Ext.define('PVE.pool.AddStorage', {
  6429. extend: 'PVE.window.Edit',
  6430.  
  6431. initComponent : function() {
  6432. /*jslint confusion: true */
  6433. var me = this;
  6434.  
  6435. if (!me.pool) {
  6436. throw "no pool specified";
  6437. }
  6438.  
  6439. me.create = true;
  6440. me.isAdd = true;
  6441. me.url = "/pools/" + me.pool;
  6442. me.method = 'PUT';
  6443.  
  6444. Ext.apply(me, {
  6445. subject: gettext('Storage'),
  6446. width: 350,
  6447. items: [
  6448. {
  6449. xtype: 'PVE.form.StorageSelector',
  6450. name: 'storage',
  6451. nodename: 'localhost',
  6452. autoSelect: false,
  6453. value: '',
  6454. fieldLabel: gettext("Storage")
  6455. }
  6456. ]
  6457. });
  6458.  
  6459. me.callParent();
  6460. }
  6461. });
  6462.  
  6463. Ext.define('PVE.grid.PoolMembers', {
  6464. extend: 'Ext.grid.GridPanel',
  6465. alias: ['widget.pvePoolMembers'],
  6466.  
  6467. // fixme: dynamic status update ?
  6468.  
  6469. initComponent : function() {
  6470. var me = this;
  6471.  
  6472. if (!me.pool) {
  6473. throw "no pool specified";
  6474. }
  6475.  
  6476. var store = Ext.create('Ext.data.Store', {
  6477. model: 'PVEResources',
  6478. sorters: [
  6479. {
  6480. property : 'type',
  6481. direction: 'ASC'
  6482. }
  6483. ],
  6484. proxy: {
  6485. type: 'pve',
  6486. root: 'data.members',
  6487. url: "/api2/json/pools/" + me.pool
  6488. }
  6489. });
  6490.  
  6491. var coldef = PVE.data.ResourceStore.defaultColums();
  6492.  
  6493. var reload = function() {
  6494. store.load();
  6495. };
  6496.  
  6497. var sm = Ext.create('Ext.selection.RowModel', {});
  6498.  
  6499. var remove_btn = new PVE.button.Button({
  6500. text: gettext('Remove'),
  6501. disabled: true,
  6502. selModel: sm,
  6503. confirmMsg: function (rec) {
  6504. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  6505. "'" + rec.data.id + "'");
  6506. },
  6507. handler: function(btn, event, rec) {
  6508. var params = { 'delete': 1 };
  6509. if (rec.data.type === 'storage') {
  6510. params.storage = rec.data.storage;
  6511. } else if (rec.data.type === 'qemu' || rec.data.type === 'openvz') {
  6512. params.vms = rec.data.vmid;
  6513. } else {
  6514. throw "unknown resource type";
  6515. }
  6516.  
  6517. PVE.Utils.API2Request({
  6518. url: '/pools/' + me.pool,
  6519. method: 'PUT',
  6520. params: params,
  6521. waitMsgTarget: me,
  6522. callback: function() {
  6523. reload();
  6524. },
  6525. failure: function (response, opts) {
  6526. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  6527. }
  6528. });
  6529. }
  6530. });
  6531.  
  6532. Ext.apply(me, {
  6533. store: store,
  6534. selModel: sm,
  6535. tbar: [
  6536. {
  6537. text: gettext('Add'),
  6538. menu: new Ext.menu.Menu({
  6539. items: [
  6540. {
  6541. text: gettext('Virtual Machine'),
  6542. iconCls: 'pve-itype-icon-qemu',
  6543. handler: function() {
  6544. var win = Ext.create('PVE.pool.AddVM', { pool: me.pool });
  6545. win.on('destroy', reload);
  6546. win.show();
  6547. }
  6548. },
  6549. {
  6550. text: gettext('Storage'),
  6551. iconCls: 'pve-itype-icon-storage',
  6552. handler: function() {
  6553. var win = Ext.create('PVE.pool.AddStorage', { pool: me.pool });
  6554. win.on('destroy', reload);
  6555. win.show();
  6556. }
  6557. }
  6558. ]
  6559. })
  6560. },
  6561. remove_btn
  6562. ],
  6563. viewConfig: {
  6564. stripeRows: true
  6565. },
  6566. columns: coldef,
  6567. listeners: {
  6568. show: reload
  6569. }
  6570. });
  6571.  
  6572. me.callParent();
  6573. }
  6574. });Ext.define('PVE.tree.ResourceTree', {
  6575. extend: 'Ext.tree.TreePanel',
  6576. alias: ['widget.pveResourceTree'],
  6577.  
  6578. statics: {
  6579. typeDefaults: {
  6580. node: {
  6581. iconCls: 'x-tree-node-server',
  6582. text: gettext('Node list')
  6583. },
  6584. pool: {
  6585. iconCls: 'x-tree-node-pool',
  6586. text: gettext('Resource Pool')
  6587. },
  6588. storage: {
  6589. iconCls: 'x-tree-node-harddisk',
  6590. text: gettext('Storage list')
  6591. },
  6592. qemu: {
  6593. iconCls: 'x-tree-node-computer',
  6594. text: gettext('Virtual Machine')
  6595. },
  6596. openvz: {
  6597. iconCls: 'x-tree-node-openvz',
  6598. text: gettext('OpenVZ Container')
  6599. }
  6600. }
  6601. },
  6602.  
  6603. // private
  6604. nodeSortFn: function(node1, node2) {
  6605. var n1 = node1.data;
  6606. var n2 = node2.data;
  6607.  
  6608. if ((n1.groupbyid && n2.groupbyid) ||
  6609. !(n1.groupbyid || n2.groupbyid)) {
  6610.  
  6611. var tcmp;
  6612.  
  6613. var v1 = n1.type;
  6614. var v2 = n2.type;
  6615.  
  6616. if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
  6617. return tcmp;
  6618. }
  6619.  
  6620. // numeric compare for VM IDs
  6621. if (v1 === 'qemu' || v1 === 'openvz') {
  6622. v1 = n1.vmid;
  6623. v2 = n2.vmid;
  6624. if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
  6625. return tcmp;
  6626. }
  6627. }
  6628.  
  6629. return n1.text > n2.text ? 1 : (n1.text < n2.text ? -1 : 0);
  6630. } else if (n1.groupbyid) {
  6631. return -1;
  6632. } else if (n2.groupbyid) {
  6633. return 1;
  6634. }
  6635. },
  6636.  
  6637. // private: fast binary search
  6638. findInsertIndex: function(node, child, start, end) {
  6639. var me = this;
  6640.  
  6641. var diff = end - start;
  6642.  
  6643. var mid = start + (diff>>1);
  6644.  
  6645. if (diff <= 0) {
  6646. return start;
  6647. }
  6648.  
  6649. var res = me.nodeSortFn(child, node.childNodes[mid]);
  6650. if (res <= 0) {
  6651. return me.findInsertIndex(node, child, start, mid);
  6652. } else {
  6653. return me.findInsertIndex(node, child, mid + 1, end);
  6654. }
  6655. },
  6656.  
  6657. setIconCls: function(info) {
  6658. var me = this;
  6659.  
  6660. var defaults = PVE.tree.ResourceTree.typeDefaults[info.type];
  6661. if (defaults && defaults.iconCls) {
  6662. if (info.running) {
  6663. info.iconCls = defaults.iconCls + "-running";
  6664. } else {
  6665. info.iconCls = defaults.iconCls;
  6666. }
  6667. }
  6668. },
  6669.  
  6670. // private
  6671. addChildSorted: function(node, info) {
  6672. var me = this;
  6673.  
  6674. me.setIconCls(info);
  6675.  
  6676. var defaults;
  6677. if (info.groupbyid) {
  6678. info.text = info.groupbyid;
  6679. if (info.type === 'type') {
  6680. defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
  6681. if (defaults && defaults.text) {
  6682. info.text = defaults.text;
  6683. }
  6684. }
  6685. }
  6686. var child = Ext.ModelMgr.create(info, 'PVETree', info.id);
  6687.  
  6688. var cs = node.childNodes;
  6689. var pos;
  6690. if (cs) {
  6691. pos = cs[me.findInsertIndex(node, child, 0, cs.length)];
  6692. }
  6693.  
  6694. node.insertBefore(child, pos);
  6695.  
  6696. return child;
  6697. },
  6698.  
  6699. // private
  6700. groupChild: function(node, info, groups, level) {
  6701. var me = this;
  6702.  
  6703. var groupby = groups[level];
  6704. var v = info[groupby];
  6705.  
  6706. if (v) {
  6707. var group = node.findChild('groupbyid', v);
  6708. if (!group) {
  6709. var groupinfo;
  6710. if (info.type === groupby) {
  6711. groupinfo = info;
  6712. } else {
  6713. groupinfo = {
  6714. type: groupby,
  6715. id : groupby + "/" + v
  6716. };
  6717. if (groupby !== 'type') {
  6718. groupinfo[groupby] = v;
  6719. }
  6720. }
  6721. groupinfo.leaf = false;
  6722. groupinfo.groupbyid = v;
  6723. group = me.addChildSorted(node, groupinfo);
  6724. // fixme: remove when EXTJS has fixed those bugs?!
  6725. group.expand(); group.collapse();
  6726. }
  6727. if (info.type === groupby) {
  6728. return group;
  6729. }
  6730. if (group) {
  6731. return me.groupChild(group, info, groups, level + 1);
  6732. }
  6733. }
  6734.  
  6735. return me.addChildSorted(node, info);
  6736. },
  6737.  
  6738. initComponent : function() {
  6739. var me = this;
  6740.  
  6741. var rstore = PVE.data.ResourceStore;
  6742. var sp = Ext.state.Manager.getProvider();
  6743.  
  6744. if (!me.viewFilter) {
  6745. me.viewFilter = {};
  6746. }
  6747.  
  6748. var pdata = {
  6749. dataIndex: {},
  6750. updateCount: 0
  6751. };
  6752.  
  6753. var store = Ext.create('Ext.data.TreeStore', {
  6754. model: 'PVETree',
  6755. root: {
  6756. expanded: true,
  6757. id: 'root',
  6758. text: gettext('Datacenter')
  6759. }
  6760. });
  6761.  
  6762. var stateid = 'rid';
  6763.  
  6764. var updateTree = function() {
  6765. var tmp;
  6766.  
  6767. // fixme: suspend events ?
  6768.  
  6769. var rootnode = me.store.getRootNode();
  6770.  
  6771. // remember selected node (and all parents)
  6772. var sm = me.getSelectionModel();
  6773.  
  6774. var lastsel = sm.getSelection()[0];
  6775. var parents = [];
  6776. var p = lastsel;
  6777. while (p && !!(p = p.parentNode)) {
  6778. parents.push(p);
  6779. }
  6780.  
  6781. var index = pdata.dataIndex;
  6782.  
  6783. var groups = me.viewFilter.groups || [];
  6784. var filterfn = me.viewFilter.filterfn;
  6785.  
  6786. // remove vanished or changed items
  6787. var key;
  6788. for (key in index) {
  6789. if (index.hasOwnProperty(key)) {
  6790. var olditem = index[key];
  6791.  
  6792. // getById() use find(), which is slow (ExtJS4 DP5)
  6793. //var item = rstore.getById(olditem.data.id);
  6794. var item = rstore.data.get(olditem.data.id);
  6795.  
  6796. var changed = false;
  6797. if (item) {
  6798. // test if any grouping attributes changed
  6799. var i, len;
  6800. for (i = 0, len = groups.length; i < len; i++) {
  6801. var attr = groups[i];
  6802. if (item.data[attr] != olditem.data[attr]) {
  6803. //console.log("changed " + attr);
  6804. changed = true;
  6805. break;
  6806. }
  6807. }
  6808. if ((item.data.text !== olditem.data.text) ||
  6809. (item.data.node !== olditem.data.node) ||
  6810. (item.data.running !== olditem.data.running)) {
  6811. //console.log("changed node/text/running " + olditem.data.id);
  6812. changed = true;
  6813. }
  6814.  
  6815. // fixme: also test filterfn()?
  6816. }
  6817.  
  6818. if (!item || changed) {
  6819. //console.log("REM UID: " + key + " ITEM " + olditem.data.id);
  6820. if (olditem.isLeaf()) {
  6821. delete index[key];
  6822. var parentNode = olditem.parentNode;
  6823. parentNode.removeChild(olditem, true);
  6824. } else {
  6825. if (item && changed) {
  6826. olditem.beginEdit();
  6827. //console.log("REM UPDATE UID: " + key + " ITEM " + item.data.running);
  6828. var info = olditem.data;
  6829. Ext.apply(info, item.data);
  6830. me.setIconCls(info);
  6831. olditem.commit();
  6832. }
  6833. }
  6834. }
  6835. }
  6836. }
  6837.  
  6838. // add new items
  6839. rstore.each(function(item) {
  6840. var olditem = index[item.data.id];
  6841. if (olditem) {
  6842. return;
  6843. }
  6844.  
  6845. if (filterfn && !filterfn(item)) {
  6846. return;
  6847. }
  6848.  
  6849. //console.log("ADD UID: " + item.data.id);
  6850.  
  6851. var info = Ext.apply({ leaf: true }, item.data);
  6852.  
  6853. var child = me.groupChild(rootnode, info, groups, 0);
  6854. if (child) {
  6855. index[item.data.id] = child;
  6856. }
  6857. });
  6858.  
  6859. // select parent node is selection vanished
  6860. if (lastsel && !rootnode.findChild('id', lastsel.data.id, true)) {
  6861. lastsel = rootnode;
  6862. while (!!(p = parents.shift())) {
  6863. if (!!(tmp = rootnode.findChild('id', p.data.id, true))) {
  6864. lastsel = tmp;
  6865. break;
  6866. }
  6867. }
  6868. me.selectById(lastsel.data.id);
  6869. }
  6870.  
  6871. if (!pdata.updateCount) {
  6872. rootnode.collapse();
  6873. rootnode.expand();
  6874. me.applyState(sp.get(stateid));
  6875. }
  6876.  
  6877. pdata.updateCount++;
  6878. };
  6879.  
  6880. var statechange = function(sp, key, value) {
  6881. if (key === stateid) {
  6882. me.applyState(value);
  6883. }
  6884. };
  6885.  
  6886. sp.on('statechange', statechange);
  6887.  
  6888. Ext.apply(me, {
  6889. store: store,
  6890. viewConfig: {
  6891. // note: animate cause problems with applyState
  6892. animate: false
  6893. },
  6894. //useArrows: true,
  6895. //rootVisible: false,
  6896. //title: 'Resource Tree',
  6897. listeners: {
  6898. itemcontextmenu: function(v, record, item, index, event) {
  6899. event.stopEvent();
  6900. //v.select(record);
  6901. var menu;
  6902.  
  6903. if (record.data.type === 'qemu') {
  6904. menu = Ext.create('PVE.qemu.CmdMenu', {
  6905. pveSelNode: record
  6906. });
  6907. } else if (record.data.type === 'openvz') {
  6908. menu = Ext.create('PVE.openvz.CmdMenu', {
  6909. pveSelNode: record
  6910. });
  6911. } else {
  6912. return;
  6913. }
  6914.  
  6915. menu.showAt(event.getXY());
  6916. },
  6917. destroy: function() {
  6918. rstore.un("load", updateTree);
  6919. }
  6920. },
  6921. setViewFilter: function(view) {
  6922. me.viewFilter = view;
  6923. me.clearTree();
  6924. updateTree();
  6925. },
  6926. clearTree: function() {
  6927. pdata.updateCount = 0;
  6928. var rootnode = me.store.getRootNode();
  6929. rootnode.collapse();
  6930. rootnode.removeAll(true);
  6931. pdata.dataIndex = {};
  6932. me.getSelectionModel().deselectAll();
  6933. },
  6934. selectExpand: function(node) {
  6935. var sm = me.getSelectionModel();
  6936. if (!sm.isSelected(node)) {
  6937. sm.select(node);
  6938. var cn = node;
  6939. while (!!(cn = cn.parentNode)) {
  6940. if (!cn.isExpanded()) {
  6941. cn.expand();
  6942. }
  6943. }
  6944. }
  6945. },
  6946. selectById: function(nodeid) {
  6947. var rootnode = me.store.getRootNode();
  6948. var sm = me.getSelectionModel();
  6949. var node;
  6950. if (nodeid === 'root') {
  6951. node = rootnode;
  6952. } else {
  6953. node = rootnode.findChild('id', nodeid, true);
  6954. }
  6955. if (node) {
  6956. me.selectExpand(node);
  6957. }
  6958. },
  6959. checkVmMigration: function(record) {
  6960. if (!(record.data.type === 'qemu' || record.data.type === 'openvz')) {
  6961. throw "not a vm type";
  6962. }
  6963.  
  6964. var rootnode = me.store.getRootNode();
  6965. var node = rootnode.findChild('id', record.data.id, true);
  6966.  
  6967. if (node && node.data.type === record.data.type &&
  6968. node.data.node !== record.data.node) {
  6969. // defer select (else we get strange errors)
  6970. Ext.defer(function() { me.selectExpand(node); }, 100, me);
  6971. }
  6972. },
  6973. applyState : function(state) {
  6974. var sm = me.getSelectionModel();
  6975. if (state && state.value) {
  6976. me.selectById(state.value);
  6977. } else {
  6978. sm.deselectAll();
  6979. }
  6980. }
  6981. });
  6982.  
  6983. me.callParent();
  6984.  
  6985. var sm = me.getSelectionModel();
  6986. sm.on('select', function(sm, n) {
  6987. sp.set(stateid, { value: n.data.id});
  6988. });
  6989.  
  6990. rstore.on("load", updateTree);
  6991. rstore.startUpdate();
  6992. //rstore.stopUpdate();
  6993. }
  6994.  
  6995. });
  6996. Ext.define('PVE.panel.Config', {
  6997. extend: 'Ext.panel.Panel',
  6998. alias: 'widget.pvePanelConfig',
  6999.  
  7000. initComponent: function() {
  7001. var me = this;
  7002.  
  7003. var stateid = me.hstateid;
  7004.  
  7005. var sp = Ext.state.Manager.getProvider();
  7006.  
  7007. var activeTab;
  7008.  
  7009. if (stateid) {
  7010. var state = sp.get(stateid);
  7011. if (state && state.value) {
  7012. activeTab = state.value;
  7013. }
  7014. }
  7015.  
  7016. var items = me.items || [];
  7017. me.items = undefined;
  7018.  
  7019. var tbar = me.tbar || [];
  7020. me.tbar = undefined;
  7021.  
  7022. var title = me.title || me.pveSelNode.data.text;
  7023. me.title = undefined;
  7024.  
  7025. tbar.unshift('->');
  7026. tbar.unshift({
  7027. xtype: 'tbtext',
  7028. text: title,
  7029. baseCls: 'x-panel-header-text',
  7030. padding: '0 0 5 0'
  7031. });
  7032.  
  7033. Ext.applyIf(me, { showSearch: true });
  7034.  
  7035. if (me.showSearch) {
  7036. items.unshift({
  7037. itemId: 'search',
  7038. xtype: 'pveResourceGrid'
  7039. });
  7040. }
  7041.  
  7042. var toolbar = Ext.create('Ext.toolbar.Toolbar', {
  7043. items: tbar,
  7044. style: 'border:0px;',
  7045. height: 28
  7046. });
  7047.  
  7048. var tab = Ext.create('Ext.tab.Panel', {
  7049. flex: 1,
  7050. border: true,
  7051. activeTab: activeTab,
  7052. defaults: Ext.apply(me.defaults || {}, {
  7053. pveSelNode: me.pveSelNode,
  7054. viewFilter: me.viewFilter,
  7055. workspace: me.workspace,
  7056. border: false
  7057. }),
  7058. items: items,
  7059. listeners: {
  7060. afterrender: function(tp) {
  7061. var first = tp.items.get(0);
  7062. if (first) {
  7063. first.fireEvent('show', first);
  7064. }
  7065. },
  7066. tabchange: function(tp, newcard, oldcard) {
  7067. var ntab = newcard.itemId;
  7068. // Note: '' is alias for first tab.
  7069. // First tab can be 'search' or something else
  7070. if (newcard.itemId === items[0].itemId) {
  7071. ntab = '';
  7072. }
  7073. var state = { value: ntab };
  7074. if (stateid) {
  7075. sp.set(stateid, state);
  7076. }
  7077. }
  7078. }
  7079. });
  7080.  
  7081. Ext.apply(me, {
  7082. layout: { type: 'vbox', align: 'stretch' },
  7083. items: [ toolbar, tab]
  7084. });
  7085.  
  7086. me.callParent();
  7087.  
  7088. var statechange = function(sp, key, state) {
  7089. if (stateid && key === stateid) {
  7090. var atab = tab.getActiveTab().itemId;
  7091. var ntab = state.value || items[0].itemId;
  7092. if (state && ntab && (atab != ntab)) {
  7093. tab.setActiveTab(ntab);
  7094. }
  7095. }
  7096. };
  7097.  
  7098. if (stateid) {
  7099. me.mon(sp, 'statechange', statechange);
  7100. }
  7101. }
  7102. });
  7103. Ext.define('PVE.grid.BackupView', {
  7104. extend: 'Ext.grid.GridPanel',
  7105.  
  7106. alias: ['widget.pveBackupView'],
  7107.  
  7108.  
  7109. initComponent : function() {
  7110. var me = this;
  7111.  
  7112. var nodename = me.pveSelNode.data.node;
  7113. if (!nodename) {
  7114. throw "no node name specified";
  7115. }
  7116.  
  7117. var vmid = me.pveSelNode.data.vmid;
  7118. if (!vmid) {
  7119. throw "no VM ID specified";
  7120. }
  7121.  
  7122. var vmtype = me.pveSelNode.data.type;
  7123. if (!vmtype) {
  7124. throw "no VM type specified";
  7125. }
  7126.  
  7127. var filterFn;
  7128. if (vmtype === 'openvz') {
  7129. filterFn = function(item) {
  7130. return item.data.volid.match(':backup/vzdump-openvz-');
  7131. };
  7132. } else if (vmtype === 'qemu') {
  7133. filterFn = function(item) {
  7134. return item.data.volid.match(':backup/vzdump-qemu-');
  7135. };
  7136. } else {
  7137. throw "unsupported VM type '" + vmtype + "'";
  7138. }
  7139.  
  7140. me.store = Ext.create('Ext.data.Store', {
  7141. model: 'pve-storage-content',
  7142. sorters: {
  7143. property: 'volid',
  7144. order: 'DESC'
  7145. },
  7146. filters: { filterFn: filterFn }
  7147. });
  7148.  
  7149. var reload = Ext.Function.createBuffered(function() {
  7150. if (me.store.proxy.url) {
  7151. me.store.load();
  7152. }
  7153. }, 100);
  7154.  
  7155. var setStorage = function(storage) {
  7156. var url = '/api2/json/nodes/' + nodename + '/storage/' + storage + '/content';
  7157. url += '?content=backup';
  7158.  
  7159. me.store.setProxy({
  7160. type: 'pve',
  7161. url: url
  7162. });
  7163.  
  7164. reload();
  7165. };
  7166.  
  7167. var storagesel = Ext.create('PVE.form.StorageSelector', {
  7168. nodename: nodename,
  7169. fieldLabel: gettext('Storage'),
  7170. labelAlign: 'right',
  7171. storageContent: 'backup',
  7172. allowBlank: false,
  7173. listeners: {
  7174. change: function(f, value) {
  7175. setStorage(value);
  7176. }
  7177. }
  7178. });
  7179.  
  7180. var sm = Ext.create('Ext.selection.RowModel', {});
  7181.  
  7182. var backup_btn = Ext.create('Ext.button.Button', {
  7183. text: gettext('Backup now'),
  7184. handler: function() {
  7185. var win = Ext.create('PVE.window.Backup', {
  7186. nodename: nodename,
  7187. vmid: vmid,
  7188. vmtype: vmtype,
  7189. storage: storagesel.getValue()
  7190. });
  7191. win.show();
  7192. }
  7193. });
  7194.  
  7195. var restore_btn = Ext.create('PVE.button.Button', {
  7196. text: gettext('Restore'),
  7197. disabled: true,
  7198. selModel: sm,
  7199. enableFn: function(rec) {
  7200. return !!rec;
  7201. },
  7202. handler: function(b, e, rec) {
  7203. var volid = rec.data.volid;
  7204.  
  7205. var win = Ext.create('PVE.window.Restore', {
  7206. nodename: nodename,
  7207. vmid: vmid,
  7208. volid: rec.data.volid,
  7209. volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
  7210. vmtype: vmtype
  7211. });
  7212. win.show();
  7213. win.on('destroy', reload);
  7214. }
  7215. });
  7216.  
  7217. var delete_btn = Ext.create('PVE.button.Button', {
  7218. text: gettext('Remove'),
  7219. disabled: true,
  7220. selModel: sm,
  7221. confirmMsg: function(rec) {
  7222. var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  7223. "'" + rec.data.volid + "'");
  7224. msg += " " + gettext('This will permanently erase all image data.');
  7225.  
  7226. return msg;
  7227. },
  7228. enableFn: function(rec) {
  7229. return !!rec;
  7230. },
  7231. handler: function(b, e, rec){
  7232. var storage = storagesel.getValue();
  7233. if (!storage) {
  7234. return;
  7235. }
  7236.  
  7237. var volid = rec.data.volid;
  7238. PVE.Utils.API2Request({
  7239. url: "/nodes/" + nodename + "/storage/" + storage + "/content/" + volid,
  7240. method: 'DELETE',
  7241. waitMsgTarget: me,
  7242. failure: function(response, opts) {
  7243. Ext.Msg.alert('Error', response.htmlStatus);
  7244. },
  7245. success: function(response, options) {
  7246. reload();
  7247. }
  7248. });
  7249. }
  7250. });
  7251.  
  7252. Ext.apply(me, {
  7253. stateful: false,
  7254. selModel: sm,
  7255. tbar: [ backup_btn, restore_btn, delete_btn, '->', storagesel ],
  7256. columns: [
  7257. {
  7258. header: gettext('Name'),
  7259. flex: 1,
  7260. sortable: true,
  7261. renderer: PVE.Utils.render_storage_content,
  7262. dataIndex: 'volid'
  7263. },
  7264. {
  7265. header: gettext('Format'),
  7266. width: 100,
  7267. dataIndex: 'format'
  7268. },
  7269. {
  7270. header: gettext('Size'),
  7271. width: 100,
  7272. renderer: PVE.Utils.format_size,
  7273. dataIndex: 'size'
  7274. }
  7275. ],
  7276. listeners: {
  7277. show: reload
  7278. }
  7279. });
  7280.  
  7281. me.callParent();
  7282. }
  7283. });
  7284. Ext.define('PVE.panel.LogView', {
  7285. extend: 'Ext.panel.Panel',
  7286.  
  7287. alias: ['widget.pveLogView'],
  7288.  
  7289. pageSize: 500,
  7290.  
  7291. lineHeight: 16,
  7292.  
  7293. viewInfo: undefined,
  7294.  
  7295. scrollToEnd: true,
  7296.  
  7297. getMaxDown: function(scrollToEnd) {
  7298. var me = this;
  7299.  
  7300. var target = me.getTargetEl();
  7301. var dom = target.dom;
  7302. if (scrollToEnd) {
  7303. dom.scrollTop = dom.scrollHeight - dom.clientHeight;
  7304. }
  7305.  
  7306. var maxDown = dom.scrollHeight - dom.clientHeight -
  7307. dom.scrollTop;
  7308.  
  7309. return maxDown;
  7310. },
  7311.  
  7312. updateView: function(start, end, total, text) {
  7313. var me = this;
  7314. var el = me.dataCmp.el;
  7315.  
  7316. if (me.viewInfo && me.viewInfo.start === start &&
  7317. me.viewInfo.end === end && me.viewInfo.total === total &&
  7318. me.viewInfo.textLength === text.length) {
  7319. return; // same content
  7320. }
  7321.  
  7322. var maxDown = me.getMaxDown();
  7323. var scrollToEnd = (maxDown <= 0) && me.scrollToEnd;
  7324.  
  7325. el.setStyle('padding-top', start*me.lineHeight);
  7326. el.update(text);
  7327. me.dataCmp.setHeight(total*me.lineHeight);
  7328.  
  7329. if (scrollToEnd) {
  7330. me.getMaxDown(true);
  7331. }
  7332.  
  7333. me.viewInfo = {
  7334. start: start,
  7335. end: end,
  7336. total: total,
  7337. textLength: text.length
  7338. };
  7339. },
  7340.  
  7341. doAttemptLoad: function(start) {
  7342. var me = this;
  7343.  
  7344. PVE.Utils.API2Request({
  7345. url: me.url,
  7346. params: {
  7347. start: start,
  7348. limit: me.pageSize
  7349. },
  7350. method: 'GET',
  7351. success: function(response) {
  7352. PVE.Utils.setErrorMask(me, false);
  7353. var list = response.result.data;
  7354. var total = response.result.total;
  7355. var first = 0, last = 0;
  7356. var text = '';
  7357. Ext.Array.each(list, function(item) {
  7358. if (!first|| item.n < first) {
  7359. first = item.n;
  7360. }
  7361. if (!last || item.n > last) {
  7362. last = item.n;
  7363. }
  7364. text = text + Ext.htmlEncode(item.t) + "<br>";
  7365. });
  7366.  
  7367. if (first && last && total) {
  7368. me.updateView(first -1 , last -1, total, text);
  7369. } else {
  7370. me.updateView(0, 0, 0, '');
  7371. }
  7372. },
  7373. failure: function(response) {
  7374. var msg = response.htmlStatus;
  7375. PVE.Utils.setErrorMask(me, msg);
  7376. }
  7377. });
  7378. },
  7379.  
  7380. attemptLoad: function(start) {
  7381. var me = this;
  7382. if (!me.loadTask) {
  7383. me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
  7384. }
  7385. me.loadTask.delay(200, me.doAttemptLoad, me, [start]);
  7386. },
  7387.  
  7388. requestUpdate: function(top, force) {
  7389. var me = this;
  7390.  
  7391. if (top === undefined) {
  7392. var target = me.getTargetEl();
  7393. top = target.dom.scrollTop;
  7394. }
  7395.  
  7396. var viewStart = parseInt((top / me.lineHeight) - 1, 10);
  7397. if (viewStart < 0) {
  7398. viewStart = 0;
  7399. }
  7400. var viewEnd = parseInt(((top + me.getHeight())/ me.lineHeight) + 1, 10);
  7401. var info = me.viewInfo;
  7402.  
  7403. if (info && !force) {
  7404. if (viewStart >= info.start && viewEnd <= info.end) {
  7405. return;
  7406. }
  7407. }
  7408.  
  7409. var line = parseInt((top / me.lineHeight) - (me.pageSize / 2) + 10, 10);
  7410. if (line < 0) {
  7411. line = 0;
  7412. }
  7413.  
  7414. me.attemptLoad(line);
  7415. },
  7416.  
  7417. afterRender: function() {
  7418. var me = this;
  7419.  
  7420. me.callParent(arguments);
  7421.  
  7422. Ext.Function.defer(function() {
  7423. var target = me.getTargetEl();
  7424. target.on('scroll', function(e) {
  7425. me.requestUpdate();
  7426. });
  7427. me.requestUpdate(0);
  7428. }, 20);
  7429. },
  7430.  
  7431. initComponent : function() {
  7432. /*jslint confusion: true */
  7433.  
  7434. var me = this;
  7435.  
  7436. if (!me.url) {
  7437. throw "no url specified";
  7438. }
  7439.  
  7440. me.dataCmp = Ext.create('Ext.Component', {
  7441. style: 'font:normal 11px tahoma, arial, verdana, sans-serif;' +
  7442. 'line-height: ' + me.lineHeight.toString() + 'px; white-space: pre;'
  7443. });
  7444.  
  7445. me.task = Ext.TaskManager.start({
  7446. run: function() {
  7447. if (!me.isVisible() || !me.scrollToEnd || !me.viewInfo) {
  7448. return;
  7449. }
  7450.  
  7451. var maxDown = me.getMaxDown();
  7452. if (maxDown > 0) {
  7453. return;
  7454. }
  7455.  
  7456. me.requestUpdate(undefined, true);
  7457. },
  7458. interval: 1000
  7459. });
  7460.  
  7461. Ext.apply(me, {
  7462. autoScroll: true,
  7463. layout: 'auto',
  7464. items: me.dataCmp,
  7465. bodyStyle: 'padding: 5px;',
  7466. listeners: {
  7467. show: function() {
  7468. var target = me.getTargetEl();
  7469. if (target && target.dom) {
  7470. target.dom.scrollTop = me.savedScrollTop;
  7471. }
  7472. },
  7473. beforehide: function() {
  7474. // Hack: chrome reset scrollTop to 0, so we save/restore
  7475. var target = me.getTargetEl();
  7476. if (target && target.dom) {
  7477. me.savedScrollTop = target.dom.scrollTop;
  7478. }
  7479. },
  7480. destroy: function() {
  7481. Ext.TaskManager.stop(me.task);
  7482. }
  7483. }
  7484. });
  7485.  
  7486. me.callParent();
  7487. }
  7488. });
  7489. Ext.define('PVE.node.DNSEdit', {
  7490. extend: 'PVE.window.Edit',
  7491. alias: ['widget.pveNodeDNSEdit'],
  7492.  
  7493. initComponent : function() {
  7494. var me = this;
  7495.  
  7496. var nodename = me.pveSelNode.data.node;
  7497. if (!nodename) {
  7498. throw "no node name specified";
  7499. }
  7500.  
  7501. me.items = [
  7502. {
  7503. xtype: 'textfield',
  7504. fieldLabel: 'Search domain',
  7505. name: 'search',
  7506. allowBlank: false
  7507. },
  7508. {
  7509. xtype: 'pvetextfield',
  7510. fieldLabel: gettext('DNS server') + " 1",
  7511. vtype: 'IPAddress',
  7512. skipEmptyText: true,
  7513. name: 'dns1'
  7514. },
  7515. {
  7516. xtype: 'pvetextfield',
  7517. fieldLabel: gettext('DNS server') + " 2",
  7518. vtype: 'IPAddress',
  7519. skipEmptyText: true,
  7520. name: 'dns2'
  7521. },
  7522. {
  7523. xtype: 'pvetextfield',
  7524. fieldLabel: gettext('DNS server') + " 3",
  7525. vtype: 'IPAddress',
  7526. skipEmptyText: true,
  7527. name: 'dns3'
  7528. }
  7529. ];
  7530.  
  7531. Ext.applyIf(me, {
  7532. subject: 'DNS',
  7533. url: "/api2/extjs/nodes/" + nodename + "/dns",
  7534. fieldDefaults: {
  7535. labelWidth: 120
  7536. }
  7537. });
  7538.  
  7539. me.callParent();
  7540.  
  7541. me.load();
  7542. }
  7543. });
  7544. Ext.define('PVE.node.DNSView', {
  7545. extend: 'PVE.grid.ObjectGrid',
  7546. alias: ['widget.pveNodeDNSView'],
  7547.  
  7548. initComponent : function() {
  7549. var me = this;
  7550.  
  7551. var nodename = me.pveSelNode.data.node;
  7552. if (!nodename) {
  7553. throw "no node name specified";
  7554. }
  7555.  
  7556. var run_editor = function() {
  7557. var win = Ext.create('PVE.node.DNSEdit', {
  7558. pveSelNode: me.pveSelNode
  7559. });
  7560. win.show();
  7561. };
  7562.  
  7563. Ext.applyIf(me, {
  7564. url: "/api2/json/nodes/" + nodename + "/dns",
  7565. cwidth1: 130,
  7566. interval: 1000,
  7567. rows: {
  7568. search: { header: 'Search domain', required: true },
  7569. dns1: { header: gettext('DNS server') + " 1", required: true },
  7570. dns2: { header: gettext('DNS server') + " 2" },
  7571. dns3: { header: gettext('DNS server') + " 3" }
  7572. },
  7573. tbar: [
  7574. {
  7575. text: gettext("Edit"),
  7576. handler: run_editor
  7577. }
  7578. ],
  7579. listeners: {
  7580. itemdblclick: run_editor
  7581. }
  7582. });
  7583.  
  7584. me.callParent();
  7585.  
  7586. me.on('show', me.rstore.startUpdate);
  7587. me.on('hide', me.rstore.stopUpdate);
  7588. me.on('destroy', me.rstore.stopUpdate);
  7589. }
  7590. });
  7591. Ext.define('PVE.node.TimeView', {
  7592. extend: 'PVE.grid.ObjectGrid',
  7593. alias: ['widget.pveNodeTimeView'],
  7594.  
  7595. initComponent : function() {
  7596. var me = this;
  7597.  
  7598. var nodename = me.pveSelNode.data.node;
  7599. if (!nodename) {
  7600. throw "no node name specified";
  7601. }
  7602.  
  7603. var tzoffset = (new Date()).getTimezoneOffset()*60000;
  7604. var renderlocaltime = function(value) {
  7605. var servertime = new Date((value * 1000) + tzoffset);
  7606. return Ext.Date.format(servertime, 'Y-m-d H:i:s');
  7607. };
  7608.  
  7609. var run_editor = function() {
  7610. var win = Ext.create('PVE.node.TimeEdit', {
  7611. pveSelNode: me.pveSelNode
  7612. });
  7613. win.show();
  7614. };
  7615.  
  7616. Ext.applyIf(me, {
  7617. url: "/api2/json/nodes/" + nodename + "/time",
  7618. cwidth1: 150,
  7619. interval: 1000,
  7620. rows: {
  7621. timezone: {
  7622. header: gettext('Time zone'),
  7623. required: true
  7624. },
  7625. localtime: {
  7626. header: gettext('Server time'),
  7627. required: true,
  7628. renderer: renderlocaltime
  7629. }
  7630. },
  7631. tbar: [
  7632. {
  7633. text: gettext("Edit"),
  7634. handler: run_editor
  7635. }
  7636. ],
  7637. listeners: {
  7638. itemdblclick: run_editor
  7639. }
  7640. });
  7641.  
  7642. me.callParent();
  7643.  
  7644. me.on('show', me.rstore.startUpdate);
  7645. me.on('hide', me.rstore.stopUpdate);
  7646. me.on('destroy', me.rstore.stopUpdate);
  7647. }
  7648. });
  7649. Ext.define('PVE.node.TimeEdit', {
  7650. extend: 'PVE.window.Edit',
  7651. alias: ['widget.pveNodeTimeEdit'],
  7652.  
  7653. initComponent : function() {
  7654. var me = this;
  7655.  
  7656. var nodename = me.pveSelNode.data.node;
  7657. if (!nodename) {
  7658. throw "no node name specified";
  7659. }
  7660.  
  7661. Ext.applyIf(me, {
  7662. subject: gettext('Time zone'),
  7663. url: "/api2/extjs/nodes/" + nodename + "/time",
  7664. fieldDefaults: {
  7665. labelWidth: 70
  7666. },
  7667. width: 400,
  7668. items: {
  7669. xtype: 'combo',
  7670. fieldLabel: gettext('Time zone'),
  7671. name: 'timezone',
  7672. queryMode: 'local',
  7673. store: new PVE.data.TimezoneStore({autoDestory: true}),
  7674. valueField: 'zone',
  7675. displayField: 'zone',
  7676. triggerAction: 'all',
  7677. forceSelection: true,
  7678. editable: false,
  7679. allowBlank: false
  7680. }
  7681. });
  7682.  
  7683. me.callParent();
  7684.  
  7685. me.load();
  7686. }
  7687. });
  7688. Ext.define('PVE.node.StatusView', {
  7689. extend: 'PVE.grid.ObjectGrid',
  7690. alias: ['widget.pveNodeStatusView'],
  7691.  
  7692. initComponent : function() {
  7693. var me = this;
  7694.  
  7695. var nodename = me.pveSelNode.data.node;
  7696. if (!nodename) {
  7697. throw "no node name specified";
  7698. }
  7699.  
  7700. var render_cpuinfo = function(value) {
  7701. return value.cpus + " x " + value.model;
  7702. };
  7703.  
  7704. var render_loadavg = function(value) {
  7705. return value[0] + ", " + value[1] + ", " + value[2];
  7706. };
  7707.  
  7708. var render_cpu = function(value) {
  7709. var per = value * 100;
  7710. return per.toFixed(2) + "%";
  7711. };
  7712.  
  7713. var render_meminfo = function(value) {
  7714. var per = (value.used / value.total)*100;
  7715. var text = "<div>Total: " + PVE.Utils.format_size(value.total) + "</div>" +
  7716. "<div>Used: " + PVE.Utils.format_size(value.used) + "</div>";
  7717. return text;
  7718. };
  7719.  
  7720. var rows = {
  7721. uptime: { header: 'Uptime', required: true, renderer: PVE.Utils.format_duration_long },
  7722. loadavg: { header: 'Load average', required: true, renderer: render_loadavg },
  7723. cpuinfo: { header: 'CPUs', required: true, renderer: render_cpuinfo },
  7724. cpu: { header: 'CPU usage',required: true, renderer: render_cpu },
  7725. wait: { header: 'IO delay', required: true, renderer: render_cpu },
  7726. memory: { header: 'RAM usage', required: true, renderer: render_meminfo },
  7727. swap: { header: 'SWAP usage', required: true, renderer: render_meminfo },
  7728. rootfs: { header: 'HD space (root)', required: true, renderer: render_meminfo },
  7729. pveversion: { header: 'PVE Manager version', required: true },
  7730. kversion: { header: 'Kernel version', required: true }
  7731. };
  7732.  
  7733. Ext.applyIf(me, {
  7734. cwidth1: 150,
  7735. //height: 276,
  7736. rows: rows
  7737. });
  7738.  
  7739. me.callParent();
  7740. }
  7741. });
  7742. /*jslint confusion: true */
  7743. Ext.define('PVE.node.BCFailCnt', {
  7744. extend: 'Ext.grid.GridPanel',
  7745. alias: ['widget.pveNodeBCFailCnt'],
  7746.  
  7747. initComponent : function() {
  7748. var me = this;
  7749.  
  7750. var nodename = me.pveSelNode.data.node;
  7751. if (!nodename) {
  7752. throw "no node name specified";
  7753. }
  7754.  
  7755. var store = new Ext.data.Store({
  7756. model: 'pve-openvz-ubc',
  7757. proxy: {
  7758. type: 'pve',
  7759. url: '/api2/json/nodes/' + nodename + '/ubcfailcnt'
  7760. },
  7761. sorters: [
  7762. {
  7763. property : 'id',
  7764. direction: 'ASC'
  7765. }
  7766. ]
  7767. });
  7768.  
  7769. var reload = function() {
  7770. store.load();
  7771. };
  7772.  
  7773. Ext.applyIf(me, {
  7774. store: store,
  7775. stateful: false,
  7776. columns: [
  7777. {
  7778. header: 'Container',
  7779. width: 100,
  7780. dataIndex: 'id'
  7781. },
  7782. {
  7783. header: 'failcnt',
  7784. flex: 1,
  7785. dataIndex: 'failcnt'
  7786. }
  7787. ],
  7788. listeners: {
  7789. show: reload,
  7790. itemdblclick: function(v, record) {
  7791. var ws = me.up('pveStdWorkspace');
  7792. ws.selectById('openvz/' + record.data.id);
  7793. }
  7794. }
  7795. });
  7796.  
  7797. me.callParent();
  7798.  
  7799. }
  7800. }, function() {
  7801.  
  7802. Ext.define('pve-openvz-ubc', {
  7803. extend: "Ext.data.Model",
  7804. fields: [ 'id', { name: 'failcnt', type: 'number' } ]
  7805. });
  7806.  
  7807. });
  7808. Ext.define('PVE.node.Summary', {
  7809. extend: 'Ext.panel.Panel',
  7810. alias: 'widget.pveNodeSummary',
  7811.  
  7812. initComponent: function() {
  7813. var me = this;
  7814.  
  7815. var nodename = me.pveSelNode.data.node;
  7816. if (!nodename) {
  7817. throw "no node name specified";
  7818. }
  7819.  
  7820. if (!me.statusStore) {
  7821. throw "no status storage specified";
  7822. }
  7823.  
  7824. var rstore = me.statusStore;
  7825.  
  7826. var statusview = Ext.create('PVE.node.StatusView', {
  7827. title: 'Status',
  7828. pveSelNode: me.pveSelNode,
  7829. style: 'padding-top:0px',
  7830. rstore: rstore
  7831. });
  7832.  
  7833. var rrdurl = "/api2/png/nodes/" + nodename + "/rrd";
  7834.  
  7835. Ext.apply(me, {
  7836. autoScroll: true,
  7837. bodyStyle: 'padding:10px',
  7838. defaults: {
  7839. width: 800,
  7840. style: 'padding-top:10px'
  7841. },
  7842. tbar: [ '->', { xtype: 'pveRRDTypeSelector' } ],
  7843. items: [
  7844. statusview,
  7845. {
  7846. xtype: 'pveRRDView',
  7847. title: "CPU usage %",
  7848. datasource: 'cpu,iowait',
  7849. rrdurl: rrdurl
  7850. },
  7851. {
  7852. xtype: 'pveRRDView',
  7853. title: "Server load",
  7854. datasource: 'loadavg',
  7855. rrdurl: rrdurl
  7856. },
  7857. {
  7858. xtype: 'pveRRDView',
  7859. title: "Memory usage",
  7860. datasource: 'memtotal,memused',
  7861. rrdurl: rrdurl
  7862. },
  7863. {
  7864. xtype: 'pveRRDView',
  7865. title: "Network traffic",
  7866. datasource: 'netin,netout',
  7867. rrdurl: rrdurl
  7868. }
  7869. ],
  7870. listeners: {
  7871. show: rstore.startUpdate,
  7872. hide: rstore.stopUpdate,
  7873. destroy: rstore.stopUpdate
  7874. }
  7875. });
  7876.  
  7877. me.callParent();
  7878. }
  7879. });
  7880. Ext.define('PVE.node.ServiceView', {
  7881. extend: 'Ext.grid.GridPanel',
  7882.  
  7883. alias: ['widget.pveNodeServiceView'],
  7884.  
  7885. initComponent : function() {
  7886. var me = this;
  7887.  
  7888. var nodename = me.pveSelNode.data.node;
  7889. if (!nodename) {
  7890. throw "no node name specified";
  7891. }
  7892.  
  7893. var rstore = Ext.create('PVE.data.UpdateStore', {
  7894. interval: 1000,
  7895. storeid: 'pve-services',
  7896. model: 'pve-services',
  7897. proxy: {
  7898. type: 'pve',
  7899. url: "/api2/json/nodes/" + nodename + "/services"
  7900. }
  7901. });
  7902.  
  7903. var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  7904.  
  7905. var service_cmd = function(cmd) {
  7906. var sm = me.getSelectionModel();
  7907. var rec = sm.getSelection()[0];
  7908. PVE.Utils.API2Request({
  7909. url: "/nodes/" + nodename + "/services/" + rec.data.service + "/" + cmd,
  7910. method: 'POST',
  7911. failure: function(response, opts) {
  7912. Ext.Msg.alert('Error', response.htmlStatus);
  7913. me.loading = true;
  7914. },
  7915. success: function(response, opts) {
  7916. rstore.startUpdate();
  7917. var upid = response.result.data;
  7918.  
  7919. var win = Ext.create('PVE.window.TaskViewer', {
  7920. upid: upid
  7921. });
  7922. win.show();
  7923. }
  7924. });
  7925. };
  7926.  
  7927. var start_btn = new Ext.Button({
  7928. text: gettext('Start'),
  7929. disabled: true,
  7930. handler: function(){
  7931. service_cmd("start");
  7932. }
  7933. });
  7934.  
  7935. var stop_btn = new Ext.Button({
  7936. text: gettext('Stop'),
  7937. disabled: true,
  7938. handler: function(){
  7939. service_cmd("stop");
  7940. }
  7941. });
  7942.  
  7943. var restart_btn = new Ext.Button({
  7944. text: gettext('Restart'),
  7945. disabled: true,
  7946. handler: function(){
  7947. service_cmd("restart");
  7948. }
  7949. });
  7950.  
  7951. var set_button_status = function() {
  7952. var sm = me.getSelectionModel();
  7953. var rec = sm.getSelection()[0];
  7954.  
  7955. if (!rec) {
  7956. start_btn.disable();
  7957. stop_btn.disable();
  7958. restart_btn.disable();
  7959. return;
  7960. }
  7961. var service = rec.data.service;
  7962. var state = rec.data.state;
  7963. if (service == 'apache' ||
  7964. service == 'pvecluster' ||
  7965. service == 'pvedaemon') {
  7966. if (state == 'running') {
  7967. start_btn.disable();
  7968. restart_btn.enable();
  7969. } else {
  7970. start_btn.enable();
  7971. restart_btn.disable();
  7972. }
  7973. stop_btn.disable();
  7974. } else {
  7975. if (state == 'running') {
  7976. start_btn.disable();
  7977. restart_btn.enable();
  7978. stop_btn.enable();
  7979. } else {
  7980. start_btn.enable();
  7981. restart_btn.disable();
  7982. stop_btn.disable();
  7983. }
  7984. }
  7985. };
  7986.  
  7987. me.mon(store, 'datachanged', set_button_status);
  7988.  
  7989. PVE.Utils.monStoreErrors(me, rstore);
  7990.  
  7991. Ext.apply(me, {
  7992. store: store,
  7993. stateful: false,
  7994. tbar: [ start_btn, stop_btn, restart_btn ],
  7995. columns: [
  7996. {
  7997. header: gettext('Name'),
  7998. width: 100,
  7999. sortable: true,
  8000. dataIndex: 'name'
  8001. },
  8002. {
  8003. header: gettext('Status'),
  8004. width: 100,
  8005. sortable: true,
  8006. dataIndex: 'state'
  8007. },
  8008. {
  8009. header: gettext('Description'),
  8010. dataIndex: 'desc',
  8011. flex: 1
  8012. }
  8013. ],
  8014. listeners: {
  8015. selectionchange: set_button_status,
  8016. show: rstore.startUpdate,
  8017. hide: rstore.stopUpdate,
  8018. destroy: rstore.stopUpdate
  8019. }
  8020. });
  8021.  
  8022. me.callParent();
  8023. }
  8024. }, function() {
  8025.  
  8026. Ext.define('pve-services', {
  8027. extend: 'Ext.data.Model',
  8028. fields: [ 'service', 'name', 'desc', 'state' ],
  8029. idProperty: 'service'
  8030. });
  8031.  
  8032. });
  8033. Ext.define('PVE.node.NetworkEdit', {
  8034. extend: 'PVE.window.Edit',
  8035. alias: ['widget.pveNodeNetworkEdit'],
  8036.  
  8037. initComponent : function() {
  8038. var me = this;
  8039.  
  8040. var nodename = me.pveSelNode.data.node;
  8041. if (!nodename) {
  8042. throw "no node name specified";
  8043. }
  8044.  
  8045. if (!me.iftype) {
  8046. throw "no network device type specified";
  8047. }
  8048.  
  8049. me.create = !me.iface;
  8050.  
  8051. var iface_vtype;
  8052.  
  8053. if (me.iftype === 'bridge') {
  8054. me.subject = "Bridge";
  8055. iface_vtype = 'BridgeName';
  8056. } else if (me.iftype === 'bond') {
  8057. me.subject = "Bond";
  8058. iface_vtype = 'BondName';
  8059. } else if (me.iftype === 'eth' && !me.create) {
  8060. me.subject = gettext("Network Device");
  8061. } else {
  8062. throw "no known network device type specified";
  8063. }
  8064.  
  8065. var column2 = [
  8066. {
  8067. xtype: 'pvecheckbox',
  8068. fieldLabel: 'Autostart',
  8069. name: 'autostart',
  8070. uncheckedValue: 0,
  8071. checked: me.create ? true : undefined
  8072. }
  8073. ];
  8074.  
  8075. if (me.iftype === 'bridge') {
  8076. column2.push({
  8077. xtype: 'textfield',
  8078. fieldLabel: 'Bridge ports',
  8079. name: 'bridge_ports'
  8080. });
  8081. } else if (me.iftype === 'bond') {
  8082. column2.push({
  8083. xtype: 'textfield',
  8084. fieldLabel: 'Slaves',
  8085. name: 'slaves'
  8086. });
  8087. column2.push({
  8088. xtype: 'bondModeSelector',
  8089. fieldLabel: 'Mode',
  8090. name: 'bond_mode',
  8091. value: me.create ? 'balance-rr' : undefined,
  8092. allowBlank: false
  8093. });
  8094. }
  8095.  
  8096. var url;
  8097. var method;
  8098.  
  8099. if (me.create) {
  8100. url = "/api2/extjs/nodes/" + nodename + "/network";
  8101. method = 'POST';
  8102. } else {
  8103. url = "/api2/extjs/nodes/" + nodename + "/network/" + me.iface;
  8104. method = 'PUT';
  8105. }
  8106.  
  8107. var column1 = [
  8108. {
  8109. xtype: me.create ? 'textfield' : 'displayfield',
  8110. fieldLabel: gettext('Name'),
  8111. height: 22, // hack: set same height as text fields
  8112. name: 'iface',
  8113. value: me.iface,
  8114. vtype: iface_vtype,
  8115. allowBlank: false
  8116. },
  8117. {
  8118. xtype: 'pvetextfield',
  8119. deleteEmpty: !me.create,
  8120. fieldLabel: gettext('IP address'),
  8121. vtype: 'IPAddress',
  8122. name: 'address'
  8123. },
  8124. {
  8125. xtype: 'pvetextfield',
  8126. deleteEmpty: !me.create,
  8127. fieldLabel: gettext('Subnet mask'),
  8128. vtype: 'IPAddress',
  8129. name: 'netmask',
  8130. validator: function(value) {
  8131. /*jslint confusion: true */
  8132. if (!me.items) {
  8133. return true;
  8134. }
  8135. var address = me.down('field[name=address]').getValue();
  8136. if (value !== '') {
  8137. if (address === '') {
  8138. return "Subnet mask requires option 'IP address'";
  8139. }
  8140. } else {
  8141. if (address !== '') {
  8142. return "Option 'IP address' requires a subnet mask";
  8143. }
  8144. }
  8145.  
  8146. return true;
  8147. }
  8148. },
  8149. {
  8150. xtype: 'pvetextfield',
  8151. deleteEmpty: !me.create,
  8152. fieldLabel: 'Gateway',
  8153. vtype: 'IPAddress',
  8154. name: 'gateway'
  8155. }
  8156. ];
  8157.  
  8158. Ext.applyIf(me, {
  8159. url: url,
  8160. method: method,
  8161. items: {
  8162. xtype: 'inputpanel',
  8163. column1: column1,
  8164. column2: column2
  8165. }
  8166. });
  8167.  
  8168. me.callParent();
  8169.  
  8170. if (me.create) {
  8171. me.down('field[name=iface]').setValue(me.iface_default);
  8172. } else {
  8173. me.load({
  8174. success: function(response, options) {
  8175. var data = response.result.data;
  8176. if (data.type !== me.iftype) {
  8177. var msg = "Got unexpected device type";
  8178. Ext.Msg.alert(gettext('Error'), msg, function() {
  8179. me.close();
  8180. });
  8181. return;
  8182. }
  8183. me.setValues(data);
  8184. me.isValid(); // trigger validation
  8185. }
  8186. });
  8187. }
  8188. }
  8189. });
  8190. Ext.define('PVE.node.NetworkView', {
  8191. extend: 'Ext.panel.Panel',
  8192.  
  8193. alias: ['widget.pveNodeNetworkView'],
  8194.  
  8195. initComponent : function() {
  8196. var me = this;
  8197.  
  8198. var nodename = me.pveSelNode.data.node;
  8199. if (!nodename) {
  8200. throw "no node name specified";
  8201. }
  8202.  
  8203. var store = Ext.create('Ext.data.Store', {
  8204. model: 'pve-networks',
  8205. proxy: {
  8206. type: 'pve',
  8207. url: "/api2/json/nodes/" + nodename + "/network"
  8208. },
  8209. sorters: [
  8210. {
  8211. property : 'iface',
  8212. direction: 'ASC'
  8213. }
  8214. ]
  8215. });
  8216.  
  8217. var reload = function() {
  8218. var changeitem = me.down('#changes');
  8219. PVE.Utils.API2Request({
  8220. url: '/nodes/' + nodename + '/network',
  8221. failure: function(response, opts) {
  8222. changeitem.update('Error: ' + response.htmlStatus);
  8223. store.loadData({});
  8224. },
  8225. success: function(response, opts) {
  8226. var result = Ext.decode(response.responseText);
  8227. store.loadData(result.data);
  8228. var changes = result.changes;
  8229. if (changes === undefined || changes === '') {
  8230. changes = gettext("No changes");
  8231. }
  8232. changeitem.update("<pre>" + Ext.htmlEncode(changes) + "</pre>");
  8233. }
  8234. });
  8235. };
  8236.  
  8237. var run_editor = function() {
  8238. var grid = me.down('gridpanel');
  8239. var sm = grid.getSelectionModel();
  8240. var rec = sm.getSelection()[0];
  8241. if (!rec) {
  8242. return;
  8243. }
  8244.  
  8245. var win = Ext.create('PVE.node.NetworkEdit', {
  8246. pveSelNode: me.pveSelNode,
  8247. iface: rec.data.iface,
  8248. iftype: rec.data.type
  8249. });
  8250. win.show();
  8251. win.on('destroy', reload);
  8252. };
  8253.  
  8254. var edit_btn = new Ext.Button({
  8255. text: gettext('Edit'),
  8256. disabled: true,
  8257. handler: run_editor
  8258. });
  8259.  
  8260. var del_btn = new Ext.Button({
  8261. text: gettext('Remove'),
  8262. disabled: true,
  8263. handler: function(){
  8264. var grid = me.down('gridpanel');
  8265. var sm = grid.getSelectionModel();
  8266. var rec = sm.getSelection()[0];
  8267. if (!rec) {
  8268. return;
  8269. }
  8270.  
  8271. var iface = rec.data.iface;
  8272.  
  8273. PVE.Utils.API2Request({
  8274. url: '/nodes/' + nodename + '/network/' + iface,
  8275. method: 'DELETE',
  8276. waitMsgTarget: me,
  8277. callback: function() {
  8278. reload();
  8279. },
  8280. failure: function(response, opts) {
  8281. Ext.Msg.alert('Error', response.htmlStatus);
  8282. }
  8283. });
  8284. }
  8285. });
  8286.  
  8287. var set_button_status = function() {
  8288. var grid = me.down('gridpanel');
  8289. var sm = grid.getSelectionModel();
  8290. var rec = sm.getSelection()[0];
  8291.  
  8292. edit_btn.setDisabled(!rec);
  8293. del_btn.setDisabled(!rec);
  8294. };
  8295.  
  8296. PVE.Utils.monStoreErrors(me, store);
  8297.  
  8298. var render_ports = function(value, metaData, record) {
  8299. if (value === 'bridge') {
  8300. return record.data.bridge_ports;
  8301. } else if (value === 'bond') {
  8302. return record.data.slaves;
  8303. }
  8304. };
  8305.  
  8306. Ext.apply(me, {
  8307. layout: 'border',
  8308. tbar: [
  8309. {
  8310. text: gettext('Create'),
  8311. menu: new Ext.menu.Menu({
  8312. items: [
  8313. {
  8314. text: 'Bridge',
  8315. handler: function() {
  8316. var next;
  8317. for (next = 0; next <= 9999; next++) {
  8318. if (!store.data.get('vmbr' + next.toString())) {
  8319. break;
  8320. }
  8321. }
  8322.  
  8323. var win = Ext.create('PVE.node.NetworkEdit', {
  8324. pveSelNode: me.pveSelNode,
  8325. iftype: 'bridge',
  8326. iface_default: 'vmbr' + next.toString()
  8327. });
  8328. win.on('destroy', reload);
  8329. win.show();
  8330. }
  8331. },
  8332. {
  8333. text: 'Bond',
  8334. handler: function() {
  8335. var next;
  8336. for (next = 0; next <= 9999; next++) {
  8337. if (!store.data.get('bond' + next.toString())) {
  8338. break;
  8339. }
  8340. }
  8341. var win = Ext.create('PVE.node.NetworkEdit', {
  8342. pveSelNode: me.pveSelNode,
  8343. iftype: 'bond',
  8344. iface_default: 'bond' + next.toString()
  8345. });
  8346. win.on('destroy', reload);
  8347. win.show();
  8348. }
  8349. }
  8350. ]
  8351. })
  8352. }, ' ',
  8353. {
  8354. text: gettext('Revert changes'),
  8355. handler: function() {
  8356. PVE.Utils.API2Request({
  8357. url: '/nodes/' + nodename + '/network',
  8358. method: 'DELETE',
  8359. waitMsgTarget: me,
  8360. callback: function() {
  8361. reload();
  8362. },
  8363. failure: function(response, opts) {
  8364. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  8365. }
  8366. });
  8367. }
  8368. },
  8369. edit_btn,
  8370. del_btn
  8371. ],
  8372. items: [
  8373. {
  8374. xtype: 'gridpanel',
  8375. stateful: false,
  8376. store: store,
  8377. region: 'center',
  8378. border: false,
  8379. columns: [
  8380. {
  8381. header: gettext('Name'),
  8382. width: 100,
  8383. sortable: true,
  8384. dataIndex: 'iface'
  8385. },
  8386. {
  8387. xtype: 'booleancolumn',
  8388. header: gettext('Active'),
  8389. width: 80,
  8390. sortable: true,
  8391. dataIndex: 'active',
  8392. trueText: 'Yes',
  8393. falseText: 'No',
  8394. undefinedText: 'No'
  8395. },
  8396. {
  8397. xtype: 'booleancolumn',
  8398. header: 'Autostart',
  8399. width: 80,
  8400. sortable: true,
  8401. dataIndex: 'autostart',
  8402. trueText: 'Yes',
  8403. falseText: 'No',
  8404. undefinedText: 'No'
  8405. },
  8406. {
  8407. header: 'Ports/Slaves',
  8408. dataIndex: 'type',
  8409. renderer: render_ports
  8410. },
  8411. {
  8412. header: gettext('IP address'),
  8413. sortable: true,
  8414. dataIndex: 'address'
  8415. },
  8416. {
  8417. header: gettext('Subnet mask'),
  8418. sortable: true,
  8419. dataIndex: 'netmask'
  8420. },
  8421. {
  8422. header: 'Gateway',
  8423. sortable: true,
  8424. dataIndex: 'gateway'
  8425. }
  8426. ],
  8427. listeners: {
  8428. selectionchange: set_button_status,
  8429. itemdblclick: run_editor
  8430. }
  8431. },
  8432. {
  8433. border: false,
  8434. region: 'south',
  8435. autoScroll: true,
  8436. itemId: 'changes',
  8437. tbar: [
  8438. gettext('Pending changes') + ' (' +
  8439. gettext('Please reboot to activate changes') + ')'
  8440. ],
  8441. split: true,
  8442. bodyPadding: 5,
  8443. flex: 0.6,
  8444. html: gettext("No changes")
  8445. }
  8446. ],
  8447. listeners: {
  8448. show: reload
  8449. }
  8450. });
  8451.  
  8452. me.callParent();
  8453. }
  8454. }, function() {
  8455.  
  8456. Ext.define('pve-networks', {
  8457. extend: 'Ext.data.Model',
  8458. fields: [
  8459. 'iface', 'type', 'active', 'autostart',
  8460. 'bridge_ports', 'slaves', 'address',
  8461. 'netmask', 'gateway'
  8462. ],
  8463. idProperty: 'iface'
  8464. });
  8465.  
  8466. });
  8467. Ext.define('PVE.node.Tasks', {
  8468. extend: 'Ext.grid.GridPanel',
  8469.  
  8470. alias: ['widget.pveNodeTasks'],
  8471.  
  8472. initComponent : function() {
  8473. var me = this;
  8474.  
  8475. var nodename = me.pveSelNode.data.node;
  8476. if (!nodename) {
  8477. throw "no node name specified";
  8478. }
  8479.  
  8480. var store = Ext.create('Ext.data.Store', {
  8481. pageSize: 500,
  8482. buffered: true,
  8483. remoteFilter: true,
  8484. model: 'pve-tasks',
  8485. proxy: {
  8486. type: 'pve',
  8487. startParam: 'start',
  8488. limitParam: 'limit',
  8489. url: "/api2/json/nodes/" + nodename + "/tasks"
  8490. }
  8491. });
  8492.  
  8493. var userfilter = '';
  8494. var filter_errors = 0;
  8495.  
  8496. // fixme: scroller update fails
  8497. // http://www.sencha.com/forum/showthread.php?133677-scroller-does-not-adjust-to-the-filtered-grid-data&p=602887
  8498. var reload_task = new Ext.util.DelayedTask(function() {
  8499. var params = {
  8500. errors: filter_errors
  8501. };
  8502. if (userfilter) {
  8503. params.userfilter = userfilter;
  8504. }
  8505. store.proxy.extraParams = params;
  8506. store.filter();
  8507. });
  8508.  
  8509. var run_task_viewer = function() {
  8510. var sm = me.getSelectionModel();
  8511. var rec = sm.getSelection()[0];
  8512. if (!rec) {
  8513. return;
  8514. }
  8515.  
  8516. var win = Ext.create('PVE.window.TaskViewer', {
  8517. upid: rec.data.upid
  8518. });
  8519. win.show();
  8520. };
  8521.  
  8522. var view_btn = new Ext.Button({
  8523. text: gettext('View'),
  8524. disabled: true,
  8525. handler: run_task_viewer
  8526. });
  8527.  
  8528.  
  8529. Ext.apply(me, {
  8530. store: store,
  8531. stateful: false,
  8532. verticalScrollerType: 'paginggridscroller',
  8533. loadMask: true,
  8534. invalidateScrollerOnRefresh: false,
  8535. viewConfig: {
  8536. trackOver: false,
  8537. stripeRows: false, // does not work with getRowClass()
  8538.  
  8539. getRowClass: function(record, index) {
  8540. var status = record.get('status');
  8541.  
  8542. if (status && status != 'OK') {
  8543. return "x-form-invalid-field";
  8544. }
  8545. }
  8546. },
  8547. tbar: [
  8548. view_btn, '->', gettext('User name') +':', ' ',
  8549. {
  8550. xtype: 'textfield',
  8551. width: 200,
  8552. value: userfilter,
  8553. enableKeyEvents: true,
  8554. listeners: {
  8555. keyup: function(field, e) {
  8556. userfilter = field.getValue();
  8557. reload_task.delay(500);
  8558. }
  8559. }
  8560. }, ' ', gettext('Only Errors') + ':', ' ',
  8561. {
  8562. xtype: 'checkbox',
  8563. hideLabel: true,
  8564. checked: filter_errors,
  8565. listeners: {
  8566. change: function(field, checked) {
  8567. filter_errors = checked ? 1 : 0;
  8568. reload_task.delay(10);
  8569. }
  8570. }
  8571. }, ' '
  8572. ],
  8573. sortableColumns: false,
  8574. columns: [
  8575. {
  8576. header: gettext("Start Time"),
  8577. dataIndex: 'starttime',
  8578. width: 100,
  8579. renderer: function(value) {
  8580. return Ext.Date.format(value, "M d H:i:s");
  8581. }
  8582. },
  8583. {
  8584. header: gettext("End Time"),
  8585. dataIndex: 'endtime',
  8586. width: 100,
  8587. renderer: function(value, metaData, record) {
  8588. return Ext.Date.format(value,"M d H:i:s");
  8589. }
  8590. },
  8591. {
  8592. header: gettext("Node"),
  8593. dataIndex: 'node',
  8594. width: 100
  8595. },
  8596. {
  8597. header: gettext("User name"),
  8598. dataIndex: 'user',
  8599. width: 150
  8600. },
  8601. {
  8602. header: gettext("Description"),
  8603. dataIndex: 'upid',
  8604. flex: 1,
  8605. renderer: PVE.Utils.render_upid
  8606. },
  8607. {
  8608. header: gettext("Status"),
  8609. dataIndex: 'status',
  8610. width: 200,
  8611. renderer: function(value, metaData, record) {
  8612. if (value == 'OK') {
  8613. return 'OK';
  8614. }
  8615. // metaData.attr = 'style="color:red;"';
  8616. return "ERROR: " + value;
  8617. }
  8618. }
  8619. ],
  8620. listeners: {
  8621. itemdblclick: run_task_viewer,
  8622. selectionchange: function(v, selections) {
  8623. view_btn.setDisabled(!(selections && selections[0]));
  8624. },
  8625. show: function() { reload_task.delay(10); }
  8626. }
  8627. });
  8628.  
  8629. me.callParent();
  8630.  
  8631. store.guaranteeRange(0, store.pageSize - 1);
  8632. }
  8633. });
  8634.  
  8635. Ext.define('PVE.node.Config', {
  8636. extend: 'PVE.panel.Config',
  8637. alias: 'widget.PVE.node.Config',
  8638.  
  8639. initComponent: function() {
  8640. var me = this;
  8641.  
  8642. var nodename = me.pveSelNode.data.node;
  8643. if (!nodename) {
  8644. throw "no node name specified";
  8645. }
  8646.  
  8647. me.statusStore = Ext.create('PVE.data.ObjectStore', {
  8648. url: "/api2/json/nodes/" + nodename + "/status",
  8649. interval: 1000
  8650. });
  8651.  
  8652. var node_command = function(cmd) {
  8653. PVE.Utils.API2Request({
  8654. params: { command: cmd },
  8655. url: '/nodes/' + nodename + '/status',
  8656. method: 'POST',
  8657. waitMsgTarget: me,
  8658. failure: function(response, opts) {
  8659. Ext.Msg.alert('Error', response.htmlStatus);
  8660. }
  8661. });
  8662. };
  8663.  
  8664. var restartBtn = Ext.create('PVE.button.Button', {
  8665. text: gettext('Restart'),
  8666. confirmMsg: Ext.String.format(gettext("Do you really want to restart node {0}?"), nodename),
  8667. handler: function() {
  8668. node_command('reboot');
  8669. }
  8670. });
  8671.  
  8672. var shutdownBtn = Ext.create('PVE.button.Button', {
  8673. text: gettext('Shutdown'),
  8674. confirmMsg: Ext.String.format(gettext("Do you really want to shutdown node {0}?"), nodename),
  8675. handler: function() {
  8676. node_command('shutdown');
  8677. }
  8678. });
  8679.  
  8680. var shellBtn = Ext.create('Ext.Button', {
  8681. text: gettext('Shell'),
  8682. handler: function() {
  8683. var url = Ext.urlEncode({
  8684. console: 'shell',
  8685. node: nodename
  8686. });
  8687. var nw = window.open("?" + url, '_blank',
  8688. "innerWidth=745,innerheight=427");
  8689. nw.focus();
  8690. }
  8691. });
  8692.  
  8693. Ext.apply(me, {
  8694. title: gettext('Node') + " '" + nodename + "'",
  8695. hstateid: 'nodetab',
  8696. defaults: { statusStore: me.statusStore },
  8697. tbar: [ restartBtn, shutdownBtn, shellBtn ],
  8698. items: [
  8699. {
  8700. title: gettext('Summary'),
  8701. itemId: 'summary',
  8702. xtype: 'pveNodeSummary'
  8703. },
  8704. {
  8705. title: gettext('Services'),
  8706. itemId: 'services',
  8707. xtype: 'pveNodeServiceView'
  8708. },
  8709. {
  8710. title: gettext('Network'),
  8711. itemId: 'network',
  8712. xtype: 'pveNodeNetworkView'
  8713. },
  8714. {
  8715. title: 'DNS',
  8716. itemId: 'dns',
  8717. xtype: 'pveNodeDNSView'
  8718. },
  8719. {
  8720. title: gettext('Time'),
  8721. itemId: 'time',
  8722. xtype: 'pveNodeTimeView'
  8723. },
  8724. {
  8725. title: 'Syslog',
  8726. itemId: 'syslog',
  8727. xtype: 'pveLogView',
  8728. url: "/api2/extjs/nodes/" + nodename + "/syslog"
  8729. },
  8730. {
  8731. title: 'Task History',
  8732. itemId: 'tasks',
  8733. xtype: 'pveNodeTasks'
  8734. },
  8735. {
  8736. title: 'UBC',
  8737. itemId: 'ubc',
  8738. xtype: 'pveNodeBCFailCnt'
  8739. }
  8740. ]
  8741. });
  8742.  
  8743. me.callParent();
  8744.  
  8745. me.statusStore.on('load', function(s, records, success) {
  8746. var uptimerec = s.data.get('uptime');
  8747. var uptime = uptimerec ? uptimerec.data.value : false;
  8748.  
  8749. restartBtn.setDisabled(!uptime);
  8750. shutdownBtn.setDisabled(!uptime);
  8751. shellBtn.setDisabled(!uptime);
  8752. });
  8753.  
  8754. me.on('afterrender', function() {
  8755. me.statusStore.startUpdate();
  8756. });
  8757.  
  8758. me.on('destroy', function() {
  8759. me.statusStore.stopUpdate();
  8760. });
  8761. }
  8762. });
  8763. Ext.define('PVE.qemu.StatusView', {
  8764. extend: 'PVE.grid.ObjectGrid',
  8765. alias: ['widget.pveQemuStatusView'],
  8766.  
  8767. initComponent : function() {
  8768. var me = this;
  8769.  
  8770. var nodename = me.pveSelNode.data.node;
  8771. if (!nodename) {
  8772. throw "no node name specified";
  8773. }
  8774.  
  8775. var vmid = me.pveSelNode.data.vmid;
  8776. if (!vmid) {
  8777. throw "no VM ID specified";
  8778. }
  8779.  
  8780. var render_cpu = function(value, metaData, record, rowIndex, colIndex, store) {
  8781. if (!me.getObjectValue('uptime')) {
  8782. return '-';
  8783. }
  8784.  
  8785. var maxcpu = me.getObjectValue('cpus', 1);
  8786.  
  8787. if (!(Ext.isNumeric(value) && Ext.isNumeric(maxcpu) && (maxcpu >= 1))) {
  8788. return '-';
  8789. }
  8790.  
  8791. var per = (value * 100);
  8792.  
  8793. return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
  8794. };
  8795.  
  8796. var render_mem = function(value, metaData, record, rowIndex, colIndex, store) {
  8797. var maxmem = me.getObjectValue('maxmem', 0);
  8798. var per = (value / maxmem)*100;
  8799. var text = "<div>Total: " + PVE.Utils.format_size(maxmem) + "</div>" +
  8800. "<div>Used: " + PVE.Utils.format_size(value) + "</div>";
  8801. return text;
  8802. };
  8803.  
  8804. var rows = {
  8805. name: { header: gettext('Name'), defaultValue: 'no name specified' },
  8806. status: { header: gettext('Status'), defaultValue: 'unknown' },
  8807. cpu: { header: 'CPU usage', required: true, renderer: render_cpu },
  8808. cpus: { visible: false },
  8809. mem: { header: 'Memory usage', required: true, renderer: render_mem },
  8810. maxmem: { visible: false },
  8811. uptime: { header: gettext('Uptime'), required: true, renderer: PVE.Utils.render_uptime },
  8812. ha: { header: 'Managed by HA', required: true, renderer: PVE.Utils.format_boolean }
  8813. };
  8814.  
  8815. Ext.applyIf(me, {
  8816. cwidth1: 150,
  8817. height: 166,
  8818. rows: rows
  8819. });
  8820.  
  8821. me.callParent();
  8822. }
  8823. });
  8824. Ext.define('PVE.window.Migrate', {
  8825. extend: 'Ext.window.Window',
  8826.  
  8827. resizable: false,
  8828.  
  8829. migrate: function(target, online) {
  8830. var me = this;
  8831. PVE.Utils.API2Request({
  8832. params: { target: target, online: online },
  8833. url: '/nodes/' + me.nodename + '/' + me.vmtype + '/' + me.vmid + "/migrate",
  8834. waitMsgTarget: me,
  8835. method: 'POST',
  8836. failure: function(response, opts) {
  8837. Ext.Msg.alert('Error', response.htmlStatus);
  8838. },
  8839. success: function(response, options) {
  8840. var upid = response.result.data;
  8841.  
  8842. var win = Ext.create('PVE.window.TaskViewer', {
  8843. upid: upid
  8844. });
  8845. win.show();
  8846. me.close();
  8847. }
  8848. });
  8849. },
  8850.  
  8851. initComponent : function() {
  8852. var me = this;
  8853.  
  8854. if (!me.nodename) {
  8855. throw "no node name specified";
  8856. }
  8857.  
  8858. if (!me.vmid) {
  8859. throw "no VM ID specified";
  8860. }
  8861.  
  8862. if (!me.vmtype) {
  8863. throw "no VM type specified";
  8864. }
  8865.  
  8866. me.formPanel = Ext.create('Ext.form.Panel', {
  8867. bodyPadding: 10,
  8868. border: false,
  8869. fieldDefaults: {
  8870. labelWidth: 100,
  8871. anchor: '100%'
  8872. },
  8873. items: [
  8874. {
  8875. xtype: 'PVE.form.NodeSelector',
  8876. name: 'target',
  8877. fieldLabel: 'Target node',
  8878. allowBlank: false,
  8879. onlineValidator: true
  8880. },
  8881. {
  8882. xtype: 'pvecheckbox',
  8883. name: 'online',
  8884. uncheckedValue: 0,
  8885. defaultValue: 0,
  8886. fieldLabel: 'Online'
  8887. }
  8888. ]
  8889. });
  8890.  
  8891. var form = me.formPanel.getForm();
  8892.  
  8893. var submitBtn = Ext.create('Ext.Button', {
  8894. text: 'Migrate',
  8895. handler: function() {
  8896. var values = form.getValues();
  8897. me.migrate(values.target, values.online);
  8898. }
  8899. });
  8900.  
  8901. Ext.apply(me, {
  8902. title: "Migrate VM " + me.vmid,
  8903. width: 350,
  8904. modal: true,
  8905. layout: 'auto',
  8906. border: false,
  8907. items: [ me.formPanel ],
  8908. buttons: [ submitBtn ]
  8909. });
  8910.  
  8911. me.callParent();
  8912. }
  8913. });
  8914. Ext.define('PVE.qemu.Monitor', {
  8915. extend: 'Ext.panel.Panel',
  8916.  
  8917. alias: 'widget.pveQemuMonitor',
  8918.  
  8919. maxLines: 500,
  8920.  
  8921. initComponent : function() {
  8922. var me = this;
  8923.  
  8924. var nodename = me.pveSelNode.data.node;
  8925. if (!nodename) {
  8926. throw "no node name specified";
  8927. }
  8928.  
  8929. var vmid = me.pveSelNode.data.vmid;
  8930. if (!vmid) {
  8931. throw "no VM ID specified";
  8932. }
  8933.  
  8934. var lines = [];
  8935.  
  8936. var textbox = Ext.createWidget('panel', {
  8937. region: 'center',
  8938. xtype: 'panel',
  8939. autoScroll: true,
  8940. border: true,
  8941. margins: '5 5 5 5',
  8942. bodyStyle: 'font-family: monospace;'
  8943. });
  8944.  
  8945. var scrollToEnd = function() {
  8946. var el = textbox.getTargetEl();
  8947. var dom = Ext.getDom(el);
  8948.  
  8949. var clientHeight = dom.clientHeight;
  8950. // BrowserBug: clientHeight reports 0 in IE9 StrictMode
  8951. // Instead we are using offsetHeight and hardcoding borders
  8952. if (Ext.isIE9 && Ext.isStrict) {
  8953. clientHeight = dom.offsetHeight + 2;
  8954. }
  8955. dom.scrollTop = dom.scrollHeight - clientHeight;
  8956. };
  8957.  
  8958. var refresh = function() {
  8959. textbox.update('<pre>' + lines.join('\n') + '</pre>');
  8960. scrollToEnd();
  8961. };
  8962.  
  8963. var addLine = function(line) {
  8964. lines.push(line);
  8965. if (lines.length > me.maxLines) {
  8966. lines.shift();
  8967. }
  8968. };
  8969.  
  8970. var executeCmd = function(cmd) {
  8971. addLine("# " + Ext.htmlEncode(cmd));
  8972. refresh();
  8973. PVE.Utils.API2Request({
  8974. params: { command: cmd },
  8975. url: '/nodes/' + nodename + '/qemu/' + vmid + "/monitor",
  8976. method: 'POST',
  8977. waitMsgTarget: me,
  8978. success: function(response, opts) {
  8979. var res = response.result.data;
  8980. Ext.Array.each(res.split('\n'), function(line) {
  8981. addLine(Ext.htmlEncode(line));
  8982. });
  8983. refresh();
  8984. },
  8985. failure: function(response, opts) {
  8986. Ext.Msg.alert('Error', response.htmlStatus);
  8987. }
  8988. });
  8989. };
  8990.  
  8991. Ext.apply(me, {
  8992. layout: { type: 'border' },
  8993. border: false,
  8994. items: [
  8995. textbox,
  8996. {
  8997. region: 'south',
  8998. margins:'0 5 5 5',
  8999. border: false,
  9000. xtype: 'textfield',
  9001. name: 'cmd',
  9002. value: '',
  9003. fieldStyle: 'font-family: monospace;',
  9004. allowBlank: true,
  9005. listeners: {
  9006. afterrender: function(f) {
  9007. f.focus(false);
  9008. addLine("Type 'help' for help.");
  9009. refresh();
  9010. },
  9011. specialkey: function(f, e) {
  9012. if (e.getKey() === e.ENTER) {
  9013. var cmd = f.getValue();
  9014. f.setValue('');
  9015. executeCmd(cmd);
  9016. }
  9017. }
  9018. }
  9019. }
  9020. ],
  9021. listeners: {
  9022. show: function() {
  9023. var field = me.query('textfield[name="cmd"]')[0];
  9024. field.focus(false, true);
  9025. }
  9026. }
  9027. });
  9028.  
  9029. me.callParent();
  9030. }
  9031. });
  9032. Ext.define('PVE.qemu.Summary', {
  9033. extend: 'Ext.panel.Panel',
  9034. alias: 'widget.pveQemuSummary',
  9035.  
  9036. initComponent: function() {
  9037. var me = this;
  9038.  
  9039. var nodename = me.pveSelNode.data.node;
  9040. if (!nodename) {
  9041. throw "no node name specified";
  9042. }
  9043.  
  9044. var vmid = me.pveSelNode.data.vmid;
  9045. if (!vmid) {
  9046. throw "no VM ID specified";
  9047. }
  9048.  
  9049. if (!me.workspace) {
  9050. throw "no workspace specified";
  9051. }
  9052.  
  9053. if (!me.statusStore) {
  9054. throw "no status storage specified";
  9055. }
  9056.  
  9057. var rstore = me.statusStore;
  9058.  
  9059. var statusview = Ext.create('PVE.qemu.StatusView', {
  9060. title: 'Status',
  9061. pveSelNode: me.pveSelNode,
  9062. width: 400,
  9063. rstore: rstore
  9064. });
  9065.  
  9066. var rrdurl = "/api2/png/nodes/" + nodename + "/qemu/" + vmid + "/rrd";
  9067.  
  9068. var notesview = Ext.create('PVE.panel.NotesView', {
  9069. pveSelNode: me.pveSelNode,
  9070. flex: 1
  9071. });
  9072.  
  9073. Ext.apply(me, {
  9074. tbar: [ '->', { xtype: 'pveRRDTypeSelector' } ],
  9075. autoScroll: true,
  9076. bodyStyle: 'padding:10px',
  9077. defaults: {
  9078. style: 'padding-top:10px',
  9079. width: 800
  9080. },
  9081. items: [
  9082. {
  9083. style: 'padding-top:0px',
  9084. layout: {
  9085. type: 'hbox',
  9086. align: 'stretchmax'
  9087. },
  9088. border: false,
  9089. items: [ statusview, notesview ]
  9090. },
  9091. {
  9092. xtype: 'pveRRDView',
  9093. title: "CPU usage %",
  9094. pveSelNode: me.pveSelNode,
  9095. datasource: 'cpu',
  9096. rrdurl: rrdurl
  9097. },
  9098. {
  9099. xtype: 'pveRRDView',
  9100. title: "Memory usage",
  9101. pveSelNode: me.pveSelNode,
  9102. datasource: 'mem,maxmem',
  9103. rrdurl: rrdurl
  9104. },
  9105. {
  9106. xtype: 'pveRRDView',
  9107. title: "Network traffic",
  9108. pveSelNode: me.pveSelNode,
  9109. datasource: 'netin,netout',
  9110. rrdurl: rrdurl
  9111. },
  9112. {
  9113. xtype: 'pveRRDView',
  9114. title: "Disk IO",
  9115. pveSelNode: me.pveSelNode,
  9116. datasource: 'diskread,diskwrite',
  9117. rrdurl: rrdurl
  9118. }
  9119. ]
  9120. });
  9121.  
  9122. me.on('show', function() {
  9123. notesview.load();
  9124. });
  9125.  
  9126. me.callParent();
  9127. }
  9128. });
  9129. Ext.define('PVE.qemu.OSTypeInputPanel', {
  9130. extend: 'PVE.panel.InputPanel',
  9131. alias: 'widget.PVE.qemu.OSTypeInputPanel',
  9132.  
  9133. initComponent : function() {
  9134. var me = this;
  9135.  
  9136. me.column1 = [
  9137. {
  9138. xtype: 'component',
  9139. html: 'Microsoft Windows',
  9140. cls:'x-form-check-group-label'
  9141. },
  9142. {
  9143. xtype: 'radiofield',
  9144. name: 'ostype',
  9145. inputValue: 'win7'
  9146. },
  9147. {
  9148. xtype: 'radiofield',
  9149. name: 'ostype',
  9150. inputValue: 'w2k8'
  9151. },
  9152. {
  9153. xtype: 'radiofield',
  9154. name: 'ostype',
  9155. inputValue: 'wxp'
  9156. },
  9157. {
  9158. xtype: 'radiofield',
  9159. name: 'ostype',
  9160. inputValue: 'w2k'
  9161. }
  9162. ];
  9163.  
  9164. me.column2 = [
  9165. {
  9166. xtype: 'component',
  9167. html: 'Linux/Other',
  9168. cls:'x-form-check-group-label'
  9169. },
  9170. {
  9171. xtype: 'radiofield',
  9172. name: 'ostype',
  9173. inputValue: 'l26'
  9174. },
  9175. {
  9176. xtype: 'radiofield',
  9177. name: 'ostype',
  9178. inputValue: 'l24'
  9179. },
  9180. {
  9181. xtype: 'radiofield',
  9182. name: 'ostype',
  9183. inputValue: 'other'
  9184. }
  9185. ];
  9186.  
  9187. Ext.Array.each(me.column1, function(def) {
  9188. if (def.inputValue) {
  9189. def.boxLabel = PVE.Utils.render_kvm_ostype(def.inputValue);
  9190. }
  9191. });
  9192. Ext.Array.each(me.column2, function(def) {
  9193. if (def.inputValue) {
  9194. def.boxLabel = PVE.Utils.render_kvm_ostype(def.inputValue);
  9195. }
  9196. });
  9197.  
  9198. Ext.apply(me, {
  9199. useFieldContainer: {
  9200. xtype: 'radiogroup',
  9201. allowBlank: false
  9202. }
  9203. });
  9204.  
  9205. me.callParent();
  9206. }
  9207. });
  9208.  
  9209. Ext.define('PVE.qemu.OSTypeEdit', {
  9210. extend: 'PVE.window.Edit',
  9211.  
  9212. initComponent : function() {
  9213. var me = this;
  9214.  
  9215. Ext.apply(me, {
  9216. subject: 'OS Type',
  9217. items: Ext.create('PVE.qemu.OSTypeInputPanel')
  9218. });
  9219.  
  9220. me.callParent();
  9221.  
  9222. me.load({
  9223. success: function(response, options) {
  9224. var value = response.result.data.ostype || 'other';
  9225. me.setValues({ ostype: value});
  9226. }
  9227. });
  9228. }
  9229. });
  9230. Ext.define('PVE.qemu.ProcessorInputPanel', {
  9231. extend: 'PVE.panel.InputPanel',
  9232. alias: 'widget.PVE.qemu.ProcessorInputPanel',
  9233.  
  9234. initComponent : function() {
  9235. var me = this;
  9236.  
  9237. me.column1 = [
  9238. {
  9239. xtype: 'numberfield',
  9240. name: 'sockets',
  9241. minValue: 1,
  9242. maxValue: 4,
  9243. value: '1',
  9244. fieldLabel: 'Sockets',
  9245. allowBlank: false,
  9246. listeners: {
  9247. change: function(f, value) {
  9248. var sockets = me.down('field[name=sockets]').getValue();
  9249. var cores = me.down('field[name=cores]').getValue();
  9250. me.down('field[name=totalcores]').setValue(sockets*cores);
  9251. }
  9252. }
  9253. },
  9254. {
  9255. xtype: 'numberfield',
  9256. name: 'cores',
  9257. minValue: 1,
  9258. maxValue: 32,
  9259. value: '1',
  9260. fieldLabel: 'Cores',
  9261. allowBlank: false,
  9262. listeners: {
  9263. change: function(f, value) {
  9264. var sockets = me.down('field[name=sockets]').getValue();
  9265. var cores = me.down('field[name=cores]').getValue();
  9266. me.down('field[name=totalcores]').setValue(sockets*cores);
  9267. }
  9268. }
  9269. }
  9270. ];
  9271.  
  9272.  
  9273. me.column2 = [
  9274. {
  9275. xtype: 'CPUModelSelector',
  9276. name: 'cpu',
  9277. value: '',
  9278. fieldLabel: 'CPU type'
  9279. },
  9280. {
  9281. xtype: 'displayfield',
  9282. fieldLabel: 'Total cores',
  9283. name: 'totalcores',
  9284. value: '1'
  9285. }
  9286.  
  9287. ];
  9288.  
  9289. me.callParent();
  9290. }
  9291. });
  9292.  
  9293. Ext.define('PVE.qemu.ProcessorEdit', {
  9294. extend: 'PVE.window.Edit',
  9295.  
  9296. initComponent : function() {
  9297. var me = this;
  9298.  
  9299. Ext.apply(me, {
  9300. subject: gettext('Processors'),
  9301. items: Ext.create('PVE.qemu.ProcessorInputPanel')
  9302. });
  9303.  
  9304. me.callParent();
  9305.  
  9306. me.load();
  9307. }
  9308. });Ext.define('PVE.qemu.BootOrderPanel', {
  9309. extend: 'PVE.panel.InputPanel',
  9310.  
  9311. vmconfig: {}, // store loaded vm config
  9312.  
  9313. bootdisk: undefined,
  9314. curSel1: '',
  9315. curSel2: '',
  9316. curSel3: '',
  9317.  
  9318. onGetValues: function(values) {
  9319. var me = this;
  9320.  
  9321. var order = '';
  9322.  
  9323. if (me.curSel1) {
  9324. order = order + me.curSel1;
  9325. }
  9326. if (me.curSel2) {
  9327. order = order + me.curSel2;
  9328. }
  9329. if (me.curSel3) {
  9330. order = order + me.curSel3;
  9331. }
  9332.  
  9333. var res = { boot: order };
  9334. if (me.bootdisk && (me.curSel1 === 'c' || me.curSel2 === 'c' || me.curSel3 === 'c') ) {
  9335. res.bootdisk = me.bootdisk;
  9336. } else {
  9337. res['delete'] = 'bootdisk';
  9338. }
  9339.  
  9340. return res;
  9341. },
  9342.  
  9343. setVMConfig: function(vmconfig) {
  9344. var me = this;
  9345.  
  9346. me.vmconfig = vmconfig;
  9347.  
  9348. var order = me.vmconfig.boot || 'cdn';
  9349. me.bootdisk = me.vmconfig.bootdisk;
  9350. if (!me.vmconfig[me.bootdisk]) {
  9351. me.bootdisk = undefined;
  9352. }
  9353. me.curSel1 = order.substring(0, 1) || '';
  9354. me.curSel2 = order.substring(1, 2) || '';
  9355. me.curSel3 = order.substring(2, 3) || '';
  9356.  
  9357. me.compute_sel1();
  9358.  
  9359. me.kv1.resetOriginalValue();
  9360. me.kv2.resetOriginalValue();
  9361. me.kv3.resetOriginalValue();
  9362. },
  9363.  
  9364. genList: function(includeNone, sel1, sel2) {
  9365. var me = this;
  9366. var list = [];
  9367.  
  9368. if (sel1 !== 'c' && (sel2 !== 'c')) {
  9369. Ext.Object.each(me.vmconfig, function(key, value) {
  9370. if ((/^(ide|scsi|virtio)\d+$/).test(key) &&
  9371. !(/media=cdrom/).test(value)) {
  9372. list.push([key, "Disk '" + key + "'"]);
  9373. }
  9374. });
  9375. }
  9376.  
  9377. if (sel1 !== 'd' && (sel2 !== 'd')) {
  9378. list.push(['d', 'CD-ROM']);
  9379. }
  9380. if (sel1 !== 'n' && (sel2 !== 'n')) {
  9381. list.push(['n', gettext('Network')]);
  9382. }
  9383. //if (sel1 !== 'a' && (sel2 !== 'a')) {
  9384. // list.push(['a', 'Floppy']);
  9385. //}
  9386.  
  9387. if (includeNone) {
  9388. list.push(['', 'none']);
  9389. }
  9390.  
  9391. return list;
  9392. },
  9393.  
  9394. compute_sel3: function() {
  9395. var me = this;
  9396. var list = me.genList(true, me.curSel1, me.curSel2);
  9397. me.kv3.store.loadData(list);
  9398. me.kv3.setValue((me.curSel3 === 'c') ? me.bootdisk : me.curSel3);
  9399. },
  9400.  
  9401. compute_sel2: function() {
  9402. var me = this;
  9403. var list = me.genList(true, me.curSel1);
  9404. me.kv2.store.loadData(list);
  9405. me.kv2.setValue((me.curSel2 === 'c') ? me.bootdisk : me.curSel2);
  9406. me.compute_sel3();
  9407. },
  9408.  
  9409. compute_sel1: function() {
  9410. var me = this;
  9411. var list = me.genList(false);
  9412. me.kv1.store.loadData(list);
  9413. me.kv1.setValue((me.curSel1 === 'c') ? me.bootdisk : me.curSel1);
  9414. me.compute_sel2();
  9415. },
  9416.  
  9417. initComponent : function() {
  9418. var me = this;
  9419.  
  9420. me.kv1 = Ext.create('PVE.form.KVComboBox', {
  9421. fieldLabel: gettext('Boot device') + " 1",
  9422. labelWidth: 120,
  9423. name: 'bd1',
  9424. allowBlank: false,
  9425. data: []
  9426. });
  9427.  
  9428. me.kv2 = Ext.create('PVE.form.KVComboBox', {
  9429. fieldLabel: gettext('Boot device') + " 2",
  9430. labelWidth: 120,
  9431. name: 'bd2',
  9432. allowBlank: false,
  9433. data: []
  9434. });
  9435.  
  9436. me.kv3 = Ext.create('PVE.form.KVComboBox', {
  9437. fieldLabel: gettext('Boot device') + " 3",
  9438. labelWidth: 120,
  9439. name: 'bd3',
  9440. allowBlank: false,
  9441. data: []
  9442. });
  9443.  
  9444. me.mon(me.kv1, 'change', function(t, value) {
  9445. if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9446. me.curSel1 = 'c';
  9447. me.bootdisk = value;
  9448. } else {
  9449. me.curSel1 = value;
  9450. }
  9451. me.compute_sel2();
  9452. });
  9453.  
  9454. me.mon(me.kv2, 'change', function(t, value) {
  9455. if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9456. me.curSel2 = 'c';
  9457. me.bootdisk = value;
  9458. } else {
  9459. me.curSel2 = value;
  9460. }
  9461. me.compute_sel3();
  9462. });
  9463.  
  9464. me.mon(me.kv3, 'change', function(t, value) {
  9465. if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9466. me.curSel3 = 'c';
  9467. me.bootdisk = value;
  9468. } else {
  9469. me.curSel3 = value;
  9470. }
  9471. });
  9472.  
  9473. Ext.apply(me, {
  9474. items: [ me.kv1, me.kv2, me.kv3 ]
  9475. });
  9476.  
  9477. me.callParent();
  9478. }
  9479. });
  9480.  
  9481. Ext.define('PVE.qemu.BootOrderEdit', {
  9482. extend: 'PVE.window.Edit',
  9483.  
  9484. initComponent : function() {
  9485. var me = this;
  9486.  
  9487. var ipanel = Ext.create('PVE.qemu.BootOrderPanel', {});
  9488.  
  9489. me.items = [ ipanel ];
  9490.  
  9491. me.subject = gettext('Boot order');
  9492.  
  9493. me.callParent();
  9494.  
  9495. me.load({
  9496. success: function(response, options) {
  9497. ipanel.setVMConfig(response.result.data);
  9498. }
  9499. });
  9500. }
  9501. });
  9502. Ext.define('PVE.qemu.MemoryInputPanel', {
  9503. extend: 'PVE.panel.InputPanel',
  9504. alias: 'widget.PVE.qemu.MemoryInputPanel',
  9505.  
  9506. insideWizard: false,
  9507.  
  9508. initComponent : function() {
  9509. var me = this;
  9510.  
  9511. var labelWidth = 120;
  9512.  
  9513. var items = {
  9514. xtype: 'numberfield',
  9515. name: 'memory',
  9516. minValue: 32,
  9517. maxValue: 128*1024,
  9518. value: '512',
  9519. step: 32,
  9520. fieldLabel: gettext('Memory') + ' (MB)',
  9521. labelWidth: labelWidth,
  9522. allowBlank: false
  9523. };
  9524.  
  9525. if (me.insideWizard) {
  9526. me.column1 = items;
  9527. } else {
  9528. me.items = items;
  9529. }
  9530.  
  9531. me.callParent();
  9532. }
  9533. });
  9534.  
  9535. Ext.define('PVE.qemu.MemoryEdit', {
  9536. extend: 'PVE.window.Edit',
  9537.  
  9538. initComponent : function() {
  9539. var me = this;
  9540.  
  9541. Ext.apply(me, {
  9542. subject: gettext('Memory'),
  9543. items: Ext.create('PVE.qemu.MemoryInputPanel')
  9544. });
  9545.  
  9546. me.callParent();
  9547.  
  9548. me.load();
  9549. }
  9550. });Ext.define('PVE.qemu.NetworkInputPanel', {
  9551. extend: 'PVE.panel.InputPanel',
  9552. alias: 'widget.PVE.qemu.NetworkInputPanel',
  9553.  
  9554. insideWizard: false,
  9555.  
  9556. onGetValues: function(values) {
  9557. var me = this;
  9558.  
  9559. me.network.model = values.model;
  9560. if (values.networkmode === 'none') {
  9561. return {};
  9562. } else if (values.networkmode === 'bridge') {
  9563. me.network.bridge = values.bridge;
  9564. me.network.tag = values.tag;
  9565. } else {
  9566. me.network.bridge = undefined;
  9567. }
  9568. me.network.macaddr = values.macaddr;
  9569.  
  9570. if (values.rate) {
  9571. me.network.rate = values.rate;
  9572. } else {
  9573. delete me.network.rate;
  9574. }
  9575.  
  9576. var params = {};
  9577.  
  9578. params[me.confid] = PVE.Parser.printQemuNetwork(me.network);
  9579.  
  9580. return params;
  9581. },
  9582.  
  9583. setNetwork: function(confid, data) {
  9584. var me = this;
  9585.  
  9586. me.confid = confid;
  9587.  
  9588. if (data) {
  9589. data.networkmode = data.bridge ? 'bridge' : 'nat';
  9590. } else {
  9591. data = {};
  9592. data.networkmode = 'bridge';
  9593. }
  9594. me.network = data;
  9595.  
  9596. me.setValues(me.network);
  9597. },
  9598.  
  9599. setNodename: function(nodename) {
  9600. var me = this;
  9601.  
  9602. me.bridgesel.setNodename(nodename);
  9603. },
  9604.  
  9605. initComponent : function() {
  9606. var me = this;
  9607.  
  9608. me.network = {};
  9609. me.confid = 'net0';
  9610.  
  9611. me.bridgesel = Ext.create('PVE.form.BridgeSelector', {
  9612. name: 'bridge',
  9613. fieldLabel: 'Bridge',
  9614. nodename: me.nodename,
  9615. labelAlign: 'right',
  9616. autoSelect: true,
  9617. allowBlank: false
  9618. });
  9619.  
  9620. me.column1 = [
  9621. {
  9622. xtype: 'radiofield',
  9623. name: 'networkmode',
  9624. height: 22, // hack: set same height as text fields
  9625. inputValue: 'bridge',
  9626. boxLabel: 'Bridged mode',
  9627. checked: true,
  9628. listeners: {
  9629. change: function(f, value) {
  9630. if (!me.rendered) {
  9631. return;
  9632. }
  9633. me.down('field[name=bridge]').setDisabled(!value);
  9634. me.down('field[name=bridge]').validate();
  9635. me.down('field[name=tag]').setDisabled(!value);
  9636. }
  9637. }
  9638. },
  9639. me.bridgesel,
  9640. {
  9641. xtype: 'numberfield',
  9642. name: 'tag',
  9643. minValue: 1,
  9644. maxValue: 4094,
  9645. value: '',
  9646. emptyText: 'no VLAN',
  9647. fieldLabel: 'VLAN Tag',
  9648. labelAlign: 'right',
  9649. allowBlank: true
  9650. },
  9651. {
  9652. xtype: 'radiofield',
  9653. name: 'networkmode',
  9654. height: 22, // hack: set same height as text fields
  9655. inputValue: 'nat',
  9656. boxLabel: 'NAT mode'
  9657. }
  9658. ];
  9659.  
  9660. if (me.insideWizard) {
  9661. me.column1.push({
  9662. xtype: 'radiofield',
  9663. name: 'networkmode',
  9664. height: 22, // hack: set same height as text fields
  9665. inputValue: 'none',
  9666. boxLabel: 'No network device'
  9667. });
  9668. }
  9669.  
  9670. me.column2 = [
  9671. {
  9672. xtype: 'PVE.form.NetworkCardSelector',
  9673. name: 'model',
  9674. fieldLabel: 'Model',
  9675. value: 'rtl8139',
  9676. allowBlank: false
  9677. },
  9678. {
  9679. xtype: 'textfield',
  9680. name: 'macaddr',
  9681. fieldLabel: 'MAC address',
  9682. vtype: 'MacAddress',
  9683. allowBlank: true,
  9684. emptyText: 'auto'
  9685. },
  9686. {
  9687. xtype: 'numberfield',
  9688. name: 'rate',
  9689. fieldLabel: 'Rate limit (MB/s)',
  9690. minValue: 0,
  9691. maxValue: 10*1024,
  9692. value: '',
  9693. emptyText: 'unlimited',
  9694. allowBlank: true
  9695. }
  9696. ];
  9697.  
  9698. me.callParent();
  9699. }
  9700. });
  9701.  
  9702. Ext.define('PVE.qemu.NetworkEdit', {
  9703. extend: 'PVE.window.Edit',
  9704.  
  9705. isAdd: true,
  9706.  
  9707. initComponent : function() {
  9708. /*jslint confusion: true */
  9709.  
  9710. var me = this;
  9711.  
  9712. var nodename = me.pveSelNode.data.node;
  9713. if (!nodename) {
  9714. throw "no node name specified";
  9715. }
  9716.  
  9717. me.create = me.confid ? false : true;
  9718.  
  9719. var ipanel = Ext.create('PVE.qemu.NetworkInputPanel', {
  9720. confid: me.confid,
  9721. nodename: nodename
  9722. });
  9723.  
  9724. Ext.applyIf(me, {
  9725. subject: gettext('Network Device'),
  9726. items: ipanel
  9727. });
  9728.  
  9729. me.callParent();
  9730.  
  9731. me.load({
  9732. success: function(response, options) {
  9733. var i, confid;
  9734. me.vmconfig = response.result.data;
  9735. if (!me.create) {
  9736. var value = me.vmconfig[me.confid];
  9737. var network = PVE.Parser.parseQemuNetwork(me.confid, value);
  9738. if (!network) {
  9739. Ext.Msg.alert('Error', 'Unable to parse network options');
  9740. me.close();
  9741. return;
  9742. }
  9743. ipanel.setNetwork(me.confid, network);
  9744. } else {
  9745. for (i = 0; i < 100; i++) {
  9746. confid = 'net' + i.toString();
  9747. if (!Ext.isDefined(me.vmconfig[confid])) {
  9748. me.confid = confid;
  9749. break;
  9750. }
  9751. }
  9752. ipanel.setNetwork(me.confid);
  9753. }
  9754. }
  9755. });
  9756. }
  9757. });
  9758. // fixme: howto avoid jslint type confusion?
  9759. /*jslint confusion: true */
  9760. Ext.define('PVE.qemu.CDInputPanel', {
  9761. extend: 'PVE.panel.InputPanel',
  9762. alias: 'widget.PVE.qemu.CDInputPanel',
  9763.  
  9764. insideWizard: false,
  9765.  
  9766. onGetValues: function(values) {
  9767. var me = this;
  9768.  
  9769. var confid = me.confid || (values.controller + values.deviceid);
  9770.  
  9771. me.drive.media = 'cdrom';
  9772. if (values.mediaType === 'iso') {
  9773. me.drive.file = values.cdimage;
  9774. } else if (values.mediaType === 'cdrom') {
  9775. me.drive.file = 'cdrom';
  9776. } else {
  9777. me.drive.file = 'none';
  9778. }
  9779.  
  9780. var params = {};
  9781.  
  9782. params[confid] = PVE.Parser.printQemuDrive(me.drive);
  9783.  
  9784. return params;
  9785. },
  9786.  
  9787. setVMConfig: function(vmconfig) {
  9788. var me = this;
  9789.  
  9790. if (me.bussel) {
  9791. me.bussel.setVMConfig(vmconfig, 'cdrom');
  9792. }
  9793. },
  9794.  
  9795. setDrive: function(drive) {
  9796. var me = this;
  9797.  
  9798. var values = {};
  9799. if (drive.file === 'cdrom') {
  9800. values.mediaType = 'cdrom';
  9801. } else if (drive.file === 'none') {
  9802. values.mediaType = 'none';
  9803. } else {
  9804. values.mediaType = 'iso';
  9805. var match = drive.file.match(/^([^:]+):/);
  9806. if (match) {
  9807. values.cdstorage = match[1];
  9808. values.cdimage = drive.file;
  9809. }
  9810. }
  9811.  
  9812. me.drive = drive;
  9813.  
  9814. me.setValues(values);
  9815. },
  9816.  
  9817. setNodename: function(nodename) {
  9818. var me = this;
  9819.  
  9820. me.cdstoragesel.setNodename(nodename);
  9821. me.cdfilesel.setStorage(undefined, nodename);
  9822. },
  9823.  
  9824. initComponent : function() {
  9825. var me = this;
  9826.  
  9827. me.drive = {};
  9828.  
  9829. var items = [];
  9830.  
  9831. if (!me.confid) {
  9832. me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
  9833. noVirtIO: true
  9834. });
  9835. items.push(me.bussel);
  9836. }
  9837.  
  9838. items.push({
  9839. xtype: 'radiofield',
  9840. name: 'mediaType',
  9841. inputValue: 'iso',
  9842. boxLabel: 'Use CD/DVD disc image file (iso)',
  9843. checked: true,
  9844. listeners: {
  9845. change: function(f, value) {
  9846. if (!me.rendered) {
  9847. return;
  9848. }
  9849. me.down('field[name=cdstorage]').setDisabled(!value);
  9850. me.down('field[name=cdimage]').setDisabled(!value);
  9851. me.down('field[name=cdimage]').validate();
  9852. }
  9853. }
  9854. });
  9855.  
  9856. me.cdfilesel = Ext.create('PVE.form.FileSelector', {
  9857. name: 'cdimage',
  9858. nodename: me.nodename,
  9859. storageContent: 'iso',
  9860. fieldLabel: 'ISO Image',
  9861. labelAlign: 'right',
  9862. allowBlank: false
  9863. });
  9864.  
  9865. me.cdstoragesel = Ext.create('PVE.form.StorageSelector', {
  9866. name: 'cdstorage',
  9867. nodename: me.nodename,
  9868. fieldLabel: gettext('Storage'),
  9869. labelAlign: 'right',
  9870. storageContent: 'iso',
  9871. allowBlank: false,
  9872. autoSelect: me.insideWizard,
  9873. listeners: {
  9874. change: function(f, value) {
  9875. me.cdfilesel.setStorage(value);
  9876. }
  9877. }
  9878. });
  9879.  
  9880. items.push(me.cdstoragesel);
  9881. items.push(me.cdfilesel);
  9882.  
  9883. items.push({
  9884. xtype: 'radiofield',
  9885. name: 'mediaType',
  9886. inputValue: 'cdrom',
  9887. boxLabel: 'Use physical CD/DVD Drive'
  9888. });
  9889.  
  9890. items.push({
  9891. xtype: 'radiofield',
  9892. name: 'mediaType',
  9893. inputValue: 'none',
  9894. boxLabel: 'Do not use any media'
  9895. });
  9896.  
  9897. if (me.insideWizard) {
  9898. me.column1 = items;
  9899. } else {
  9900. me.items = items;
  9901. }
  9902.  
  9903. me.callParent();
  9904. }
  9905. });
  9906.  
  9907. Ext.define('PVE.qemu.CDEdit', {
  9908. extend: 'PVE.window.Edit',
  9909.  
  9910. initComponent : function() {
  9911. var me = this;
  9912.  
  9913. var nodename = me.pveSelNode.data.node;
  9914. if (!nodename) {
  9915. throw "no node name specified";
  9916. }
  9917.  
  9918. me.create = me.confid ? false : true;
  9919.  
  9920. var ipanel = Ext.create('PVE.qemu.CDInputPanel', {
  9921. confid: me.confid,
  9922. nodename: nodename
  9923. });
  9924.  
  9925. Ext.applyIf(me, {
  9926. subject: 'CD/DVD Drive',
  9927. items: [ ipanel ]
  9928. });
  9929.  
  9930. me.callParent();
  9931.  
  9932. me.load({
  9933. success: function(response, options) {
  9934. ipanel.setVMConfig(response.result.data);
  9935. if (me.confid) {
  9936. var value = response.result.data[me.confid];
  9937. var drive = PVE.Parser.parseQemuDrive(me.confid, value);
  9938. if (!drive) {
  9939. Ext.Msg.alert('Error', 'Unable to parse drive options');
  9940. me.close();
  9941. return;
  9942. }
  9943. ipanel.setDrive(drive);
  9944. }
  9945. }
  9946. });
  9947. }
  9948. });
  9949. // fixme: howto avoid jslint type confusion?
  9950. /*jslint confusion: true */
  9951. Ext.define('PVE.qemu.HDInputPanel', {
  9952. extend: 'PVE.panel.InputPanel',
  9953. alias: 'widget.PVE.qemu.HDInputPanel',
  9954.  
  9955. insideWizard: false,
  9956.  
  9957. unused: false, // ADD usused disk imaged
  9958.  
  9959. vmconfig: {}, // used to select usused disks
  9960.  
  9961. onGetValues: function(values) {
  9962. var me = this;
  9963.  
  9964. var confid = me.confid || (values.controller + values.deviceid);
  9965.  
  9966. if (me.unused) {
  9967. me.drive.file = me.vmconfig[values.unusedId];
  9968. confid = values.controller + values.deviceid;
  9969. } else if (me.create) {
  9970. if (values.hdimage) {
  9971. me.drive.file = values.hdimage;
  9972. } else {
  9973. me.drive.file = values.hdstorage + ":" + values.disksize;
  9974. }
  9975. me.drive.format = values.diskformat;
  9976. }
  9977.  
  9978. if (values.cache) {
  9979. me.drive.cache = values.cache;
  9980. } else {
  9981. delete me.drive.cache;
  9982. }
  9983.  
  9984. if (values.nobackup) {
  9985. me.drive.backup = 'no';
  9986. } else {
  9987. delete me.drive.backup;
  9988. }
  9989.  
  9990. var params = {};
  9991.  
  9992. params[confid] = PVE.Parser.printQemuDrive(me.drive);
  9993.  
  9994. return params;
  9995. },
  9996.  
  9997. setVMConfig: function(vmconfig) {
  9998. var me = this;
  9999.  
  10000. me.vmconfig = vmconfig;
  10001.  
  10002. if (me.bussel) {
  10003. me.bussel.setVMConfig(vmconfig, true);
  10004. }
  10005. if (me.unusedDisks) {
  10006. var disklist = [];
  10007. Ext.Object.each(vmconfig, function(key, value) {
  10008. if (key.match(/^unused\d+$/)) {
  10009. disklist.push([key, value]);
  10010. }
  10011. });
  10012. me.unusedDisks.store.loadData(disklist);
  10013. me.unusedDisks.setValue(me.confid);
  10014. }
  10015. },
  10016.  
  10017. setDrive: function(drive) {
  10018. var me = this;
  10019.  
  10020. me.drive = drive;
  10021.  
  10022. var values = {};
  10023. var match = drive.file.match(/^([^:]+):/);
  10024. if (match) {
  10025. values.hdstorage = match[1];
  10026. }
  10027.  
  10028. values.hdimage = drive.file;
  10029. values.nobackup = (drive.backup === 'no');
  10030. values.diskformat = drive.format || 'raw';
  10031. values.cache = drive.cache || '';
  10032.  
  10033. me.setValues(values);
  10034. },
  10035.  
  10036. setNodename: function(nodename) {
  10037. var me = this;
  10038. me.hdstoragesel.setNodename(nodename);
  10039. me.hdfilesel.setStorage(undefined, nodename);
  10040. },
  10041.  
  10042. initComponent : function() {
  10043. var me = this;
  10044.  
  10045. me.drive = {};
  10046.  
  10047. me.column1 = [];
  10048. me.column2 = [];
  10049.  
  10050. if (!me.confid || me.unused) {
  10051. me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
  10052. // boot from scsi does not work in kvm 1.0
  10053. noScsi: me.insideWizard ? true : false,
  10054. vmconfig: me.insideWizard ? {ide2: 'cdrom'} : {}
  10055. });
  10056. me.column1.push(me.bussel);
  10057. }
  10058.  
  10059. if (me.unused) {
  10060. me.unusedDisks = Ext.create('PVE.form.KVComboBox', {
  10061. name: 'unusedId',
  10062. fieldLabel: gettext('Disk image'),
  10063. matchFieldWidth: false,
  10064. listConfig: {
  10065. width: 350
  10066. },
  10067. data: [],
  10068. allowBlank: false
  10069. });
  10070. me.column1.push(me.unusedDisks);
  10071. } else if (me.create) {
  10072. me.formatsel = Ext.create('PVE.form.DiskFormatSelector', {
  10073. name: 'diskformat',
  10074. fieldLabel: gettext('Format'),
  10075. value: 'raw',
  10076. allowBlank: false
  10077. });
  10078.  
  10079. me.hdfilesel = Ext.create('PVE.form.FileSelector', {
  10080. name: 'hdimage',
  10081. nodename: me.nodename,
  10082. storageContent: 'images',
  10083. fieldLabel: gettext('Disk image'),
  10084. disabled: true,
  10085. hidden: true,
  10086. allowBlank: false
  10087. });
  10088.  
  10089. me.hdsizesel = Ext.createWidget('numberfield', {
  10090. name: 'disksize',
  10091. minValue: 1,
  10092. maxValue: 128*1024,
  10093. value: '32',
  10094. fieldLabel: gettext('Disk size') + ' (GB)',
  10095. allowBlank: false
  10096. });
  10097.  
  10098. me.hdstoragesel = Ext.create('PVE.form.StorageSelector', {
  10099. name: 'hdstorage',
  10100. nodename: me.nodename,
  10101. fieldLabel: gettext('Storage'),
  10102. storageContent: 'images',
  10103. autoSelect: me.insideWizard,
  10104. allowBlank: false,
  10105. listeners: {
  10106. change: function(f, value) {
  10107. var rec = f.store.getById(value);
  10108. if (rec.data.type === 'iscsi') {
  10109. me.hdfilesel.setStorage(value);
  10110. me.hdfilesel.setDisabled(false);
  10111. me.formatsel.setValue('raw');
  10112. me.formatsel.setDisabled(true);
  10113. me.hdfilesel.setVisible(true);
  10114. me.hdsizesel.setDisabled(true);
  10115. me.hdsizesel.setVisible(false);
  10116. } else if (rec.data.type === 'lvm') {
  10117. me.hdfilesel.setDisabled(true);
  10118. me.hdfilesel.setVisible(false);
  10119. me.formatsel.setValue('raw');
  10120. me.formatsel.setDisabled(true);
  10121. me.hdsizesel.setDisabled(false);
  10122. me.hdsizesel.setVisible(true);
  10123. } else {
  10124. me.hdfilesel.setDisabled(true);
  10125. me.hdfilesel.setVisible(false);
  10126. me.formatsel.setDisabled(false);
  10127. me.hdsizesel.setDisabled(false);
  10128. me.hdsizesel.setVisible(true);
  10129. }
  10130. }
  10131. }
  10132. });
  10133. me.column1.push(me.hdstoragesel);
  10134. me.column1.push(me.hdfilesel);
  10135. me.column1.push(me.hdsizesel);
  10136. me.column2.push(me.formatsel);
  10137.  
  10138. } else {
  10139. me.column1.push({
  10140. xtype: 'displayfield',
  10141. fieldLabel: gettext('Disk image'),
  10142. labelWidth: 50,
  10143. name: 'hdimage'
  10144. });
  10145. }
  10146.  
  10147. me.column2.push({
  10148. xtype: 'CacheTypeSelector',
  10149. name: 'cache',
  10150. value: '',
  10151. fieldLabel: 'Cache'
  10152. });
  10153.  
  10154. if (!me.insideWizard) {
  10155. me.column2.push({
  10156. xtype: 'pvecheckbox',
  10157. fieldLabel: gettext('No backup'),
  10158. name: 'nobackup'
  10159. });
  10160. }
  10161.  
  10162. me.callParent();
  10163. }
  10164. });
  10165.  
  10166. Ext.define('PVE.qemu.HDEdit', {
  10167. extend: 'PVE.window.Edit',
  10168.  
  10169. isAdd: true,
  10170.  
  10171. initComponent : function() {
  10172. var me = this;
  10173.  
  10174. var nodename = me.pveSelNode.data.node;
  10175. if (!nodename) {
  10176. throw "no node name specified";
  10177. }
  10178.  
  10179. var unused = me.confid && me.confid.match(/^unused\d+$/);
  10180.  
  10181. me.create = me.confid ? unused : true;
  10182.  
  10183. var ipanel = Ext.create('PVE.qemu.HDInputPanel', {
  10184. confid: me.confid,
  10185. nodename: nodename,
  10186. unused: unused,
  10187. create: me.create
  10188. });
  10189.  
  10190. var subject;
  10191. if (unused) {
  10192. me.subject = gettext('Unused Disk');
  10193. } else if (me.create) {
  10194. me.subject = gettext('Hard Disk');
  10195. } else {
  10196. me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
  10197. }
  10198.  
  10199. me.items = [ ipanel ];
  10200.  
  10201. me.callParent();
  10202.  
  10203. me.load({
  10204. success: function(response, options) {
  10205. ipanel.setVMConfig(response.result.data);
  10206. if (me.confid) {
  10207. var value = response.result.data[me.confid];
  10208. var drive = PVE.Parser.parseQemuDrive(me.confid, value);
  10209. if (!drive) {
  10210. Ext.Msg.alert('Error', 'Unable to parse drive options');
  10211. me.close();
  10212. return;
  10213. }
  10214. ipanel.setDrive(drive);
  10215. me.isValid(); // trigger validation
  10216. }
  10217. }
  10218. });
  10219. }
  10220. });
  10221. Ext.define('PVE.qemu.DisplayEdit', {
  10222. extend: 'PVE.window.Edit',
  10223.  
  10224. initComponent : function() {
  10225. var me = this;
  10226.  
  10227. Ext.apply(me, {
  10228. subject: gettext('Display'),
  10229. width: 350,
  10230. items: {
  10231. xtype: 'DisplaySelector',
  10232. name: 'vga',
  10233. value: '',
  10234. fieldLabel: gettext('Graphic card')
  10235. }
  10236. });
  10237.  
  10238. me.callParent();
  10239.  
  10240. me.load();
  10241. }
  10242. });
  10243. Ext.define('PVE.qemu.KeyboardEdit', {
  10244. extend: 'PVE.window.Edit',
  10245.  
  10246. initComponent : function() {
  10247. var me = this;
  10248.  
  10249. Ext.applyIf(me, {
  10250. subject: gettext('Keyboard Layout'),
  10251. items: {
  10252. xtype: 'VNCKeyboardSelector',
  10253. name: 'keyboard',
  10254. value: '',
  10255. fieldLabel: gettext('Keyboard Layout')
  10256. }
  10257. });
  10258.  
  10259. me.callParent();
  10260.  
  10261. me.load();
  10262. }
  10263. });
  10264. // fixme: howto avoid jslint type confusion?
  10265. /*jslint confusion: true */
  10266. Ext.define('PVE.qemu.HardwareView', {
  10267. extend: 'PVE.grid.ObjectGrid',
  10268. alias: ['widget.PVE.qemu.HardwareView'],
  10269.  
  10270. renderKey: function(key, metaData, record, rowIndex, colIndex, store) {
  10271. var me = this;
  10272. var rows = me.rows;
  10273. var rowdef = rows[key] || {};
  10274.  
  10275. if (rowdef.tdCls) {
  10276. metaData.tdCls = rowdef.tdCls;
  10277. if (rowdef.tdCls == 'pve-itype-icon-storage') {
  10278. if (record.data.value.match(/media=cdrom/)) {
  10279. metaData.tdCls = 'pve-itype-icon-cdrom';
  10280. return rowdef.cdheader;
  10281. }
  10282. }
  10283. }
  10284. return rowdef.header || key;
  10285. },
  10286.  
  10287. initComponent : function() {
  10288. var me = this;
  10289. var i, confid;
  10290.  
  10291. var nodename = me.pveSelNode.data.node;
  10292. if (!nodename) {
  10293. throw "no node name specified";
  10294. }
  10295.  
  10296. var vmid = me.pveSelNode.data.vmid;
  10297. if (!vmid) {
  10298. throw "no VM ID specified";
  10299. }
  10300.  
  10301. var rows = {
  10302. memory: {
  10303. header: gettext('Memory'),
  10304. editor: 'PVE.qemu.MemoryEdit',
  10305. never_delete: true,
  10306. tdCls: 'pve-itype-icon-memory',
  10307. renderer: function(value) {
  10308. return PVE.Utils.format_size(value*1024*1024);
  10309. }
  10310. },
  10311. sockets: {
  10312. header: gettext('Processors'),
  10313. never_delete: true,
  10314. editor: 'PVE.qemu.ProcessorEdit',
  10315. tdCls: 'pve-itype-icon-processor',
  10316. defaultValue: 1,
  10317. renderer: function(value, metaData, record, rowIndex, colIndex, store) {
  10318. var model = me.getObjectValue('cpu');
  10319. var cores = me.getObjectValue('cores');
  10320. var res = '';
  10321. if (!cores || (cores <= 1)) {
  10322. res = value;
  10323. } else {
  10324. res = (value*cores) + ' (' + value + ' sockets, ' + cores + ' cores)';
  10325. }
  10326. if (model) {
  10327. res += ' [' + model + ']';
  10328. }
  10329. return res;
  10330. }
  10331. },
  10332. keyboard: {
  10333. header: gettext('Keyboard Layout'),
  10334. never_delete: true,
  10335. editor: 'PVE.qemu.KeyboardEdit',
  10336. tdCls: 'pve-itype-icon-keyboard',
  10337. defaultValue: '',
  10338. renderer: PVE.Utils.render_kvm_language
  10339. },
  10340. vga: {
  10341. header: gettext('Display'),
  10342. editor: 'PVE.qemu.DisplayEdit',
  10343. never_delete: true,
  10344. tdCls: 'pve-itype-icon-display',
  10345. defaultValue: '',
  10346. renderer: PVE.Utils.render_kvm_vga_driver
  10347. },
  10348. cores: {
  10349. visible: false
  10350. },
  10351. cpu: {
  10352. visible: false
  10353. }
  10354. };
  10355.  
  10356. for (i = 0; i < 4; i++) {
  10357. confid = "ide" + i;
  10358. rows[confid] = {
  10359. group: 1,
  10360. tdCls: 'pve-itype-icon-storage',
  10361. editor: 'PVE.qemu.HDEdit',
  10362. header: gettext('Hard Disk') + ' (' + confid +')',
  10363. cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
  10364. };
  10365. }
  10366. for (i = 0; i < 16; i++) {
  10367. confid = "scsi" + i;
  10368. rows[confid] = {
  10369. group: 1,
  10370. tdCls: 'pve-itype-icon-storage',
  10371. editor: 'PVE.qemu.HDEdit',
  10372. header: gettext('Hard Disk') + ' (' + confid +')',
  10373. cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
  10374. };
  10375. }
  10376. for (i = 0; i < 16; i++) {
  10377. confid = "virtio" + i;
  10378. rows[confid] = {
  10379. group: 1,
  10380. tdCls: 'pve-itype-icon-storage',
  10381. editor: 'PVE.qemu.HDEdit',
  10382. header: gettext('Hard Disk') + ' (' + confid +')',
  10383. cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
  10384. };
  10385. }
  10386. for (i = 0; i < 32; i++) {
  10387. confid = "net" + i;
  10388. rows[confid] = {
  10389. group: 2,
  10390. tdCls: 'pve-itype-icon-network',
  10391. editor: 'PVE.qemu.NetworkEdit',
  10392. header: gettext('Network Device') + ' (' + confid +')'
  10393. };
  10394. }
  10395. for (i = 0; i < 8; i++) {
  10396. rows["unused" + i] = {
  10397. group: 3,
  10398. tdCls: 'pve-itype-icon-storage',
  10399. editor: 'PVE.qemu.HDEdit',
  10400. header: gettext('Unused Disk') + ' ' + i
  10401. };
  10402. }
  10403.  
  10404. var sorterFn = function(rec1, rec2) {
  10405. var v1 = rec1.data.key;
  10406. var v2 = rec2.data.key;
  10407. var g1 = rows[v1].group || 0;
  10408. var g2 = rows[v2].group || 0;
  10409.  
  10410. return (g1 !== g2) ?
  10411. (g1 > g2 ? 1 : -1) : (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
  10412. };
  10413.  
  10414. var reload = function() {
  10415. me.rstore.load();
  10416. };
  10417.  
  10418. var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
  10419.  
  10420. var sm = Ext.create('Ext.selection.RowModel', {});
  10421.  
  10422. var run_editor = function() {
  10423. var rec = sm.getSelection()[0];
  10424. if (!rec) {
  10425. return;
  10426. }
  10427.  
  10428. var rowdef = rows[rec.data.key];
  10429. if (!rowdef.editor) {
  10430. return;
  10431. }
  10432.  
  10433. var editor = rowdef.editor;
  10434. if (rowdef.tdCls == 'pve-itype-icon-storage') {
  10435. if (rec.data.value.match(/media=cdrom/)) {
  10436. editor = 'PVE.qemu.CDEdit';
  10437. }
  10438. }
  10439.  
  10440. var win = Ext.create(editor, {
  10441. pveSelNode: me.pveSelNode,
  10442. confid: rec.data.key,
  10443. url: '/api2/extjs/' + baseurl
  10444. });
  10445.  
  10446. win.show();
  10447. win.on('destroy', reload);
  10448. };
  10449.  
  10450. var edit_btn = new PVE.button.Button({
  10451. text: gettext('Edit'),
  10452. selModel: sm,
  10453. disabled: true,
  10454. enableFn: function(rec) {
  10455. if (!rec) {
  10456. return false;
  10457. }
  10458. var rowdef = rows[rec.data.key];
  10459. return !!rowdef.editor;
  10460. },
  10461. handler: run_editor
  10462. });
  10463.  
  10464. var remove_btn = new PVE.button.Button({
  10465. text: gettext('Remove'),
  10466. selModel: sm,
  10467. disabled: true,
  10468. confirmMsg: function(rec) {
  10469. var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  10470. "'" + me.renderKey(rec.data.key, {}, rec) + "'");
  10471. if (rec.data.key.match(/^unused\d+$/)) {
  10472. msg += " " + gettext('This will permanently erase all image data.');
  10473. }
  10474.  
  10475. return msg;
  10476. },
  10477. enableFn: function(rec) {
  10478. if (!rec) {
  10479. return false;
  10480. }
  10481. var rowdef = rows[rec.data.key];
  10482.  
  10483. return rowdef.never_delete !== true;
  10484. },
  10485. handler: function(b, e, rec) {
  10486. PVE.Utils.API2Request({
  10487. url: '/api2/extjs/' + baseurl,
  10488. waitMsgTarget: me,
  10489. method: 'PUT',
  10490. params: {
  10491. 'delete': rec.data.key
  10492. },
  10493. callback: function() {
  10494. reload();
  10495. },
  10496. failure: function (response, opts) {
  10497. Ext.Msg.alert('Error',response.htmlStatus);
  10498. }
  10499. });
  10500. }
  10501. });
  10502.  
  10503. Ext.applyIf(me, {
  10504. url: '/api2/json/' + baseurl,
  10505. selModel: sm,
  10506. cwidth1: 170,
  10507. tbar: [
  10508. {
  10509. text: gettext('Add'),
  10510. menu: new Ext.menu.Menu({
  10511. items: [
  10512. {
  10513. text: gettext('Hard Disk'),
  10514. iconCls: 'pve-itype-icon-storage',
  10515. handler: function() {
  10516. var win = Ext.create('PVE.qemu.HDEdit', {
  10517. url: '/api2/extjs/' + baseurl,
  10518. pveSelNode: me.pveSelNode
  10519. });
  10520. win.on('destroy', reload);
  10521. win.show();
  10522. }
  10523. },
  10524. {
  10525. text: gettext('CD/DVD Drive'),
  10526. iconCls: 'pve-itype-icon-cdrom',
  10527. handler: function() {
  10528. var win = Ext.create('PVE.qemu.CDEdit', {
  10529. url: '/api2/extjs/' + baseurl,
  10530. pveSelNode: me.pveSelNode
  10531. });
  10532. win.on('destroy', reload);
  10533. win.show();
  10534. }
  10535. },
  10536. {
  10537. text: gettext('Network Device'),
  10538. iconCls: 'pve-itype-icon-network',
  10539. handler: function() {
  10540. var win = Ext.create('PVE.qemu.NetworkEdit', {
  10541. url: '/api2/extjs/' + baseurl,
  10542. pveSelNode: me.pveSelNode
  10543. });
  10544. win.on('destroy', reload);
  10545. win.show();
  10546. }
  10547. }
  10548. ]
  10549. })
  10550. },
  10551. remove_btn,
  10552. edit_btn
  10553. ],
  10554. rows: rows,
  10555. sorterFn: sorterFn,
  10556. listeners: {
  10557. show: reload,
  10558. itemdblclick: run_editor
  10559. }
  10560. });
  10561.  
  10562. me.callParent();
  10563. }
  10564. });
  10565. /*jslint confusion: true */
  10566. Ext.define('PVE.qemu.Options', {
  10567. extend: 'PVE.grid.ObjectGrid',
  10568. alias: ['widget.PVE.qemu.Options'],
  10569.  
  10570. initComponent : function() {
  10571. var me = this;
  10572. var i;
  10573.  
  10574. var nodename = me.pveSelNode.data.node;
  10575. if (!nodename) {
  10576. throw "no node name specified";
  10577. }
  10578.  
  10579. var vmid = me.pveSelNode.data.vmid;
  10580. if (!vmid) {
  10581. throw "no VM ID specified";
  10582. }
  10583.  
  10584. var rows = {
  10585. name: {
  10586. required: true,
  10587. defaultValue: me.pveSelNode.data.name,
  10588. header: gettext('Name'),
  10589. editor: {
  10590. xtype: 'pveWindowEdit',
  10591. subject: gettext('Name'),
  10592. items: {
  10593. xtype: 'textfield',
  10594. name: 'name',
  10595. vtype: 'DnsName',
  10596. value: '',
  10597. fieldLabel: gettext('Name'),
  10598. allowBlank: true
  10599. }
  10600. }
  10601. },
  10602. onboot: {
  10603. header: gettext('Start at boot'),
  10604. defaultValue: '',
  10605. renderer: PVE.Utils.format_boolean,
  10606. editor: {
  10607. xtype: 'pveWindowEdit',
  10608. subject: gettext('Start at boot'),
  10609. items: {
  10610. xtype: 'pvecheckbox',
  10611. name: 'onboot',
  10612. uncheckedValue: 0,
  10613. defaultValue: 0,
  10614. deleteDefaultValue: true,
  10615. fieldLabel: gettext('Start at boot')
  10616. }
  10617. }
  10618. },
  10619. ostype: {
  10620. header: 'OS Type',
  10621. editor: 'PVE.qemu.OSTypeEdit',
  10622. renderer: PVE.Utils.render_kvm_ostype,
  10623. defaultValue: 'other'
  10624. },
  10625. bootdisk: {
  10626. visible: false
  10627. },
  10628. boot: {
  10629. header: gettext('Boot order'),
  10630. defaultValue: 'cdn',
  10631. editor: 'PVE.qemu.BootOrderEdit',
  10632. renderer: function(order) {
  10633. var i;
  10634. var text = '';
  10635. var bootdisk = me.getObjectValue('bootdisk');
  10636. order = order || 'cdn';
  10637. for (i = 0; i < order.length; i++) {
  10638. var sel = order.substring(i, i + 1);
  10639. if (text) {
  10640. text += ', ';
  10641. }
  10642. if (sel === 'c') {
  10643. if (bootdisk) {
  10644. text += "Disk '" + bootdisk + "'";
  10645. } else {
  10646. text += "Disk";
  10647. }
  10648. } else if (sel === 'n') {
  10649. text += 'Network';
  10650. } else if (sel === 'a') {
  10651. text += 'Floppy';
  10652. } else if (sel === 'd') {
  10653. text += 'CD-ROM';
  10654. } else {
  10655. text += sel;
  10656. }
  10657. }
  10658. return text;
  10659. }
  10660. },
  10661. acpi: {
  10662. header: 'ACPI support',
  10663. defaultValue: true,
  10664. renderer: PVE.Utils.format_boolean,
  10665. editor: {
  10666. xtype: 'pveWindowEdit',
  10667. subject: 'ACPI support',
  10668. items: {
  10669. xtype: 'pvecheckbox',
  10670. name: 'acpi',
  10671. checked: true,
  10672. uncheckedValue: 0,
  10673. defaultValue: 1,
  10674. deleteDefaultValue: true,
  10675. fieldLabel: gettext('Enabled')
  10676. }
  10677. }
  10678. },
  10679. kvm: {
  10680. header: 'KVM hardware virtualization',
  10681. defaultValue: true,
  10682. renderer: PVE.Utils.format_boolean,
  10683. editor: {
  10684. xtype: 'pveWindowEdit',
  10685. subject: 'KVM hardware virtualization',
  10686. items: {
  10687. xtype: 'pvecheckbox',
  10688. name: 'kvm',
  10689. checked: true,
  10690. uncheckedValue: 0,
  10691. defaultValue: 1,
  10692. deleteDefaultValue: true,
  10693. fieldLabel: gettext('Enabled')
  10694. }
  10695. }
  10696. },
  10697. freeze: {
  10698. header: 'Freeze CPU at startup',
  10699. defaultValue: false,
  10700. renderer: PVE.Utils.format_boolean,
  10701. editor: {
  10702. xtype: 'pveWindowEdit',
  10703. subject: 'Freeze CPU at startup',
  10704. items: {
  10705. xtype: 'pvecheckbox',
  10706. name: 'freeze',
  10707. uncheckedValue: 0,
  10708. defaultValue: 0,
  10709. deleteDefaultValue: true,
  10710. labelWidth: 140,
  10711. fieldLabel: 'Freeze CPU at startup'
  10712. }
  10713. }
  10714. },
  10715. localtime: {
  10716. header: 'Use local time for RTC',
  10717. defaultValue: false,
  10718. renderer: PVE.Utils.format_boolean,
  10719. editor: {
  10720. xtype: 'pveWindowEdit',
  10721. subject: 'Use local time for RTC',
  10722. items: {
  10723. xtype: 'pvecheckbox',
  10724. name: 'localtime',
  10725. uncheckedValue: 0,
  10726. defaultValue: 0,
  10727. deleteDefaultValue: true,
  10728. labelWidth: 140,
  10729. fieldLabel: 'Use local time for RTC'
  10730. }
  10731. }
  10732.  
  10733. },
  10734. startdate: {
  10735. header: 'RTC start date',
  10736. defaultValue: 'now',
  10737. editor: {
  10738. xtype: 'pveWindowEdit',
  10739. subject: 'RTC start date',
  10740. items: {
  10741. xtype: 'pvetextfield',
  10742. name: 'startdate',
  10743. deleteEmpty: true,
  10744. value: 'now',
  10745. fieldLabel: 'RTC start date',
  10746. vtype: 'QemuStartDate',
  10747. allowBlank: true
  10748. }
  10749. }
  10750. }
  10751. };
  10752.  
  10753. var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
  10754.  
  10755. var reload = function() {
  10756. me.rstore.load();
  10757. };
  10758.  
  10759. var run_editor = function() {
  10760. var sm = me.getSelectionModel();
  10761. var rec = sm.getSelection()[0];
  10762. if (!rec) {
  10763. return;
  10764. }
  10765.  
  10766. var rowdef = rows[rec.data.key];
  10767. if (!rowdef.editor) {
  10768. return;
  10769. }
  10770.  
  10771. var win;
  10772. if (Ext.isString(rowdef.editor)) {
  10773. win = Ext.create(rowdef.editor, {
  10774. pveSelNode: me.pveSelNode,
  10775. confid: rec.data.key,
  10776. url: '/api2/extjs/' + baseurl
  10777. });
  10778. } else {
  10779. var config = Ext.apply({
  10780. pveSelNode: me.pveSelNode,
  10781. confid: rec.data.key,
  10782. url: '/api2/extjs/' + baseurl
  10783. }, rowdef.editor);
  10784. win = Ext.createWidget(rowdef.editor.xtype, config);
  10785. win.load();
  10786. }
  10787.  
  10788. win.show();
  10789. win.on('destroy', reload);
  10790. };
  10791.  
  10792. var edit_btn = new Ext.Button({
  10793. text: gettext('Edit'),
  10794. disabled: true,
  10795. handler: run_editor
  10796. });
  10797.  
  10798. var set_button_status = function() {
  10799. var sm = me.getSelectionModel();
  10800. var rec = sm.getSelection()[0];
  10801.  
  10802. if (!rec) {
  10803. edit_btn.disable();
  10804. return;
  10805. }
  10806. var rowdef = rows[rec.data.key];
  10807. edit_btn.setDisabled(!rowdef.editor);
  10808. };
  10809.  
  10810. Ext.applyIf(me, {
  10811. url: "/api2/json/nodes/" + nodename + "/qemu/" + vmid + "/config",
  10812. cwidth1: 150,
  10813. tbar: [ edit_btn ],
  10814. rows: rows,
  10815. listeners: {
  10816. itemdblclick: run_editor,
  10817. selectionchange: set_button_status
  10818. }
  10819. });
  10820.  
  10821. me.callParent();
  10822.  
  10823. me.on('show', reload);
  10824. }
  10825. });
  10826.  
  10827. Ext.define('PVE.qemu.Config', {
  10828. extend: 'PVE.panel.Config',
  10829. alias: 'widget.PVE.qemu.Config',
  10830.  
  10831. initComponent: function() {
  10832. var me = this;
  10833.  
  10834. var nodename = me.pveSelNode.data.node;
  10835. if (!nodename) {
  10836. throw "no node name specified";
  10837. }
  10838.  
  10839. var vmid = me.pveSelNode.data.vmid;
  10840. if (!vmid) {
  10841. throw "no VM ID specified";
  10842. }
  10843.  
  10844. me.statusStore = Ext.create('PVE.data.ObjectStore', {
  10845. url: "/api2/json/nodes/" + nodename + "/qemu/" + vmid + "/status/current",
  10846. interval: 1000
  10847. });
  10848.  
  10849. var vm_command = function(cmd, params) {
  10850. PVE.Utils.API2Request({
  10851. params: params,
  10852. url: '/nodes/' + nodename + '/qemu/' + vmid + "/status/" + cmd,
  10853. waitMsgTarget: me,
  10854. method: 'POST',
  10855. failure: function(response, opts) {
  10856. Ext.Msg.alert('Error', response.htmlStatus);
  10857. }
  10858. });
  10859. };
  10860.  
  10861. var startBtn = Ext.create('Ext.Button', {
  10862. text: gettext('Start'),
  10863. handler: function() {
  10864. vm_command('start');
  10865. }
  10866. });
  10867.  
  10868. var stopBtn = Ext.create('PVE.button.Button', {
  10869. text: gettext('Stop'),
  10870. confirmMsg: Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid),
  10871. handler: function() {
  10872. vm_command("stop", { timeout: 30 });
  10873. }
  10874. });
  10875.  
  10876. var migrateBtn = Ext.create('Ext.Button', {
  10877. text: gettext('Migrate'),
  10878. handler: function() {
  10879. var win = Ext.create('PVE.window.Migrate', {
  10880. vmtype: 'qemu',
  10881. nodename: nodename,
  10882. vmid: vmid
  10883. });
  10884. win.show();
  10885. }
  10886. });
  10887.  
  10888. var resetBtn = Ext.create('PVE.button.Button', {
  10889. text: gettext('Reset'),
  10890. confirmMsg: Ext.String.format(gettext("Do you really want to reset VM {0}?"), vmid),
  10891. handler: function() {
  10892. vm_command("reset");
  10893. }
  10894. });
  10895.  
  10896. var shutdownBtn = Ext.create('PVE.button.Button', {
  10897. text: gettext('Shutdown'),
  10898. confirmMsg: Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid),
  10899. handler: function() {
  10900. vm_command('shutdown', { timeout: 30 });
  10901. }
  10902. });
  10903.  
  10904. var removeBtn = Ext.create('PVE.button.Button', {
  10905. text: gettext('Remove'),
  10906. confirmMsg: Ext.String.format(gettext('Are you sure you want to remove VM {0}? This will permanently erase all VM data.'), vmid),
  10907. handler: function() {
  10908. PVE.Utils.API2Request({
  10909. url: '/nodes/' + nodename + '/qemu/' + vmid,
  10910. method: 'DELETE',
  10911. waitMsgTarget: me,
  10912. failure: function(response, opts) {
  10913. Ext.Msg.alert('Error', response.htmlStatus);
  10914. }
  10915. });
  10916. }
  10917. });
  10918.  
  10919. var vmname = me.pveSelNode.data.name;
  10920.  
  10921. var consoleBtn = Ext.create('Ext.Button', {
  10922. text: gettext('Console'),
  10923. handler: function() {
  10924. PVE.Utils.openConoleWindow('kvm', vmid, nodename, vmname);
  10925. }
  10926. });
  10927.  
  10928. var descr = vmid + " (" + (vmname ? "'" + vmname + "' " : "'VM " + vmid + "'") + ")";
  10929.  
  10930. Ext.apply(me, {
  10931. title: Ext.String.format(gettext("Virtual Machine {0} on node {1}"), descr, "'" + nodename + "'"),
  10932. hstateid: 'kvmtab',
  10933. tbar: [ startBtn, shutdownBtn, stopBtn, resetBtn,
  10934. removeBtn, migrateBtn, consoleBtn ],
  10935. defaults: { statusStore: me.statusStore },
  10936. items: [
  10937. {
  10938. title: gettext('Summary'),
  10939. xtype: 'pveQemuSummary',
  10940. itemId: 'summary'
  10941. },
  10942. {
  10943. title: gettext('Hardware'),
  10944. itemId: 'hardware',
  10945. xtype: 'PVE.qemu.HardwareView'
  10946. },
  10947. {
  10948. title: gettext('Options'),
  10949. itemId: 'options',
  10950. xtype: 'PVE.qemu.Options'
  10951. },
  10952. {
  10953. title: gettext('Monitor'),
  10954. itemId: 'monitor',
  10955. xtype: 'pveQemuMonitor'
  10956. },
  10957. {
  10958. title: gettext('Backup'),
  10959. xtype: 'pveBackupView',
  10960. itemId: 'backup'
  10961. },
  10962. {
  10963. xtype: 'pveACLView',
  10964. title: gettext('Permissions'),
  10965. itemId: 'permissions',
  10966. path: '/vms/' + vmid
  10967. }
  10968. ]
  10969. });
  10970.  
  10971. me.callParent();
  10972.  
  10973. me.statusStore.on('load', function(s, records, success) {
  10974. var status;
  10975. if (!success) {
  10976. me.workspace.checkVmMigration(me.pveSelNode);
  10977. status = 'unknown';
  10978. } else {
  10979. var rec = s.data.get('status');
  10980. status = rec ? rec.data.value : 'unknown';
  10981. }
  10982.  
  10983. startBtn.setDisabled(status === 'running');
  10984. resetBtn.setDisabled(status !== 'running');
  10985. shutdownBtn.setDisabled(status !== 'running');
  10986. stopBtn.setDisabled(status === 'stopped');
  10987. removeBtn.setDisabled(status !== 'stopped');
  10988. });
  10989.  
  10990. me.on('afterrender', function() {
  10991. me.statusStore.startUpdate();
  10992. });
  10993.  
  10994. me.on('destroy', function() {
  10995. me.statusStore.stopUpdate();
  10996. });
  10997. }
  10998. });
  10999. // fixme: howto avoid jslint type confusion?
  11000. /*jslint confusion: true */
  11001. Ext.define('PVE.qemu.CreateWizard', {
  11002. extend: 'PVE.window.Wizard',
  11003.  
  11004. initComponent: function() {
  11005. var me = this;
  11006.  
  11007. var nextvmid = PVE.data.ResourceStore.findNextVMID();
  11008.  
  11009. var summarystore = Ext.create('Ext.data.Store', {
  11010. model: 'KeyValue',
  11011. sorters: [
  11012. {
  11013. property : 'key',
  11014. direction: 'ASC'
  11015. }
  11016. ]
  11017. });
  11018.  
  11019. var cdpanel = Ext.create('PVE.qemu.CDInputPanel', {
  11020. title: 'CD/DVD',
  11021. confid: 'ide2',
  11022. insideWizard: true
  11023. });
  11024.  
  11025. var hdpanel = Ext.create('PVE.qemu.HDInputPanel', {
  11026. title: gettext('Hard Disk'),
  11027. create: true,
  11028. insideWizard: true
  11029. });
  11030.  
  11031. var networkpanel = Ext.create('PVE.qemu.NetworkInputPanel', {
  11032. title: gettext('Network'),
  11033. insideWizard: true
  11034. });
  11035.  
  11036. Ext.applyIf(me, {
  11037. subject: gettext('Virtual Machine'),
  11038. items: [
  11039. {
  11040. xtype: 'inputpanel',
  11041. title: gettext('General'),
  11042. column1: [
  11043. {
  11044. xtype: 'PVE.form.NodeSelector',
  11045. name: 'nodename',
  11046. fieldLabel: gettext('Node'),
  11047. allowBlank: false,
  11048. onlineValidator: true,
  11049. listeners: {
  11050. change: function(f, value) {
  11051. networkpanel.setNodename(value);
  11052. hdpanel.setNodename(value);
  11053. cdpanel.setNodename(value);
  11054. }
  11055. }
  11056. },
  11057. {
  11058. xtype: 'pveVMIDSelector',
  11059. name: 'vmid',
  11060. value: nextvmid,
  11061. validateExists: false
  11062. },
  11063. {
  11064. xtype: 'textfield',
  11065. name: 'name',
  11066. value: '',
  11067. fieldLabel: gettext('Name'),
  11068. allowBlank: true
  11069. }
  11070. ],
  11071. column2: [
  11072. {
  11073. xtype: 'pvePoolSelector',
  11074. fieldLabel: gettext('Resource Pool'),
  11075. name: 'pool',
  11076. value: '',
  11077. allowBlank: true
  11078. }
  11079. ],
  11080. onGetValues: function(values) {
  11081. if (!values.name) {
  11082. delete values.name;
  11083. }
  11084. if (!values.pool) {
  11085. delete values.pool;
  11086. }
  11087. return values;
  11088. }
  11089. },
  11090. {
  11091. title: 'OS',
  11092. xtype: 'PVE.qemu.OSTypeInputPanel'
  11093. },
  11094. cdpanel,
  11095. hdpanel,
  11096. {
  11097. xtype: 'PVE.qemu.ProcessorInputPanel',
  11098. title: 'CPU'
  11099. },
  11100. {
  11101. xtype: 'PVE.qemu.MemoryInputPanel',
  11102. insideWizard: true,
  11103. title: gettext('Memory')
  11104. },
  11105. networkpanel,
  11106. {
  11107. title: gettext('Confirm'),
  11108. layout: 'fit',
  11109. items: [
  11110. {
  11111. title: gettext('Settings'),
  11112. xtype: 'grid',
  11113. store: summarystore,
  11114. columns: [
  11115. {header: 'Key', width: 150, dataIndex: 'key'},
  11116. {header: 'Value', flex: 1, dataIndex: 'value'}
  11117. ]
  11118. }
  11119. ],
  11120. listeners: {
  11121. show: function(panel) {
  11122. var form = me.down('form').getForm();
  11123. var kv = me.getValues();
  11124. var data = [];
  11125. Ext.Object.each(kv, function(key, value) {
  11126. if (key === 'delete') { // ignore
  11127. return;
  11128. }
  11129. var html = Ext.htmlEncode(Ext.JSON.encode(value));
  11130. data.push({ key: key, value: value });
  11131. });
  11132. summarystore.suspendEvents();
  11133. summarystore.removeAll();
  11134. summarystore.add(data);
  11135. summarystore.sort();
  11136. summarystore.resumeEvents();
  11137. summarystore.fireEvent('datachanged', summarystore);
  11138.  
  11139. }
  11140. },
  11141. onSubmit: function() {
  11142. var kv = me.getValues();
  11143. delete kv['delete'];
  11144.  
  11145. var nodename = kv.nodename;
  11146. delete kv.nodename;
  11147.  
  11148. PVE.Utils.API2Request({
  11149. url: '/nodes/' + nodename + '/qemu',
  11150. waitMsgTarget: me,
  11151. method: 'POST',
  11152. params: kv,
  11153. success: function(response){
  11154. me.close();
  11155. },
  11156. failure: function(response, opts) {
  11157. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  11158. }
  11159. });
  11160. }
  11161. }
  11162. ]
  11163. });
  11164.  
  11165. me.callParent();
  11166. }
  11167. });
  11168.  
  11169.  
  11170.  
  11171.  
  11172. Ext.define('PVE.openvz.StatusView', {
  11173. extend: 'PVE.grid.ObjectGrid',
  11174.  
  11175. initComponent : function() {
  11176. var me = this;
  11177.  
  11178. var nodename = me.pveSelNode.data.node;
  11179. if (!nodename) {
  11180. throw "no node name specified";
  11181. }
  11182.  
  11183. var vmid = me.pveSelNode.data.vmid;
  11184. if (!vmid) {
  11185. throw "no VM ID specified";
  11186. }
  11187.  
  11188. var render_cpu = function(value, metaData, record, rowIndex, colIndex, store) {
  11189. if (!me.getObjectValue('uptime')) {
  11190. return '-';
  11191. }
  11192.  
  11193. var maxcpu = me.getObjectValue('cpus', 1);
  11194.  
  11195. if (!(Ext.isNumeric(value) && Ext.isNumeric(maxcpu) && (maxcpu >= 1))) {
  11196. return '-';
  11197. }
  11198.  
  11199. var cpu = value * 100;
  11200. return cpu.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
  11201.  
  11202. };
  11203.  
  11204. var render_mem = function(value, metaData, record, rowIndex, colIndex, store) {
  11205. var maxmem = me.getObjectValue('maxmem', 0);
  11206. var per = (value / maxmem)*100;
  11207. var text = "<div>Total: " + PVE.Utils.format_size(maxmem) + "</div>" +
  11208. "<div>Used: " + PVE.Utils.format_size(value) + "</div>";
  11209. return text;
  11210. };
  11211.  
  11212. var render_swap = function(value, metaData, record, rowIndex, colIndex, store) {
  11213. var maxswap = me.getObjectValue('maxswap', 0);
  11214. var per = (value / maxswap)*100;
  11215. var text = "<div>Total: " + PVE.Utils.format_size(maxswap) + "</div>" +
  11216. "<div>Used: " + PVE.Utils.format_size(value) + "</div>";
  11217. return text;
  11218. };
  11219.  
  11220. var render_status = function(value, metaData, record, rowIndex, colIndex, store) {
  11221. var failcnt = me.getObjectValue('failcnt', 0);
  11222. if (failcnt > 0) {
  11223. return value + " (failure count " + failcnt.toString() + ")";
  11224. }
  11225. return value;
  11226. };
  11227.  
  11228. var rows = {
  11229. name: { header: gettext('Name'), defaultValue: 'no name specified' },
  11230. status: { header: gettext('Status'), defaultValue: 'unknown', renderer: render_status },
  11231. failcnt: { visible: false },
  11232. cpu: { header: 'CPU usage', required: true, renderer: render_cpu },
  11233. cpus: { visible: false },
  11234. mem: { header: 'Memory usage', required: true, renderer: render_mem },
  11235. maxmem: { visible: false },
  11236. swap: { header: 'VSwap usage', required: true, renderer: render_swap },
  11237. maxswap: { visible: false },
  11238. uptime: { header: gettext('Uptime'), required: true, renderer: PVE.Utils.render_uptime },
  11239. ha: { header: 'Managed by HA', required: true, renderer: PVE.Utils.format_boolean }
  11240. };
  11241.  
  11242. Ext.applyIf(me, {
  11243. cwidth1: 150,
  11244. height: 200,
  11245. rows: rows
  11246. });
  11247.  
  11248. me.callParent();
  11249. }
  11250. });
  11251. Ext.define('PVE.openvz.Summary', {
  11252. extend: 'Ext.panel.Panel',
  11253. alias: 'widget.pveOpenVZSummary',
  11254.  
  11255. initComponent: function() {
  11256. var me = this;
  11257.  
  11258. var nodename = me.pveSelNode.data.node;
  11259. if (!nodename) {
  11260. throw "no node name specified";
  11261. }
  11262.  
  11263. var vmid = me.pveSelNode.data.vmid;
  11264. if (!vmid) {
  11265. throw "no VM ID specified";
  11266. }
  11267.  
  11268. if (!me.workspace) {
  11269. throw "no workspace specified";
  11270. }
  11271.  
  11272. if (!me.statusStore) {
  11273. throw "no status storage specified";
  11274. }
  11275.  
  11276. var rstore = me.statusStore;
  11277.  
  11278. var statusview = Ext.create('PVE.openvz.StatusView', {
  11279. title: 'Status',
  11280. pveSelNode: me.pveSelNode,
  11281. width: 400,
  11282. rstore: rstore
  11283. });
  11284.  
  11285. var rrdurl = "/api2/png/nodes/" + nodename + "/openvz/" + vmid + "/rrd";
  11286.  
  11287. var notesview = Ext.create('PVE.panel.NotesView', {
  11288. pveSelNode: me.pveSelNode,
  11289. flex: 1
  11290. });
  11291.  
  11292. Ext.apply(me, {
  11293. tbar: [
  11294. '->',
  11295. {
  11296. xtype: 'pveRRDTypeSelector'
  11297. }
  11298. ],
  11299. autoScroll: true,
  11300. bodyStyle: 'padding:10px',
  11301. defaults: {
  11302. style: 'padding-top:10px',
  11303. width: 800
  11304. },
  11305. items: [
  11306. {
  11307. style: 'padding-top:0px',
  11308. layout: {
  11309. type: 'hbox',
  11310. align: 'stretchmax'
  11311. },
  11312. border: false,
  11313. items: [ statusview, notesview ]
  11314. },
  11315. {
  11316. xtype: 'pveRRDView',
  11317. title: "CPU usage %",
  11318. pveSelNode: me.pveSelNode,
  11319. datasource: 'cpu',
  11320. rrdurl: rrdurl
  11321. },
  11322. {
  11323. xtype: 'pveRRDView',
  11324. title: "Memory usage",
  11325. pveSelNode: me.pveSelNode,
  11326. datasource: 'mem,maxmem',
  11327. rrdurl: rrdurl
  11328. },
  11329. {
  11330. xtype: 'pveRRDView',
  11331. title: "Network traffic",
  11332. pveSelNode: me.pveSelNode,
  11333. datasource: 'netin,netout',
  11334. rrdurl: rrdurl
  11335. },
  11336. {
  11337. xtype: 'pveRRDView',
  11338. title: "Disk IO",
  11339. pveSelNode: me.pveSelNode,
  11340. datasource: 'diskread,diskwrite',
  11341. rrdurl: rrdurl
  11342. }
  11343. ]
  11344. });
  11345.  
  11346. me.on('show', function() {
  11347. notesview.load();
  11348. });
  11349.  
  11350. me.callParent();
  11351. }
  11352. });
  11353. Ext.define('PVE.openvz.RessourceInputPanel', {
  11354. extend: 'PVE.panel.InputPanel',
  11355. alias: 'widget.pveOpenVZResourceInputPanel',
  11356.  
  11357. insideWizard: false,
  11358.  
  11359. initComponent : function() {
  11360. var me = this;
  11361.  
  11362. var labelWidth = 120;
  11363.  
  11364. me.column1 = [
  11365. {
  11366. xtype: 'numberfield',
  11367. name: 'memory',
  11368. minValue: 32,
  11369. maxValue: 128*1024,
  11370. value: '512',
  11371. step: 32,
  11372. fieldLabel: gettext('Memory') + ' (MB)',
  11373. labelWidth: labelWidth,
  11374. allowBlank: false
  11375. },
  11376. {
  11377. xtype: 'numberfield',
  11378. name: 'swap',
  11379. minValue: 0,
  11380. maxValue: 128*1024,
  11381. value: '512',
  11382. step: 32,
  11383. fieldLabel: gettext('Swap') + ' (MB)',
  11384. labelWidth: labelWidth,
  11385. allowBlank: false
  11386. }
  11387. ];
  11388.  
  11389. me.column2 = [
  11390. {
  11391. xtype: 'numberfield',
  11392. name: 'disk',
  11393. minValue: 0.5,
  11394. value: '4',
  11395. step: 1,
  11396. fieldLabel: gettext('Disk size') + ' (GB)',
  11397. labelWidth: labelWidth,
  11398. allowBlank: false
  11399. },
  11400. {
  11401. xtype: 'numberfield',
  11402. name: 'cpus',
  11403. minValue: 1,
  11404. value: '1',
  11405. step: 1,
  11406. fieldLabel: 'CPUs',
  11407. labelWidth: labelWidth,
  11408. allowBlank: false
  11409. }
  11410. ];
  11411.  
  11412. me.callParent();
  11413. }
  11414. });
  11415.  
  11416. Ext.define('PVE.openvz.RessourceEdit', {
  11417. extend: 'PVE.window.Edit',
  11418.  
  11419. initComponent : function() {
  11420. var me = this;
  11421.  
  11422. Ext.apply(me, {
  11423. subject: gettext('Resources'),
  11424. items: Ext.create('PVE.openvz.RessourceInputPanel')
  11425. });
  11426.  
  11427. me.callParent();
  11428.  
  11429. me.load();
  11430. }
  11431. });// fixme: howto avoid jslint type confusion?
  11432. /*jslint confusion: true */
  11433. Ext.define('PVE.openvz.RessourceView', {
  11434. extend: 'PVE.grid.ObjectGrid',
  11435. alias: ['widget.pveOpenVZRessourceView'],
  11436.  
  11437. initComponent : function() {
  11438. var me = this;
  11439. var i, confid;
  11440.  
  11441. var nodename = me.pveSelNode.data.node;
  11442. if (!nodename) {
  11443. throw "no node name specified";
  11444. }
  11445.  
  11446. var vmid = me.pveSelNode.data.vmid;
  11447. if (!vmid) {
  11448. throw "no VM ID specified";
  11449. }
  11450.  
  11451. var rows = {
  11452. memory: {
  11453. header: gettext('Memory'),
  11454. editor: 'PVE.openvz.RessourceEdit',
  11455. never_delete: true,
  11456. renderer: function(value) {
  11457. return PVE.Utils.format_size(value*1024*1024);
  11458. }
  11459. },
  11460. swap: {
  11461. header: gettext('Swap'),
  11462. editor: 'PVE.openvz.RessourceEdit',
  11463. never_delete: true,
  11464. renderer: function(value) {
  11465. return PVE.Utils.format_size(value*1024*1024);
  11466. }
  11467. },
  11468. cpus: {
  11469. header: gettext('Processors'),
  11470. never_delete: true,
  11471. editor: 'PVE.openvz.RessourceEdit',
  11472. defaultValue: 1
  11473. },
  11474. disk: {
  11475. header: gettext('Disk size'),
  11476. editor: 'PVE.openvz.RessourceEdit',
  11477. never_delete: true,
  11478. renderer: function(value) {
  11479. return PVE.Utils.format_size(value*1024*1024*1024);
  11480. }
  11481. }
  11482. };
  11483.  
  11484. var reload = function() {
  11485. me.rstore.load();
  11486. };
  11487.  
  11488. var baseurl = 'nodes/' + nodename + '/openvz/' + vmid + '/config';
  11489.  
  11490. var sm = Ext.create('Ext.selection.RowModel', {});
  11491.  
  11492. var run_editor = function() {
  11493. var rec = sm.getSelection()[0];
  11494. if (!rec) {
  11495. return;
  11496. }
  11497.  
  11498. var rowdef = rows[rec.data.key];
  11499. if (!rowdef.editor) {
  11500. return;
  11501. }
  11502.  
  11503. var editor = rowdef.editor;
  11504.  
  11505. var win = Ext.create(editor, {
  11506. pveSelNode: me.pveSelNode,
  11507. confid: rec.data.key,
  11508. url: '/api2/extjs/' + baseurl
  11509. });
  11510.  
  11511. win.show();
  11512. win.on('destroy', reload);
  11513. };
  11514.  
  11515. var edit_btn = new PVE.button.Button({
  11516. text: gettext('Edit'),
  11517. selModel: sm,
  11518. disabled: true,
  11519. enableFn: function(rec) {
  11520. if (!rec) {
  11521. return false;
  11522. }
  11523. var rowdef = rows[rec.data.key];
  11524. return !!rowdef.editor;
  11525. },
  11526. handler: run_editor
  11527. });
  11528.  
  11529. Ext.applyIf(me, {
  11530. url: '/api2/json/' + baseurl,
  11531. selModel: sm,
  11532. cwidth1: 170,
  11533. tbar: [ edit_btn ],
  11534. rows: rows,
  11535. listeners: {
  11536. show: reload,
  11537. itemdblclick: run_editor
  11538. }
  11539. });
  11540.  
  11541. me.callParent();
  11542. }
  11543. });
  11544. /*jslint confusion: true */
  11545. Ext.define('PVE.openvz.Options', {
  11546. extend: 'PVE.grid.ObjectGrid',
  11547. alias: ['widget.pveOpenVZOptions'],
  11548.  
  11549. initComponent : function() {
  11550. var me = this;
  11551. var i;
  11552.  
  11553. var nodename = me.pveSelNode.data.node;
  11554. if (!nodename) {
  11555. throw "no node name specified";
  11556. }
  11557.  
  11558. var vmid = me.pveSelNode.data.vmid;
  11559. if (!vmid) {
  11560. throw "no VM ID specified";
  11561. }
  11562.  
  11563. var rows = {
  11564. onboot: {
  11565. header: gettext('Start at boot'),
  11566. defaultValue: '',
  11567. renderer: PVE.Utils.format_boolean,
  11568. editor: {
  11569. xtype: 'pveWindowEdit',
  11570. subject: gettext('Start at boot'),
  11571. items: {
  11572. xtype: 'pvecheckbox',
  11573. name: 'onboot',
  11574. uncheckedValue: 0,
  11575. defaultValue: 0,
  11576. fieldLabel: gettext('Start at boot')
  11577. }
  11578. }
  11579. },
  11580. ostemplate: {
  11581. header: gettext('Template'),
  11582. defaultValue: 'no set'
  11583. },
  11584. storage: {
  11585. header: gettext('Storage'),
  11586. defaultValue: 'no set'
  11587. },
  11588. cpuunits: {
  11589. header: 'CPU units',
  11590. defaultValue: '1000',
  11591. editor: {
  11592. xtype: 'pveWindowEdit',
  11593. subject: 'CPU units',
  11594. items: {
  11595. xtype: 'numberfield',
  11596. name: 'cpuunits',
  11597. fieldLabel: 'CPU units',
  11598. minValue: 8,
  11599. maxValue: 500000,
  11600. allowBlank: false
  11601. }
  11602. }
  11603. },
  11604. quotaugidlimit: {
  11605. header: 'Quota UGID limit',
  11606. defaultValue: '0',
  11607. renderer: function(value) {
  11608. if (value == 0) {
  11609. return 'User quotas disabled.';
  11610. }
  11611. return value;
  11612. },
  11613. editor: {
  11614. xtype: 'pveWindowEdit',
  11615. subject: 'Quota UGID limit (0 to disable user quotas)',
  11616. items: {
  11617. xtype: 'numberfield',
  11618. name: 'quotaugidlimit',
  11619. fieldLabel: 'UGID limit',
  11620. minValue: 0,
  11621. allowBlank: false
  11622. }
  11623. }
  11624. },
  11625. quotatime: {
  11626. header: 'Quota Grace period',
  11627. defaultValue: '0',
  11628. editor: {
  11629. xtype: 'pveWindowEdit',
  11630. subject: 'Quota Grace period (seconds)',
  11631. items: {
  11632. xtype: 'numberfield',
  11633. name: 'quotatime',
  11634. minValue: 0,
  11635. allowBlank: false,
  11636. fieldLabel: 'Grace period'
  11637. }
  11638. }
  11639. }
  11640. };
  11641.  
  11642. var baseurl = 'nodes/' + nodename + '/openvz/' + vmid + '/config';
  11643.  
  11644. var reload = function() {
  11645. me.rstore.load();
  11646. };
  11647.  
  11648. var sm = Ext.create('Ext.selection.RowModel', {});
  11649.  
  11650. var run_editor = function() {
  11651. var rec = sm.getSelection()[0];
  11652. if (!rec) {
  11653. return;
  11654. }
  11655.  
  11656. var rowdef = rows[rec.data.key];
  11657. if (!rowdef.editor) {
  11658. return;
  11659. }
  11660.  
  11661. var config = Ext.apply({
  11662. pveSelNode: me.pveSelNode,
  11663. confid: rec.data.key,
  11664. url: '/api2/extjs/' + baseurl
  11665. }, rowdef.editor);
  11666. var win = Ext.createWidget(rowdef.editor.xtype, config);
  11667. win.load();
  11668.  
  11669. win.show();
  11670. win.on('destroy', reload);
  11671. };
  11672.  
  11673. var edit_btn = new PVE.button.Button({
  11674. text: gettext('Edit'),
  11675. disabled: true,
  11676. selModel: sm,
  11677. enableFn: function(rec) {
  11678. var rowdef = rows[rec.data.key];
  11679. return !!rowdef.editor;
  11680. },
  11681. handler: run_editor
  11682. });
  11683.  
  11684. Ext.applyIf(me, {
  11685. url: "/api2/json/nodes/" + nodename + "/openvz/" + vmid + "/config",
  11686. selModel: sm,
  11687. cwidth1: 150,
  11688. tbar: [ edit_btn ],
  11689. rows: rows,
  11690. listeners: {
  11691. itemdblclick: run_editor
  11692. }
  11693. });
  11694.  
  11695. me.callParent();
  11696.  
  11697. me.on('show', reload);
  11698. }
  11699. });
  11700.  
  11701. /*jslint confusion: true */
  11702. Ext.define('PVE.OpenVZ.NetIfEdit', {
  11703. extend: 'PVE.window.Edit',
  11704.  
  11705. isAdd: true,
  11706.  
  11707. getValues: function() {
  11708. var me = this;
  11709.  
  11710. var values = me.formPanel.getValues();
  11711.  
  11712. if (!me.create) {
  11713. values.ifname = me.ifname;
  11714. }
  11715.  
  11716. var newdata = Ext.clone(me.netif);
  11717. newdata[values.ifname] = values;
  11718. return { netif: PVE.Parser.printOpenVZNetIf(newdata) };
  11719. },
  11720.  
  11721. initComponent : function() {
  11722. var me = this;
  11723.  
  11724. if (!me.dataCache) {
  11725. throw "no dataCache specified";
  11726. }
  11727.  
  11728. if (!me.nodename) {
  11729. throw "no node name specified";
  11730. }
  11731.  
  11732. me.netif = PVE.Parser.parseOpenVZNetIf(me.dataCache.netif) || {};
  11733.  
  11734. var cdata = {};
  11735.  
  11736. if (!me.create) {
  11737. if (!me.ifname) {
  11738. throw "no interface name specified";
  11739. }
  11740. cdata = me.netif[me.ifname];
  11741. if (!cdata) {
  11742. throw "no such interface '" + me.ifname + "'";
  11743. }
  11744. }
  11745.  
  11746. Ext.apply(me, {
  11747. subject: gettext('Network Device') + ' (veth)',
  11748. digest: me.dataCache.digest,
  11749. width: 350,
  11750. fieldDefaults: {
  11751. labelWidth: 130
  11752. },
  11753. items: [
  11754. {
  11755. xtype: me.create ? 'textfield' : 'displayfield',
  11756. name: 'ifname',
  11757. height: 22, // hack: set same height as text fields
  11758. fieldLabel: gettext('Name') + ' (i.e. eth0)',
  11759. allowBlank: false,
  11760. value: cdata.ifname,
  11761. validator: function(value) {
  11762. if (me.create && me.netif[value]) {
  11763. return "interface name already in use";
  11764. }
  11765. return true;
  11766. }
  11767. },
  11768. {
  11769. xtype: 'textfield',
  11770. name: 'mac',
  11771. fieldLabel: 'MAC',
  11772. vtype: 'MacAddress',
  11773. value: cdata.mac,
  11774. allowBlank: me.create,
  11775. emptyText: 'auto'
  11776. },
  11777. {
  11778. xtype: 'PVE.form.BridgeSelector',
  11779. name: 'bridge',
  11780. nodename: me.nodename,
  11781. fieldLabel: 'Bridge',
  11782. value: cdata.bridge,
  11783. allowBlank: false
  11784. },
  11785. {
  11786. xtype: 'textfield',
  11787. name: 'host_ifname',
  11788. fieldLabel: 'Host device name',
  11789. value: cdata.host_ifname,
  11790. allowBlank: true,
  11791. emptyText: 'auto'
  11792. },
  11793. {
  11794. xtype: 'textfield',
  11795. name: 'host_mac',
  11796. fieldLabel: 'Host MAC address',
  11797. vtype: 'MacAddress',
  11798. value: cdata.host_mac,
  11799. allowBlank: true,
  11800. emptyText: 'auto'
  11801. }
  11802. ]
  11803. });
  11804.  
  11805. me.callParent();
  11806. }
  11807. });
  11808.  
  11809. Ext.define('PVE.OpenVZ.IPAdd', {
  11810. extend: 'PVE.window.Edit',
  11811.  
  11812. isAdd: true,
  11813.  
  11814. create: true,
  11815.  
  11816. getValues: function() {
  11817. var me = this;
  11818.  
  11819. var values = me.formPanel.getValues();
  11820. console.dir(values);
  11821.  
  11822. if (me.dataCache.ip_address) {
  11823. return { ip_address: me.dataCache.ip_address + ' ' + values.ipaddress };
  11824. } else {
  11825. return { ip_address: values.ipaddress };
  11826. }
  11827. },
  11828.  
  11829. initComponent : function() {
  11830. var me = this;
  11831.  
  11832. if (!me.dataCache) {
  11833. throw "no dataCache specified";
  11834. }
  11835.  
  11836. Ext.apply(me, {
  11837. subject: gettext('IP address') + ' (venet)',
  11838. digest: me.dataCache.digest,
  11839. width: 350,
  11840. items: {
  11841. xtype: 'textfield',
  11842. name: 'ipaddress',
  11843. fieldLabel: gettext('IP address'),
  11844. vtype: 'IPAddress',
  11845. allowBlank: false
  11846. }
  11847. });
  11848.  
  11849. me.callParent();
  11850. }
  11851. });
  11852.  
  11853.  
  11854. Ext.define('PVE.openvz.NetworkView', {
  11855. extend: 'Ext.grid.GridPanel',
  11856. alias: ['widget.pveOpenVZNetworkView'],
  11857.  
  11858. dataCache: {}, // used to store result of last load
  11859.  
  11860. ipAddressText: gettext('IP address'),
  11861. networkText: gettext('Network'),
  11862. networkDeviceText: gettext('Network Device'),
  11863.  
  11864. renderType: function(value, metaData, record, rowIndex, colIndex, store) {
  11865. var me = this;
  11866. if (value === 'ip') {
  11867. return me.ipAddressText;
  11868. } else if (value === 'net') {
  11869. return me.networkText;
  11870. } else if (value === 'veth') {
  11871. return me.networkDeviceText;
  11872. } else {
  11873. return value;
  11874. }
  11875. },
  11876.  
  11877. renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
  11878. var type = record.data.type;
  11879. if (type === 'veth') {
  11880. return record.data.ifname;
  11881. } else {
  11882. return value;
  11883. }
  11884. },
  11885.  
  11886. load: function() {
  11887. var me = this;
  11888.  
  11889. PVE.Utils.setErrorMask(me, true);
  11890.  
  11891. PVE.Utils.API2Request({
  11892. url: me.url,
  11893. failure: function(response, opts) {
  11894. PVE.Utils.setErrorMask(me, gettext('Error') + ': ' + response.htmlStatus);
  11895. },
  11896. success: function(response, opts) {
  11897. PVE.Utils.setErrorMask(me, false);
  11898. var result = Ext.decode(response.responseText);
  11899. var data = result.data || {};
  11900. me.dataCache = data;
  11901. var ipAddress = data.ip_address;
  11902. var records = [];
  11903. if (ipAddress) {
  11904. var ind = 0;
  11905. Ext.Array.each(ipAddress.split(' '), function(value) {
  11906. if (value) {
  11907. records.push({
  11908. type: 'ip',
  11909. id: 'ip' + ind,
  11910. value: value
  11911. });
  11912. ind++;
  11913. }
  11914. });
  11915. }
  11916. var netif = PVE.Parser.parseOpenVZNetIf(me.dataCache.netif);
  11917. if (netif) {
  11918. Ext.Object.each(netif, function(iface, data) {
  11919.  
  11920. records.push(Ext.apply({
  11921. type: 'veth',
  11922. id: iface,
  11923. value: data.raw
  11924. }, data));
  11925. });
  11926. }
  11927. me.store.loadData(records);
  11928. }
  11929. });
  11930. },
  11931.  
  11932. initComponent : function() {
  11933. var me = this;
  11934.  
  11935. var nodename = me.pveSelNode.data.node;
  11936. if (!nodename) {
  11937. throw "no node name specified";
  11938. }
  11939.  
  11940. var vmid = me.pveSelNode.data.vmid;
  11941. if (!vmid) {
  11942. throw "no VM ID specified";
  11943. }
  11944.  
  11945. me.url = '/nodes/' + nodename + '/openvz/' + vmid + '/config';
  11946.  
  11947. var store = new Ext.data.Store({
  11948. model: 'pve-openvz-network'
  11949. });
  11950.  
  11951. var sm = Ext.create('Ext.selection.RowModel', {});
  11952.  
  11953. var remove_btn = new PVE.button.Button({
  11954. text: gettext('Remove'),
  11955. disabled: true,
  11956. selModel: sm,
  11957. confirmMsg: function (rec) {
  11958. var idtext = rec.id;
  11959. if (rec.data.type === 'ip') {
  11960. idtext = rec.data.value;
  11961. } else if (rec.data.type === 'veth') {
  11962. idtext = rec.data.id;
  11963. }
  11964. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  11965. "'" + idtext + "'");
  11966. },
  11967. handler: function(btn, event, rec) {
  11968. var values = { digest: me.dataCache.digest };
  11969.  
  11970. if (rec.data.type === 'ip') {
  11971. var ipa = [];
  11972. Ext.Array.each(me.dataCache.ip_address.split(' '), function(value) {
  11973. if (value && value !== rec.data.value) {
  11974. ipa.push(value);
  11975. }
  11976. });
  11977. values.ip_address = ipa.join(' ');
  11978. } else if (rec.data.type === 'veth') {
  11979. var netif = PVE.Parser.parseOpenVZNetIf(me.dataCache.netif);
  11980. delete netif[rec.data.id];
  11981. values.netif = PVE.Parser.printOpenVZNetIf(netif);
  11982. } else {
  11983. return; // not implemented
  11984. }
  11985.  
  11986. PVE.Utils.API2Request({
  11987. url: me.url,
  11988. waitMsgTarget: me,
  11989. method: 'PUT',
  11990. params: values,
  11991. callback: function() {
  11992. me.load();
  11993. },
  11994. failure: function (response, opts) {
  11995. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  11996. }
  11997. });
  11998. }
  11999. });
  12000.  
  12001. var run_editor = function() {
  12002. var rec = sm.getSelection()[0];
  12003. if (!rec || rec.data.type !== 'veth') {
  12004. return;
  12005. }
  12006.  
  12007. var win = Ext.create('PVE.OpenVZ.NetIfEdit', {
  12008. url: me.url,
  12009. nodename: nodename,
  12010. dataCache: me.dataCache,
  12011. ifname: rec.data.id
  12012. });
  12013. win.on('destroy', me.load, me);
  12014. win.show();
  12015. };
  12016.  
  12017. var edit_btn = new PVE.button.Button({
  12018. text: gettext('Edit'),
  12019. selModel: sm,
  12020. disabled: true,
  12021. enableFn: function(rec) {
  12022. return rec.data.type === 'veth';
  12023. },
  12024. handler: run_editor
  12025. });
  12026.  
  12027.  
  12028. Ext.applyIf(me, {
  12029. store: store,
  12030. selModel: sm,
  12031. stateful: false,
  12032. tbar: [
  12033. {
  12034. text: gettext('Add'),
  12035. menu: new Ext.menu.Menu({
  12036. items: [
  12037. {
  12038. text: gettext('IP address') + ' (venet)',
  12039. //plain: true,
  12040. //iconCls: 'pve-itype-icon-storage',
  12041. handler: function() {
  12042. var win = Ext.create('PVE.OpenVZ.IPAdd', {
  12043. url: me.url,
  12044. dataCache: me.dataCache
  12045. });
  12046. win.on('destroy', me.load, me);
  12047. win.show();
  12048. }
  12049. },
  12050. {
  12051. text: gettext('Network Device') + ' (veth)',
  12052. //plain: true,
  12053. //iconCls: 'pve-itype-icon-storage',
  12054. handler: function() {
  12055. var win = Ext.create('PVE.OpenVZ.NetIfEdit', {
  12056. url: me.url,
  12057. nodename: nodename,
  12058. create: true,
  12059. dataCache: me.dataCache
  12060. });
  12061. win.on('destroy', me.load, me);
  12062. win.show();
  12063. }
  12064. }
  12065. ]
  12066. })
  12067. },
  12068. remove_btn,
  12069. edit_btn
  12070. ],
  12071. columns: [
  12072. {
  12073. header: gettext('Type'),
  12074. width: 110,
  12075. dataIndex: 'type',
  12076. renderer: me.renderType
  12077. },
  12078. {
  12079. header: gettext('IP address') +'/' + gettext('Name'),
  12080. width: 110,
  12081. dataIndex: 'value',
  12082. renderer: me.renderValue
  12083. },
  12084. {
  12085. header: 'Bridge',
  12086. width: 110,
  12087. dataIndex: 'bridge'
  12088. },
  12089. {
  12090. header: 'MAC',
  12091. width: 110,
  12092. dataIndex: 'mac'
  12093. },
  12094. {
  12095. header: 'Host ifname',
  12096. width: 110,
  12097. dataIndex: 'host_ifname'
  12098. },
  12099. {
  12100. header: 'Host MAC',
  12101. width: 110,
  12102. dataIndex: 'host_mac'
  12103. }
  12104. ],
  12105. listeners: {
  12106. show: me.load,
  12107. itemdblclick: run_editor
  12108. }
  12109. });
  12110.  
  12111. me.callParent();
  12112.  
  12113. me.load();
  12114. }
  12115. }, function() {
  12116.  
  12117. Ext.define('pve-openvz-network', {
  12118. extend: "Ext.data.Model",
  12119. proxy: { type: 'memory' },
  12120. fields: [ 'id', 'type', 'value', 'ifname', 'mac', 'bridge', 'host_ifname', 'host_mac' ]
  12121. });
  12122.  
  12123. });
  12124.  
  12125. /*jslint confusion: true */
  12126. Ext.define('PVE.openvz.DNS', {
  12127. extend: 'PVE.grid.ObjectGrid',
  12128. alias: ['widget.pveOpenVZDNS'],
  12129.  
  12130. initComponent : function() {
  12131. var me = this;
  12132. var i;
  12133.  
  12134. var nodename = me.pveSelNode.data.node;
  12135. if (!nodename) {
  12136. throw "no node name specified";
  12137. }
  12138.  
  12139. var vmid = me.pveSelNode.data.vmid;
  12140. if (!vmid) {
  12141. throw "no VM ID specified";
  12142. }
  12143.  
  12144. var rows = {
  12145. hostname: {
  12146. required: true,
  12147. defaultValue: me.pveSelNode.data.name,
  12148. header: 'Hostname',
  12149. editor: {
  12150. xtype: 'pveWindowEdit',
  12151. subject: 'Hostname',
  12152. items: {
  12153. xtype: 'textfield',
  12154. name: 'hostname',
  12155. value: '',
  12156. fieldLabel: 'Hostname',
  12157. allowBlank: true,
  12158. emptyText: me.pveSelNode.data.name
  12159. }
  12160. }
  12161. },
  12162. searchdomain: {
  12163. header: 'DNS domain',
  12164. defaultValue: '',
  12165. editor: {
  12166. xtype: 'pveWindowEdit',
  12167. subject: 'DNS domain',
  12168. items: {
  12169. xtype: 'pvetextfield',
  12170. name: 'searchdomain',
  12171. fieldLabel: 'DNS domain',
  12172. allowBlank: false
  12173. }
  12174. }
  12175. },
  12176. nameserver: {
  12177. header: gettext('DNS server'),
  12178. defaultValue: '',
  12179. editor: {
  12180. xtype: 'pveWindowEdit',
  12181. subject: gettext('DNS server'),
  12182. items: {
  12183. xtype: 'pvetextfield',
  12184. name: 'nameserver',
  12185. fieldLabel: gettext('DNS server'),
  12186. allowBlank: false
  12187. }
  12188. }
  12189. }
  12190. };
  12191.  
  12192. var baseurl = 'nodes/' + nodename + '/openvz/' + vmid + '/config';
  12193.  
  12194. var reload = function() {
  12195. me.rstore.load();
  12196. };
  12197.  
  12198. var sm = Ext.create('Ext.selection.RowModel', {});
  12199.  
  12200. var run_editor = function() {
  12201. var rec = sm.getSelection()[0];
  12202. if (!rec) {
  12203. return;
  12204. }
  12205.  
  12206. var rowdef = rows[rec.data.key];
  12207. if (!rowdef.editor) {
  12208. return;
  12209. }
  12210.  
  12211. var config = Ext.apply({
  12212. pveSelNode: me.pveSelNode,
  12213. confid: rec.data.key,
  12214. url: '/api2/extjs/' + baseurl
  12215. }, rowdef.editor);
  12216. var win = Ext.createWidget(rowdef.editor.xtype, config);
  12217. win.load();
  12218.  
  12219. win.show();
  12220. win.on('destroy', reload);
  12221. };
  12222.  
  12223. var edit_btn = new PVE.button.Button({
  12224. text: gettext('Edit'),
  12225. disabled: true,
  12226. selModel: sm,
  12227. enableFn: function(rec) {
  12228. var rowdef = rows[rec.data.key];
  12229. return !!rowdef.editor;
  12230. },
  12231. handler: run_editor
  12232. });
  12233.  
  12234. var set_button_status = function() {
  12235. var sm = me.getSelectionModel();
  12236. var rec = sm.getSelection()[0];
  12237.  
  12238. if (!rec) {
  12239. edit_btn.disable();
  12240. return;
  12241. }
  12242. var rowdef = rows[rec.data.key];
  12243. edit_btn.setDisabled(!rowdef.editor);
  12244. };
  12245.  
  12246. Ext.applyIf(me, {
  12247. url: "/api2/json/nodes/" + nodename + "/openvz/" + vmid + "/config",
  12248. selModel: sm,
  12249. cwidth1: 150,
  12250. tbar: [ edit_btn ],
  12251. rows: rows,
  12252. listeners: {
  12253. itemdblclick: run_editor,
  12254. selectionchange: set_button_status
  12255. }
  12256. });
  12257.  
  12258. me.callParent();
  12259.  
  12260. me.on('show', reload);
  12261. }
  12262. });
  12263.  
  12264. /*jslint confusion: true */
  12265. Ext.define('PVE.openvz.BeanCounterGrid', {
  12266. extend: 'Ext.grid.GridPanel',
  12267. alias: ['widget.pveBeanCounterGrid'],
  12268.  
  12269. renderUbc: function(value, metaData, record, rowIndex, colIndex, store) {
  12270.  
  12271. if (value === 9223372036854775807) {
  12272. return '-';
  12273. }
  12274.  
  12275. if (record.id.match(/pages$/)) {
  12276. return PVE.Utils.format_size(value*4096);
  12277. }
  12278. if (record.id.match(/(size|buf)$/)) {
  12279. return PVE.Utils.format_size(value);
  12280. }
  12281.  
  12282. return value;
  12283. },
  12284.  
  12285. initComponent : function() {
  12286. var me = this;
  12287.  
  12288. if (!me.url) {
  12289. throw "no url specified";
  12290. }
  12291.  
  12292. var store = new Ext.data.Store({
  12293. model: 'pve-openvz-ubc',
  12294. proxy: {
  12295. type: 'pve',
  12296. url: me.url
  12297. },
  12298. sorters: [
  12299. {
  12300. property : 'id',
  12301. direction: 'ASC'
  12302. }
  12303. ]
  12304. });
  12305.  
  12306. var reload = function() {
  12307. store.load();
  12308. };
  12309.  
  12310. Ext.applyIf(me, {
  12311. store: store,
  12312. stateful: false,
  12313. columns: [
  12314. {
  12315. header: 'Ressource',
  12316. width: 100,
  12317. dataIndex: 'id'
  12318. },
  12319. {
  12320. header: 'held',
  12321. width: 100,
  12322. renderer: me.renderUbc,
  12323. dataIndex: 'held'
  12324. },
  12325. {
  12326. header: 'maxheld',
  12327. width: 100,
  12328. renderer: me.renderUbc,
  12329. dataIndex: 'maxheld'
  12330. },
  12331. {
  12332. header: 'barrier',
  12333. width: 100,
  12334. renderer: me.renderUbc,
  12335. dataIndex: 'bar'
  12336. },
  12337. {
  12338. header: 'limit',
  12339. width: 100,
  12340. renderer: me.renderUbc,
  12341. dataIndex: 'lim'
  12342. },
  12343. {
  12344. header: 'failcnt',
  12345. width: 100,
  12346. dataIndex: 'failcnt'
  12347. }
  12348. ],
  12349. listeners: {
  12350. show: reload
  12351. }
  12352. });
  12353.  
  12354. me.callParent();
  12355.  
  12356. }
  12357. }, function() {
  12358.  
  12359. Ext.define('pve-openvz-ubc', {
  12360. extend: "Ext.data.Model",
  12361. fields: [ 'id',
  12362. { name: 'held', type: 'number' },
  12363. { name: 'maxheld', type: 'number' },
  12364. { name: 'bar', type: 'number' },
  12365. { name: 'lim', type: 'number' },
  12366. { name: 'failcnt', type: 'number' }
  12367. ]
  12368. });
  12369.  
  12370. });
  12371. Ext.define('PVE.openvz.Config', {
  12372. extend: 'PVE.panel.Config',
  12373. alias: 'widget.PVE.openvz.Config',
  12374.  
  12375. initComponent: function() {
  12376. var me = this;
  12377.  
  12378. var nodename = me.pveSelNode.data.node;
  12379. if (!nodename) {
  12380. throw "no node name specified";
  12381. }
  12382.  
  12383. var vmid = me.pveSelNode.data.vmid;
  12384. if (!vmid) {
  12385. throw "no VM ID specified";
  12386. }
  12387.  
  12388. me.statusStore = Ext.create('PVE.data.ObjectStore', {
  12389. url: "/api2/json/nodes/" + nodename + "/openvz/" + vmid + "/status/current",
  12390. interval: 1000
  12391. });
  12392.  
  12393. var vm_command = function(cmd, params) {
  12394. PVE.Utils.API2Request({
  12395. params: params,
  12396. url: '/nodes/' + nodename + '/openvz/' + vmid + "/status/" + cmd,
  12397. waitMsgTarget: me,
  12398. method: 'POST',
  12399. failure: function(response, opts) {
  12400. Ext.Msg.alert('Error', response.htmlStatus);
  12401. }
  12402. });
  12403. };
  12404.  
  12405. var startBtn = Ext.create('Ext.Button', {
  12406. text: gettext('Start'),
  12407. handler: function() {
  12408. vm_command('start');
  12409. }
  12410. });
  12411.  
  12412. var umountBtn = Ext.create('Ext.Button', {
  12413. text: gettext('Unmount'),
  12414. disabled: true,
  12415. hidden: true,
  12416. handler: function() {
  12417. vm_command('umount');
  12418. }
  12419. });
  12420.  
  12421. var stopBtn = Ext.create('PVE.button.Button', {
  12422. text: gettext('Stop'),
  12423. confirmMsg: Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid),
  12424. handler: function() {
  12425. vm_command("stop");
  12426. }
  12427. });
  12428.  
  12429. var shutdownBtn = Ext.create('PVE.button.Button', {
  12430. text: gettext('Shutdown'),
  12431. confirmMsg: Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid),
  12432. handler: function() {
  12433. vm_command('shutdown');
  12434. }
  12435. });
  12436.  
  12437. var migrateBtn = Ext.create('Ext.Button', {
  12438. text: gettext('Migrate'),
  12439. handler: function() {
  12440. var win = Ext.create('PVE.window.Migrate', {
  12441. vmtype: 'openvz',
  12442. nodename: nodename,
  12443. vmid: vmid
  12444. });
  12445. win.show();
  12446. }
  12447. });
  12448.  
  12449. var removeBtn = Ext.create('PVE.button.Button', {
  12450. text: gettext('Remove'),
  12451. confirmMsg: Ext.String.format(gettext('Are you sure you want to remove VM {0}? This will permanently erase all VM data.'), vmid),
  12452. handler: function() {
  12453. PVE.Utils.API2Request({
  12454. url: '/nodes/' + nodename + '/openvz/' + vmid,
  12455. method: 'DELETE',
  12456. waitMsgTarget: me,
  12457. failure: function(response, opts) {
  12458. Ext.Msg.alert('Error', response.htmlStatus);
  12459. }
  12460. });
  12461. }
  12462. });
  12463.  
  12464. var vmname = me.pveSelNode.data.name;
  12465.  
  12466. var consoleBtn = Ext.create('Ext.Button', {
  12467. text: gettext('Console'),
  12468. handler: function() {
  12469. PVE.Utils.openConoleWindow('openvz', vmid, nodename, vmname);
  12470. }
  12471. });
  12472.  
  12473. var descr = vmid + " (" + (vmname ? "'" + vmname + "' " : "'CT " + vmid + "'") + ")";
  12474.  
  12475. Ext.apply(me, {
  12476. title: Ext.String.format(gettext("Container {0} on node {1}"), descr, "'" + nodename + "'"),
  12477. hstateid: 'ovztab',
  12478. tbar: [ startBtn, shutdownBtn, umountBtn, stopBtn, removeBtn,
  12479. migrateBtn, consoleBtn ],
  12480. defaults: { statusStore: me.statusStore },
  12481. items: [
  12482. {
  12483. title: gettext('Summary'),
  12484. xtype: 'pveOpenVZSummary',
  12485. itemId: 'summary'
  12486. },
  12487. {
  12488. title: gettext('Resources'),
  12489. itemId: 'resources',
  12490. xtype: 'pveOpenVZRessourceView'
  12491. },
  12492. {
  12493. title: gettext('Network'),
  12494. itemId: 'network',
  12495. xtype: 'pveOpenVZNetworkView'
  12496. },
  12497. {
  12498. title: 'DNS',
  12499. itemId: 'dns',
  12500. xtype: 'pveOpenVZDNS'
  12501. },
  12502. {
  12503. title: gettext('Options'),
  12504. itemId: 'options',
  12505. xtype: 'pveOpenVZOptions'
  12506. },
  12507. {
  12508. title: 'UBC',
  12509. itemId: 'ubc',
  12510. xtype: 'pveBeanCounterGrid',
  12511. url: '/api2/json/nodes/' + nodename + '/openvz/' + vmid + '/status/ubc'
  12512. },
  12513. {
  12514. title: "InitLog",
  12515. itemId: 'initlog',
  12516. xtype: 'pveLogView',
  12517. url: '/api2/extjs/nodes/' + nodename + '/openvz/' + vmid + '/initlog'
  12518. },
  12519. {
  12520. title: gettext('Backup'),
  12521. xtype: 'pveBackupView',
  12522. itemId: 'backup'
  12523. },
  12524. {
  12525. xtype: 'pveACLView',
  12526. title: gettext('Permissions'),
  12527. itemId: 'permissions',
  12528. path: '/vms/' + vmid
  12529. }
  12530. ]
  12531. });
  12532.  
  12533. me.callParent();
  12534.  
  12535. me.statusStore.on('load', function(s, records, success) {
  12536. var status;
  12537. if (!success) {
  12538. me.workspace.checkVmMigration(me.pveSelNode);
  12539. status = 'unknown';
  12540. } else {
  12541. var rec = s.data.get('status');
  12542. status = rec ? rec.data.value : 'unknown';
  12543. }
  12544. startBtn.setDisabled(status === 'running');
  12545. shutdownBtn.setDisabled(status !== 'running');
  12546. stopBtn.setDisabled(status === 'stopped');
  12547. removeBtn.setDisabled(status !== 'stopped');
  12548.  
  12549. if (status === 'mounted') {
  12550. umountBtn.setDisabled(false);
  12551. umountBtn.setVisible(true);
  12552. stopBtn.setVisible(false);
  12553. } else {
  12554. umountBtn.setDisabled(true);
  12555. umountBtn.setVisible(false);
  12556. stopBtn.setVisible(true);
  12557. }
  12558. });
  12559.  
  12560. me.on('afterrender', function() {
  12561. me.statusStore.startUpdate();
  12562. });
  12563.  
  12564. me.on('destroy', function() {
  12565. me.statusStore.stopUpdate();
  12566. });
  12567. }
  12568. });
  12569. /*jslint confusion: true */
  12570. Ext.define('PVE.openvz.CreateWizard', {
  12571. extend: 'PVE.window.Wizard',
  12572.  
  12573. initComponent: function() {
  12574. var me = this;
  12575.  
  12576. var nextvmid = PVE.data.ResourceStore.findNextVMID();
  12577.  
  12578. var summarystore = Ext.create('Ext.data.Store', {
  12579. model: 'KeyValue',
  12580. sorters: [
  12581. {
  12582. property : 'key',
  12583. direction: 'ASC'
  12584. }
  12585. ]
  12586. });
  12587.  
  12588. var storagesel = Ext.create('PVE.form.StorageSelector', {
  12589. name: 'storage',
  12590. fieldLabel: gettext('Storage'),
  12591. storageContent: 'rootdir',
  12592. autoSelect: true,
  12593. allowBlank: false
  12594. });
  12595.  
  12596. var tmplsel = Ext.create('PVE.form.FileSelector', {
  12597. name: 'ostemplate',
  12598. storageContent: 'vztmpl',
  12599. fieldLabel: gettext('Template'),
  12600. allowBlank: false
  12601. });
  12602.  
  12603. var tmplstoragesel = Ext.create('PVE.form.StorageSelector', {
  12604. name: 'tmplstorage',
  12605. fieldLabel: gettext('Storage'),
  12606. storageContent: 'vztmpl',
  12607. autoSelect: true,
  12608. allowBlank: false,
  12609. listeners: {
  12610. change: function(f, value) {
  12611. tmplsel.setStorage(value);
  12612. }
  12613. }
  12614. });
  12615.  
  12616. var bridgesel = Ext.create('PVE.form.BridgeSelector', {
  12617. name: 'bridge',
  12618. fieldLabel: 'Bridge',
  12619. labelAlign: 'right',
  12620. autoSelect: true,
  12621. disabled: true,
  12622. allowBlank: false
  12623. });
  12624.  
  12625. Ext.applyIf(me, {
  12626. subject: gettext('OpenVZ Container'),
  12627. items: [
  12628. {
  12629. xtype: 'inputpanel',
  12630. title: gettext('General'),
  12631. column1: [
  12632. {
  12633. xtype: 'PVE.form.NodeSelector',
  12634. name: 'nodename',
  12635. fieldLabel: gettext('Node'),
  12636. allowBlank: false,
  12637. onlineValidator: true,
  12638. listeners: {
  12639. change: function(f, value) {
  12640. tmplstoragesel.setNodename(value);
  12641. tmplsel.setStorage(undefined, value);
  12642. bridgesel.setNodename(value);
  12643. storagesel.setNodename(value);
  12644. }
  12645. }
  12646. },
  12647. {
  12648. xtype: 'pveVMIDSelector',
  12649. name: 'vmid',
  12650. value: nextvmid,
  12651. validateExists: false
  12652. },
  12653. {
  12654. xtype: 'pvetextfield',
  12655. name: 'hostname',
  12656. value: '',
  12657. fieldLabel: 'Hostname',
  12658. skipEmptyText: true,
  12659. allowBlank: true
  12660. }
  12661. ],
  12662. column2: [
  12663. {
  12664. xtype: 'pvePoolSelector',
  12665. fieldLabel: gettext('Resource Pool'),
  12666. name: 'pool',
  12667. value: '',
  12668. allowBlank: true
  12669. },
  12670. storagesel,
  12671. {
  12672. xtype: 'textfield',
  12673. inputType: 'password',
  12674. name: 'password',
  12675. value: '',
  12676. fieldLabel: gettext('Password'),
  12677. allowBlank: false,
  12678. minLength: 5,
  12679. change: function(f, value) {
  12680. if (!me.rendered) {
  12681. return;
  12682. }
  12683. me.down('field[name=confirmpw]').validate();
  12684. }
  12685. },
  12686. {
  12687. xtype: 'textfield',
  12688. inputType: 'password',
  12689. name: 'confirmpw',
  12690. value: '',
  12691. fieldLabel: gettext('Confirm password'),
  12692. allowBlank: false,
  12693. validator: function(value) {
  12694. var pw = me.down('field[name=password]').getValue();
  12695. if (pw !== value) {
  12696. return "Passowords does not match!";
  12697. }
  12698. return true;
  12699. }
  12700. }
  12701. ],
  12702. onGetValues: function(values) {
  12703. delete values.confirmpw;
  12704. if (!values.pool) {
  12705. delete values.pool;
  12706. }
  12707. return values;
  12708. }
  12709. },
  12710. {
  12711. xtype: 'inputpanel',
  12712. title: gettext('Template'),
  12713. column1: [ tmplstoragesel, tmplsel]
  12714. },
  12715. {
  12716. xtype: 'pveOpenVZResourceInputPanel',
  12717. title: gettext('Resources')
  12718. },
  12719. {
  12720. xtype: 'inputpanel',
  12721. title: gettext('Network'),
  12722. column1: [
  12723. {
  12724. xtype: 'radiofield',
  12725. name: 'networkmode',
  12726. inputValue: 'routed',
  12727. boxLabel: 'Routed mode (venet)',
  12728. checked: true,
  12729. listeners: {
  12730. change: function(f, value) {
  12731. if (!me.rendered) {
  12732. return;
  12733. }
  12734. me.down('field[name=ip_address]').setDisabled(!value);
  12735. me.down('field[name=ip_address]').validate();
  12736. }
  12737. }
  12738. },
  12739. {
  12740. xtype: 'textfield',
  12741. name: 'ip_address',
  12742. vtype: 'IPAddress',
  12743. value: '',
  12744. fieldLabel: gettext('IP address'),
  12745. labelAlign: 'right',
  12746. allowBlank: false
  12747. }
  12748. ],
  12749. column2: [
  12750. {
  12751. xtype: 'radiofield',
  12752. name: 'networkmode',
  12753. inputValue: 'bridge',
  12754. boxLabel: 'Bridged mode',
  12755. checked: false,
  12756. listeners: {
  12757. change: function(f, value) {
  12758. if (!me.rendered) {
  12759. return;
  12760. }
  12761. me.down('field[name=bridge]').setDisabled(!value);
  12762. me.down('field[name=bridge]').validate();
  12763. }
  12764. }
  12765. },
  12766. bridgesel
  12767. ],
  12768. onGetValues: function(values) {
  12769. if (values.networkmode === 'bridge') {
  12770. return { netif: 'ifname=eth0,bridge=' + values.bridge };
  12771. } else {
  12772. return { ip_address: values.ip_address };
  12773. }
  12774. }
  12775. },
  12776. {
  12777. xtype: 'inputpanel',
  12778. title: 'DNS',
  12779. column1: [
  12780. {
  12781. xtype: 'pvetextfield',
  12782. name: 'searchdomain',
  12783. skipEmptyText: true,
  12784. fieldLabel: 'DNS domain',
  12785. emptyText: 'use host settings',
  12786. allowBlank: true,
  12787. listeners: {
  12788. change: function(f, value) {
  12789. if (!me.rendered) {
  12790. return;
  12791. }
  12792. var field = me.down('#dns1');
  12793. field.setDisabled(!value);
  12794. field.clearInvalid();
  12795. field = me.down('#dns2');
  12796. field.setDisabled(!value);
  12797. field.clearInvalid();
  12798. }
  12799. }
  12800. },
  12801. {
  12802. xtype: 'pvetextfield',
  12803. fieldLabel: gettext('DNS server') + " 1",
  12804. vtype: 'IPAddress',
  12805. allowBlank: true,
  12806. disabled: true,
  12807. name: 'nameserver',
  12808. itemId: 'dns1'
  12809. },
  12810. {
  12811. xtype: 'pvetextfield',
  12812. fieldLabel: gettext('DNS server') + " 2",
  12813. vtype: 'IPAddress',
  12814. skipEmptyText: true,
  12815. disabled: true,
  12816. name: 'nameserver',
  12817. itemId: 'dns2'
  12818. }
  12819. ]
  12820. },
  12821. {
  12822. title: gettext('Confirm'),
  12823. layout: 'fit',
  12824. items: [
  12825. {
  12826. title: gettext('Settings'),
  12827. xtype: 'grid',
  12828. store: summarystore,
  12829. columns: [
  12830. {header: 'Key', width: 150, dataIndex: 'key'},
  12831. {header: 'Value', flex: 1, dataIndex: 'value'}
  12832. ]
  12833. }
  12834. ],
  12835. listeners: {
  12836. show: function(panel) {
  12837. var form = me.down('form').getForm();
  12838. var kv = me.getValues();
  12839. var data = [];
  12840. Ext.Object.each(kv, function(key, value) {
  12841. if (key === 'delete' || key === 'tmplstorage') { // ignore
  12842. return;
  12843. }
  12844. if (key === 'password') { // don't show pw
  12845. return;
  12846. }
  12847. var html = Ext.htmlEncode(Ext.JSON.encode(value));
  12848. data.push({ key: key, value: value });
  12849. });
  12850. summarystore.suspendEvents();
  12851. summarystore.removeAll();
  12852. summarystore.add(data);
  12853. summarystore.sort();
  12854. summarystore.resumeEvents();
  12855. summarystore.fireEvent('datachanged', summarystore);
  12856. }
  12857. },
  12858. onSubmit: function() {
  12859. var kv = me.getValues();
  12860. delete kv['delete'];
  12861.  
  12862. var nodename = kv.nodename;
  12863. delete kv.nodename;
  12864. delete kv.tmplstorage;
  12865.  
  12866. PVE.Utils.API2Request({
  12867. url: '/nodes/' + nodename + '/openvz',
  12868. waitMsgTarget: me,
  12869. method: 'POST',
  12870. params: kv,
  12871. success: function(response, opts){
  12872. var upid = response.result.data;
  12873.  
  12874. var win = Ext.create('PVE.window.TaskViewer', {
  12875. upid: upid
  12876. });
  12877. win.show();
  12878. me.close();
  12879. },
  12880. failure: function(response, opts) {
  12881. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  12882. }
  12883. });
  12884. }
  12885. }
  12886. ]
  12887. });
  12888.  
  12889. me.callParent();
  12890. }
  12891. });
  12892.  
  12893.  
  12894.  
  12895. Ext.define('PVE.pool.StatusView', {
  12896. extend: 'PVE.grid.ObjectGrid',
  12897. alias: ['widget.pvePoolStatusView'],
  12898.  
  12899. initComponent : function() {
  12900. var me = this;
  12901.  
  12902. var pool = me.pveSelNode.data.pool;
  12903. if (!pool) {
  12904. throw "no pool specified";
  12905. }
  12906.  
  12907. var rows = {
  12908. comment: {
  12909. header: gettext('Comment'),
  12910. required: true
  12911. }
  12912. };
  12913.  
  12914. Ext.applyIf(me, {
  12915. title: gettext('Status'),
  12916. url: "/api2/json/pools/" + pool,
  12917. cwidth1: 150,
  12918. interval: 30000,
  12919. //height: 195,
  12920. rows: rows
  12921. });
  12922.  
  12923. me.callParent();
  12924. }
  12925. });
  12926. Ext.define('PVE.pool.Summary', {
  12927. extend: 'Ext.panel.Panel',
  12928. alias: 'widget.pvePoolSummary',
  12929.  
  12930. initComponent: function() {
  12931. var me = this;
  12932.  
  12933. var pool = me.pveSelNode.data.pool;
  12934. if (!pool) {
  12935. throw "no pool specified";
  12936. }
  12937.  
  12938. var statusview = Ext.create('PVE.pool.StatusView', {
  12939. pveSelNode: me.pveSelNode,
  12940. style: 'padding-top:0px'
  12941. });
  12942.  
  12943. var rstore = statusview.rstore;
  12944.  
  12945. Ext.apply(me, {
  12946. autoScroll: true,
  12947. bodyStyle: 'padding:10px',
  12948. defaults: {
  12949. style: 'padding-top:10px',
  12950. width: 800
  12951. },
  12952. items: [ statusview ]
  12953. });
  12954.  
  12955. me.on('show', rstore.startUpdate);
  12956. me.on('hide', rstore.stopUpdate);
  12957. me.on('destroy', rstore.stopUpdate);
  12958.  
  12959. me.callParent();
  12960. }
  12961. });
  12962. Ext.define('PVE.pool.Config', {
  12963. extend: 'PVE.panel.Config',
  12964. alias: 'widget.pvePoolConfig',
  12965.  
  12966. initComponent: function() {
  12967. var me = this;
  12968.  
  12969. var pool = me.pveSelNode.data.pool;
  12970. if (!pool) {
  12971. throw "no pool specified";
  12972. }
  12973.  
  12974. Ext.apply(me, {
  12975. title: Ext.String.format(gettext("Resource Pool") + ': ' + pool),
  12976. hstateid: 'pooltab',
  12977. items: [
  12978. {
  12979. title: gettext('Summary'),
  12980. xtype: 'pvePoolSummary',
  12981. itemId: 'summary'
  12982. },
  12983. {
  12984. title: gettext('Members'),
  12985. xtype: 'pvePoolMembers',
  12986. pool: pool,
  12987. itemId: 'members'
  12988. },
  12989. {
  12990. xtype: 'pveACLView',
  12991. title: gettext('Permissions'),
  12992. itemId: 'permissions',
  12993. path: '/pool/' + pool
  12994. }
  12995. ]
  12996. });
  12997.  
  12998. me.callParent();
  12999. }
  13000. });
  13001. Ext.define('PVE.grid.TemplateSelector', {
  13002. extend: 'Ext.grid.GridPanel',
  13003.  
  13004. alias: ['widget.pveTemplateSelector'],
  13005.  
  13006. initComponent : function() {
  13007. var me = this;
  13008.  
  13009. if (!me.nodename) {
  13010. throw "no node name specified";
  13011. }
  13012.  
  13013. var baseurl = "/nodes/" + me.nodename + "/aplinfo";
  13014. var store = new Ext.data.Store({
  13015. model: 'pve-aplinfo',
  13016. groupField: 'section',
  13017. proxy: {
  13018. type: 'pve',
  13019. url: '/api2/json' + baseurl
  13020. }
  13021. });
  13022.  
  13023. var sm = Ext.create('Ext.selection.RowModel', {});
  13024.  
  13025. var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
  13026. groupHeaderTpl: '{[ "Section: " + values.name ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
  13027. });
  13028.  
  13029. var reload = function() {
  13030. store.load();
  13031. };
  13032.  
  13033. PVE.Utils.monStoreErrors(me, store);
  13034.  
  13035. Ext.apply(me, {
  13036. store: store,
  13037. selModel: sm,
  13038. stateful: false,
  13039. viewConfig: {
  13040. trackOver: false
  13041. },
  13042. features: [ groupingFeature ],
  13043. columns: [
  13044. {
  13045. header: gettext('Type'),
  13046. width: 80,
  13047. dataIndex: 'type'
  13048. },
  13049. {
  13050. header: gettext('Package'),
  13051. flex: 1,
  13052. dataIndex: 'package'
  13053. },
  13054. {
  13055. header: gettext('Version'),
  13056. width: 80,
  13057. dataIndex: 'version'
  13058. },
  13059. {
  13060. header: gettext('Description'),
  13061. flex: 1.5,
  13062. dataIndex: 'headline'
  13063. }
  13064. ],
  13065. listeners: {
  13066. afterRender: reload
  13067. }
  13068. });
  13069.  
  13070. me.callParent();
  13071. }
  13072.  
  13073. }, function() {
  13074.  
  13075. Ext.define('pve-aplinfo', {
  13076. extend: 'Ext.data.Model',
  13077. fields: [
  13078. 'template', 'type', 'package', 'version', 'headline', 'infopage',
  13079. 'description', 'os', 'section'
  13080. ],
  13081. idProperty: 'template'
  13082. });
  13083.  
  13084. });
  13085.  
  13086. Ext.define('PVE.storage.TemplateDownload', {
  13087. extend: 'Ext.window.Window',
  13088. alias: ['widget.pveTemplateDownload'],
  13089.  
  13090. modal: true,
  13091.  
  13092. initComponent : function() {
  13093. /*jslint confusion: true */
  13094. var me = this;
  13095.  
  13096. var grid = Ext.create('PVE.grid.TemplateSelector', {
  13097. border: false,
  13098. autoScroll: true,
  13099. nodename: me.nodename
  13100. });
  13101.  
  13102. var sm = grid.getSelectionModel();
  13103.  
  13104. var submitBtn = Ext.create('PVE.button.Button', {
  13105. text: gettext('Download'),
  13106. disabled: true,
  13107. selModel: sm,
  13108. handler: function(button, event, rec) {
  13109. PVE.Utils.API2Request({
  13110. url: '/nodes/' + me.nodename + '/aplinfo',
  13111. params: {
  13112. storage: me.storage,
  13113. template: rec.data.template
  13114. },
  13115. method: 'POST',
  13116. failure: function (response, opts) {
  13117. Ext.Msg.alert('Error', response.htmlStatus);
  13118. },
  13119. success: function(response, options) {
  13120. var upid = response.result.data;
  13121.  
  13122. var win = Ext.create('PVE.window.TaskViewer', {
  13123. upid: upid
  13124. });
  13125. win.show();
  13126. me.close();
  13127. }
  13128. });
  13129. }
  13130. });
  13131.  
  13132. Ext.applyIf(me, {
  13133. title: gettext('Templates'),
  13134. layout: 'fit',
  13135. width: 600,
  13136. height: 400,
  13137. items: grid,
  13138. buttons: [ submitBtn ]
  13139. });
  13140.  
  13141. me.callParent();
  13142. }
  13143. });
  13144.  
  13145. Ext.define('PVE.storage.Upload', {
  13146. extend: 'Ext.window.Window',
  13147. alias: ['widget.pveStorageUpload'],
  13148.  
  13149. resizable: false,
  13150.  
  13151. modal: true,
  13152.  
  13153. initComponent : function() {
  13154. /*jslint confusion: true */
  13155. var me = this;
  13156.  
  13157. var xhr;
  13158.  
  13159. if (!me.nodename) {
  13160. throw "no node name specified";
  13161. }
  13162.  
  13163. if (!me.storage) {
  13164. throw "no storage ID specified";
  13165. }
  13166.  
  13167. var baseurl = "/nodes/" + me.nodename + "/storage/" + me.storage + "/upload";
  13168.  
  13169. var pbar = Ext.create('Ext.ProgressBar', {
  13170. text: 'Ready',
  13171. hidden: true
  13172. });
  13173.  
  13174. me.formPanel = Ext.create('Ext.form.Panel', {
  13175. method: 'POST',
  13176. waitMsgTarget: true,
  13177. bodyPadding: 10,
  13178. border: false,
  13179. width: 300,
  13180. fieldDefaults: {
  13181. labelWidth: 100,
  13182. anchor: '100%'
  13183. },
  13184. items: [
  13185. {
  13186. xtype: 'pveKVComboBox',
  13187. data: [
  13188. ['iso', 'ISO image'],
  13189. ['backup', 'VZDump backup file'],
  13190. ['vztmpl', 'OpenVZ template']
  13191. ],
  13192. fieldLabel: gettext('Content'),
  13193. name: 'content',
  13194. value: 'iso'
  13195. },
  13196. {
  13197. xtype: 'filefield',
  13198. name: 'filename',
  13199. buttonText: gettext('Select File...'),
  13200. allowBlank: false
  13201. },
  13202. pbar
  13203. ]
  13204. });
  13205.  
  13206. var form = me.formPanel.getForm();
  13207.  
  13208. var doStandardSubmit = function() {
  13209. form.submit({
  13210. url: "/api2/htmljs" + baseurl,
  13211. waitMsg: gettext('Uploading file...'),
  13212. success: function(f, action) {
  13213. me.close();
  13214. },
  13215. failure: function(f, action) {
  13216. var msg = PVE.Utils.extractFormActionError(action);
  13217. Ext.Msg.alert(gettext('Error'), msg);
  13218. }
  13219. });
  13220. };
  13221.  
  13222. var updateProgress = function(per, bytes) {
  13223. var text = (per * 100).toFixed(2) + '%';
  13224. if (bytes) {
  13225. text += " (" + PVE.Utils.format_size(bytes) + ')';
  13226. }
  13227. pbar.updateProgress(per, text);
  13228. };
  13229.  
  13230. var abortBtn = Ext.create('Ext.Button', {
  13231. text: gettext('Abort'),
  13232. disabled: true,
  13233. handler: function() {
  13234. me.close();
  13235. }
  13236. });
  13237.  
  13238. var submitBtn = Ext.create('Ext.Button', {
  13239. text: gettext('Upload'),
  13240. disabled: true,
  13241. handler: function(button) {
  13242. var fd;
  13243. try {
  13244. fd = new FormData();
  13245. } catch (err) {
  13246. doStandardSubmit();
  13247. return;
  13248. }
  13249.  
  13250. button.setDisabled(true);
  13251. abortBtn.setDisabled(false);
  13252.  
  13253. var field = form.findField('content');
  13254. fd.append("content", field.getValue());
  13255. field.setDisabled(true);
  13256.  
  13257. field = form.findField('filename');
  13258. var file = field.fileInputEl.dom;
  13259. fd.append("filename", file.files[0]);
  13260. field.setDisabled(true);
  13261.  
  13262. pbar.setVisible(true);
  13263. updateProgress(0);
  13264.  
  13265. xhr = new XMLHttpRequest();
  13266.  
  13267. xhr.addEventListener("load", function(e) {
  13268. if (xhr.status == 200) {
  13269. me.close();
  13270. } else {
  13271. var msg = "Error " + xhr.status.toString() + ": " + Ext.htmlEncode(xhr.statusText);
  13272. Ext.Msg.alert(gettext('Error'), msg, function(btn) {
  13273. me.close();
  13274. });
  13275.  
  13276. }
  13277. }, false);
  13278.  
  13279. xhr.addEventListener("error", function(e) {
  13280. var msg = "Error " + e.target.status.toString() + " occurred while receiving the document.";
  13281. Ext.Msg.alert(gettext('Error'), msg, function(btn) {
  13282. me.close();
  13283. });
  13284. });
  13285.  
  13286. xhr.upload.addEventListener("progress", function(evt) {
  13287. if (evt.lengthComputable) {
  13288. var percentComplete = evt.loaded / evt.total;
  13289. updateProgress(percentComplete, evt.loaded);
  13290. }
  13291. }, false);
  13292.  
  13293. xhr.open("POST", "/api2/json" + baseurl, true);
  13294. xhr.send(fd);
  13295. }
  13296. });
  13297.  
  13298. form.on('validitychange', function(f, valid) {
  13299. submitBtn.setDisabled(!valid);
  13300. });
  13301.  
  13302. Ext.applyIf(me, {
  13303. title: gettext('Upload'),
  13304. items: me.formPanel,
  13305. buttons: [ abortBtn, submitBtn ],
  13306. listeners: {
  13307. close: function() {
  13308. if (xhr) {
  13309. xhr.abort();
  13310. }
  13311. }
  13312. }
  13313. });
  13314.  
  13315. me.callParent();
  13316. }
  13317. });
  13318.  
  13319. Ext.define('PVE.storage.ContentView', {
  13320. extend: 'Ext.grid.GridPanel',
  13321.  
  13322. alias: ['widget.pveStorageContentView'],
  13323.  
  13324. initComponent : function() {
  13325. var me = this;
  13326.  
  13327. var nodename = me.pveSelNode.data.node;
  13328. if (!nodename) {
  13329. throw "no node name specified";
  13330. }
  13331.  
  13332. var storage = me.pveSelNode.data.storage;
  13333. if (!storage) {
  13334. throw "no storage ID specified";
  13335. }
  13336.  
  13337. var baseurl = "/nodes/" + nodename + "/storage/" + storage + "/content";
  13338. var store = new Ext.data.Store({
  13339. model: 'pve-storage-content',
  13340. groupField: 'content',
  13341. proxy: {
  13342. type: 'pve',
  13343. url: '/api2/json' + baseurl
  13344. },
  13345. sorters: {
  13346. property: 'volid',
  13347. order: 'DESC'
  13348. }
  13349. });
  13350.  
  13351. var sm = Ext.create('Ext.selection.RowModel', {});
  13352.  
  13353. var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
  13354. groupHeaderTpl: '{[ PVE.Utils.format_content_types(values.name) ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
  13355. });
  13356.  
  13357. var reload = function() {
  13358. store.load();
  13359. };
  13360.  
  13361. PVE.Utils.monStoreErrors(me, store);
  13362.  
  13363. Ext.apply(me, {
  13364. store: store,
  13365. selModel: sm,
  13366. stateful: false,
  13367. viewConfig: {
  13368. trackOver: false
  13369. },
  13370. features: [ groupingFeature ],
  13371. tbar: [
  13372. {
  13373. xtype: 'pveButton',
  13374. text: gettext('Restore'),
  13375. selModel: sm,
  13376. disabled: true,
  13377. enableFn: function(rec) {
  13378. return rec && rec.data.content === 'backup';
  13379. },
  13380. handler: function(b, e, rec) {
  13381. var vmtype;
  13382. if (rec.data.volid.match(/vzdump-qemu-/)) {
  13383. vmtype = 'qemu';
  13384. } else if (rec.data.volid.match(/vzdump-openvz-/)) {
  13385. vmtype = 'openvz';
  13386. } else {
  13387. return;
  13388. }
  13389.  
  13390. var win = Ext.create('PVE.window.Restore', {
  13391. nodename: nodename,
  13392. volid: rec.data.volid,
  13393. volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
  13394. vmtype: vmtype
  13395. });
  13396. win.show();
  13397. win.on('destroy', reload);
  13398. }
  13399. },
  13400. {
  13401. xtype: 'pveButton',
  13402. text: gettext('Remove'),
  13403. selModel: sm,
  13404. disabled: true,
  13405. confirmMsg: function(rec) {
  13406. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  13407. "'" + rec.data.volid + "'");
  13408. },
  13409. enableFn: function(rec) {
  13410. return rec && rec.data.content !== 'images';
  13411. },
  13412. handler: function(b, e, rec) {
  13413. PVE.Utils.API2Request({
  13414. url: baseurl + '/' + rec.data.volid,
  13415. method: 'DELETE',
  13416. waitMsgTarget: me,
  13417. callback: function() {
  13418. reload();
  13419. },
  13420. failure: function (response, opts) {
  13421. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  13422. }
  13423. });
  13424. }
  13425. },
  13426. {
  13427. text: gettext('Templates'),
  13428. handler: function() {
  13429. var win = Ext.create('PVE.storage.TemplateDownload', {
  13430. nodename: nodename,
  13431. storage: storage
  13432. });
  13433. win.show();
  13434. win.on('destroy', reload);
  13435. }
  13436. },
  13437. {
  13438. text: gettext('Upload'),
  13439. handler: function() {
  13440. var win = Ext.create('PVE.storage.Upload', {
  13441. nodename: nodename,
  13442. storage: storage
  13443. });
  13444. win.show();
  13445. win.on('destroy', reload);
  13446. }
  13447. }
  13448. ],
  13449. columns: [
  13450. {
  13451. header: gettext('Name'),
  13452. flex: 1,
  13453. sortable: true,
  13454. renderer: PVE.Utils.render_storage_content,
  13455. dataIndex: 'text'
  13456. },
  13457. {
  13458. header: gettext('Format'),
  13459. width: 100,
  13460. dataIndex: 'format'
  13461. },
  13462. {
  13463. header: gettext('Size'),
  13464. width: 100,
  13465. renderer: PVE.Utils.format_size,
  13466. dataIndex: 'size'
  13467. }
  13468. ],
  13469. listeners: {
  13470. show: reload
  13471. }
  13472. });
  13473.  
  13474. me.callParent();
  13475. }
  13476. }, function() {
  13477.  
  13478. Ext.define('pve-storage-content', {
  13479. extend: 'Ext.data.Model',
  13480. fields: [
  13481. 'volid', 'content', 'format', 'size', 'used', 'vmid',
  13482. 'channel', 'id', 'lun',
  13483. {
  13484. name: 'text',
  13485. convert: function(value, record) {
  13486. if (value) {
  13487. return value;
  13488. }
  13489. return PVE.Utils.render_storage_content(value, {}, record);
  13490. }
  13491. }
  13492. ],
  13493. idProperty: 'volid'
  13494. });
  13495.  
  13496. });Ext.define('PVE.storage.StatusView', {
  13497. extend: 'PVE.grid.ObjectGrid',
  13498. alias: ['widget.pveStorageStatusView'],
  13499.  
  13500. initComponent : function() {
  13501. var me = this;
  13502.  
  13503. var nodename = me.pveSelNode.data.node;
  13504. if (!nodename) {
  13505. throw "no node name specified";
  13506. }
  13507.  
  13508. var storage = me.pveSelNode.data.storage;
  13509. if (!storage) {
  13510. throw "no storage ID specified";
  13511. }
  13512.  
  13513. var rows = {
  13514. disable: {
  13515. header: gettext('Enabled'),
  13516. required: true,
  13517. renderer: PVE.Utils.format_neg_boolean
  13518. },
  13519. active: {
  13520. header: gettext('Active'),
  13521. required: true,
  13522. renderer: PVE.Utils.format_boolean
  13523. },
  13524. content: {
  13525. header: gettext('Content'),
  13526. required: true,
  13527. renderer: PVE.Utils.format_content_types
  13528. },
  13529. type: {
  13530. header: gettext('Type'),
  13531. required: true,
  13532. renderer: PVE.Utils.format_storage_type
  13533. },
  13534. shared: {
  13535. header: gettext('Shared'),
  13536. required: true,
  13537. renderer: PVE.Utils.format_boolean
  13538. },
  13539. total: {
  13540. header: gettext('Size'),
  13541. required: true,
  13542. renderer: PVE.Utils.render_size
  13543. },
  13544. used: {
  13545. header: gettext('Used'),
  13546. required: true,
  13547. renderer: function(value) {
  13548. // do not confuse users with filesystem details
  13549. var total = me.getObjectValue('total', 0);
  13550. var avail = me.getObjectValue('avail', 0);
  13551. return PVE.Utils.render_size(total - avail);
  13552. }
  13553. },
  13554. avail: {
  13555. header: gettext('Avail'),
  13556. required: true,
  13557. renderer: PVE.Utils.render_size
  13558. }
  13559. };
  13560.  
  13561. Ext.applyIf(me, {
  13562. title: gettext('Status'),
  13563. url: "/api2/json/nodes/" + nodename + "/storage/" + storage + "/status",
  13564. cwidth1: 150,
  13565. interval: 30000,
  13566. //height: 195,
  13567. rows: rows
  13568. });
  13569.  
  13570. me.callParent();
  13571. }
  13572. });
  13573. Ext.define('PVE.storage.Summary', {
  13574. extend: 'Ext.panel.Panel',
  13575. alias: 'widget.pveStorageSummary',
  13576.  
  13577. initComponent: function() {
  13578. var me = this;
  13579.  
  13580. var nodename = me.pveSelNode.data.node;
  13581. if (!nodename) {
  13582. throw "no node name specified";
  13583. }
  13584.  
  13585. var storage = me.pveSelNode.data.storage;
  13586. if (!storage) {
  13587. throw "no storage ID specified";
  13588. }
  13589.  
  13590. var statusview = Ext.create('PVE.storage.StatusView', {
  13591. pveSelNode: me.pveSelNode,
  13592. style: 'padding-top:0px'
  13593. });
  13594.  
  13595. var rstore = statusview.rstore;
  13596.  
  13597. var rrdurl = "/api2/png/nodes/" + nodename + "/storage/" + storage + "/rrd";
  13598.  
  13599. Ext.apply(me, {
  13600. autoScroll: true,
  13601. bodyStyle: 'padding:10px',
  13602. defaults: {
  13603. style: 'padding-top:10px',
  13604. width: 800
  13605. },
  13606. tbar: [
  13607. '->',
  13608. {
  13609. xtype: 'pveRRDTypeSelector'
  13610. }
  13611. ],
  13612. items: [
  13613. statusview,
  13614. {
  13615. xtype: 'pveRRDView',
  13616. title: "Usage",
  13617. pveSelNode: me.pveSelNode,
  13618. datasource: 'total,used',
  13619. rrdurl: rrdurl
  13620. }
  13621. ]
  13622. });
  13623.  
  13624. me.on('show', rstore.startUpdate);
  13625. me.on('hide', rstore.stopUpdate);
  13626. me.on('destroy', rstore.stopUpdate);
  13627.  
  13628. me.callParent();
  13629. }
  13630. });
  13631. Ext.define('PVE.storage.Browser', {
  13632. extend: 'PVE.panel.Config',
  13633. alias: 'widget.PVE.storage.Browser',
  13634.  
  13635. initComponent: function() {
  13636. var me = this;
  13637.  
  13638. var nodename = me.pveSelNode.data.node;
  13639. if (!nodename) {
  13640. throw "no node name specified";
  13641. }
  13642.  
  13643. var storeid = me.pveSelNode.data.storage;
  13644. if (!storeid) {
  13645. throw "no storage ID specified";
  13646. }
  13647.  
  13648. Ext.apply(me, {
  13649. title: Ext.String.format(gettext("Storage {0} on node {1}"),
  13650. "'" + storeid + "'", "'" + nodename + "'"),
  13651. hstateid: 'storagetab',
  13652. items: [
  13653. {
  13654. title: gettext('Summary'),
  13655. xtype: 'pveStorageSummary',
  13656. itemId: 'summary'
  13657. },
  13658. {
  13659. xtype: 'pveStorageContentView',
  13660. title: gettext('Content'),
  13661. itemId: 'content'
  13662. },
  13663. {
  13664. xtype: 'pveACLView',
  13665. title: gettext('Permissions'),
  13666. itemId: 'permissions',
  13667. path: '/storage/' + storeid
  13668. }
  13669. ]
  13670. });
  13671.  
  13672. me.callParent();
  13673. }
  13674. });
  13675. Ext.define('PVE.storage.DirInputPanel', {
  13676. extend: 'PVE.panel.InputPanel',
  13677.  
  13678. onGetValues: function(values) {
  13679. var me = this;
  13680.  
  13681. if (me.create) {
  13682. values.type = 'dir';
  13683. } else {
  13684. delete values.storage;
  13685. }
  13686.  
  13687. values.disable = values.enable ? 0 : 1;
  13688. delete values.enable;
  13689.  
  13690. return values;
  13691. },
  13692.  
  13693. initComponent : function() {
  13694. var me = this;
  13695.  
  13696.  
  13697. me.column1 = [
  13698. {
  13699. xtype: me.create ? 'textfield' : 'displayfield',
  13700. name: 'storage',
  13701. height: 22, // hack: set same height as text fields
  13702. value: me.storageId || '',
  13703. fieldLabel: 'ID',
  13704. vtype: 'StorageId',
  13705. allowBlank: false
  13706. },
  13707. {
  13708. xtype: me.create ? 'textfield' : 'displayfield',
  13709. height: 22, // hack: set same height as text fields
  13710. name: 'path',
  13711. value: '',
  13712. fieldLabel: gettext('Directory'),
  13713. allowBlank: false
  13714. },
  13715. {
  13716. xtype: 'pveContentTypeSelector',
  13717. name: 'content',
  13718. value: 'images',
  13719. multiSelect: true,
  13720. fieldLabel: gettext('Content'),
  13721. allowBlank: false
  13722. }
  13723. ];
  13724.  
  13725. me.column2 = [
  13726. {
  13727. xtype: 'pvecheckbox',
  13728. name: 'enable',
  13729. checked: true,
  13730. uncheckedValue: 0,
  13731. fieldLabel: gettext('Enable')
  13732. },
  13733. {
  13734. xtype: 'pvecheckbox',
  13735. name: 'shared',
  13736. uncheckedValue: 0,
  13737. fieldLabel: gettext('Shared')
  13738. },
  13739. {
  13740. xtype: 'numberfield',
  13741. fieldLabel: gettext('Max Backups'),
  13742. name: 'maxfiles',
  13743. minValue: 0,
  13744. maxValue: 365,
  13745. value: me.create ? '1' : undefined,
  13746. allowBlank: false
  13747. }
  13748. ];
  13749.  
  13750. if (me.create || me.storageId !== 'local') {
  13751. me.column2.unshift({
  13752. xtype: 'PVE.form.NodeSelector',
  13753. name: 'nodes',
  13754. fieldLabel: gettext('Nodes'),
  13755. emptyText: gettext('All') + ' (' +
  13756. gettext('No restrictions') +')',
  13757. multiSelect: true,
  13758. autoSelect: false
  13759. });
  13760. }
  13761.  
  13762. me.callParent();
  13763. }
  13764. });
  13765.  
  13766. Ext.define('PVE.storage.DirEdit', {
  13767. extend: 'PVE.window.Edit',
  13768.  
  13769. initComponent : function() {
  13770. var me = this;
  13771.  
  13772. me.create = !me.storageId;
  13773.  
  13774. if (me.create) {
  13775. me.url = '/api2/extjs/storage';
  13776. me.method = 'POST';
  13777. } else {
  13778. me.url = '/api2/extjs/storage/' + me.storageId;
  13779. me.method = 'PUT';
  13780. }
  13781.  
  13782. var ipanel = Ext.create('PVE.storage.DirInputPanel', {
  13783. create: me.create,
  13784. storageId: me.storageId
  13785. });
  13786.  
  13787. Ext.apply(me, {
  13788. subject: 'Directory',
  13789. isAdd: true,
  13790. items: [ ipanel ]
  13791. });
  13792.  
  13793. me.callParent();
  13794.  
  13795. if (!me.create) {
  13796. me.load({
  13797. success: function(response, options) {
  13798. var values = response.result.data;
  13799. var ctypes = values.content || '';
  13800.  
  13801. values.content = ctypes.split(',');
  13802.  
  13803. if (values.nodes) {
  13804. values.nodes = values.nodes.split(',');
  13805. }
  13806. values.enable = values.disable ? 0 : 1;
  13807.  
  13808. ipanel.setValues(values);
  13809. }
  13810. });
  13811. }
  13812. }
  13813. });
  13814. Ext.define('PVE.storage.NFSScan', {
  13815. extend: 'Ext.form.field.ComboBox',
  13816. alias: 'widget.pveNFSScan',
  13817.  
  13818. queryParam: 'server',
  13819.  
  13820. doRawQuery: function() {
  13821. },
  13822.  
  13823. onTriggerClick: function() {
  13824. var me = this;
  13825.  
  13826. if (!me.queryCaching || me.lastQuery !== me.nfsServer) {
  13827. me.store.removeAll();
  13828. }
  13829.  
  13830. me.allQuery = me.nfsServer;
  13831.  
  13832. me.callParent();
  13833. },
  13834.  
  13835. setServer: function(server) {
  13836. var me = this;
  13837.  
  13838. me.nfsServer = server;
  13839. },
  13840.  
  13841. initComponent : function() {
  13842. var me = this;
  13843.  
  13844. if (!me.nodename) {
  13845. me.nodename = 'localhost';
  13846. }
  13847.  
  13848. var store = Ext.create('Ext.data.Store', {
  13849. fields: [ 'path', 'options' ],
  13850. proxy: {
  13851. type: 'pve',
  13852. url: '/api2/json/nodes/' + me.nodename + '/scan/nfs'
  13853. }
  13854. });
  13855.  
  13856. Ext.apply(me, {
  13857. store: store,
  13858. valueField: 'path',
  13859. displayField: 'path',
  13860. matchFieldWidth: false,
  13861. listConfig: {
  13862. loadingText: 'Scanning...',
  13863. width: 350
  13864. }
  13865. });
  13866.  
  13867. me.callParent();
  13868. }
  13869. });
  13870.  
  13871. Ext.define('PVE.storage.NFSInputPanel', {
  13872. extend: 'PVE.panel.InputPanel',
  13873.  
  13874. onGetValues: function(values) {
  13875. var me = this;
  13876.  
  13877. if (me.create) {
  13878. values.type = 'nfs';
  13879. // hack: for now we always create nvf v3
  13880. // fixme: make this configurable
  13881. values.options = 'vers=3';
  13882. } else {
  13883. delete values.storage;
  13884. }
  13885.  
  13886. values.disable = values.enable ? 0 : 1;
  13887. delete values.enable;
  13888.  
  13889. return values;
  13890. },
  13891.  
  13892. initComponent : function() {
  13893. var me = this;
  13894.  
  13895.  
  13896. me.column1 = [
  13897. {
  13898. xtype: me.create ? 'textfield' : 'displayfield',
  13899. name: 'storage',
  13900. height: 22, // hack: set same height as text fields
  13901. value: me.storageId || '',
  13902. fieldLabel: 'ID',
  13903. vtype: 'StorageId',
  13904. allowBlank: false
  13905. },
  13906. {
  13907. xtype: me.create ? 'textfield' : 'displayfield',
  13908. height: 22, // hack: set same height as text fields
  13909. name: 'server',
  13910. value: '',
  13911. fieldLabel: gettext('Server'),
  13912. allowBlank: false,
  13913. listeners: {
  13914. change: function(f, value) {
  13915. if (me.create) {
  13916. var exportField = me.down('field[name=export]');
  13917. exportField.setServer(value);
  13918. exportField.setValue('');
  13919. }
  13920. }
  13921. }
  13922. },
  13923. {
  13924. xtype: me.create ? 'pveNFSScan' : 'displayfield',
  13925. height: 22, // hack: set same height as text fields
  13926. name: 'export',
  13927. value: '',
  13928. fieldLabel: 'Export',
  13929. allowBlank: false
  13930. },
  13931. {
  13932. xtype: 'pveContentTypeSelector',
  13933. name: 'content',
  13934. value: 'images',
  13935. multiSelect: true,
  13936. fieldLabel: gettext('Content'),
  13937. allowBlank: false
  13938. }
  13939. ];
  13940.  
  13941. me.column2 = [
  13942. {
  13943. xtype: 'PVE.form.NodeSelector',
  13944. name: 'nodes',
  13945. fieldLabel: gettext('Nodes'),
  13946. emptyText: gettext('All') + ' (' +
  13947. gettext('No restrictions') +')',
  13948. multiSelect: true,
  13949. autoSelect: false
  13950. },
  13951. {
  13952. xtype: 'pvecheckbox',
  13953. name: 'enable',
  13954. checked: true,
  13955. uncheckedValue: 0,
  13956. fieldLabel: gettext('Enable')
  13957. },
  13958. {
  13959. xtype: 'numberfield',
  13960. fieldLabel: gettext('Max Backups'),
  13961. name: 'maxfiles',
  13962. minValue: 0,
  13963. maxValue: 365,
  13964. value: me.create ? '1' : undefined,
  13965. allowBlank: false
  13966. }
  13967. ];
  13968.  
  13969. me.callParent();
  13970. }
  13971. });
  13972.  
  13973. Ext.define('PVE.storage.NFSEdit', {
  13974. extend: 'PVE.window.Edit',
  13975.  
  13976. initComponent : function() {
  13977. var me = this;
  13978.  
  13979. me.create = !me.storageId;
  13980.  
  13981. if (me.create) {
  13982. me.url = '/api2/extjs/storage';
  13983. me.method = 'POST';
  13984. } else {
  13985. me.url = '/api2/extjs/storage/' + me.storageId;
  13986. me.method = 'PUT';
  13987. }
  13988.  
  13989. var ipanel = Ext.create('PVE.storage.NFSInputPanel', {
  13990. create: me.create,
  13991. storageId: me.storageId
  13992. });
  13993.  
  13994. Ext.apply(me, {
  13995. subject: 'NFS share',
  13996. isAdd: true,
  13997. items: [ ipanel ]
  13998. });
  13999.  
  14000. me.callParent();
  14001.  
  14002. if (!me.create) {
  14003. me.load({
  14004. success: function(response, options) {
  14005. var values = response.result.data;
  14006. var ctypes = values.content || '';
  14007.  
  14008. values.content = ctypes.split(',');
  14009.  
  14010. if (values.nodes) {
  14011. values.nodes = values.nodes.split(',');
  14012. }
  14013. values.enable = values.disable ? 0 : 1;
  14014. ipanel.setValues(values);
  14015. }
  14016. });
  14017. }
  14018. }
  14019. });
  14020. Ext.define('PVE.storage.IScsiScan', {
  14021. extend: 'Ext.form.field.ComboBox',
  14022. alias: 'widget.pveIScsiScan',
  14023.  
  14024. queryParam: 'portal',
  14025.  
  14026. doRawQuery: function() {
  14027. },
  14028.  
  14029. onTriggerClick: function() {
  14030. var me = this;
  14031.  
  14032. if (!me.queryCaching || me.lastQuery !== me.portal) {
  14033. me.store.removeAll();
  14034. }
  14035.  
  14036. me.allQuery = me.portal;
  14037.  
  14038. me.callParent();
  14039. },
  14040.  
  14041. setPortal: function(portal) {
  14042. var me = this;
  14043.  
  14044. me.portal = portal;
  14045. },
  14046.  
  14047. initComponent : function() {
  14048. var me = this;
  14049.  
  14050. if (!me.nodename) {
  14051. me.nodename = 'localhost';
  14052. }
  14053.  
  14054. var store = Ext.create('Ext.data.Store', {
  14055. fields: [ 'target', 'portal' ],
  14056. proxy: {
  14057. type: 'pve',
  14058. url: '/api2/json/nodes/' + me.nodename + '/scan/iscsi'
  14059. }
  14060. });
  14061.  
  14062. Ext.apply(me, {
  14063. store: store,
  14064. valueField: 'target',
  14065. displayField: 'target',
  14066. matchFieldWidth: false,
  14067. listConfig: {
  14068. loadingText: 'Scanning...',
  14069. width: 350
  14070. }
  14071. });
  14072.  
  14073. me.callParent();
  14074. }
  14075. });
  14076.  
  14077. Ext.define('PVE.storage.IScsiInputPanel', {
  14078. extend: 'PVE.panel.InputPanel',
  14079.  
  14080. onGetValues: function(values) {
  14081. var me = this;
  14082.  
  14083. if (me.create) {
  14084. values.type = 'iscsi';
  14085. } else {
  14086. delete values.storage;
  14087. }
  14088.  
  14089. values.content = values.luns ? 'images' : 'none';
  14090. delete values.luns;
  14091.  
  14092. values.disable = values.enable ? 0 : 1;
  14093. delete values.enable;
  14094.  
  14095. return values;
  14096. },
  14097.  
  14098. initComponent : function() {
  14099. var me = this;
  14100.  
  14101.  
  14102. me.column1 = [
  14103. {
  14104. xtype: me.create ? 'textfield' : 'displayfield',
  14105. name: 'storage',
  14106. height: 22, // hack: set same height as text fields
  14107. value: me.storageId || '',
  14108. fieldLabel: 'ID',
  14109. vtype: 'StorageId',
  14110. allowBlank: false
  14111. },
  14112. {
  14113. xtype: me.create ? 'textfield' : 'displayfield',
  14114. height: 22, // hack: set same height as text fields
  14115. name: 'portal',
  14116. value: '',
  14117. fieldLabel: 'Portal',
  14118. allowBlank: false,
  14119. listeners: {
  14120. change: function(f, value) {
  14121. if (me.create) {
  14122. var exportField = me.down('field[name=target]');
  14123. exportField.setPortal(value);
  14124. exportField.setValue('');
  14125. }
  14126. }
  14127. }
  14128. },
  14129. {
  14130. readOnly: !me.create,
  14131. xtype: me.create ? 'pveIScsiScan' : 'displayfield',
  14132. name: 'target',
  14133. value: '',
  14134. fieldLabel: 'Target',
  14135. allowBlank: false
  14136. }
  14137. ];
  14138.  
  14139. me.column2 = [
  14140. {
  14141. xtype: 'PVE.form.NodeSelector',
  14142. name: 'nodes',
  14143. fieldLabel: gettext('Nodes'),
  14144. emptyText: gettext('All') + ' (' +
  14145. gettext('No restrictions') +')',
  14146. multiSelect: true,
  14147. autoSelect: false
  14148. },
  14149. {
  14150. xtype: 'pvecheckbox',
  14151. name: 'enable',
  14152. checked: true,
  14153. uncheckedValue: 0,
  14154. fieldLabel: gettext('Enable')
  14155. },
  14156. {
  14157. xtype: 'checkbox',
  14158. name: 'luns',
  14159. checked: true,
  14160. fieldLabel: gettext('Use LUNs directly')
  14161. }
  14162. ];
  14163.  
  14164. me.callParent();
  14165. }
  14166. });
  14167.  
  14168. Ext.define('PVE.storage.IScsiEdit', {
  14169. extend: 'PVE.window.Edit',
  14170.  
  14171. initComponent : function() {
  14172. var me = this;
  14173.  
  14174. me.create = !me.storageId;
  14175.  
  14176. if (me.create) {
  14177. me.url = '/api2/extjs/storage';
  14178. me.method = 'POST';
  14179. } else {
  14180. me.url = '/api2/extjs/storage/' + me.storageId;
  14181. me.method = 'PUT';
  14182. }
  14183.  
  14184. var ipanel = Ext.create('PVE.storage.IScsiInputPanel', {
  14185. create: me.create,
  14186. storageId: me.storageId
  14187. });
  14188.  
  14189. Ext.apply(me, {
  14190. subject: 'iSCSI target',
  14191. isAdd: true,
  14192. items: [ ipanel ]
  14193. });
  14194.  
  14195. me.callParent();
  14196.  
  14197. if (!me.create) {
  14198. me.load({
  14199. success: function(response, options) {
  14200. var values = response.result.data;
  14201. var ctypes = values.content || '';
  14202.  
  14203. if (values.storage === 'local') {
  14204. values.content = ctypes.split(',');
  14205. }
  14206. if (values.nodes) {
  14207. values.nodes = values.nodes.split(',');
  14208. }
  14209. values.enable = values.disable ? 0 : 1;
  14210. values.luns = (values.content === 'images') ? true : false;
  14211.  
  14212. ipanel.setValues(values);
  14213. }
  14214. });
  14215. }
  14216. }
  14217. });
  14218. Ext.define('PVE.storage.VgSelector', {
  14219. extend: 'Ext.form.field.ComboBox',
  14220. alias: 'widget.pveVgSelector',
  14221.  
  14222. initComponent : function() {
  14223. var me = this;
  14224.  
  14225. if (!me.nodename) {
  14226. me.nodename = 'localhost';
  14227. }
  14228.  
  14229. var store = Ext.create('Ext.data.Store', {
  14230. autoLoad: {}, // true,
  14231. fields: [ 'vg', 'size', 'free' ],
  14232. proxy: {
  14233. type: 'pve',
  14234. url: '/api2/json/nodes/' + me.nodename + '/scan/lvm'
  14235. }
  14236. });
  14237.  
  14238. Ext.apply(me, {
  14239. store: store,
  14240. valueField: 'vg',
  14241. displayField: 'vg',
  14242. queryMode: 'local',
  14243. editable: false,
  14244. listConfig: {
  14245. loadingText: 'Scanning...'
  14246. }
  14247. });
  14248.  
  14249. me.callParent();
  14250. }
  14251. });
  14252.  
  14253. Ext.define('PVE.storage.BaseStorageSelector', {
  14254. extend: 'Ext.form.field.ComboBox',
  14255. alias: 'widget.pveBaseStorageSelector',
  14256.  
  14257. existingGroupsText: gettext("Existing volume groups"),
  14258.  
  14259. initComponent : function() {
  14260. var me = this;
  14261.  
  14262. var store = Ext.create('Ext.data.Store', {
  14263. autoLoad: {
  14264. addRecords: true,
  14265. params: {
  14266. type: 'iscsi'
  14267. }
  14268. },
  14269. fields: [ 'storage', 'type', 'content',
  14270. {
  14271. name: 'text',
  14272. convert: function(value, record) {
  14273. if (record.data.storage) {
  14274. return record.data.storage + " (iSCSI)";
  14275. } else {
  14276. return me.existingGroupsText;
  14277. }
  14278. }
  14279. }],
  14280. proxy: {
  14281. type: 'pve',
  14282. url: '/api2/json/storage/'
  14283. }
  14284. });
  14285.  
  14286. store.loadData([{ storage: '' }], true);
  14287.  
  14288. Ext.apply(me, {
  14289. store: store,
  14290. queryMode: 'local',
  14291. editable: false,
  14292. value: '',
  14293. valueField: 'storage',
  14294. displayField: 'text'
  14295. });
  14296.  
  14297. me.callParent();
  14298. }
  14299. });
  14300.  
  14301. Ext.define('PVE.storage.LVMInputPanel', {
  14302. extend: 'PVE.panel.InputPanel',
  14303.  
  14304. onGetValues: function(values) {
  14305. var me = this;
  14306.  
  14307. if (me.create) {
  14308. values.type = 'lvm';
  14309. values.content = 'images';
  14310. } else {
  14311. delete values.storage;
  14312. }
  14313.  
  14314. values.disable = values.enable ? 0 : 1;
  14315. delete values.enable;
  14316.  
  14317. return values;
  14318. },
  14319.  
  14320. initComponent : function() {
  14321. var me = this;
  14322.  
  14323. me.column1 = [
  14324. {
  14325. xtype: me.create ? 'textfield' : 'displayfield',
  14326. name: 'storage',
  14327. height: 22, // hack: set same height as text fields
  14328. value: me.storageId || '',
  14329. fieldLabel: 'ID',
  14330. vtype: 'StorageId',
  14331. submitValue: !!me.create,
  14332. allowBlank: false
  14333. }
  14334. ];
  14335.  
  14336. var vgnameField = Ext.createWidget(me.create ? 'textfield' : 'displayfield', {
  14337. height: 22, // hack: set same height as text fields
  14338. name: 'vgname',
  14339. hidden: !!me.create,
  14340. disabled: !!me.create,
  14341. value: '',
  14342. fieldLabel: gettext('Volume group'),
  14343. allowBlank: false
  14344. });
  14345.  
  14346. if (me.create) {
  14347. var vgField = Ext.create('PVE.storage.VgSelector', {
  14348. name: 'vgname',
  14349. fieldLabel: gettext('Volume group'),
  14350. allowBlank: false
  14351. });
  14352.  
  14353. var baseField = Ext.createWidget('pveFileSelector', {
  14354. name: 'base',
  14355. hidden: true,
  14356. disabled: true,
  14357. nodename: 'localhost',
  14358. storageContent: 'images',
  14359. fieldLabel: gettext('Base volume'),
  14360. allowBlank: false
  14361. });
  14362.  
  14363. me.column1.push({
  14364. xtype: 'pveBaseStorageSelector',
  14365. name: 'basesel',
  14366. fieldLabel: gettext('Base storage'),
  14367. submitValue: false,
  14368. listeners: {
  14369. change: function(f, value) {
  14370. if (value) {
  14371. vgnameField.setVisible(true);
  14372. vgnameField.setDisabled(false);
  14373. vgField.setVisible(false);
  14374. vgField.setDisabled(true);
  14375. baseField.setVisible(true);
  14376. baseField.setDisabled(false);
  14377. } else {
  14378. vgnameField.setVisible(false);
  14379. vgnameField.setDisabled(true);
  14380. vgField.setVisible(true);
  14381. vgField.setDisabled(false);
  14382. baseField.setVisible(false);
  14383. baseField.setDisabled(true);
  14384. }
  14385. baseField.setStorage(value);
  14386. }
  14387. }
  14388. });
  14389.  
  14390. me.column1.push(baseField);
  14391.  
  14392. me.column1.push(vgField);
  14393. }
  14394.  
  14395. me.column1.push(vgnameField);
  14396.  
  14397. me.column2 = [
  14398. {
  14399. xtype: 'PVE.form.NodeSelector',
  14400. name: 'nodes',
  14401. fieldLabel: gettext('Nodes'),
  14402. emptyText: gettext('All') + ' (' +
  14403. gettext('No restrictions') +')',
  14404. multiSelect: true,
  14405. autoSelect: false
  14406. },
  14407. {
  14408. xtype: 'pvecheckbox',
  14409. name: 'enable',
  14410. checked: true,
  14411. uncheckedValue: 0,
  14412. fieldLabel: gettext('Enable')
  14413. },
  14414. {
  14415. xtype: 'pvecheckbox',
  14416. name: 'shared',
  14417. uncheckedValue: 0,
  14418. fieldLabel: gettext('Shared')
  14419. }
  14420. ];
  14421.  
  14422. me.callParent();
  14423. }
  14424. });
  14425.  
  14426. Ext.define('PVE.storage.LVMEdit', {
  14427. extend: 'PVE.window.Edit',
  14428.  
  14429. initComponent : function() {
  14430. var me = this;
  14431.  
  14432. me.create = !me.storageId;
  14433.  
  14434. if (me.create) {
  14435. me.url = '/api2/extjs/storage';
  14436. me.method = 'POST';
  14437. } else {
  14438. me.url = '/api2/extjs/storage/' + me.storageId;
  14439. me.method = 'PUT';
  14440. }
  14441.  
  14442. var ipanel = Ext.create('PVE.storage.LVMInputPanel', {
  14443. create: me.create,
  14444. storageId: me.storageId
  14445. });
  14446.  
  14447. Ext.apply(me, {
  14448. subject: 'LVM group',
  14449. isAdd: true,
  14450. items: [ ipanel ]
  14451. });
  14452.  
  14453. me.callParent();
  14454.  
  14455. if (!me.create) {
  14456. me.load({
  14457. success: function(response, options) {
  14458. var values = response.result.data;
  14459. if (values.nodes) {
  14460. values.nodes = values.nodes.split(',');
  14461. }
  14462. values.enable = values.disable ? 0 : 1;
  14463. ipanel.setValues(values);
  14464. }
  14465. });
  14466. }
  14467. }
  14468. });
  14469. Ext.define('PVE.dc.NodeView', {
  14470. extend: 'Ext.grid.GridPanel',
  14471.  
  14472. alias: ['widget.pveDcNodeView'],
  14473.  
  14474. initComponent : function() {
  14475. var me = this;
  14476.  
  14477. var rstore = Ext.create('PVE.data.UpdateStore', {
  14478. interval: 3000,
  14479. storeid: 'pve-dc-nodes',
  14480. model: 'pve-dc-nodes',
  14481. proxy: {
  14482. type: 'pve',
  14483. url: "/api2/json/cluster/status"
  14484. },
  14485. filters: {
  14486. property: 'type',
  14487. value : 'node'
  14488. }
  14489. });
  14490.  
  14491. var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  14492.  
  14493. Ext.apply(me, {
  14494. store: store,
  14495. stateful: false,
  14496. columns: [
  14497. {
  14498. header: gettext('Name'),
  14499. width: 200,
  14500. sortable: true,
  14501. dataIndex: 'name'
  14502. },
  14503. {
  14504. header: 'ID',
  14505. width: 50,
  14506. sortable: true,
  14507. dataIndex: 'nodeid'
  14508. },
  14509. {
  14510. header: gettext('Online'),
  14511. width: 100,
  14512. sortable: true,
  14513. dataIndex: 'state',
  14514. renderer: PVE.Utils.format_boolean
  14515. },
  14516. {
  14517. header: gettext('Support'),
  14518. width: 100,
  14519. sortable: true,
  14520. dataIndex: 'level',
  14521. renderer: PVE.Utils.render_support_level
  14522. },
  14523. {
  14524. header: gettext('Estranged'),
  14525. width: 100,
  14526. sortable: true,
  14527. dataIndex: 'estranged',
  14528. renderer: PVE.Utils.format_boolean
  14529. },
  14530. {
  14531. header: gettext('Server Address'),
  14532. width: 100,
  14533. sortable: true,
  14534. dataIndex: 'ip'
  14535. },
  14536. {
  14537. header: gettext('Services'),
  14538. flex: 1,
  14539. width: 80,
  14540. sortable: true,
  14541. dataIndex: 'pmxcfs',
  14542. renderer: function(value, metaData, record) {
  14543. var list = [];
  14544. var data = record.data;
  14545. if (data) {
  14546. if (data.pmxcfs) {
  14547. list.push('PVECluster');
  14548. }
  14549. if (data.rgmanager) {
  14550. list.push('RGManager');
  14551. }
  14552.  
  14553. }
  14554. return list.join(', ');
  14555. }
  14556. }
  14557. ],
  14558. listeners: {
  14559. show: rstore.startUpdate,
  14560. hide: rstore.stopUpdate,
  14561. destroy: rstore.stopUpdate
  14562. }
  14563. });
  14564.  
  14565. me.callParent();
  14566. }
  14567. }, function() {
  14568.  
  14569. Ext.define('pve-dc-nodes', {
  14570. extend: 'Ext.data.Model',
  14571. fields: [ 'id', 'type', 'name', 'state', 'nodeid', 'ip',
  14572. 'pmxcfs', 'rgmanager', 'estranged', 'level' ],
  14573. idProperty: 'id'
  14574. });
  14575.  
  14576. });
  14577.  
  14578. Ext.define('PVE.dc.HAServiceView', {
  14579. extend: 'Ext.grid.GridPanel',
  14580.  
  14581. alias: ['widget.pveHaServiceView'],
  14582.  
  14583. initComponent : function() {
  14584. var me = this;
  14585.  
  14586. var rstore = Ext.create('PVE.data.UpdateStore', {
  14587. interval: 3000,
  14588. storeid: 'pve-ha-services',
  14589. model: 'pve-ha-services',
  14590. proxy: {
  14591. type: 'pve',
  14592. url: "/api2/json/cluster/status"
  14593. },
  14594. filters: {
  14595. property: 'type',
  14596. value : 'group'
  14597. }
  14598. });
  14599.  
  14600. var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  14601.  
  14602. var noClusterText = gettext("Standalone node - no cluster defined");
  14603. var status = Ext.create('Ext.Component', {
  14604. padding: 2,
  14605. html: '&nbsp;',
  14606. dock: 'bottom'
  14607. });
  14608.  
  14609. Ext.apply(me, {
  14610. store: store,
  14611. stateful: false,
  14612. //tbar: [ 'start', 'stop' ],
  14613. bbar: [ status ],
  14614. columns: [
  14615. {
  14616. header: gettext('Name'),
  14617. flex: 1,
  14618. sortable: true,
  14619. dataIndex: 'name'
  14620. },
  14621. {
  14622. header: gettext('Owner'),
  14623. flex: 1,
  14624. sortable: true,
  14625. dataIndex: 'owner'
  14626. },
  14627. {
  14628. header: gettext('Status'),
  14629. width: 80,
  14630. sortable: true,
  14631. dataIndex: 'state_str'
  14632. },
  14633. {
  14634. header: gettext('Restarts'),
  14635. width: 80,
  14636. sortable: true,
  14637. dataIndex: 'restarts'
  14638. },
  14639. {
  14640. header: gettext('Last transition'),
  14641. width: 200,
  14642. sortable: true,
  14643. dataIndex: 'last_transition'
  14644. },
  14645. {
  14646. header: gettext('Last owner'),
  14647. flex: 1,
  14648. sortable: true,
  14649. dataIndex: 'last_owner'
  14650. }
  14651. ],
  14652. listeners: {
  14653. show: rstore.startUpdate,
  14654. hide: rstore.stopUpdate,
  14655. destroy: rstore.stopUpdate
  14656. }
  14657. });
  14658.  
  14659. me.callParent();
  14660.  
  14661. rstore.on('load', function(s, records, success) {
  14662. if (!success) {
  14663. return;
  14664. }
  14665.  
  14666. var cluster_rec = rstore.getById('cluster');
  14667. var quorum_rec = rstore.getById('quorum');
  14668.  
  14669. if (!(cluster_rec && quorum_rec)) {
  14670. status.update(noClusterText);
  14671. return;
  14672. }
  14673.  
  14674. var cluster_raw = cluster_rec.raw;
  14675. var quorum_raw = quorum_rec.raw;
  14676. if (!(cluster_raw && quorum_raw)) {
  14677. status.update(noClusterText);
  14678. return;
  14679. }
  14680.  
  14681. status.update("Quorate: " + PVE.Utils.format_boolean(quorum_raw.quorate));
  14682. });
  14683.  
  14684. }
  14685. }, function() {
  14686.  
  14687. Ext.define('pve-ha-services', {
  14688. extend: 'Ext.data.Model',
  14689. fields: [ 'id', 'type', 'name', 'owner', 'last_owner', 'state_str', 'restarts',
  14690. { name: 'last_transition', type: 'date', dateFormat: 'timestamp'}
  14691. ],
  14692. idProperty: 'id'
  14693. });
  14694.  
  14695. });
  14696.  
  14697.  
  14698. Ext.define('PVE.dc.Summary', {
  14699. extend: 'Ext.panel.Panel',
  14700.  
  14701. alias: ['widget.pveDcSummary'],
  14702.  
  14703. initComponent: function() {
  14704. var me = this;
  14705.  
  14706. var hagrid = Ext.create('PVE.dc.HAServiceView', {
  14707. title: gettext('HA Service Status'),
  14708. region: 'south',
  14709. border: false,
  14710. split: true,
  14711. flex: 1
  14712. });
  14713.  
  14714. var nodegrid = Ext.create('PVE.dc.NodeView', {
  14715. title: gettext('Nodes'),
  14716. border: false,
  14717. region: 'center',
  14718. flex: 3
  14719. });
  14720.  
  14721. Ext.apply(me, {
  14722. layout: 'border',
  14723. items: [ nodegrid, hagrid ],
  14724. listeners: {
  14725. show: function() {
  14726. hagrid.fireEvent('show', hagrid);
  14727. nodegrid.fireEvent('show', hagrid);
  14728. },
  14729. hide: function() {
  14730. hagrid.fireEvent('hide', hagrid);
  14731. nodegrid.fireEvent('hide', hagrid);
  14732. }
  14733. }
  14734. });
  14735.  
  14736. me.callParent();
  14737. }
  14738. });
  14739. Ext.define('PVE.dc.HttpProxyEdit', {
  14740. extend: 'PVE.window.Edit',
  14741.  
  14742. initComponent : function() {
  14743. var me = this;
  14744.  
  14745. Ext.applyIf(me, {
  14746. subject: 'HTTP proxy',
  14747. items: {
  14748. xtype: 'pvetextfield',
  14749. name: 'http_proxy',
  14750. vtype: 'HttpProxy',
  14751. emptyText: gettext('Do not use any proxy'),
  14752. deleteEmpty: true,
  14753. value: '',
  14754. fieldLabel: 'HTTP proxy'
  14755. }
  14756. });
  14757.  
  14758. me.callParent();
  14759.  
  14760. me.load();
  14761. }
  14762. });
  14763.  
  14764. Ext.define('PVE.dc.KeyboardEdit', {
  14765. extend: 'PVE.window.Edit',
  14766.  
  14767. initComponent : function() {
  14768. var me = this;
  14769.  
  14770. Ext.applyIf(me, {
  14771. subject: gettext('Keyboard Layout'),
  14772. items: {
  14773. xtype: 'VNCKeyboardSelector',
  14774. name: 'keyboard',
  14775. value: '',
  14776. fieldLabel: gettext('Keyboard Layout')
  14777. }
  14778. });
  14779.  
  14780. me.callParent();
  14781.  
  14782. me.load();
  14783. }
  14784. });
  14785.  
  14786. Ext.define('PVE.dc.OptionView', {
  14787. extend: 'PVE.grid.ObjectGrid',
  14788. alias: ['widget.pveDcOptionView'],
  14789.  
  14790. noProxyText: gettext('Do not use any proxy'),
  14791.  
  14792. initComponent : function() {
  14793. var me = this;
  14794.  
  14795. var reload = function() {
  14796. me.rstore.load();
  14797. };
  14798.  
  14799. var rows = {
  14800. keyboard: {
  14801. header: gettext('Keyboard Layout'),
  14802. editor: 'PVE.dc.KeyboardEdit',
  14803. renderer: PVE.Utils.render_kvm_language,
  14804. required: true
  14805. },
  14806. http_proxy: {
  14807. header: 'HTTP proxy',
  14808. editor: 'PVE.dc.HttpProxyEdit',
  14809. required: true,
  14810. renderer: function(value) {
  14811. if (!value) {
  14812. return me.noProxyText;
  14813. }
  14814. return value;
  14815. }
  14816. }
  14817. };
  14818.  
  14819. var sm = Ext.create('Ext.selection.RowModel', {});
  14820.  
  14821. var run_editor = function() {
  14822. var rec = sm.getSelection()[0];
  14823. if (!rec) {
  14824. return;
  14825. }
  14826.  
  14827. var rowdef = rows[rec.data.key];
  14828. if (!rowdef.editor) {
  14829. return;
  14830. }
  14831.  
  14832. var win = Ext.create(rowdef.editor, {
  14833. url: "/api2/extjs/cluster/options",
  14834. confid: rec.data.key
  14835. });
  14836. win.show();
  14837. win.on('destroy', reload);
  14838. };
  14839.  
  14840. var edit_btn = new PVE.button.Button({
  14841. text: gettext('Edit'),
  14842. disabled: true,
  14843. selModel: sm,
  14844. handler: run_editor
  14845. });
  14846.  
  14847. Ext.applyIf(me, {
  14848. url: "/api2/json/cluster/options",
  14849. cwidth1: 130,
  14850. interval: 1000,
  14851. selModel: sm,
  14852. tbar: [ edit_btn ],
  14853. rows: rows,
  14854. listeners: {
  14855. itemdblclick: run_editor
  14856. }
  14857. });
  14858.  
  14859. me.callParent();
  14860.  
  14861. me.on('show', reload);
  14862. }
  14863. });
  14864. Ext.define('PVE.dc.StorageView', {
  14865. extend: 'Ext.grid.GridPanel',
  14866.  
  14867. alias: ['widget.pveStorageView'],
  14868.  
  14869. initComponent : function() {
  14870. var me = this;
  14871.  
  14872. var store = new Ext.data.Store({
  14873. model: 'pve-storage',
  14874. proxy: {
  14875. type: 'pve',
  14876. url: "/api2/json/storage"
  14877. },
  14878. sorters: {
  14879. property: 'storage',
  14880. order: 'DESC'
  14881. }
  14882. });
  14883.  
  14884. var reload = function() {
  14885. store.load();
  14886. };
  14887.  
  14888. var sm = Ext.create('Ext.selection.RowModel', {});
  14889.  
  14890. var run_editor = function() {
  14891. var rec = sm.getSelection()[0];
  14892. if (!rec) {
  14893. return;
  14894. }
  14895. var type = rec.data.type;
  14896.  
  14897. var editor;
  14898. if (type === 'dir') {
  14899. editor = 'PVE.storage.DirEdit';
  14900. } else if (type === 'nfs') {
  14901. editor = 'PVE.storage.NFSEdit';
  14902. } else if (type === 'lvm') {
  14903. editor = 'PVE.storage.LVMEdit';
  14904. } else if (type === 'iscsi') {
  14905. editor = 'PVE.storage.IScsiEdit';
  14906. } else {
  14907. return;
  14908. }
  14909. var win = Ext.create(editor, {
  14910. storageId: rec.data.storage
  14911. });
  14912.  
  14913. win.show();
  14914. win.on('destroy', reload);
  14915. };
  14916.  
  14917. var edit_btn = new PVE.button.Button({
  14918. text: gettext('Edit'),
  14919. disabled: true,
  14920. selModel: sm,
  14921. handler: run_editor
  14922. });
  14923.  
  14924. var remove_btn = new PVE.button.Button({
  14925. text: gettext('Remove'),
  14926. disabled: true,
  14927. selModel: sm,
  14928. confirmMsg: function (rec) {
  14929. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  14930. "'" + rec.data.storage + "'");
  14931. },
  14932. handler: function(btn, event, rec) {
  14933. PVE.Utils.API2Request({
  14934. url: '/storage/' + rec.data.storage,
  14935. method: 'DELETE',
  14936. waitMsgTarget: me,
  14937. callback: function() {
  14938. reload();
  14939. },
  14940. failure: function (response, opts) {
  14941. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  14942. }
  14943. });
  14944. }
  14945. });
  14946.  
  14947. Ext.apply(me, {
  14948. store: store,
  14949. selModel: sm,
  14950. stateful: false,
  14951. viewConfig: {
  14952. trackOver: false
  14953. },
  14954. tbar: [
  14955. {
  14956. text: gettext('Add'),
  14957. menu: new Ext.menu.Menu({
  14958. items: [
  14959. {
  14960. text: 'Directory',
  14961. iconCls: 'pve-itype-icon-itype',
  14962. handler: function() {
  14963. var win = Ext.create('PVE.storage.DirEdit', {});
  14964. win.on('destroy', reload);
  14965. win.show();
  14966. }
  14967.  
  14968. },
  14969. {
  14970. text: 'LVM group',
  14971. handler: function() {
  14972. var win = Ext.create('PVE.storage.LVMEdit', {});
  14973. win.on('destroy', reload);
  14974. win.show();
  14975. }
  14976. },
  14977. {
  14978. text: 'NFS share',
  14979. iconCls: 'pve-itype-icon-node',
  14980. handler: function() {
  14981. var win = Ext.create('PVE.storage.NFSEdit', {});
  14982. win.on('destroy', reload);
  14983. win.show();
  14984. }
  14985. },
  14986. {
  14987. text: 'iSCSI target',
  14988. iconCls: 'pve-itype-icon-node',
  14989. handler: function() {
  14990. var win = Ext.create('PVE.storage.IScsiEdit', {});
  14991. win.on('destroy', reload);
  14992. win.show();
  14993. }
  14994. }
  14995. ]
  14996. })
  14997. },
  14998. remove_btn,
  14999. edit_btn
  15000. ],
  15001. columns: [
  15002. {
  15003. header: 'ID',
  15004. width: 100,
  15005. sortable: true,
  15006. dataIndex: 'storage'
  15007. },
  15008. {
  15009. header: gettext('Type'),
  15010. width: 60,
  15011. sortable: true,
  15012. dataIndex: 'type',
  15013. renderer: PVE.Utils.format_storage_type
  15014. },
  15015. {
  15016. header: gettext('Content'),
  15017. width: 150,
  15018. sortable: true,
  15019. dataIndex: 'content',
  15020. renderer: PVE.Utils.format_content_types
  15021. },
  15022. {
  15023. header: 'Path/Target',
  15024. flex: 1,
  15025. sortable: true,
  15026. dataIndex: 'path',
  15027. renderer: function(value, metaData, record) {
  15028. if (record.data.target) {
  15029. return record.data.target;
  15030. }
  15031. return value;
  15032. }
  15033. },
  15034. {
  15035. header: gettext('Shared'),
  15036. width: 80,
  15037. sortable: true,
  15038. dataIndex: 'shared',
  15039. renderer: PVE.Utils.format_boolean
  15040. },
  15041. {
  15042. header: gettext('Enable'),
  15043. width: 80,
  15044. sortable: true,
  15045. dataIndex: 'disable',
  15046. renderer: PVE.Utils.format_neg_boolean
  15047. }
  15048. ],
  15049. listeners: {
  15050. show: reload,
  15051. itemdblclick: run_editor
  15052. }
  15053. });
  15054.  
  15055. me.callParent();
  15056. }
  15057. }, function() {
  15058.  
  15059. Ext.define('pve-storage', {
  15060. extend: 'Ext.data.Model',
  15061. fields: [
  15062. 'path', 'type', 'content', 'server', 'portal', 'target', 'export', 'storage',
  15063. { name: 'shared', type: 'boolean'},
  15064. { name: 'disable', type: 'boolean'}
  15065. ],
  15066. idProperty: 'storage'
  15067. });
  15068.  
  15069. });Ext.define('PVE.dc.UserEdit', {
  15070. extend: 'PVE.window.Edit',
  15071. alias: ['widget.pveDcUserEdit'],
  15072.  
  15073. isAdd: true,
  15074.  
  15075. initComponent : function() {
  15076. var me = this;
  15077.  
  15078. me.create = !me.userid;
  15079.  
  15080. var url;
  15081. var method;
  15082. var realm;
  15083.  
  15084. if (me.create) {
  15085. url = '/api2/extjs/access/users';
  15086. method = 'POST';
  15087. } else {
  15088. url = '/api2/extjs/access/users/' + me.userid;
  15089. method = 'PUT';
  15090. }
  15091.  
  15092. var verifypw;
  15093. var pwfield;
  15094.  
  15095. var validate_pw = function() {
  15096. if (verifypw.getValue() !== pwfield.getValue()) {
  15097. return gettext("Passwords does not match");
  15098. }
  15099. return true;
  15100. };
  15101.  
  15102. verifypw = Ext.createWidget('textfield', {
  15103. inputType: 'password',
  15104. fieldLabel: gettext('Confirm password'),
  15105. name: 'verifypassword',
  15106. submitValue: false,
  15107. disabled: true,
  15108. hidden: true,
  15109. validator: validate_pw
  15110. });
  15111.  
  15112. pwfield = Ext.createWidget('textfield', {
  15113. inputType: 'password',
  15114. fieldLabel: gettext('Password'),
  15115. minLength: 5,
  15116. name: 'password',
  15117. disabled: true,
  15118. hidden: true,
  15119. validator: validate_pw
  15120. });
  15121.  
  15122. var update_passwd_field = function(realm) {
  15123. if (realm === 'pve') {
  15124. pwfield.setVisible(true);
  15125. pwfield.setDisabled(false);
  15126. verifypw.setVisible(true);
  15127. verifypw.setDisabled(false);
  15128. } else {
  15129. pwfield.setVisible(false);
  15130. pwfield.setDisabled(true);
  15131. verifypw.setVisible(false);
  15132. verifypw.setDisabled(true);
  15133. }
  15134.  
  15135. };
  15136.  
  15137. var column1 = [
  15138. {
  15139. xtype: me.create ? 'textfield' : 'displayfield',
  15140. height: 22, // hack: set same height as text fields
  15141. name: 'userid',
  15142. fieldLabel: gettext('User name'),
  15143. value: me.userid,
  15144. allowBlank: false,
  15145. submitValue: me.create ? true : false
  15146. },
  15147. pwfield, verifypw,
  15148. {
  15149. xtype: 'pveGroupSelector',
  15150. name: 'groups',
  15151. multiSelect: true,
  15152. allowBlank: true,
  15153. fieldLabel: gettext('Group')
  15154. },
  15155. {
  15156. xtype: 'datefield',
  15157. name: 'expire',
  15158. emptyText: 'never',
  15159. format: 'Y-m-d',
  15160. submitFormat: 'U',
  15161. fieldLabel: gettext('Expire')
  15162. },
  15163. {
  15164. xtype: 'pvecheckbox',
  15165. fieldLabel: gettext('Enabled'),
  15166. name: 'enable',
  15167. uncheckedValue: 0,
  15168. defaultValue: 1,
  15169. checked: true
  15170. }
  15171. ];
  15172.  
  15173. var column2 = [
  15174. {
  15175. xtype: 'textfield',
  15176. name: 'firstname',
  15177. fieldLabel: gettext('First Name')
  15178. },
  15179. {
  15180. xtype: 'textfield',
  15181. name: 'lastname',
  15182. fieldLabel: gettext('Last Name')
  15183. },
  15184. {
  15185. xtype: 'textfield',
  15186. name: 'email',
  15187. fieldLabel: 'E-Mail',
  15188. vtype: 'email'
  15189. },
  15190. {
  15191. xtype: 'textfield',
  15192. name: 'comment',
  15193. fieldLabel: gettext('Comment')
  15194. }
  15195. ];
  15196.  
  15197. if (me.create) {
  15198. column1.splice(1,0,{
  15199. xtype: 'pveRealmComboBox',
  15200. name: 'realm',
  15201. fieldLabel: gettext('Realm'),
  15202. allowBlank: false,
  15203. matchFieldWidth: false,
  15204. listConfig: { width: 300 },
  15205. listeners: {
  15206. change: function(combo, newValue){
  15207. realm = newValue;
  15208. update_passwd_field(realm);
  15209. }
  15210. },
  15211. submitValue: false
  15212. });
  15213. }
  15214.  
  15215. var ipanel = Ext.create('PVE.panel.InputPanel', {
  15216. column1: column1,
  15217. column2: column2,
  15218. onGetValues: function(values) {
  15219. // hack: ExtJS datefield does not submit 0, so we need to set that
  15220. if (!values.expire) {
  15221. values.expire = 0;
  15222. }
  15223.  
  15224. if (realm) {
  15225. values.userid = values.userid + '@' + realm;
  15226. }
  15227.  
  15228. if (!values.password) {
  15229. delete values.password;
  15230. }
  15231.  
  15232. return values;
  15233. }
  15234. });
  15235.  
  15236. Ext.applyIf(me, {
  15237. subject: gettext('User'),
  15238. url: url,
  15239. method: method,
  15240. items: [ ipanel ]
  15241. });
  15242.  
  15243. me.callParent();
  15244.  
  15245. if (!me.create) {
  15246. me.load({
  15247. success: function(response, options) {
  15248. var data = response.result.data;
  15249. if (Ext.isDefined(data.expire)) {
  15250. if (data.expire) {
  15251. data.expire = new Date(data.expire * 1000);
  15252. } else {
  15253. // display 'never' instead of '1970-01-01'
  15254. data.expire = null;
  15255. }
  15256. }
  15257. me.setValues(data);
  15258. }
  15259. });
  15260. }
  15261. }
  15262. });
  15263. Ext.define('PVE.window.PasswordEdit', {
  15264. extend: 'PVE.window.Edit',
  15265.  
  15266. initComponent : function() {
  15267. var me = this;
  15268.  
  15269. if (!me.userid) {
  15270. throw "no userid specified";
  15271. }
  15272.  
  15273. var verifypw;
  15274. var pwfield;
  15275.  
  15276. var validate_pw = function() {
  15277. if (verifypw.getValue() !== pwfield.getValue()) {
  15278. return gettext("Passwords does not match");
  15279. }
  15280. return true;
  15281. };
  15282.  
  15283. verifypw = Ext.createWidget('textfield', {
  15284. inputType: 'password',
  15285. fieldLabel: gettext('Confirm password'),
  15286. name: 'verifypassword',
  15287. submitValue: false,
  15288. validator: validate_pw
  15289. });
  15290.  
  15291. pwfield = Ext.createWidget('textfield', {
  15292. inputType: 'password',
  15293. fieldLabel: gettext('Password'),
  15294. minLength: 5,
  15295. name: 'password',
  15296. validator: validate_pw
  15297. });
  15298.  
  15299. Ext.apply(me, {
  15300. subject: gettext('Password'),
  15301. url: '/api2/extjs/access/password',
  15302. items: [
  15303. pwfield, verifypw,
  15304. {
  15305. xtype: 'hiddenfield',
  15306. name: 'userid',
  15307. value: me.userid
  15308. }
  15309. ]
  15310. });
  15311.  
  15312. me.callParent();
  15313. }
  15314. });
  15315.  
  15316. Ext.define('PVE.dc.UserView', {
  15317. extend: 'Ext.grid.GridPanel',
  15318.  
  15319. alias: ['widget.pveUserView'],
  15320.  
  15321. initComponent : function() {
  15322. var me = this;
  15323.  
  15324. var store = new Ext.data.Store({
  15325. id: "users",
  15326. model: 'pve-users',
  15327. sorters: {
  15328. property: 'userid',
  15329. order: 'DESC'
  15330. }
  15331. });
  15332.  
  15333. var reload = function() {
  15334. store.load();
  15335. };
  15336.  
  15337. var sm = Ext.create('Ext.selection.RowModel', {});
  15338.  
  15339. var remove_btn = new PVE.button.Button({
  15340. text: gettext('Remove'),
  15341. disabled: true,
  15342. selModel: sm,
  15343. enableFn: function(rec) {
  15344. return rec.data.userid !== 'root@pam';
  15345. },
  15346. confirmMsg: function (rec) {
  15347. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  15348. "'" + rec.data.userid + "'");
  15349. },
  15350. handler: function(btn, event, rec) {
  15351. var userid = rec.data.userid;
  15352.  
  15353. PVE.Utils.API2Request({
  15354. url: '/access/users/' + userid,
  15355. method: 'DELETE',
  15356. waitMsgTarget: me,
  15357. callback: function() {
  15358. reload();
  15359. },
  15360. failure: function (response, opts) {
  15361. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  15362. }
  15363. });
  15364. }
  15365. });
  15366.  
  15367. var run_editor = function() {
  15368. var rec = sm.getSelection()[0];
  15369. if (!rec) {
  15370. return;
  15371. }
  15372.  
  15373. var win = Ext.create('PVE.dc.UserEdit',{
  15374. userid: rec.data.userid
  15375. });
  15376. win.on('destroy', reload);
  15377. win.show();
  15378. };
  15379.  
  15380. var edit_btn = new PVE.button.Button({
  15381. text: gettext('Edit'),
  15382. disabled: true,
  15383. selModel: sm,
  15384. handler: run_editor
  15385. });
  15386.  
  15387. var pwchange_btn = new PVE.button.Button({
  15388. text: gettext('Password'),
  15389. disabled: true,
  15390. selModel: sm,
  15391. handler: function(btn, event, rec) {
  15392. var win = Ext.create('PVE.window.PasswordEdit',{
  15393. userid: rec.data.userid
  15394. });
  15395. win.on('destroy', reload);
  15396. win.show();
  15397. }
  15398. });
  15399.  
  15400. var tbar = [
  15401. {
  15402. text: gettext('Add'),
  15403. handler: function() {
  15404. var win = Ext.create('PVE.dc.UserEdit',{
  15405. });
  15406. win.on('destroy', reload);
  15407. win.show();
  15408. }
  15409. },
  15410. edit_btn, remove_btn, pwchange_btn
  15411. ];
  15412.  
  15413. var render_full_name = function(firstname, metaData, record) {
  15414.  
  15415. var first = firstname || '';
  15416. var last = record.data.lastname || '';
  15417. return first + " " + last;
  15418. };
  15419.  
  15420. var render_username = function(userid) {
  15421. return userid.match(/^([^@]+)/)[1];
  15422. };
  15423.  
  15424. var render_realm = function(userid) {
  15425. return userid.match(/@([^@]+)$/)[1];
  15426. };
  15427.  
  15428. Ext.apply(me, {
  15429. store: store,
  15430. selModel: sm,
  15431. stateful: false,
  15432. tbar: tbar,
  15433. viewConfig: {
  15434. trackOver: false
  15435. },
  15436. columns: [
  15437. {
  15438. header: gettext('User name'),
  15439. width: 200,
  15440. sortable: true,
  15441. renderer: render_username,
  15442. dataIndex: 'userid'
  15443. },
  15444. {
  15445. header: gettext('Realm'),
  15446. width: 100,
  15447. sortable: true,
  15448. renderer: render_realm,
  15449. dataIndex: 'userid'
  15450. },
  15451. {
  15452. header: gettext('Enabled'),
  15453. width: 80,
  15454. sortable: true,
  15455. renderer: PVE.Utils.format_boolean,
  15456. dataIndex: 'enable'
  15457. },
  15458. {
  15459. header: gettext('Expire'),
  15460. width: 80,
  15461. sortable: true,
  15462. renderer: PVE.Utils.format_expire,
  15463. dataIndex: 'expire'
  15464. },
  15465. {
  15466. header: gettext('Name'),
  15467. width: 150,
  15468. sortable: true,
  15469. renderer: render_full_name,
  15470. dataIndex: 'firstname'
  15471. },
  15472. {
  15473. id: 'comment',
  15474. header: gettext('Comment'),
  15475. sortable: false,
  15476. dataIndex: 'comment',
  15477. flex: 1
  15478. }
  15479. ],
  15480. listeners: {
  15481. show: reload,
  15482. itemdblclick: run_editor
  15483. }
  15484. });
  15485.  
  15486. me.callParent();
  15487. }
  15488. });
  15489. Ext.define('PVE.dc.PoolView', {
  15490. extend: 'Ext.grid.GridPanel',
  15491.  
  15492. alias: ['widget.pvePoolView'],
  15493.  
  15494. initComponent : function() {
  15495. var me = this;
  15496.  
  15497. var store = new Ext.data.Store({
  15498. model: 'pve-pools',
  15499. sorters: {
  15500. property: 'poolid',
  15501. order: 'DESC'
  15502. }
  15503. });
  15504.  
  15505. var reload = function() {
  15506. store.load();
  15507. };
  15508.  
  15509. var sm = Ext.create('Ext.selection.RowModel', {});
  15510.  
  15511. var remove_btn = new PVE.button.Button({
  15512. text: gettext('Remove'),
  15513. disabled: true,
  15514. selModel: sm,
  15515. confirmMsg: function (rec) {
  15516. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  15517. "'" + rec.data.poolid + "'");
  15518. },
  15519. handler: function(btn, event, rec) {
  15520. PVE.Utils.API2Request({
  15521. url: '/pools/' + rec.data.poolid,
  15522. method: 'DELETE',
  15523. waitMsgTarget: me,
  15524. callback: function() {
  15525. reload();
  15526. },
  15527. failure: function (response, opts) {
  15528. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  15529. }
  15530. });
  15531. }
  15532. });
  15533.  
  15534. var run_editor = function() {
  15535. var rec = sm.getSelection()[0];
  15536. if (!rec) {
  15537. return;
  15538. }
  15539.  
  15540. var win = Ext.create('PVE.dc.PoolEdit',{
  15541. poolid: rec.data.poolid
  15542. });
  15543. win.on('destroy', reload);
  15544. win.show();
  15545. };
  15546.  
  15547. var edit_btn = new PVE.button.Button({
  15548. text: gettext('Edit'),
  15549. disabled: true,
  15550. selModel: sm,
  15551. handler: run_editor
  15552. });
  15553.  
  15554. var tbar = [
  15555. {
  15556. text: gettext('Create'),
  15557. handler: function() {
  15558. var win = Ext.create('PVE.dc.PoolEdit', {});
  15559. win.on('destroy', reload);
  15560. win.show();
  15561. }
  15562. },
  15563. edit_btn, remove_btn
  15564. ];
  15565.  
  15566. PVE.Utils.monStoreErrors(me, store);
  15567.  
  15568. Ext.apply(me, {
  15569. store: store,
  15570. selModel: sm,
  15571. stateful: false,
  15572. tbar: tbar,
  15573. viewConfig: {
  15574. trackOver: false
  15575. },
  15576. columns: [
  15577. {
  15578. header: gettext('Name'),
  15579. width: 200,
  15580. sortable: true,
  15581. dataIndex: 'poolid'
  15582. },
  15583. {
  15584. header: gettext('Comment'),
  15585. sortable: false,
  15586. dataIndex: 'comment',
  15587. flex: 1
  15588. }
  15589. ],
  15590. listeners: {
  15591. show: reload,
  15592. itemdblclick: run_editor
  15593. }
  15594. });
  15595.  
  15596. me.callParent();
  15597. }
  15598. });
  15599. Ext.define('PVE.dc.PoolEdit', {
  15600. extend: 'PVE.window.Edit',
  15601. alias: ['widget.pveDcPoolEdit'],
  15602.  
  15603. initComponent : function() {
  15604. var me = this;
  15605.  
  15606. me.create = !me.poolid;
  15607.  
  15608. var url;
  15609. var method;
  15610.  
  15611. if (me.create) {
  15612. url = '/api2/extjs/pools';
  15613. method = 'POST';
  15614. } else {
  15615. url = '/api2/extjs/pools/' + me.poolid;
  15616. method = 'PUT';
  15617. }
  15618.  
  15619. Ext.applyIf(me, {
  15620. subject: gettext('Pool'),
  15621. url: url,
  15622. method: method,
  15623. items: [
  15624. {
  15625. xtype: me.create ? 'pvetextfield' : 'displayfield',
  15626. fieldLabel: gettext('Name'),
  15627. name: 'poolid',
  15628. value: me.poolid,
  15629. allowBlank: false
  15630. },
  15631. {
  15632. xtype: 'textfield',
  15633. fieldLabel: gettext('Comment'),
  15634. name: 'comment',
  15635. allowBlank: true
  15636. }
  15637. ]
  15638. });
  15639.  
  15640. me.callParent();
  15641.  
  15642. if (!me.create) {
  15643. me.load();
  15644. }
  15645. }
  15646. });
  15647. Ext.define('PVE.dc.GroupView', {
  15648. extend: 'Ext.grid.GridPanel',
  15649.  
  15650. alias: ['widget.pveGroupView'],
  15651.  
  15652. initComponent : function() {
  15653. var me = this;
  15654.  
  15655. var store = new Ext.data.Store({
  15656. model: 'pve-groups',
  15657. sorters: {
  15658. property: 'groupid',
  15659. order: 'DESC'
  15660. }
  15661. });
  15662.  
  15663. var reload = function() {
  15664. store.load();
  15665. };
  15666.  
  15667. var sm = Ext.create('Ext.selection.RowModel', {});
  15668.  
  15669. var remove_btn = new PVE.button.Button({
  15670. text: gettext('Remove'),
  15671. disabled: true,
  15672. selModel: sm,
  15673. confirmMsg: function (rec) {
  15674. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  15675. "'" + rec.data.groupid + "'");
  15676. },
  15677. handler: function(btn, event, rec) {
  15678. PVE.Utils.API2Request({
  15679. url: '/access/groups/' + rec.data.groupid,
  15680. method: 'DELETE',
  15681. waitMsgTarget: me,
  15682. callback: function() {
  15683. reload();
  15684. },
  15685. failure: function (response, opts) {
  15686. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  15687. }
  15688. });
  15689. }
  15690. });
  15691.  
  15692. var run_editor = function() {
  15693. var rec = sm.getSelection()[0];
  15694. if (!rec) {
  15695. return;
  15696. }
  15697.  
  15698. var win = Ext.create('PVE.dc.GroupEdit',{
  15699. groupid: rec.data.groupid
  15700. });
  15701. win.on('destroy', reload);
  15702. win.show();
  15703. };
  15704.  
  15705. var edit_btn = new PVE.button.Button({
  15706. text: gettext('Edit'),
  15707. disabled: true,
  15708. selModel: sm,
  15709. handler: run_editor
  15710. });
  15711.  
  15712. var tbar = [
  15713. {
  15714. text: gettext('Create'),
  15715. handler: function() {
  15716. var win = Ext.create('PVE.dc.GroupEdit', {});
  15717. win.on('destroy', reload);
  15718. win.show();
  15719. }
  15720. },
  15721. edit_btn, remove_btn
  15722. ];
  15723.  
  15724. PVE.Utils.monStoreErrors(me, store);
  15725.  
  15726. Ext.apply(me, {
  15727. store: store,
  15728. selModel: sm,
  15729. stateful: false,
  15730. tbar: tbar,
  15731. viewConfig: {
  15732. trackOver: false
  15733. },
  15734. columns: [
  15735. {
  15736. header: gettext('Name'),
  15737. width: 200,
  15738. sortable: true,
  15739. dataIndex: 'groupid'
  15740. },
  15741. {
  15742. header: gettext('Comment'),
  15743. sortable: false,
  15744. dataIndex: 'comment',
  15745. flex: 1
  15746. }
  15747. ],
  15748. listeners: {
  15749. show: reload,
  15750. itemdblclick: run_editor
  15751. }
  15752. });
  15753.  
  15754. me.callParent();
  15755. }
  15756. });
  15757. Ext.define('PVE.dc.GroupEdit', {
  15758. extend: 'PVE.window.Edit',
  15759. alias: ['widget.pveDcGroupEdit'],
  15760.  
  15761. initComponent : function() {
  15762. var me = this;
  15763.  
  15764. me.create = !me.groupid;
  15765.  
  15766. var url;
  15767. var method;
  15768.  
  15769. if (me.create) {
  15770. url = '/api2/extjs/access/groups';
  15771. method = 'POST';
  15772. } else {
  15773. url = '/api2/extjs/access/groups/' + me.groupid;
  15774. method = 'PUT';
  15775. }
  15776.  
  15777. Ext.applyIf(me, {
  15778. subject: gettext('Group'),
  15779. url: url,
  15780. method: method,
  15781. items: [
  15782. {
  15783. xtype: me.create ? 'pvetextfield' : 'displayfield',
  15784. fieldLabel: gettext('Name'),
  15785. name: 'groupid',
  15786. value: me.groupid,
  15787. allowBlank: false
  15788. },
  15789. {
  15790. xtype: 'textfield',
  15791. fieldLabel: gettext('Comment'),
  15792. name: 'comment',
  15793. allowBlank: true
  15794. }
  15795. ]
  15796. });
  15797.  
  15798. me.callParent();
  15799.  
  15800. if (!me.create) {
  15801. me.load();
  15802. }
  15803. }
  15804. });
  15805. Ext.define('PVE.dc.RoleView', {
  15806. extend: 'Ext.grid.GridPanel',
  15807.  
  15808. alias: ['widget.pveRoleView'],
  15809.  
  15810. initComponent : function() {
  15811. var me = this;
  15812.  
  15813. var store = new Ext.data.Store({
  15814. model: 'pve-roles',
  15815. sorters: {
  15816. property: 'roleid',
  15817. order: 'DESC'
  15818. }
  15819. });
  15820.  
  15821. var render_privs = function(value, metaData) {
  15822.  
  15823. if (!value) {
  15824. return '-';
  15825. }
  15826.  
  15827. // allow word wrap
  15828. metaData.style = 'white-space:normal;';
  15829.  
  15830. return value.replace(/\,/g, ' ');
  15831. };
  15832.  
  15833. PVE.Utils.monStoreErrors(me, store);
  15834.  
  15835. Ext.apply(me, {
  15836. store: store,
  15837. stateful: false,
  15838.  
  15839. viewConfig: {
  15840. trackOver: false
  15841. },
  15842. columns: [
  15843. {
  15844. header: gettext('Name'),
  15845. width: 150,
  15846. sortable: true,
  15847. dataIndex: 'roleid'
  15848. },
  15849. {
  15850. id: 'privs',
  15851. header: gettext('Privileges'),
  15852. sortable: false,
  15853. renderer: render_privs,
  15854. dataIndex: 'privs',
  15855. flex: 1
  15856. }
  15857. ],
  15858. listeners: {
  15859. show: function() {
  15860. store.load();
  15861. }
  15862. }
  15863. });
  15864.  
  15865. me.callParent();
  15866. }
  15867. });Ext.define('PVE.dc.ACLAdd', {
  15868. extend: 'PVE.window.Edit',
  15869. alias: ['widget.pveACLAdd'],
  15870.  
  15871. initComponent : function() {
  15872. /*jslint confusion: true */
  15873. var me = this;
  15874.  
  15875. me.create = true;
  15876.  
  15877. var items = [
  15878. {
  15879. xtype: me.path ? 'hiddenfield' : 'textfield',
  15880. name: 'path',
  15881. value: me.path,
  15882. allowBlank: false,
  15883. fieldLabel: gettext('Path')
  15884. }
  15885. ];
  15886.  
  15887. if (me.aclType === 'group') {
  15888. me.subject = gettext("Group Permission");
  15889. items.push({
  15890. xtype: 'pveGroupSelector',
  15891. name: 'groups',
  15892. fieldLabel: gettext('Group')
  15893. });
  15894. } else if (me.aclType === 'user') {
  15895. me.subject = gettext("User Permission");
  15896. items.push({
  15897. xtype: 'pveUserSelector',
  15898. name: 'users',
  15899. fieldLabel: gettext('User')
  15900. });
  15901. } else {
  15902. throw "unknown ACL type";
  15903. }
  15904.  
  15905. items.push({
  15906. xtype: 'pveRoleSelector',
  15907. name: 'roles',
  15908. value: 'NoAccess',
  15909. fieldLabel: gettext('Role')
  15910. });
  15911.  
  15912. if (!me.path) {
  15913. items.push({
  15914. xtype: 'pvecheckbox',
  15915. name: 'propagate',
  15916. checked: true,
  15917. fieldLabel: gettext('Propagate')
  15918. });
  15919. }
  15920.  
  15921. var ipanel = Ext.create('PVE.panel.InputPanel', {
  15922. items: items
  15923. });
  15924.  
  15925. Ext.apply(me, {
  15926. url: '/access/acl',
  15927. method: 'PUT',
  15928. isAdd: true,
  15929. items: [ ipanel ]
  15930. });
  15931.  
  15932. me.callParent();
  15933. }
  15934. });
  15935.  
  15936. Ext.define('PVE.dc.ACLView', {
  15937. extend: 'Ext.grid.GridPanel',
  15938.  
  15939. alias: ['widget.pveACLView'],
  15940.  
  15941. // use fixed path
  15942. path: undefined,
  15943.  
  15944. initComponent : function() {
  15945. var me = this;
  15946.  
  15947. var store = new Ext.data.Store({
  15948. model: 'pve-acl',
  15949. proxy: {
  15950. type: 'pve',
  15951. url: "/api2/json/access/acl"
  15952. },
  15953. sorters: {
  15954. property: 'path',
  15955. order: 'DESC'
  15956. }
  15957. });
  15958.  
  15959. if (me.path) {
  15960. store.filters.add(new Ext.util.Filter({
  15961. filterFn: function(item) {
  15962. if (item.data.path === me.path) {
  15963. return true;
  15964. }
  15965. }
  15966. }));
  15967. }
  15968.  
  15969. var render_ugid = function(ugid, metaData, record) {
  15970. if (record.data.type == 'group') {
  15971. return '@' + ugid;
  15972. }
  15973.  
  15974. return ugid;
  15975. };
  15976.  
  15977. var columns = [
  15978. {
  15979. header: gettext('User') + '/' + gettext('Group'),
  15980. flex: 1,
  15981. sortable: true,
  15982. renderer: render_ugid,
  15983. dataIndex: 'ugid'
  15984. },
  15985. {
  15986. header: gettext('Role'),
  15987. flex: 1,
  15988. sortable: true,
  15989. dataIndex: 'roleid'
  15990. }
  15991. ];
  15992.  
  15993. if (!me.path) {
  15994. columns.unshift({
  15995. header: gettext('Path'),
  15996. flex: 1,
  15997. sortable: true,
  15998. dataIndex: 'path'
  15999. });
  16000. columns.push({
  16001. header: gettext('Propagate'),
  16002. width: 80,
  16003. sortable: true,
  16004. dataIndex: 'propagate'
  16005. });
  16006. }
  16007.  
  16008. var sm = Ext.create('Ext.selection.RowModel', {});
  16009.  
  16010. var reload = function() {
  16011. store.load();
  16012. };
  16013.  
  16014. var remove_btn = new PVE.button.Button({
  16015. text: gettext('Remove'),
  16016. disabled: true,
  16017. selModel: sm,
  16018. confirmMsg: gettext('Are you sure you want to remove this entry'),
  16019. handler: function(btn, event, rec) {
  16020. var params = {
  16021. 'delete': 1,
  16022. path: rec.data.path,
  16023. roles: rec.data.roleid
  16024. };
  16025. if (rec.data.type === 'group') {
  16026. params.groups = rec.data.ugid;
  16027. } else if (rec.data.type === 'user') {
  16028. params.users = rec.data.ugid;
  16029. } else {
  16030. throw 'unknown data type';
  16031. }
  16032.  
  16033. PVE.Utils.API2Request({
  16034. url: '/access/acl',
  16035. params: params,
  16036. method: 'PUT',
  16037. waitMsgTarget: me,
  16038. callback: function() {
  16039. reload();
  16040. },
  16041. failure: function (response, opts) {
  16042. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  16043. }
  16044. });
  16045. }
  16046. });
  16047.  
  16048. PVE.Utils.monStoreErrors(me, store);
  16049.  
  16050. Ext.apply(me, {
  16051. store: store,
  16052. selModel: sm,
  16053. stateful: false,
  16054. tbar: [
  16055. {
  16056. text: gettext('Add'),
  16057. menu: new Ext.menu.Menu({
  16058. items: [
  16059. {
  16060. text: gettext('Group Permission'),
  16061. handler: function() {
  16062. var win = Ext.create('PVE.dc.ACLAdd',{
  16063. aclType: 'group',
  16064. path: me.path
  16065. });
  16066. win.on('destroy', reload);
  16067. win.show();
  16068. }
  16069. },
  16070. {
  16071. text: gettext('User Permission'),
  16072. handler: function() {
  16073. var win = Ext.create('PVE.dc.ACLAdd',{
  16074. aclType: 'user',
  16075. path: me.path
  16076. });
  16077. win.on('destroy', reload);
  16078. win.show();
  16079. }
  16080. }
  16081. ]
  16082. })
  16083. },
  16084. remove_btn
  16085. ],
  16086. viewConfig: {
  16087. trackOver: false
  16088. },
  16089. columns: columns,
  16090. listeners: {
  16091. show: reload
  16092. }
  16093. });
  16094.  
  16095. me.callParent();
  16096. }
  16097. }, function() {
  16098.  
  16099. Ext.define('pve-acl', {
  16100. extend: 'Ext.data.Model',
  16101. fields: [
  16102. 'path', 'type', 'ugid', 'roleid',
  16103. {
  16104. name: 'propagate',
  16105. type: 'boolean'
  16106. }
  16107. ]
  16108. });
  16109.  
  16110. });Ext.define('PVE.dc.AuthView', {
  16111. extend: 'Ext.grid.GridPanel',
  16112.  
  16113. alias: ['widget.pveAuthView'],
  16114.  
  16115. initComponent : function() {
  16116. var me = this;
  16117.  
  16118. var store = new Ext.data.Store({
  16119. model: 'pve-domains',
  16120. sorters: {
  16121. property: 'realm',
  16122. order: 'DESC'
  16123. }
  16124. });
  16125.  
  16126. var reload = function() {
  16127. store.load();
  16128. };
  16129.  
  16130. var sm = Ext.create('Ext.selection.RowModel', {});
  16131.  
  16132. var run_editor = function() {
  16133. var rec = sm.getSelection()[0];
  16134. if (!rec) {
  16135. return;
  16136. }
  16137.  
  16138. if (rec.data.type === 'builtin') {
  16139. return;
  16140. }
  16141.  
  16142. var win = Ext.create('PVE.dc.AuthEdit',{
  16143. realm: rec.data.realm,
  16144. authType: rec.data.type
  16145. });
  16146. win.on('destroy', reload);
  16147. win.show();
  16148. };
  16149.  
  16150. var edit_btn = new PVE.button.Button({
  16151. text: gettext('Edit'),
  16152. disabled: true,
  16153. selModel: sm,
  16154. enableFn: function(rec) {
  16155. return rec.data.type !== 'builtin';
  16156. },
  16157. handler: run_editor
  16158. });
  16159.  
  16160. var remove_btn = new PVE.button.Button({
  16161. text: gettext('Remove'),
  16162. disabled: true,
  16163. selModel: sm,
  16164. confirmMsg: function (rec) {
  16165. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  16166. "'" + rec.data.realm + "'");
  16167. },
  16168. enableFn: function(rec) {
  16169. return rec.data.type !== 'builtin';
  16170. },
  16171. handler: function(btn, event, rec) {
  16172. var realm = rec.data.realm;
  16173.  
  16174. PVE.Utils.API2Request({
  16175. url: '/access/domains/' + realm,
  16176. method: 'DELETE',
  16177. waitMsgTarget: me,
  16178. callback: function() {
  16179. reload();
  16180. },
  16181. failure: function (response, opts) {
  16182. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  16183. }
  16184. });
  16185. }
  16186. });
  16187.  
  16188. var tbar = [
  16189. {
  16190. text: gettext('Add'),
  16191. menu: new Ext.menu.Menu({
  16192. items: [
  16193. {
  16194. text: 'Active Directory Server',
  16195. handler: function() {
  16196. var win = Ext.create('PVE.dc.AuthEdit', {
  16197. authType: 'ad'
  16198. });
  16199. win.on('destroy', reload);
  16200. win.show();
  16201. }
  16202. },
  16203. {
  16204. text: 'LDAP Server',
  16205. handler: function() {
  16206. var win = Ext.create('PVE.dc.AuthEdit',{
  16207. authType: 'ldap'
  16208. });
  16209. win.on('destroy', reload);
  16210. win.show();
  16211. }
  16212. }
  16213. ]
  16214. })
  16215. },
  16216. edit_btn, remove_btn
  16217. ];
  16218.  
  16219. Ext.apply(me, {
  16220. store: store,
  16221. selModel: sm,
  16222. stateful: false,
  16223. tbar: tbar,
  16224. viewConfig: {
  16225. trackOver: false
  16226. },
  16227. columns: [
  16228. {
  16229. header: gettext('Realm'),
  16230. width: 100,
  16231. sortable: true,
  16232. dataIndex: 'realm'
  16233. },
  16234. {
  16235. header: gettext('Type'),
  16236. width: 100,
  16237. sortable: true,
  16238. dataIndex: 'type'
  16239. },
  16240. {
  16241. id: 'comment',
  16242. header: gettext('Comment'),
  16243. sortable: false,
  16244. dataIndex: 'comment',
  16245. flex: 1
  16246. }
  16247. ],
  16248. listeners: {
  16249. show: reload,
  16250. itemdblclick: run_editor
  16251. }
  16252. });
  16253.  
  16254. me.callParent();
  16255. }
  16256. });
  16257. Ext.define('PVE.dc.AuthEdit', {
  16258. extend: 'PVE.window.Edit',
  16259. alias: ['widget.pveDcAuthEdit'],
  16260.  
  16261. isAdd: true,
  16262.  
  16263. initComponent : function() {
  16264. var me = this;
  16265.  
  16266. me.create = !me.realm;
  16267.  
  16268. var url;
  16269. var method;
  16270. var serverlist;
  16271.  
  16272. if (me.create) {
  16273. url = '/api2/extjs/access/domains';
  16274. method = 'POST';
  16275. } else {
  16276. url = '/api2/extjs/access/domains/' + me.realm;
  16277. method = 'PUT';
  16278. }
  16279.  
  16280. var column1 = [
  16281. {
  16282. xtype: me.create ? 'textfield' : 'displayfield',
  16283. height: 22, // hack: set same height as text fields
  16284. name: 'realm',
  16285. fieldLabel: gettext('Realm'),
  16286. value: me.realm,
  16287. allowBlank: false
  16288. }
  16289. ];
  16290.  
  16291. if (me.authType === 'ad') {
  16292.  
  16293. me.subject = 'Active Directory Server';
  16294.  
  16295. column1.push({
  16296. xtype: 'textfield',
  16297. name: 'domain',
  16298. fieldLabel: 'Domain',
  16299. emptyText: 'company.net',
  16300. allowBlank: false
  16301. });
  16302.  
  16303. } else if (me.authType === 'ldap') {
  16304.  
  16305. me.subject = 'LDAP Server';
  16306.  
  16307. column1.push({
  16308. xtype: 'textfield',
  16309. name: 'base_dn',
  16310. fieldLabel: 'Base Domain Name',
  16311. emptyText: 'CN=Users,DC=Company,DC=net',
  16312. allowBlank: false
  16313. });
  16314.  
  16315. column1.push({
  16316. xtype: 'textfield',
  16317. name: 'user_attr',
  16318. emptyText: 'uid / sAMAccountName',
  16319. fieldLabel: 'User Attribute Name',
  16320. allowBlank: false
  16321. });
  16322.  
  16323. } else {
  16324. throw 'unknown auth type ';
  16325. }
  16326.  
  16327. column1.push({
  16328. xtype: 'textfield',
  16329. name: 'comment',
  16330. fieldLabel: gettext('Comment')
  16331. });
  16332.  
  16333. column1.push({
  16334. xtype: 'pvecheckbox',
  16335. fieldLabel: gettext('Default'),
  16336. name: 'default',
  16337. uncheckedValue: 0
  16338. });
  16339.  
  16340. var column2 = [
  16341. {
  16342. xtype: 'textfield',
  16343. fieldLabel: gettext('Server'),
  16344. name: 'server1',
  16345. allowBlank: false
  16346. },
  16347. {
  16348. xtype: 'textfield',
  16349. fieldLabel: gettext('Fallback Server'),
  16350. name: 'server2'
  16351. },
  16352. {
  16353. xtype: 'numberfield',
  16354. name: 'port',
  16355. fieldLabel: gettext('Port'),
  16356. minValue: 1,
  16357. maxValue: 65535,
  16358. emptyText: gettext('Default'),
  16359. submitEmptyText: false
  16360. },
  16361. {
  16362. xtype: 'pvecheckbox',
  16363. fieldLabel: 'SSL',
  16364. name: 'secure',
  16365. uncheckedValue: 0
  16366. }
  16367. ];
  16368.  
  16369. var ipanel = Ext.create('PVE.panel.InputPanel', {
  16370. column1: column1,
  16371. column2: column2,
  16372. onGetValues: function(values) {
  16373. if (!values.port) {
  16374. values.port = 0;
  16375. }
  16376. if (me.create) {
  16377. values.type = me.authType;
  16378. }
  16379.  
  16380. return values;
  16381. }
  16382. });
  16383.  
  16384. Ext.applyIf(me, {
  16385. url: url,
  16386. method: method,
  16387. fieldDefaults: {
  16388. labelWidth: 120
  16389. },
  16390. items: [ ipanel ]
  16391. });
  16392.  
  16393. me.callParent();
  16394.  
  16395. if (!me.create) {
  16396. me.load({
  16397. success: function(response, options) {
  16398. var data = response.result.data || {};
  16399. // just to be sure (should not happen)
  16400. if (data.type !== me.authType) {
  16401. me.close();
  16402. throw "got wrong auth type";
  16403. }
  16404. me.setValues(data);
  16405. }
  16406. });
  16407. }
  16408. }
  16409. });
  16410. Ext.define('PVE.dc.BackupEdit', {
  16411. extend: 'PVE.window.Edit',
  16412. alias: ['widget.pveDcBackupEdit'],
  16413.  
  16414. initComponent : function() {
  16415. /*jslint confusion: true */
  16416. var me = this;
  16417.  
  16418. me.create = !me.jobid;
  16419.  
  16420. var url;
  16421. var method;
  16422.  
  16423. if (me.create) {
  16424. url = '/api2/extjs/cluster/backup';
  16425. method = 'POST';
  16426. } else {
  16427. url = '/api2/extjs/cluster/backup/' + me.jobid;
  16428. method = 'PUT';
  16429. }
  16430.  
  16431. var vmidField = Ext.create('Ext.form.field.Hidden', {
  16432. name: 'vmid'
  16433. });
  16434.  
  16435. var selModeField = Ext.create('PVE.form.KVComboBox', {
  16436. xtype: 'pveKVComboBox',
  16437. data: [
  16438. ['include', gettext('Include selected VMs')],
  16439. ['all', gettext('All')],
  16440. ['exclude', gettext('Exclude selected VMs')]
  16441. ],
  16442. fieldLabel: gettext('Selection mode'),
  16443. name: 'selMode',
  16444. value: ''
  16445. });
  16446.  
  16447. var insideUpdate = false;
  16448.  
  16449. var sm = Ext.create('Ext.selection.CheckboxModel', {
  16450. mode: 'SIMPLE',
  16451. listeners: {
  16452. selectionchange: function(model, selected) {
  16453. if (!insideUpdate) { // avoid endless loop
  16454. var sel = [];
  16455. Ext.Array.each(selected, function(record) {
  16456. sel.push(record.data.vmid);
  16457. });
  16458.  
  16459. vmidField.setValue(sel);
  16460. }
  16461. }
  16462. }
  16463. });
  16464.  
  16465. var storagesel = Ext.create('PVE.form.StorageSelector', {
  16466. fieldLabel: gettext('Storage'),
  16467. nodename: 'localhost',
  16468. storageContent: 'backup',
  16469. allowBlank: false,
  16470. name: 'storage'
  16471. });
  16472.  
  16473. var store = new Ext.data.Store({
  16474. model: 'PVEResources',
  16475. sorters: {
  16476. property: 'vmid',
  16477. order: 'ASC'
  16478. }
  16479. });
  16480.  
  16481. var vmgrid = Ext.createWidget('grid', {
  16482. store: store,
  16483. border: true,
  16484. height: 300,
  16485. selModel: sm,
  16486. disabled: true,
  16487. columns: [
  16488. {
  16489. header: 'ID',
  16490. dataIndex: 'vmid',
  16491. width: 60
  16492. },
  16493. {
  16494. header: gettext('Node'),
  16495. dataIndex: 'node'
  16496. },
  16497. {
  16498. header: gettext('Status'),
  16499. dataIndex: 'uptime',
  16500. renderer: function(value) {
  16501. if (value) {
  16502. return PVE.Utils.runningText;
  16503. } else {
  16504. return PVE.Utils.stoppedText;
  16505. }
  16506. }
  16507. },
  16508. {
  16509. header: gettext('Name'),
  16510. dataIndex: 'name',
  16511. flex: 1
  16512. },
  16513. {
  16514. header: gettext('Type'),
  16515. dataIndex: 'type'
  16516. }
  16517. ]
  16518. });
  16519.  
  16520. var nodesel = Ext.create('PVE.form.NodeSelector', {
  16521. name: 'node',
  16522. fieldLabel: gettext('Node'),
  16523. allowBlank: true,
  16524. editable: true,
  16525. autoSelect: false,
  16526. emptyText: '-- ' + gettext('All') + ' --',
  16527. listeners: {
  16528. change: function(f, value) {
  16529. storagesel.setNodename(value || 'localhost');
  16530. var mode = selModeField.getValue();
  16531. store.clearFilter();
  16532. store.filterBy(function(rec) {
  16533. return (!value || rec.get('node') === value);
  16534. });
  16535. if (mode === 'all') {
  16536. sm.selectAll(true);
  16537. }
  16538. }
  16539. }
  16540. });
  16541.  
  16542. var column1 = [
  16543. nodesel,
  16544. storagesel,
  16545. {
  16546. xtype: 'pveDayOfWeekSelector',
  16547. name: 'dow',
  16548. fieldLabel: gettext('Day of week'),
  16549. multiSelect: true,
  16550. value: ['sat'],
  16551. allowBlank: false
  16552. },
  16553. {
  16554. xtype: 'timefield',
  16555. fieldLabel: gettext('Start Time'),
  16556. name: 'starttime',
  16557. format: 'H:i',
  16558. value: '00:00',
  16559. allowBlank: false
  16560. },
  16561. selModeField
  16562. ];
  16563.  
  16564. var column2 = [
  16565. {
  16566. xtype: 'textfield',
  16567. fieldLabel: gettext('Send email to'),
  16568. name: 'mailto'
  16569. },
  16570. {
  16571. xtype: 'pveCompressionSelector',
  16572. fieldLabel: gettext('Compression'),
  16573. name: 'compress',
  16574. value: me.create ? 'lzo' : ''
  16575. },
  16576. {
  16577. xtype: 'pveBackupModeSelector',
  16578. fieldLabel: gettext('Mode'),
  16579. value: 'snapshot',
  16580. name: 'mode'
  16581. },
  16582. vmidField
  16583. ];
  16584.  
  16585. var ipanel = Ext.create('PVE.panel.InputPanel', {
  16586. column1: column1,
  16587. column2: column2,
  16588. onGetValues: function(values) {
  16589. if (!values.node) {
  16590. if (!me.create) {
  16591. PVE.Utils.assemble_field_data(values, { 'delete': 'node' });
  16592. }
  16593. delete values.node;
  16594. }
  16595.  
  16596. var selMode = values.selMode;
  16597. delete values.selMode;
  16598.  
  16599. if (selMode === 'all') {
  16600. values.all = 1;
  16601. values.exclude = '';
  16602. delete values.vmid;
  16603. } else if (selMode === 'exclude') {
  16604. values.all = 1;
  16605. values.exclude = values.vmid;
  16606. delete values.vmid;
  16607. }
  16608. return values;
  16609. }
  16610. });
  16611.  
  16612. var update_vmid_selection = function(list, mode) {
  16613. if (insideUpdate) {
  16614. return; // should not happen - just to be sure
  16615. }
  16616. insideUpdate = true;
  16617. if (mode !== 'all') {
  16618. sm.deselectAll(true);
  16619. if (list) {
  16620. Ext.Array.each(list.split(','), function(vmid) {
  16621. var rec = store.findRecord('vmid', vmid);
  16622. if (rec) {
  16623. sm.select(rec, true);
  16624. }
  16625. });
  16626. }
  16627. }
  16628. insideUpdate = false;
  16629. };
  16630.  
  16631. vmidField.on('change', function(f, value) {
  16632. var mode = selModeField.getValue();
  16633. update_vmid_selection(value, mode);
  16634. });
  16635.  
  16636. selModeField.on('change', function(f, value, oldValue) {
  16637. if (value === 'all') {
  16638. sm.selectAll(true);
  16639. vmgrid.setDisabled(true);
  16640. } else {
  16641. vmgrid.setDisabled(false);
  16642. }
  16643. if (oldValue === 'all') {
  16644. sm.deselectAll(true);
  16645. vmidField.setValue('');
  16646. }
  16647. var list = vmidField.getValue();
  16648. update_vmid_selection(list, value);
  16649. });
  16650.  
  16651. var reload = function() {
  16652. store.load({
  16653. params: { type: 'vm' },
  16654. callback: function() {
  16655. var node = nodesel.getValue();
  16656. store.clearFilter();
  16657. store.filterBy(function(rec) {
  16658. return (!node || rec.get('node') === node);
  16659. });
  16660. var list = vmidField.getValue();
  16661. var mode = selModeField.getValue();
  16662. if (mode === 'all') {
  16663. sm.selectAll(true);
  16664. } else {
  16665. update_vmid_selection(list, mode);
  16666. }
  16667. }
  16668. });
  16669. };
  16670.  
  16671. Ext.applyIf(me, {
  16672. subject: gettext("Backup Job"),
  16673. url: url,
  16674. method: method,
  16675. items: [ ipanel, vmgrid ]
  16676. });
  16677.  
  16678. me.callParent();
  16679.  
  16680. if (me.create) {
  16681. selModeField.setValue('include');
  16682. } else {
  16683. me.load({
  16684. success: function(response, options) {
  16685. var data = response.result.data;
  16686.  
  16687. data.dow = data.dow.split(',');
  16688.  
  16689. if (data.all || data.exclude) {
  16690. if (data.exclude) {
  16691. data.vmid = data.exclude;
  16692. data.selMode = 'exclude';
  16693. } else {
  16694. data.vmid = '';
  16695. data.selMode = 'all';
  16696. }
  16697. } else {
  16698. data.selMode = 'include';
  16699. }
  16700.  
  16701. me.setValues(data);
  16702. }
  16703. });
  16704. }
  16705.  
  16706. reload();
  16707. }
  16708. });
  16709.  
  16710.  
  16711. Ext.define('PVE.dc.BackupView', {
  16712. extend: 'Ext.grid.GridPanel',
  16713.  
  16714. alias: ['widget.pveDcBackupView'],
  16715.  
  16716. allText: '-- ' + gettext('All') + ' --',
  16717. allExceptText: gettext('All except {0}'),
  16718.  
  16719. initComponent : function() {
  16720. var me = this;
  16721.  
  16722. var store = new Ext.data.Store({
  16723. model: 'pve-cluster-backup',
  16724. proxy: {
  16725. type: 'pve',
  16726. url: "/api2/json/cluster/backup"
  16727. }
  16728. });
  16729.  
  16730. var reload = function() {
  16731. store.load();
  16732. };
  16733.  
  16734. var sm = Ext.create('Ext.selection.RowModel', {});
  16735.  
  16736. var run_editor = function() {
  16737. var rec = sm.getSelection()[0];
  16738. if (!rec) {
  16739. return;
  16740. }
  16741.  
  16742. var win = Ext.create('PVE.dc.BackupEdit',{
  16743. jobid: rec.data.id
  16744. });
  16745. win.on('destroy', reload);
  16746. win.show();
  16747. };
  16748.  
  16749. var edit_btn = new PVE.button.Button({
  16750. text: gettext('Edit'),
  16751. disabled: true,
  16752. selModel: sm,
  16753. handler: run_editor
  16754. });
  16755.  
  16756. var remove_btn = new PVE.button.Button({
  16757. text: gettext('Remove'),
  16758. disabled: true,
  16759. selModel: sm,
  16760. confirmMsg: gettext('Are you sure you want to remove this entry'),
  16761. handler: function(btn, event, rec) {
  16762. PVE.Utils.API2Request({
  16763. url: '/cluster/backup/' + rec.data.id,
  16764. method: 'DELETE',
  16765. waitMsgTarget: me,
  16766. callback: function() {
  16767. reload();
  16768. },
  16769. failure: function (response, opts) {
  16770. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  16771. }
  16772. });
  16773. }
  16774. });
  16775.  
  16776. PVE.Utils.monStoreErrors(me, store);
  16777.  
  16778. Ext.apply(me, {
  16779. store: store,
  16780. selModel: sm,
  16781. stateful: false,
  16782. viewConfig: {
  16783. trackOver: false
  16784. },
  16785. tbar: [
  16786. {
  16787. text: gettext('Add'),
  16788. handler: function() {
  16789. var win = Ext.create('PVE.dc.BackupEdit',{});
  16790. win.on('destroy', reload);
  16791. win.show();
  16792. }
  16793. },
  16794. remove_btn,
  16795. edit_btn
  16796. ],
  16797. columns: [
  16798. {
  16799. header: gettext('Node'),
  16800. width: 100,
  16801. sortable: true,
  16802. dataIndex: 'node',
  16803. renderer: function(value) {
  16804. if (value) {
  16805. return value;
  16806. }
  16807. return me.allText;
  16808. }
  16809. },
  16810. {
  16811. header: gettext('Day of week'),
  16812. width: 200,
  16813. sortable: false,
  16814. dataIndex: 'dow'
  16815. },
  16816. {
  16817. header: gettext('Start Time'),
  16818. width: 60,
  16819. sortable: true,
  16820. dataIndex: 'starttime'
  16821. },
  16822. {
  16823. header: gettext('Storage'),
  16824. width: 100,
  16825. sortable: true,
  16826. dataIndex: 'storage'
  16827. },
  16828. {
  16829. header: gettext('Selection'),
  16830. flex: 1,
  16831. sortable: false,
  16832. dataIndex: 'vmid',
  16833. renderer: function(value, metaData, record) {
  16834. /*jslint confusion: true */
  16835. if (record.data.all) {
  16836. if (record.data.exclude) {
  16837. return Ext.String.format(me.allExceptText, record.data.exclude);
  16838. }
  16839. return me.allText;
  16840. }
  16841. if (record.data.vmid) {
  16842. return record.data.vmid;
  16843. }
  16844.  
  16845. return "-";
  16846. }
  16847. }
  16848. ],
  16849. listeners: {
  16850. show: reload,
  16851. itemdblclick: run_editor
  16852. }
  16853. });
  16854.  
  16855. me.callParent();
  16856. }
  16857. }, function() {
  16858.  
  16859. Ext.define('pve-cluster-backup', {
  16860. extend: 'Ext.data.Model',
  16861. fields: [
  16862. 'id', 'starttime', 'dow',
  16863. 'storage', 'node', 'vmid', 'exclude',
  16864. 'mailto',
  16865. { name: 'all', type: 'boolean' },
  16866. { name: 'snapshot', type: 'boolean' },
  16867. { name: 'stop', type: 'boolean' },
  16868. { name: 'suspend', type: 'boolean' },
  16869. { name: 'compress', type: 'boolean' }
  16870. ]
  16871. });
  16872. });/*jslint confusion: true */
  16873. Ext.define('PVE.dc.vmHAServiceEdit', {
  16874. extend: 'PVE.window.Edit',
  16875.  
  16876. initComponent : function() {
  16877. var me = this;
  16878.  
  16879. me.create = me.vmid ? false : true;
  16880.  
  16881. if (me.vmid) {
  16882. me.create = false;
  16883. me.url = "/cluster/ha/groups/pvevm:" + me.vmid;
  16884. me.method = 'PUT';
  16885. } else {
  16886. me.create = true;
  16887. me.url = "/cluster/ha/groups";
  16888. me.method = 'POST';
  16889. }
  16890.  
  16891. Ext.apply(me, {
  16892. subject: gettext('HA managed VM/CT'),
  16893. width: 350,
  16894. items: [
  16895. {
  16896. xtype: me.create ? 'pveVMIDSelector' : 'displayfield',
  16897. name: 'vmid',
  16898. validateExists: true,
  16899. value: me.vmid || '',
  16900. fieldLabel: "VM ID"
  16901. },
  16902. {
  16903. xtype: 'pvecheckbox',
  16904. name: 'autostart',
  16905. checked: true,
  16906. fieldLabel: 'autostart'
  16907. }
  16908. ]
  16909. });
  16910.  
  16911. me.callParent();
  16912.  
  16913. if (!me.create) {
  16914. me.load();
  16915. }
  16916. }
  16917. });
  16918.  
  16919. Ext.define('PVE.dc.HAConfig', {
  16920. extend: 'Ext.panel.Panel',
  16921. alias: 'widget.pveDcHAConfig',
  16922.  
  16923. clusterInfo: {}, // reload store data here
  16924.  
  16925. reload: function() {
  16926. var me = this;
  16927.  
  16928. var getClusterInfo = function(conf) {
  16929.  
  16930. var info = {};
  16931.  
  16932. if (!(conf && conf.children && conf.children[0])) {
  16933. return info;
  16934. }
  16935.  
  16936. var cluster = conf.children[0];
  16937.  
  16938. if (cluster.text !== 'cluster' || !cluster.config_version) {
  16939. return info;
  16940. }
  16941.  
  16942. info.version = cluster.config_version;
  16943.  
  16944. Ext.Array.each(cluster.children, function(item) {
  16945. if (item.text === 'fencedevices') {
  16946. // fixme: make sure each node uses at least one fence device
  16947. info.fenceDevices = true;
  16948. } else if (item.text === 'rm') {
  16949. info.ha = true;
  16950. }
  16951. });
  16952.  
  16953. return info;
  16954. };
  16955.  
  16956. PVE.Utils.API2Request({
  16957. url: '/cluster/ha/config',
  16958. waitMsgTarget: me,
  16959. method: 'GET',
  16960. failure: function(response, opts) {
  16961. me.clusterInfo = {};
  16962. PVE.Utils.setErrorMask(me, response.htmlStatus);
  16963. },
  16964. success: function(response, opts) {
  16965. me.clusterInfo = getClusterInfo(response.result.data);
  16966.  
  16967. me.setDisabled(!me.clusterInfo.version);
  16968.  
  16969. me.addMenu.setDisabled(!me.clusterInfo.version);
  16970.  
  16971. // note: this modifies response.result.data
  16972. me.treePanel.setRootNode(response.result.data);
  16973. me.treePanel.expandAll();
  16974.  
  16975.  
  16976. if (response.result.changes) {
  16977. me.commitBtn.setDisabled(false);
  16978. me.revertBtn.setDisabled(false);
  16979. me.diffPanel.setVisible(true);
  16980. me.diffPanel.update("<pre>" + Ext.htmlEncode(response.result.changes) + "</pre>");
  16981. } else {
  16982. me.commitBtn.setDisabled(true);
  16983. me.revertBtn.setDisabled(true);
  16984. me.diffPanel.setVisible(false);
  16985. me.diffPanel.update('');
  16986. }
  16987. }
  16988. });
  16989. },
  16990.  
  16991. initComponent: function() {
  16992. var me = this;
  16993.  
  16994. me.commitBtn = new PVE.button.Button({
  16995. text: gettext('Activate'),
  16996. disabled: true,
  16997. confirmMsg: function () {
  16998. return gettext('Are you sure you want to activate your changes');
  16999. },
  17000. handler: function(btn, event) {
  17001. PVE.Utils.API2Request({
  17002. url: '/cluster/ha/changes',
  17003. method: 'POST',
  17004. waitMsgTarget: me,
  17005. callback: function() {
  17006. me.reload();
  17007. },
  17008. failure: function (response, opts) {
  17009. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  17010. }
  17011. });
  17012. }
  17013. });
  17014.  
  17015. me.revertBtn = new PVE.button.Button({
  17016. text: gettext('Revert changes'),
  17017. disabled: true,
  17018. confirmMsg: function () {
  17019. return gettext('Are you sure you want to revert your changes');
  17020. },
  17021. handler: function(btn, event) {
  17022. PVE.Utils.API2Request({
  17023. url: '/cluster/ha/changes',
  17024. method: 'DELETE',
  17025. waitMsgTarget: me,
  17026. callback: function() {
  17027. me.reload();
  17028. },
  17029. failure: function (response, opts) {
  17030. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  17031. }
  17032. });
  17033. }
  17034. });
  17035.  
  17036. me.addMenu = new Ext.button.Button({
  17037. text: gettext('Add'),
  17038. disabled: true,
  17039. menu: new Ext.menu.Menu({
  17040. items: [
  17041. {
  17042. text: gettext('HA managed VM/CT'),
  17043. handler: function() {
  17044. var win = Ext.create('PVE.dc.vmHAServiceEdit', {});
  17045. win.show();
  17046. win.on('destroy', me.reload, me);
  17047. }
  17048. },
  17049. {
  17050. text: gettext('Failover Domain'),
  17051. handler: function() {
  17052. Ext.Msg.alert(gettext('Error'), "not implemented - sorry");
  17053. }
  17054. }
  17055. ]
  17056. })
  17057. });
  17058.  
  17059. me.treePanel = Ext.create('Ext.tree.Panel', {
  17060. rootVisible: false,
  17061. animate: false,
  17062. region: 'center',
  17063. border: false,
  17064. fields: ['text', 'id', 'vmid', 'name' ],
  17065. columns: [
  17066. {
  17067. xtype: 'treecolumn',
  17068. text: 'Tag',
  17069. dataIndex: 'text',
  17070. width: 200
  17071. },
  17072. {
  17073. text: 'Attributes',
  17074. dataIndex: 'id',
  17075. renderer: function(value, metaData, record) {
  17076. var text = '';
  17077. Ext.Object.each(record.raw, function(key, value) {
  17078. if (key === 'id' || key === 'text') {
  17079. return;
  17080. }
  17081. text += Ext.htmlEncode(key) + '="' +
  17082. Ext.htmlEncode(value) + '" ';
  17083. });
  17084. return text;
  17085. },
  17086. flex: 1
  17087. }
  17088. ]
  17089. });
  17090.  
  17091. var run_editor = function() {
  17092. var rec = me.treePanel.selModel.getSelection()[0];
  17093. if (rec && rec.data.text === 'pvevm') {
  17094. var win = Ext.create('PVE.dc.vmHAServiceEdit', {
  17095. vmid: rec.data.vmid
  17096. });
  17097. win.show();
  17098. win.on('destroy', me.reload, me);
  17099. }
  17100. };
  17101.  
  17102. me.editBtn = new Ext.button.Button({
  17103. text: gettext('Edit'),
  17104. disabled: true,
  17105. handler: run_editor
  17106. });
  17107.  
  17108. me.removeBtn = new Ext.button.Button({
  17109. text: gettext('Remove'),
  17110. disabled: true,
  17111. handler: function() {
  17112. var rec = me.treePanel.selModel.getSelection()[0];
  17113. if (rec && rec.data.text === 'pvevm') {
  17114. var groupid = 'pvevm:' + rec.data.vmid;
  17115. PVE.Utils.API2Request({
  17116. url: '/cluster/ha/groups/' + groupid,
  17117. method: 'DELETE',
  17118. waitMsgTarget: me,
  17119. callback: function() {
  17120. me.reload();
  17121. },
  17122. failure: function (response, opts) {
  17123. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  17124. }
  17125. });
  17126. }
  17127. }
  17128. });
  17129.  
  17130.  
  17131. me.diffPanel = Ext.create('Ext.panel.Panel', {
  17132. border: false,
  17133. hidden: true,
  17134. region: 'south',
  17135. autoScroll: true,
  17136. itemId: 'changes',
  17137. tbar: [ gettext('Pending changes') ],
  17138. split: true,
  17139. bodyPadding: 5,
  17140. flex: 0.6
  17141. });
  17142.  
  17143. Ext.apply(me, {
  17144. layout: 'border',
  17145. tbar: [ me.addMenu, me.removeBtn, me.editBtn, me.revertBtn, me.commitBtn ],
  17146. items: [ me.treePanel, me.diffPanel ]
  17147. });
  17148.  
  17149. me.callParent();
  17150.  
  17151. me.on('show', me.reload);
  17152.  
  17153. me.treePanel.on("selectionchange", function(sm, selected) {
  17154. var rec = selected[0];
  17155. if (rec && rec.data.text === 'pvevm') {
  17156. me.editBtn.setDisabled(false);
  17157. me.removeBtn.setDisabled(false);
  17158. } else {
  17159. me.editBtn.setDisabled(true);
  17160. me.removeBtn.setDisabled(true);
  17161.  
  17162. }
  17163. });
  17164.  
  17165. me.treePanel.on("itemdblclick", function(v, record) {
  17166. run_editor();
  17167. });
  17168. }
  17169. });
  17170. Ext.define('PVE.dc.Config', {
  17171. extend: 'PVE.panel.Config',
  17172. alias: 'widget.PVE.dc.Config',
  17173.  
  17174. initComponent: function() {
  17175. var me = this;
  17176.  
  17177. Ext.apply(me, {
  17178. title: gettext("Datacenter"),
  17179. hstateid: 'dctab',
  17180. items: [
  17181. {
  17182. title: gettext('Summary'),
  17183. xtype: 'pveDcSummary',
  17184. itemId: 'summary'
  17185. },
  17186. {
  17187. xtype: 'pveDcOptionView',
  17188. title: gettext('Options'),
  17189. itemId: 'options'
  17190. },
  17191. {
  17192. xtype: 'pveStorageView',
  17193. title: gettext('Storage'),
  17194. itemId: 'storage'
  17195. },
  17196. {
  17197. xtype: 'pveDcBackupView',
  17198. title: gettext('Backup'),
  17199. itemId: 'backup'
  17200. },
  17201. {
  17202. xtype: 'pveUserView',
  17203. title: gettext('Users'),
  17204. itemId: 'users'
  17205. },
  17206. {
  17207. xtype: 'pveGroupView',
  17208. title: gettext('Groups'),
  17209. itemId: 'groups'
  17210. },
  17211. {
  17212. xtype: 'pvePoolView',
  17213. title: gettext('Pools'),
  17214. itemId: 'pools'
  17215. },
  17216. {
  17217. xtype: 'pveACLView',
  17218. title: gettext('Permissions'),
  17219. itemId: 'permissions'
  17220. },
  17221. {
  17222. xtype: 'pveRoleView',
  17223. title: gettext('Roles'),
  17224. itemId: 'roles'
  17225. },
  17226. {
  17227. xtype: 'pveAuthView',
  17228. title: gettext('Authentication'),
  17229. itemId: 'domains'
  17230. },
  17231. {
  17232. xtype: 'pveDcHAConfig',
  17233. title: 'HA',
  17234. itemId: 'ha'
  17235. }
  17236. ]
  17237. });
  17238.  
  17239. me.callParent();
  17240. }
  17241. });
  17242. /*
  17243. * Workspace base class
  17244. *
  17245. * popup login window when auth fails (call onLogin handler)
  17246. * update (re-login) ticket every 15 minutes
  17247. *
  17248. */
  17249.  
  17250. Ext.define('PVE.Workspace', {
  17251. extend: 'Ext.container.Viewport',
  17252.  
  17253. title: 'Proxmox Virtual Environment',
  17254.  
  17255. loginData: null, // Data from last login call
  17256.  
  17257. onLogin: function(loginData) {},
  17258.  
  17259. // private
  17260. updateLoginData: function(loginData) {
  17261. var me = this;
  17262. me.loginData = loginData;
  17263. PVE.CSRFPreventionToken = loginData.CSRFPreventionToken;
  17264. PVE.UserName = loginData.username;
  17265. // creates a session cookie (expire = null)
  17266. // that way the cookie gets deleted after browser window close
  17267. Ext.util.Cookies.set('PVEAuthCookie', loginData.ticket, null, '/', null, true);
  17268. me.onLogin(loginData);
  17269. },
  17270.  
  17271. // private
  17272. showLogin: function() {
  17273. var me = this;
  17274.  
  17275. PVE.Utils.authClear();
  17276. PVE.UserName = null;
  17277. me.loginData = null;
  17278.  
  17279. if (!me.login) {
  17280. me.login = Ext.create('PVE.window.LoginWindow', {
  17281. handler: function(data) {
  17282. me.login = null;
  17283. me.updateLoginData(data);
  17284. }
  17285. });
  17286. }
  17287. me.onLogin(null);
  17288. me.login.show();
  17289. },
  17290.  
  17291. initComponent : function() {
  17292. var me = this;
  17293.  
  17294. Ext.tip.QuickTipManager.init();
  17295.  
  17296. // fixme: what about other errors
  17297. Ext.Ajax.on('requestexception', function(conn, response, options) {
  17298. if (response.status == 401) { // auth failure
  17299. me.showLogin();
  17300. }
  17301. });
  17302.  
  17303. document.title = me.title;
  17304.  
  17305. me.callParent();
  17306.  
  17307. if (!PVE.Utils.authOK()) {
  17308. me.showLogin();
  17309. } else {
  17310. if (me.loginData) {
  17311. me.onLogin(me.loginData);
  17312. }
  17313. }
  17314.  
  17315. Ext.TaskManager.start({
  17316. run: function() {
  17317. var ticket = PVE.Utils.authOK();
  17318. if (!ticket || !PVE.UserName) {
  17319. return;
  17320. }
  17321.  
  17322. Ext.Ajax.request({
  17323. params: {
  17324. username: PVE.UserName,
  17325. password: ticket
  17326. },
  17327. url: '/api2/json/access/ticket',
  17328. method: 'POST',
  17329. success: function(response, opts) {
  17330. var obj = Ext.decode(response.responseText);
  17331. me.updateLoginData(obj.data);
  17332. }
  17333. });
  17334. },
  17335. interval: 15*60*1000
  17336. });
  17337.  
  17338. }
  17339. });
  17340.  
  17341. Ext.define('PVE.ConsoleWorkspace', {
  17342. extend: 'PVE.Workspace',
  17343.  
  17344. alias: ['widget.pveConsoleWorkspace'],
  17345.  
  17346. title: gettext('Console'),
  17347.  
  17348. initComponent : function() {
  17349. var me = this;
  17350.  
  17351. var param = Ext.Object.fromQueryString(window.location.search);
  17352. var consoleType = me.consoleType || param.console;
  17353.  
  17354. var content;
  17355. if (consoleType === 'kvm') {
  17356. me.title = "VM " + param.vmid;
  17357. if (param.vmname) {
  17358. me.title += " ('" + param.vmname + "')";
  17359. }
  17360. content = {
  17361. xtype: 'pveKVMConsole',
  17362. vmid: param.vmid,
  17363. nodename: param.node,
  17364. vmname: param.vmname,
  17365. toplevel: true
  17366. };
  17367. } else if (consoleType === 'openvz') {
  17368. me.title = "CT " + param.vmid;
  17369. if (param.vmname) {
  17370. me.title += " ('" + param.vmname + "')";
  17371. }
  17372. content = {
  17373. xtype: 'pveOpenVZConsole',
  17374. vmid: param.vmid,
  17375. nodename: param.node,
  17376. vmname: param.vmname,
  17377. toplevel: true
  17378. };
  17379. } else if (consoleType === 'shell') {
  17380. me.title = "node '" + param.node;
  17381. content = {
  17382. xtype: 'pveShell',
  17383. nodename: param.node,
  17384. toplevel: true
  17385. };
  17386. } else {
  17387. content = {
  17388. border: false,
  17389. bodyPadding: 10,
  17390. html: 'Error: No such console type'
  17391. };
  17392. }
  17393.  
  17394. Ext.apply(me, {
  17395. layout: { type: 'fit' },
  17396. border: false,
  17397. items: [ content ]
  17398. });
  17399.  
  17400. me.callParent();
  17401. }
  17402. });
  17403.  
  17404. Ext.define('PVE.StdWorkspace', {
  17405. extend: 'PVE.Workspace',
  17406.  
  17407. alias: ['widget.pveStdWorkspace'],
  17408.  
  17409. // private
  17410. setContent: function(comp) {
  17411. var me = this;
  17412.  
  17413. var cont = me.child('#content');
  17414. cont.removeAll(true);
  17415.  
  17416. if (comp) {
  17417. PVE.Utils.setErrorMask(cont, false);
  17418. comp.border = false;
  17419. cont.add(comp);
  17420. cont.doLayout();
  17421. }
  17422. // else {
  17423. // TODO: display something useful
  17424.  
  17425. // Note:: error mask has wrong zindex, so we do not
  17426. // use that - see bug 114
  17427. // PVE.Utils.setErrorMask(cont, 'nothing selected');
  17428. //}
  17429. },
  17430.  
  17431. selectById: function(nodeid) {
  17432. var me = this;
  17433. var tree = me.down('pveResourceTree');
  17434. tree.selectById(nodeid);
  17435. },
  17436.  
  17437. checkVmMigration: function(record) {
  17438. var me = this;
  17439. var tree = me.down('pveResourceTree');
  17440. tree.checkVmMigration(record);
  17441. },
  17442.  
  17443. onLogin: function(loginData) {
  17444. var me = this;
  17445.  
  17446. me.updateUserInfo();
  17447.  
  17448. if (loginData) {
  17449. PVE.data.ResourceStore.startUpdate();
  17450. }
  17451. },
  17452.  
  17453. updateUserInfo: function() {
  17454. var me = this;
  17455.  
  17456. var ui = me.query('#userinfo')[0];
  17457.  
  17458. if (PVE.UserName) {
  17459. var msg = Ext.String.format(gettext("You are logged in as {0}"), "'" + PVE.UserName + "'");
  17460. ui.update('<div class="x-unselectable" style="white-space:nowrap;">' + msg + '</div>');
  17461. } else {
  17462. ui.update('');
  17463. }
  17464. ui.doLayout();
  17465. },
  17466.  
  17467. initComponent : function() {
  17468. var me = this;
  17469.  
  17470. Ext.History.init();
  17471. Ext.state.Manager.setProvider(Ext.create('PVE.StateProvider'));
  17472.  
  17473. var selview = new PVE.form.ViewSelector({});
  17474.  
  17475. var rtree = Ext.createWidget('pveResourceTree', {
  17476. viewFilter: selview.getViewFilter(),
  17477. flex: 1,
  17478. selModel: new Ext.selection.TreeModel({
  17479. listeners: {
  17480. selectionchange: function(sm, selected) {
  17481. var comp;
  17482. var tlckup = {
  17483. root: 'PVE.dc.Config',
  17484. node: 'PVE.node.Config',
  17485. qemu: 'PVE.qemu.Config',
  17486. openvz: 'PVE.openvz.Config',
  17487. storage: 'PVE.storage.Browser',
  17488. pool: 'pvePoolConfig'
  17489. };
  17490.  
  17491. if (selected.length > 0) {
  17492. var n = selected[0];
  17493. comp = {
  17494. xtype: tlckup[n.data.type || 'root'] ||
  17495. 'pvePanelConfig',
  17496. layout: { type: 'fit' },
  17497. showSearch: (n.data.id === 'root') ||
  17498. Ext.isDefined(n.data.groupbyid),
  17499. pveSelNode: n,
  17500. workspace: me,
  17501. viewFilter: selview.getViewFilter()
  17502. };
  17503. }
  17504.  
  17505. me.setContent(comp);
  17506. }
  17507. }
  17508. })
  17509. });
  17510.  
  17511. selview.on('select', function(combo, records) {
  17512. if (records && records.length) {
  17513. var view = combo.getViewFilter();
  17514. rtree.setViewFilter(view);
  17515. }
  17516. });
  17517.  
  17518. Ext.apply(me, {
  17519. layout: { type: 'border' },
  17520. border: false,
  17521. items: [
  17522. {
  17523. region: 'north',
  17524. height: 30,
  17525. layout: {
  17526. type: 'hbox',
  17527. align : 'middle'
  17528. },
  17529. baseCls: 'x-plain',
  17530. defaults: {
  17531. baseCls: 'x-plain'
  17532. },
  17533. border: false,
  17534. margins: '2 0 5 0',
  17535. items: [
  17536. {
  17537. margins: '0 0 0 4',
  17538. html: '<a class="x-unselectable" target=_blank href="http://www.proxmox.com">' +
  17539. '<img height=30 width=209 src="/pve2/images/proxmox_logo.png"/></a>'
  17540. },
  17541. {
  17542. minWidth: 200,
  17543. flex: 1,
  17544. html: '<span class="x-panel-header-text">Proxmox Virtual Environment<br>' + gettext('Version') + ' ' + PVE.GUIVersion + "</span>"
  17545. },
  17546. {
  17547. pack: 'end',
  17548. margins: '8 10 0 10',
  17549. id: 'userinfo',
  17550. stateful: false
  17551. },
  17552. {
  17553. pack: 'end',
  17554. margins: '3 5 0 0',
  17555. xtype: 'button',
  17556. baseCls: 'x-btn',
  17557. text: gettext("Logout"),
  17558. handler: function() {
  17559. PVE.data.ResourceStore.stopUpdate();
  17560. me.showLogin();
  17561. me.setContent();
  17562. var rt = me.down('pveResourceTree');
  17563. rt.clearTree();
  17564. }
  17565. },
  17566. {
  17567. pack: 'end',
  17568. margins: '3 5 0 0',
  17569. xtype: 'button',
  17570. baseCls: 'x-btn',
  17571. text: gettext("Create VM"),
  17572. handler: function() {
  17573. var wiz = Ext.create('PVE.qemu.CreateWizard', {});
  17574. wiz.show();
  17575. }
  17576. },
  17577. {
  17578. pack: 'end',
  17579. margins: '3 5 0 0',
  17580. xtype: 'button',
  17581. baseCls: 'x-btn',
  17582. text: gettext("Create CT"),
  17583. handler: function() {
  17584. var wiz = Ext.create('PVE.openvz.CreateWizard', {});
  17585. wiz.show();
  17586. }
  17587. }
  17588. ]
  17589. },
  17590. {
  17591. region: 'center',
  17592. id: 'content',
  17593. xtype: 'container',
  17594. layout: { type: 'fit' },
  17595. border: false,
  17596. stateful: false,
  17597. margins: '0 5 0 0',
  17598. items: []
  17599. },
  17600. {
  17601. region: 'west',
  17602. xtype: 'container',
  17603. border: false,
  17604. layout: { type: 'vbox', align: 'stretch' },
  17605. margins: '0 0 0 5',
  17606. split: true,
  17607. width: 200,
  17608. items: [ selview, rtree ]
  17609. },
  17610. {
  17611. xtype: 'pveStatusPanel',
  17612. region: 'south',
  17613. margins:'0 5 5 5',
  17614. height: 200,
  17615. split:true
  17616. }
  17617. ]
  17618. });
  17619.  
  17620. me.callParent();
  17621.  
  17622. me.updateUserInfo();
  17623. }
  17624. });
Add Comment
Please, Sign In to add comment