Guest User

Untitled

a guest
Oct 27th, 2012
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 402.95 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-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)\.)*([A-Za-z0-9]([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. ca: 'Catalan',
  198. ja: 'Japanese',
  199. en: 'English',
  200. da: 'Danish',
  201. de: 'German',
  202. es: 'Spanish',
  203. fr: 'French',
  204. it: 'Italian',
  205. ru: 'Russian',
  206. sv: 'Swedish',
  207. pl: 'Polish',
  208. tr: 'Turkish'
  209. },
  210.  
  211. render_language: function (value) {
  212. if (!value) {
  213. return PVE.Utils.defaultText + ' (English)';
  214. }
  215. var text = PVE.Utils.language_map[value];
  216. if (text) {
  217. return text + ' (' + value + ')';
  218. }
  219. return value;
  220. },
  221.  
  222. language_array: function() {
  223. var data = [['', PVE.Utils.render_language('')]];
  224. Ext.Object.each(PVE.Utils.language_map, function(key, value) {
  225. data.push([key, PVE.Utils.render_language(value)]);
  226. });
  227.  
  228. return data;
  229. },
  230.  
  231. render_kvm_vga_driver: function (value) {
  232. if (!value) {
  233. return PVE.Utils.defaultText;
  234. }
  235. var text = PVE.Utils.kvm_vga_drivers[value];
  236. if (text) {
  237. return text + ' (' + value + ')';
  238. }
  239. return value;
  240. },
  241.  
  242. kvm_vga_driver_array: function() {
  243. var data = [['', PVE.Utils.render_kvm_vga_driver('')]];
  244. Ext.Object.each(PVE.Utils.kvm_vga_drivers, function(key, value) {
  245. data.push([key, PVE.Utils.render_kvm_vga_driver(value)]);
  246. });
  247.  
  248. return data;
  249. },
  250.  
  251. render_kvm_startup: function(value) {
  252. var startup = PVE.Parser.parseStartup(value);
  253.  
  254. var res = 'order=';
  255. if (startup.order === undefined) {
  256. res += 'any';
  257. } else {
  258. res += startup.order;
  259. }
  260. if (startup.up !== undefined) {
  261. res += ',up=' + startup.up;
  262. }
  263. if (startup.down !== undefined) {
  264. res += ',down=' + startup.down;
  265. }
  266.  
  267. return res;
  268. },
  269.  
  270. authOK: function() {
  271. return Ext.util.Cookies.get('PVEAuthCookie');
  272. },
  273.  
  274. authClear: function() {
  275. Ext.util.Cookies.clear("PVEAuthCookie");
  276. },
  277.  
  278. // fixme: remove - not needed?
  279. gridLineHeigh: function() {
  280. return 21;
  281.  
  282. //if (Ext.isGecko)
  283. //return 23;
  284. //return 21;
  285. },
  286.  
  287. extractRequestError: function(result, verbose) {
  288. var msg = gettext('Successful');
  289.  
  290. if (!result.success) {
  291. msg = gettext("Unknown error");
  292. if (result.message) {
  293. msg = result.message;
  294. if (result.status) {
  295. msg += ' (' + result.status + ')';
  296. }
  297. }
  298. if (verbose && Ext.isObject(result.errors)) {
  299. msg += "<br>";
  300. Ext.Object.each(result.errors, function(prop, desc) {
  301. msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
  302. Ext.htmlEncode(desc);
  303. });
  304. }
  305. }
  306.  
  307. return msg;
  308. },
  309.  
  310. extractFormActionError: function(action) {
  311. var msg;
  312. switch (action.failureType) {
  313. case Ext.form.action.Action.CLIENT_INVALID:
  314. msg = gettext('Form fields may not be submitted with invalid values');
  315. break;
  316. case Ext.form.action.Action.CONNECT_FAILURE:
  317. msg = gettext('Connection error');
  318. var resp = action.response;
  319. if (resp.status && resp.statusText) {
  320. msg += " " + resp.status + ": " + resp.statusText;
  321. }
  322. break;
  323. case Ext.form.action.Action.LOAD_FAILURE:
  324. case Ext.form.action.Action.SERVER_INVALID:
  325. msg = PVE.Utils.extractRequestError(action.result, true);
  326. break;
  327. }
  328. return msg;
  329. },
  330.  
  331. // Ext.Ajax.request
  332. API2Request: function(reqOpts) {
  333.  
  334. var newopts = Ext.apply({
  335. waitMsg: gettext('Please wait...')
  336. }, reqOpts);
  337.  
  338. if (!newopts.url.match(/^\/api2/)) {
  339. newopts.url = '/api2/extjs' + newopts.url;
  340. }
  341. delete newopts.callback;
  342.  
  343. var createWrapper = function(successFn, callbackFn, failureFn) {
  344. Ext.apply(newopts, {
  345. success: function(response, options) {
  346. if (options.waitMsgTarget) {
  347. options.waitMsgTarget.setLoading(false);
  348. }
  349. var result = Ext.decode(response.responseText);
  350. response.result = result;
  351. if (!result.success) {
  352. response.htmlStatus = PVE.Utils.extractRequestError(result, true);
  353. Ext.callback(callbackFn, options.scope, [options, false, response]);
  354. Ext.callback(failureFn, options.scope, [response, options]);
  355. return;
  356. }
  357. Ext.callback(callbackFn, options.scope, [options, true, response]);
  358. Ext.callback(successFn, options.scope, [response, options]);
  359. },
  360. failure: function(response, options) {
  361. if (options.waitMsgTarget) {
  362. options.waitMsgTarget.setLoading(false);
  363. }
  364. response.result = {};
  365. try {
  366. response.result = Ext.decode(response.responseText);
  367. } catch(e) {}
  368. var msg = gettext('Connection error') + ' - server offline?';
  369. if (response.aborted) {
  370. msg = gettext('Connection error') + ' - aborted.';
  371. } else if (response.timedout) {
  372. msg = gettext('Connection error') + ' - Timeout.';
  373. } else if (response.status && response.statusText) {
  374. msg = gettext('Connection error') + ' ' + response.status + ': ' + response.statusText;
  375. }
  376. response.htmlStatus = msg;
  377. Ext.callback(callbackFn, options.scope, [options, false, response]);
  378. Ext.callback(failureFn, options.scope, [response, options]);
  379. }
  380. });
  381. };
  382.  
  383. createWrapper(reqOpts.success, reqOpts.callback, reqOpts.failure);
  384.  
  385. var target = newopts.waitMsgTarget;
  386. if (target) {
  387. // Note: ExtJS bug - this does not work when component is not rendered
  388. target.setLoading(newopts.waitMsg);
  389. }
  390. Ext.Ajax.request(newopts);
  391. },
  392.  
  393. assemble_field_data: function(values, data) {
  394. if (Ext.isObject(data)) {
  395. Ext.Object.each(data, function(name, val) {
  396. if (values.hasOwnProperty(name)) {
  397. var bucket = values[name];
  398. if (!Ext.isArray(bucket)) {
  399. bucket = values[name] = [bucket];
  400. }
  401. if (Ext.isArray(val)) {
  402. values[name] = bucket.concat(val);
  403. } else {
  404. bucket.push(val);
  405. }
  406. } else {
  407. values[name] = val;
  408. }
  409. });
  410. }
  411. },
  412.  
  413. task_desc_table: {
  414. vncproxy: [ 'VM/CT', gettext('Console') ],
  415. vncshell: [ '', gettext('Shell') ],
  416. qmcreate: [ 'VM', gettext('Create') ],
  417. qmrestore: [ 'VM', gettext('Restore') ],
  418. qmdestroy: [ 'VM', gettext('Destroy') ],
  419. qmigrate: [ 'VM', gettext('Migrate') ],
  420. qmstart: [ 'VM', gettext('Start') ],
  421. qmstop: [ 'VM', gettext('Stop') ],
  422. qmreset: [ 'VM', gettext('Reset') ],
  423. qmshutdown: [ 'VM', gettext('Shutdown') ],
  424. qmsuspend: [ 'VM', gettext('Suspend') ],
  425. qmresume: [ 'VM', gettext('Resume') ],
  426. vzcreate: ['CT', gettext('Create') ],
  427. vzrestore: ['CT', gettext('Restore') ],
  428. vzdestroy: ['CT', gettext('Destroy') ],
  429. vzmigrate: [ 'CT', gettext('Migrate') ],
  430. vzstart: ['CT', gettext('Start') ],
  431. vzstop: ['CT', gettext('Stop') ],
  432. vzmount: ['CT', gettext('Mount') ],
  433. vzumount: ['CT', gettext('Unmount') ],
  434. vzshutdown: ['CT', gettext('Shutdown') ],
  435. hamigrate: [ 'HA', gettext('Migrate') ],
  436. hastart: [ 'HA', gettext('Start') ],
  437. hastop: [ 'HA', gettext('Stop') ],
  438. srvstart: ['SRV', gettext('Start') ],
  439. srvstop: ['SRV', gettext('Stop') ],
  440. srvrestart: ['SRV', gettext('Restart') ],
  441. srvreload: ['SRV', gettext('Reload') ],
  442. imgcopy: ['', gettext('Copy data') ],
  443. imgdel: ['', gettext('Erase data') ],
  444. download: ['', gettext('Download') ],
  445. vzdump: ['', gettext('Backup') ],
  446. startall: [ '', gettext('Start all VMs and Containers') ],
  447. stopall: [ '', gettext('Stop all VMs and Containers') ]
  448. },
  449.  
  450. format_task_description: function(type, id) {
  451. var farray = PVE.Utils.task_desc_table[type];
  452. if (!farray) {
  453. return type;
  454. }
  455. var prefix = farray[0];
  456. var text = farray[1];
  457. if (prefix) {
  458. return prefix + ' ' + id + ' - ' + text;
  459. }
  460. return text;
  461. },
  462.  
  463. parse_task_upid: function(upid) {
  464. var task = {};
  465.  
  466. var res = upid.match(/^UPID:(\S+):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/);
  467. if (!res) {
  468. throw "unable to parse upid '" + upid + "'";
  469. }
  470. task.node = res[1];
  471. task.pid = parseInt(res[2], 16);
  472. task.pstart = parseInt(res[3], 16);
  473. task.starttime = parseInt(res[4], 16);
  474. task.type = res[5];
  475. task.id = res[6];
  476. task.user = res[7];
  477.  
  478. task.desc = PVE.Utils.format_task_description(task.type, task.id);
  479.  
  480. return task;
  481. },
  482.  
  483. format_size: function(size) {
  484. /*jslint confusion: true */
  485.  
  486. if (size < 1024) {
  487. return size;
  488. }
  489.  
  490. var kb = size / 1024;
  491.  
  492. if (kb < 1024) {
  493. return kb.toFixed(0) + "KB";
  494. }
  495.  
  496. var mb = size / (1024*1024);
  497.  
  498. if (mb < 1024) {
  499. return mb.toFixed(0) + "MB";
  500. }
  501.  
  502. var gb = mb / 1024;
  503.  
  504. if (gb < 1024) {
  505. return gb.toFixed(2) + "GB";
  506. }
  507.  
  508. var tb = gb / 1024;
  509.  
  510. return tb.toFixed(2) + "TB";
  511.  
  512. },
  513.  
  514. format_html_bar: function(per, text) {
  515.  
  516. return "<div class='pve-bar-wrap'>" + text + "<div class='pve-bar-border'>" +
  517. "<div class='pve-bar-inner' style='width:" + per + "%;'></div>" +
  518. "</div></div>";
  519.  
  520. },
  521.  
  522. format_cpu_bar: function(per1, per2, text) {
  523.  
  524. return "<div class='pve-bar-border'>" +
  525. "<div class='pve-bar-inner' style='width:" + per1 + "%;'></div>" +
  526. "<div class='pve-bar-inner2' style='width:" + per2 + "%;'></div>" +
  527. "<div class='pve-bar-text'>" + text + "</div>" +
  528. "</div>";
  529. },
  530.  
  531. format_large_bar: function(per, text) {
  532.  
  533. if (!text) {
  534. text = per.toFixed(1) + "%";
  535. }
  536.  
  537. return "<div class='pve-largebar-border'>" +
  538. "<div class='pve-largebar-inner' style='width:" + per + "%;'></div>" +
  539. "<div class='pve-largebar-text'>" + text + "</div>" +
  540. "</div>";
  541. },
  542.  
  543. format_duration_long: function(ut) {
  544.  
  545. var days = Math.floor(ut / 86400);
  546. ut -= days*86400;
  547. var hours = Math.floor(ut / 3600);
  548. ut -= hours*3600;
  549. var mins = Math.floor(ut / 60);
  550. ut -= mins*60;
  551.  
  552. var hours_str = '00' + hours.toString();
  553. hours_str = hours_str.substr(hours_str.length - 2);
  554. var mins_str = "00" + mins.toString();
  555. mins_str = mins_str.substr(mins_str.length - 2);
  556. var ut_str = "00" + ut.toString();
  557. ut_str = ut_str.substr(ut_str.length - 2);
  558.  
  559. if (days) {
  560. var ds = days > 1 ? PVE.Utils.daysText : PVE.Utils.dayText;
  561. return days.toString() + ' ' + ds + ' ' +
  562. hours_str + ':' + mins_str + ':' + ut_str;
  563. } else {
  564. return hours_str + ':' + mins_str + ':' + ut_str;
  565. }
  566. },
  567.  
  568. format_duration_short: function(ut) {
  569.  
  570. if (ut < 60) {
  571. return ut.toString() + 's';
  572. }
  573.  
  574. if (ut < 3600) {
  575. var mins = ut / 60;
  576. return mins.toFixed(0) + 'm';
  577. }
  578.  
  579. if (ut < 86400) {
  580. var hours = ut / 3600;
  581. return hours.toFixed(0) + 'h';
  582. }
  583.  
  584. var days = ut / 86400;
  585. return days.toFixed(0) + 'd';
  586. },
  587.  
  588. yesText: gettext('Yes'),
  589. noText: gettext('No'),
  590. errorText: gettext('Error'),
  591. unknownText: gettext('Unknown'),
  592. defaultText: gettext('Default'),
  593. daysText: gettext('days'),
  594. dayText: gettext('day'),
  595. runningText: gettext('running'),
  596. stoppedText: gettext('stopped'),
  597. neverText: gettext('never'),
  598.  
  599. format_expire: function(date) {
  600. if (!date) {
  601. return PVE.Utils.neverText;
  602. }
  603. return Ext.Date.format(date, "Y-m-d");
  604. },
  605.  
  606. format_storage_type: function(value) {
  607. if (value === 'dir') {
  608. return 'Directory';
  609. } else if (value === 'nfs') {
  610. return 'NFS';
  611. } else if (value === 'lvm') {
  612. return 'LVM';
  613. } else if (value === 'iscsi') {
  614. return 'iSCSI';
  615. } else {
  616. return PVE.Utils.unknownText;
  617. }
  618. },
  619.  
  620. format_boolean_with_default: function(value) {
  621. if (Ext.isDefined(value) && value !== '') {
  622. return value ? PVE.Utils.yesText : PVE.Utils.noText;
  623. }
  624. return PVE.Utils.defaultText;
  625. },
  626.  
  627. format_boolean: function(value) {
  628. return value ? PVE.Utils.yesText : PVE.Utils.noText;
  629. },
  630.  
  631. format_neg_boolean: function(value) {
  632. return !value ? PVE.Utils.yesText : PVE.Utils.noText;
  633. },
  634.  
  635. format_content_types: function(value) {
  636. var cta = [];
  637.  
  638. Ext.each(value.split(',').sort(), function(ct) {
  639. if (ct === 'images') {
  640. cta.push('Images');
  641. } else if (ct === 'backup') {
  642. cta.push('Backups');
  643. } else if (ct === 'vztmpl') {
  644. cta.push('Templates');
  645. } else if (ct === 'iso') {
  646. cta.push('ISO');
  647. } else if (ct === 'rootdir') {
  648. cta.push('Containers');
  649. }
  650. });
  651.  
  652. return cta.join(', ');
  653. },
  654.  
  655. render_storage_content: function(value, metaData, record) {
  656. var data = record.data;
  657. if (Ext.isNumber(data.channel) &&
  658. Ext.isNumber(data.id) &&
  659. Ext.isNumber(data.lun)) {
  660. return "CH " +
  661. Ext.String.leftPad(data.channel,2, '0') +
  662. " ID " + data.id + " LUN " + data.lun;
  663. }
  664. return data.volid.replace(/^.*:(.*\/)?/,'');
  665. },
  666.  
  667. render_serverity: function (value) {
  668. return PVE.Utils.log_severity_hash[value] || value;
  669. },
  670.  
  671. render_cpu: function(value, metaData, record, rowIndex, colIndex, store) {
  672.  
  673. if (!(record.data.uptime && Ext.isNumeric(value))) {
  674. return '';
  675. }
  676.  
  677. var maxcpu = record.data.maxcpu || 1;
  678.  
  679. if (!Ext.isNumeric(maxcpu) && (maxcpu >= 1)) {
  680. return '';
  681. }
  682.  
  683. var per = value * 100;
  684.  
  685. return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
  686. },
  687.  
  688. render_size: function(value, metaData, record, rowIndex, colIndex, store) {
  689. /*jslint confusion: true */
  690.  
  691. if (!Ext.isNumeric(value)) {
  692. return '';
  693. }
  694.  
  695. return PVE.Utils.format_size(value);
  696. },
  697.  
  698. render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
  699. var servertime = new Date(value * 1000);
  700. return Ext.Date.format(servertime, 'Y-m-d H:i:s');
  701. },
  702.  
  703. render_mem_usage: function(value, metaData, record, rowIndex, colIndex, store) {
  704.  
  705. var mem = value;
  706. var maxmem = record.data.maxmem;
  707.  
  708. if (!record.data.uptime) {
  709. return '';
  710. }
  711.  
  712. if (!(Ext.isNumeric(mem) && maxmem)) {
  713. return '';
  714. }
  715.  
  716. var per = (mem * 100) / maxmem;
  717.  
  718. return per.toFixed(1) + '%';
  719. },
  720.  
  721. render_disk_usage: function(value, metaData, record, rowIndex, colIndex, store) {
  722.  
  723. var disk = value;
  724. var maxdisk = record.data.maxdisk;
  725.  
  726. if (!(Ext.isNumeric(disk) && maxdisk)) {
  727. return '';
  728. }
  729.  
  730. var per = (disk * 100) / maxdisk;
  731.  
  732. return per.toFixed(1) + '%';
  733. },
  734.  
  735. render_resource_type: function(value, metaData, record, rowIndex, colIndex, store) {
  736.  
  737. var cls = 'pve-itype-icon-' + value;
  738.  
  739. if (record.data.running) {
  740. metaData.tdCls = cls + "-running";
  741. } else {
  742. metaData.tdCls = cls;
  743. }
  744.  
  745. return value;
  746. },
  747.  
  748. render_uptime: function(value, metaData, record, rowIndex, colIndex, store) {
  749.  
  750. var uptime = value;
  751.  
  752. if (uptime === undefined) {
  753. return '';
  754. }
  755.  
  756. if (uptime <= 0) {
  757. return '-';
  758. }
  759.  
  760. return PVE.Utils.format_duration_long(uptime);
  761. },
  762.  
  763. render_support_level: function(value, metaData, record) {
  764. return PVE.Utils.support_level_hash[value] || '-';
  765. },
  766.  
  767. render_upid: function(value, metaData, record) {
  768. var type = record.data.type;
  769. var id = record.data.id;
  770.  
  771. return PVE.Utils.format_task_description(type, id);
  772. },
  773.  
  774. dialog_title: function(subject, create, isAdd) {
  775. if (create) {
  776. if (isAdd) {
  777. return gettext('Add') + ': ' + subject;
  778. } else {
  779. return gettext('Create') + ': ' + subject;
  780. }
  781. } else {
  782. return gettext('Edit') + ': ' + subject;
  783. }
  784. },
  785.  
  786. openConoleWindow: function(vmtype, vmid, nodename, vmname) {
  787. var url = Ext.urlEncode({
  788. console: vmtype, // kvm, openvz or shell
  789. vmid: vmid,
  790. vmname: vmname,
  791. node: nodename
  792. });
  793. var nw = window.open("?" + url, '_blank',
  794. "innerWidth=745,innerheight=427");
  795. nw.focus();
  796. },
  797.  
  798. // comp.setLoading() is buggy in ExtJS 4.0.7, so we
  799. // use el.mask() instead
  800. setErrorMask: function(comp, msg) {
  801. var el = comp.el;
  802. if (!el) {
  803. return;
  804. }
  805. if (!msg) {
  806. el.unmask();
  807. } else {
  808. if (msg === true) {
  809. el.mask(gettext("Loading..."));
  810. } else {
  811. el.mask(msg);
  812. }
  813. }
  814. },
  815.  
  816. monStoreErrors: function(me, store) {
  817. me.mon(store, 'beforeload', function(s, operation, eOpts) {
  818. if (!me.loadCount) {
  819. me.loadCount = 0; // make sure it is numeric
  820. PVE.Utils.setErrorMask(me, true);
  821. }
  822. });
  823.  
  824. // only works with 'pve' proxy
  825. me.mon(store.proxy, 'afterload', function(proxy, request, success) {
  826. me.loadCount++;
  827.  
  828. if (success) {
  829. PVE.Utils.setErrorMask(me, false);
  830. return;
  831. }
  832.  
  833. var msg;
  834. var operation = request.operation;
  835. var error = operation.getError();
  836. if (error.statusText) {
  837. msg = error.statusText + ' (' + error.status + ')';
  838. } else {
  839. msg = gettext('Connection error');
  840. }
  841. PVE.Utils.setErrorMask(me, msg);
  842. });
  843. }
  844.  
  845. }});
  846.  
  847. // Some configuration values are complex strings -
  848. // so we need parsers/generators for them.
  849.  
  850. Ext.define('PVE.Parser', { statics: {
  851.  
  852. // this class only contains static functions
  853.  
  854. parseQemuNetwork: function(key, value) {
  855. if (!(key && value)) {
  856. return;
  857. }
  858.  
  859. var res = {};
  860.  
  861. var errors = false;
  862. Ext.Array.each(value.split(','), function(p) {
  863. if (!p || p.match(/^\s*$/)) {
  864. return; // continue
  865. }
  866.  
  867. var match_res;
  868.  
  869. 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) {
  870. res.model = match_res[1].toLowerCase();
  871. if (match_res[3]) {
  872. res.macaddr = match_res[3];
  873. }
  874. } else if ((match_res = p.match(/^bridge=(\S+)$/)) !== null) {
  875. res.bridge = match_res[1];
  876. } else if ((match_res = p.match(/^rate=(\d+(\.\d+)?)$/)) !== null) {
  877. res.rate = match_res[1];
  878. } else if ((match_res = p.match(/^tag=(\d+(\.\d+)?)$/)) !== null) {
  879. res.tag = match_res[1];
  880. } else {
  881. errors = true;
  882. return false; // break
  883. }
  884. });
  885.  
  886. if (errors || !res.model) {
  887. return;
  888. }
  889.  
  890. return res;
  891. },
  892.  
  893. printQemuNetwork: function(net) {
  894.  
  895. var netstr = net.model;
  896. if (net.macaddr) {
  897. netstr += "=" + net.macaddr;
  898. }
  899. if (net.bridge) {
  900. netstr += ",bridge=" + net.bridge;
  901. if (net.tag) {
  902. netstr += ",tag=" + net.tag;
  903. }
  904. }
  905. if (net.rate) {
  906. netstr += ",rate=" + net.rate;
  907. }
  908. return netstr;
  909. },
  910.  
  911. parseQemuDrive: function(key, value) {
  912. if (!(key && value)) {
  913. return;
  914. }
  915.  
  916. var res = {};
  917.  
  918. var match_res = key.match(/^([a-z]+)(\d+)$/);
  919. if (!match_res) {
  920. return;
  921. }
  922. res['interface'] = match_res[1];
  923. res.index = match_res[2];
  924.  
  925. var errors = false;
  926. Ext.Array.each(value.split(','), function(p) {
  927. if (!p || p.match(/^\s*$/)) {
  928. return; // continue
  929. }
  930. var match_res = p.match(/^([a-z_]+)=(\S+)$/);
  931. if (!match_res) {
  932. if (!p.match(/\=/)) {
  933. res.file = p;
  934. return; // continue
  935. }
  936. errors = true;
  937. return false; // break
  938. }
  939. var k = match_res[1];
  940. if (k === 'volume') {
  941. k = 'file';
  942. }
  943.  
  944. if (Ext.isDefined(res[k])) {
  945. errors = true;
  946. return false; // break
  947. }
  948.  
  949. var v = match_res[2];
  950.  
  951. if (k === 'cache' && v === 'off') {
  952. v = 'none';
  953. }
  954.  
  955. res[k] = v;
  956. });
  957.  
  958. if (errors || !res.file) {
  959. return;
  960. }
  961.  
  962. return res;
  963. },
  964.  
  965. printQemuDrive: function(drive) {
  966.  
  967. var drivestr = drive.file;
  968.  
  969. Ext.Object.each(drive, function(key, value) {
  970. if (!Ext.isDefined(value) || key === 'file' ||
  971. key === 'index' || key === 'interface') {
  972. return; // continue
  973. }
  974. drivestr += ',' + key + '=' + value;
  975. });
  976.  
  977. return drivestr;
  978. },
  979.  
  980. parseOpenVZNetIf: function(value) {
  981. if (!value) {
  982. return;
  983. }
  984.  
  985. var res = {};
  986.  
  987. var errors = false;
  988. Ext.Array.each(value.split(';'), function(item) {
  989. if (!item || item.match(/^\s*$/)) {
  990. return; // continue
  991. }
  992.  
  993. var data = {};
  994. Ext.Array.each(item.split(','), function(p) {
  995. if (!p || p.match(/^\s*$/)) {
  996. return; // continue
  997. }
  998. var match_res = p.match(/^(ifname|mac|bridge|host_ifname|host_mac)=(\S+)$/);
  999. if (!match_res) {
  1000. errors = true;
  1001. return false; // break
  1002. }
  1003. data[match_res[1]] = match_res[2];
  1004. });
  1005.  
  1006. if (errors || !data.ifname) {
  1007. errors = true;
  1008. return false; // break
  1009. }
  1010.  
  1011. data.raw = item;
  1012.  
  1013. res[data.ifname] = data;
  1014. });
  1015.  
  1016. return errors ? undefined: res;
  1017. },
  1018.  
  1019. printOpenVZNetIf: function(netif) {
  1020. var netarray = [];
  1021.  
  1022. Ext.Object.each(netif, function(iface, data) {
  1023. var tmparray = [];
  1024. Ext.Array.each(['ifname', 'mac', 'bridge', 'host_ifname' , 'host_mac'], function(key) {
  1025. var value = data[key];
  1026. if (value) {
  1027. tmparray.push(key + '=' + value);
  1028. }
  1029. });
  1030. netarray.push(tmparray.join(','));
  1031. });
  1032.  
  1033. return netarray.join(';');
  1034. },
  1035.  
  1036. parseStartup: function(value) {
  1037. if (value === undefined) {
  1038. return;
  1039. }
  1040.  
  1041. var res = {};
  1042.  
  1043. var errors = false;
  1044. Ext.Array.each(value.split(','), function(p) {
  1045. if (!p || p.match(/^\s*$/)) {
  1046. return; // continue
  1047. }
  1048.  
  1049. var match_res;
  1050.  
  1051. if ((match_res = p.match(/^(order)?=(\d+)$/)) !== null) {
  1052. res.order = match_res[2];
  1053. } else if ((match_res = p.match(/^up=(\d+)$/)) !== null) {
  1054. res.up = match_res[1];
  1055. } else if ((match_res = p.match(/^down=(\d+)$/)) !== null) {
  1056. res.down = match_res[1];
  1057. } else {
  1058. errors = true;
  1059. return false; // break
  1060. }
  1061. });
  1062.  
  1063. if (errors) {
  1064. return;
  1065. }
  1066.  
  1067. return res;
  1068. },
  1069.  
  1070. printStartup: function(startup) {
  1071. var arr = [];
  1072. if (startup.order !== undefined && startup.order !== '') {
  1073. arr.push('order=' + startup.order);
  1074. }
  1075. if (startup.up !== undefined && startup.up !== '') {
  1076. arr.push('up=' + startup.up);
  1077. }
  1078. if (startup.down !== undefined && startup.down !== '') {
  1079. arr.push('down=' + startup.down);
  1080. }
  1081.  
  1082. return arr.join(',');
  1083. }
  1084.  
  1085. }});
  1086. /* This state provider keeps part of the state inside
  1087. * the browser history.
  1088. *
  1089. * We compress (shorten) url using dictionary based compression
  1090. * i.e. use column separated list instead of url encoded hash:
  1091. * #v\d* version/format
  1092. * := indicates string values
  1093. * :\d+ lookup value in dictionary hash
  1094. * #v1:=value1:5:=value2:=value3:...
  1095. */
  1096.  
  1097. Ext.define('PVE.StateProvider', {
  1098. extend: 'Ext.state.LocalStorageProvider',
  1099.  
  1100. // private
  1101. setHV: function(name, newvalue, fireEvents) {
  1102. var me = this;
  1103.  
  1104. var changes = false;
  1105. var oldtext = Ext.encode(me.UIState[name]);
  1106. var newtext = Ext.encode(newvalue);
  1107. if (newtext != oldtext) {
  1108. changes = true;
  1109. me.UIState[name] = newvalue;
  1110. //console.log("changed old " + name + " " + oldtext);
  1111. //console.log("changed new " + name + " " + newtext);
  1112. if (fireEvents) {
  1113. me.fireEvent("statechange", me, name, { value: newvalue });
  1114. }
  1115. }
  1116. return changes;
  1117. },
  1118.  
  1119. // private
  1120. hslist: [
  1121. // order is important for notifications
  1122. // [ name, default ]
  1123. ['view', 'server'],
  1124. ['rid', 'root'],
  1125. ['ltab', 'tasks'],
  1126. ['nodetab', ''],
  1127. ['storagetab', ''],
  1128. ['pooltab', ''],
  1129. ['kvmtab', ''],
  1130. ['ovztab', ''],
  1131. ['dctab', '']
  1132. ],
  1133.  
  1134. hprefix: 'v1',
  1135.  
  1136. compDict: {
  1137. ha: 28,
  1138. support: 27,
  1139. pool: 26,
  1140. syslog: 25,
  1141. ubc: 24,
  1142. initlog: 23,
  1143. openvz: 22,
  1144. backup: 21,
  1145. ressources: 20,
  1146. content: 19,
  1147. root: 18,
  1148. domains: 17,
  1149. roles: 16,
  1150. groups: 15,
  1151. users: 14,
  1152. time: 13,
  1153. dns: 12,
  1154. network: 11,
  1155. services: 10,
  1156. options: 9,
  1157. console: 8,
  1158. hardware: 7,
  1159. permissions: 6,
  1160. summary: 5,
  1161. tasks: 4,
  1162. clog: 3,
  1163. storage: 2,
  1164. folder: 1,
  1165. server: 0
  1166. },
  1167.  
  1168. decodeHToken: function(token) {
  1169. var me = this;
  1170.  
  1171. var state = {};
  1172. if (!token) {
  1173. Ext.Array.each(me.hslist, function(rec) {
  1174. state[rec[0]] = rec[1];
  1175. });
  1176. return state;
  1177. }
  1178.  
  1179. // return Ext.urlDecode(token);
  1180.  
  1181. var items = token.split(':');
  1182. var prefix = items.shift();
  1183.  
  1184. if (prefix != me.hprefix) {
  1185. return me.decodeHToken();
  1186. }
  1187.  
  1188. Ext.Array.each(me.hslist, function(rec) {
  1189. var value = items.shift();
  1190. if (value) {
  1191. if (value[0] === '=') {
  1192. value = decodeURIComponent(value.slice(1));
  1193. } else {
  1194. Ext.Object.each(me.compDict, function(key, cv) {
  1195. if (value == cv) {
  1196. value = key;
  1197. return false;
  1198. }
  1199. });
  1200. }
  1201. }
  1202. state[rec[0]] = value;
  1203. });
  1204.  
  1205. return state;
  1206. },
  1207.  
  1208. encodeHToken: function(state) {
  1209. var me = this;
  1210.  
  1211. // return Ext.urlEncode(state);
  1212.  
  1213. var ctoken = me.hprefix;
  1214. Ext.Array.each(me.hslist, function(rec) {
  1215. var value = state[rec[0]];
  1216. if (!Ext.isDefined(value)) {
  1217. value = rec[1];
  1218. }
  1219. value = encodeURIComponent(value);
  1220. if (!value) {
  1221. ctoken += ':';
  1222. } else {
  1223. var comp = me.compDict[value];
  1224. if (Ext.isDefined(comp)) {
  1225. ctoken += ":" + comp;
  1226. } else {
  1227. ctoken += ":=" + value;
  1228. }
  1229. }
  1230. });
  1231.  
  1232. return ctoken;
  1233. },
  1234.  
  1235. constructor: function(config){
  1236. var me = this;
  1237.  
  1238. me.callParent([config]);
  1239.  
  1240. me.UIState = me.decodeHToken(); // set default
  1241.  
  1242. var history_change_cb = function(token) {
  1243. //console.log("HC " + token);
  1244. if (!token) {
  1245. var res = window.confirm('Are you sure you want to navigate away from this page?');
  1246. if (res){
  1247. // process text value and close...
  1248. Ext.History.back();
  1249. } else {
  1250. Ext.History.forward();
  1251. }
  1252. return;
  1253. }
  1254.  
  1255. var newstate = me.decodeHToken(token);
  1256. Ext.Array.each(me.hslist, function(rec) {
  1257. if (typeof newstate[rec[0]] == "undefined") {
  1258. return;
  1259. }
  1260. me.setHV(rec[0], newstate[rec[0]], true);
  1261. });
  1262. };
  1263.  
  1264. var start_token = Ext.History.getToken();
  1265. if (start_token) {
  1266. history_change_cb(start_token);
  1267. } else {
  1268. var htext = me.encodeHToken(me.UIState);
  1269. Ext.History.add(htext);
  1270. }
  1271.  
  1272. Ext.History.on('change', history_change_cb);
  1273. },
  1274.  
  1275. get: function(name, defaultValue){
  1276. /*jslint confusion: true */
  1277. var me = this;
  1278. var data;
  1279.  
  1280. if (typeof me.UIState[name] != "undefined") {
  1281. data = { value: me.UIState[name] };
  1282. } else {
  1283. data = me.callParent(arguments);
  1284. if (!data && name === 'GuiCap') {
  1285. data = { vms: {}, storage: {}, access: {}, nodes: {}, dc: {} };
  1286. }
  1287. }
  1288.  
  1289. //console.log("GET " + name + " " + Ext.encode(data));
  1290. return data;
  1291. },
  1292.  
  1293. clear: function(name){
  1294. var me = this;
  1295.  
  1296. if (typeof me.UIState[name] != "undefined") {
  1297. me.UIState[name] = null;
  1298. }
  1299.  
  1300. me.callParent(arguments);
  1301. },
  1302.  
  1303. set: function(name, value){
  1304. var me = this;
  1305.  
  1306. //console.log("SET " + name + " " + Ext.encode(value));
  1307. if (typeof me.UIState[name] != "undefined") {
  1308. var newvalue = value ? value.value : null;
  1309. if (me.setHV(name, newvalue, false)) {
  1310. var htext = me.encodeHToken(me.UIState);
  1311. Ext.History.add(htext);
  1312. }
  1313. } else {
  1314. me.callParent(arguments);
  1315. }
  1316. }
  1317. });/* Button features:
  1318. * - observe selection changes to enable/disable the button using enableFn()
  1319. * - pop up confirmation dialog using confirmMsg()
  1320. */
  1321. Ext.define('PVE.button.Button', {
  1322. extend: 'Ext.button.Button',
  1323. alias: 'widget.pveButton',
  1324.  
  1325. // the selection model to observe
  1326. selModel: undefined,
  1327.  
  1328. // if 'false' handler will not be called (button disabled)
  1329. enableFn: function(record) { },
  1330.  
  1331. // function(record) or text
  1332. confirmMsg: false,
  1333.  
  1334. // take special care in confirm box (select no as default).
  1335. dangerous: false,
  1336.  
  1337. initComponent: function() {
  1338. /*jslint confusion: true */
  1339.  
  1340. var me = this;
  1341.  
  1342. if (me.handler) {
  1343. me.realHandler = me.handler;
  1344.  
  1345. me.handler = function(button, event) {
  1346. var rec, msg;
  1347. if (me.selModel) {
  1348. rec = me.selModel.getSelection()[0];
  1349. if (!rec || (me.enableFn(rec) === false)) {
  1350. return;
  1351. }
  1352. }
  1353.  
  1354. if (me.confirmMsg) {
  1355. msg = me.confirmMsg;
  1356. if (Ext.isFunction(me.confirmMsg)) {
  1357. msg = me.confirmMsg(rec);
  1358. }
  1359. Ext.MessageBox.defaultButton = me.dangerous ? 2 : 1;
  1360. Ext.Msg.show({
  1361. title: gettext('Confirm'),
  1362. icon: me.dangerous ? Ext.Msg.WARNING : Ext.Msg.QUESTION,
  1363. msg: msg,
  1364. buttons: Ext.Msg.YESNO,
  1365. callback: function(btn) {
  1366. if (btn !== 'yes') {
  1367. return;
  1368. }
  1369. me.realHandler(button, event, rec);
  1370. }
  1371. });
  1372. } else {
  1373. me.realHandler(button, event, rec);
  1374. }
  1375. };
  1376. }
  1377.  
  1378. me.callParent();
  1379.  
  1380. if (me.selModel) {
  1381.  
  1382. me.mon(me.selModel, "selectionchange", function() {
  1383. var rec = me.selModel.getSelection()[0];
  1384. if (!rec || (me.enableFn(rec) === false)) {
  1385. me.setDisabled(true);
  1386. } else {
  1387. me.setDisabled(false);
  1388. }
  1389. });
  1390. }
  1391. }
  1392. });
  1393. Ext.define('PVE.qemu.SendKeyMenu', {
  1394. extend: 'Ext.button.Button',
  1395. alias: ['widget.pveQemuSendKeyMenu'],
  1396.  
  1397. initComponent : function() {
  1398. var me = this;
  1399.  
  1400. if (!me.nodename) {
  1401. throw "no node name specified";
  1402. }
  1403.  
  1404. if (!me.vmid) {
  1405. throw "no VM ID specified";
  1406. }
  1407.  
  1408. var sendKey = function(key) {
  1409. PVE.Utils.API2Request({
  1410. params: { key: key },
  1411. url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/sendkey",
  1412. method: 'PUT',
  1413. waitMsgTarget: me,
  1414. failure: function(response, opts) {
  1415. Ext.Msg.alert('Error', response.htmlStatus);
  1416. }
  1417. });
  1418. };
  1419.  
  1420. Ext.apply(me, {
  1421. text: 'SendKey',
  1422. menu: new Ext.menu.Menu({
  1423. height: 200,
  1424. items: [
  1425. {
  1426. text: 'Tab', handler: function() {
  1427. sendKey('tab');
  1428. }
  1429. },
  1430. {
  1431. text: 'Ctrl-Alt-Delete', handler: function() {
  1432. sendKey('ctrl-alt-delete');
  1433. }
  1434. },
  1435. {
  1436. text: 'Ctrl-Alt-Backspace', handler: function() {
  1437. sendKey('ctrl-alt-backspace');
  1438. }
  1439. },
  1440. {
  1441. text: 'Ctrl-Alt-F1', handler: function() {
  1442. sendKey('ctrl-alt-f1');
  1443. }
  1444. },
  1445. {
  1446. text: 'Ctrl-Alt-F2', handler: function() {
  1447. sendKey('ctrl-alt-f2');
  1448. }
  1449. },
  1450. {
  1451. text: 'Ctrl-Alt-F3', handler: function() {
  1452. sendKey('ctrl-alt-f3');
  1453. }
  1454. },
  1455. {
  1456. text: 'Ctrl-Alt-F4', handler: function() {
  1457. sendKey('ctrl-alt-f4');
  1458. }
  1459. },
  1460. {
  1461. text: 'Ctrl-Alt-F5', handler: function() {
  1462. sendKey('ctrl-alt-f5');
  1463. }
  1464. },
  1465. {
  1466. text: 'Ctrl-Alt-F6', handler: function() {
  1467. sendKey('ctrl-alt-f6');
  1468. }
  1469. },
  1470. {
  1471. text: 'Ctrl-Alt-F7', handler: function() {
  1472. sendKey('ctrl-alt-f7');
  1473. }
  1474. },
  1475. {
  1476. text: 'Ctrl-Alt-F8', handler: function() {
  1477. sendKey('ctrl-alt-f8');
  1478. }
  1479. },
  1480. {
  1481. text: 'Ctrl-Alt-F9', handler: function() {
  1482. sendKey('ctrl-alt-f9');
  1483. }
  1484. },
  1485. {
  1486. text: 'Ctrl-Alt-F10', handler: function() {
  1487. sendKey('ctrl-alt-f10');
  1488. }
  1489. },
  1490. {
  1491. text: 'Ctrl-Alt-F11', handler: function() {
  1492. sendKey('ctrl-alt-f11');
  1493. }
  1494. },
  1495. {
  1496. text: 'Ctrl-Alt-F12', handler: function() {
  1497. sendKey('ctrl-alt-f12');
  1498. }
  1499. }
  1500. ]
  1501. })
  1502. });
  1503.  
  1504. me.callParent();
  1505. }
  1506. });
  1507. Ext.define('PVE.qemu.CmdMenu', {
  1508. extend: 'Ext.menu.Menu',
  1509.  
  1510. initComponent: function() {
  1511. var me = this;
  1512.  
  1513. var nodename = me.pveSelNode.data.node;
  1514. if (!nodename) {
  1515. throw "no node name specified";
  1516. }
  1517.  
  1518. var vmid = me.pveSelNode.data.vmid;
  1519. if (!vmid) {
  1520. throw "no VM ID specified";
  1521. }
  1522.  
  1523. var vmname = me.pveSelNode.data.name;
  1524.  
  1525. var vm_command = function(cmd, params) {
  1526. PVE.Utils.API2Request({
  1527. params: params,
  1528. url: '/nodes/' + nodename + '/qemu/' + vmid + "/status/" + cmd,
  1529. method: 'POST',
  1530. failure: function(response, opts) {
  1531. Ext.Msg.alert('Error', response.htmlStatus);
  1532. }
  1533. });
  1534. };
  1535.  
  1536. me.title = "VM " + vmid;
  1537.  
  1538. me.items = [
  1539. {
  1540. text: gettext('Start'),
  1541. icon: '/pve2/images/start.png',
  1542. handler: function() {
  1543. vm_command('start');
  1544. }
  1545. },
  1546. {
  1547. text: gettext('Migrate'),
  1548. icon: '/pve2/images/forward.png',
  1549. handler: function() {
  1550. var win = Ext.create('PVE.window.Migrate', {
  1551. vmtype: 'qemu',
  1552. nodename: nodename,
  1553. vmid: vmid
  1554. });
  1555. win.show();
  1556. }
  1557. },
  1558. {
  1559. text: gettext('Shutdown'),
  1560. icon: '/pve2/images/stop.png',
  1561. handler: function() {
  1562. var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid);
  1563. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1564. if (btn !== 'yes') {
  1565. return;
  1566. }
  1567.  
  1568. vm_command('shutdown', { timeout: 30 });
  1569. });
  1570. }
  1571. },
  1572. {
  1573. text: gettext('Stop'),
  1574. icon: '/pve2/images/gtk-stop.png',
  1575. handler: function() {
  1576. var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid);
  1577. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1578. if (btn !== 'yes') {
  1579. return;
  1580. }
  1581.  
  1582. vm_command("stop", { timeout: 30 });
  1583. });
  1584. }
  1585. },
  1586. {
  1587. text: gettext('Console'),
  1588. icon: '/pve2/images/display.png',
  1589. handler: function() {
  1590. PVE.Utils.openConoleWindow('kvm', vmid, nodename, vmname);
  1591. }
  1592. }
  1593. ];
  1594.  
  1595. me.callParent();
  1596. }
  1597. });
  1598. Ext.define('PVE.openvz.CmdMenu', {
  1599. extend: 'Ext.menu.Menu',
  1600.  
  1601. initComponent: function() {
  1602. var me = this;
  1603.  
  1604. var nodename = me.pveSelNode.data.node;
  1605. if (!nodename) {
  1606. throw "no node name specified";
  1607. }
  1608.  
  1609. var vmid = me.pveSelNode.data.vmid;
  1610. if (!vmid) {
  1611. throw "no VM ID specified";
  1612. }
  1613.  
  1614. var vmname = me.pveSelNode.data.name;
  1615.  
  1616. var vm_command = function(cmd, params) {
  1617. PVE.Utils.API2Request({
  1618. params: params,
  1619. url: '/nodes/' + nodename + '/openvz/' + vmid + "/status/" + cmd,
  1620. method: 'POST',
  1621. failure: function(response, opts) {
  1622. Ext.Msg.alert('Error', response.htmlStatus);
  1623. }
  1624. });
  1625. };
  1626.  
  1627. me.title = "CT " + vmid;
  1628.  
  1629. me.items = [
  1630. {
  1631. text: gettext('Start'),
  1632. icon: '/pve2/images/start.png',
  1633. handler: function() {
  1634. vm_command('start');
  1635. }
  1636. },
  1637. {
  1638. text: gettext('Migrate'),
  1639. icon: '/pve2/images/forward.png',
  1640. handler: function() {
  1641. var win = Ext.create('PVE.window.Migrate', {
  1642. vmtype: 'openvz',
  1643. nodename: nodename,
  1644. vmid: vmid
  1645. });
  1646. win.show();
  1647. }
  1648. },
  1649. {
  1650. text: gettext('Shutdown'),
  1651. icon: '/pve2/images/stop.png',
  1652. handler: function() {
  1653. var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid);
  1654. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1655. if (btn !== 'yes') {
  1656. return;
  1657. }
  1658.  
  1659. vm_command('shutdown');
  1660. });
  1661. }
  1662. },
  1663. {
  1664. text: gettext('Stop'),
  1665. icon: '/pve2/images/gtk-stop.png',
  1666. handler: function() {
  1667. var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid);
  1668. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1669. if (btn !== 'yes') {
  1670. return;
  1671. }
  1672.  
  1673. vm_command("stop");
  1674. });
  1675. }
  1676. },
  1677. {
  1678. text: gettext('Console'),
  1679. icon: '/pve2/images/display.png',
  1680. handler: function() {
  1681. PVE.Utils.openConoleWindow('openvz', vmid, nodename, vmname);
  1682. }
  1683. }
  1684. ];
  1685.  
  1686. me.callParent();
  1687. }
  1688. });
  1689. PVE_vnc_console_event = function(appletid, action, err) {
  1690. //console.log("TESTINIT param1 " + appletid + " action " + action);
  1691.  
  1692. if (action === "error") {
  1693. var compid = appletid.replace("-vncapp", "");
  1694. var comp = Ext.getCmp(compid);
  1695.  
  1696. if (!comp || !comp.vmid || !comp.toplevel) {
  1697. return;
  1698. }
  1699.  
  1700. // try to detect migrated VM
  1701. PVE.Utils.API2Request({
  1702. url: '/cluster/resources',
  1703. method: 'GET',
  1704. success: function(response) {
  1705. var list = response.result.data;
  1706. Ext.Array.each(list, function(item) {
  1707. if (item.type === 'qemu' && item.vmid == comp.vmid) {
  1708. if (item.node !== comp.nodename) {
  1709. //console.log("MOVED VM to node " + item.node);
  1710. comp.nodename = item.node;
  1711. comp.url = "/nodes/" + comp.nodename + "/" + item.type + "/" + comp.vmid + "/vncproxy";
  1712. //console.log("NEW URL " + comp.url);
  1713. comp.reloadApplet();
  1714. }
  1715. return false; // break
  1716. }
  1717. });
  1718. }
  1719. });
  1720. }
  1721.  
  1722. return;
  1723. /*
  1724. var el = Ext.get(appletid);
  1725. if (!el)
  1726. return;
  1727.  
  1728. if (action === "close") {
  1729. // el.remove();
  1730. } else if (action === "error") {
  1731. // console.log("TESTERROR: " + err);
  1732. // var compid = appletid.replace("-vncapp", "");
  1733. // var comp = Ext.getCmp(compid);
  1734. }
  1735.  
  1736. //Ext.get('mytestid').remove();
  1737. */
  1738.  
  1739. };
  1740.  
  1741. Ext.define('PVE.VNCConsole', {
  1742. extend: 'Ext.panel.Panel',
  1743. alias: ['widget.pveVNCConsole'],
  1744.  
  1745. initComponent : function() {
  1746. var me = this;
  1747.  
  1748. if (!me.url) {
  1749. throw "no url specified";
  1750. }
  1751.  
  1752. var myid = me.id + "-vncapp";
  1753.  
  1754. me.appletID = myid;
  1755.  
  1756. var box = Ext.create('Ext.Component', {
  1757. border: false,
  1758. html: ""
  1759. });
  1760.  
  1761. var resize_window = function() {
  1762. //console.log("resize");
  1763.  
  1764. var applet = Ext.getDom(myid);
  1765. //console.log("resize " + myid + " " + applet);
  1766.  
  1767. // try again when dom element is available
  1768. if (!(applet && Ext.isFunction(applet.getPreferredSize))) {
  1769. return Ext.Function.defer(resize_window, 1000);
  1770. }
  1771.  
  1772. var tbar = me.getDockedItems("[dock=top]")[0];
  1773. var tbh = tbar ? tbar.getHeight() : 0;
  1774. var ps = applet.getPreferredSize();
  1775. var aw = ps.width;
  1776. var ah = ps.height;
  1777.  
  1778. if (aw < 640) { aw = 640; }
  1779. if (ah < 400) { ah = 400; }
  1780.  
  1781. var oh;
  1782. var ow;
  1783.  
  1784. //console.log("size0 " + aw + " " + ah + " tbh " + tbh);
  1785.  
  1786. if (window.innerHeight) {
  1787. oh = window.innerHeight;
  1788. ow = window.innerWidth;
  1789. } else if (document.documentElement &&
  1790. document.documentElement.clientHeight) {
  1791. oh = document.documentElement.clientHeight;
  1792. ow = document.documentElement.clientWidth;
  1793. } else if (document.body) {
  1794. oh = document.body.clientHeight;
  1795. ow = document.body.clientWidth;
  1796. } else {
  1797. throw "can't get window size";
  1798. }
  1799.  
  1800. Ext.fly(applet).setSize(aw, ah + tbh);
  1801.  
  1802. var offsetw = aw - ow;
  1803. var offseth = ah + tbh - oh;
  1804.  
  1805. if (offsetw !== 0 || offseth !== 0) {
  1806. //console.log("try resize by " + offsetw + " " + offseth);
  1807. try { window.resizeBy(offsetw, offseth); } catch (e) {}
  1808. }
  1809.  
  1810. Ext.Function.defer(resize_window, 1000);
  1811. };
  1812.  
  1813. var resize_box = function() {
  1814. var applet = Ext.getDom(myid);
  1815.  
  1816. if ((applet && Ext.isFunction(applet.getPreferredSize))) {
  1817. var ps = applet.getPreferredSize();
  1818. Ext.fly(applet).setSize(ps.width, ps.height);
  1819. }
  1820.  
  1821. Ext.Function.defer(resize_box, 1000);
  1822. };
  1823.  
  1824. var start_vnc_viewer = function(param) {
  1825. var cert = param.cert;
  1826. cert = cert.replace(/\n/g, "|");
  1827.  
  1828. box.update({
  1829. id: myid,
  1830. border: false,
  1831. tag: 'applet',
  1832. code: 'com.tigervnc.vncviewer.VncViewer',
  1833. archive: '/vncterm/VncViewer.jar',
  1834. // NOTE: set size to '100%' - else resize does not work
  1835. width: "100%",
  1836. height: "100%",
  1837. cn: [
  1838. {tag: 'param', name: 'id', value: myid},
  1839. {tag: 'param', name: 'PORT', value: param.port},
  1840. {tag: 'param', name: 'PASSWORD', value: param.ticket},
  1841. {tag: 'param', name: 'USERNAME', value: param.user},
  1842. {tag: 'param', name: 'Show Controls', value: 'No'},
  1843. {tag: 'param', name: 'Offer Relogin', value: 'No'},
  1844. {tag: 'param', name: 'PVECert', value: cert}
  1845. ]
  1846. });
  1847. if (me.toplevel) {
  1848. Ext.Function.defer(resize_window, 1000);
  1849. } else {
  1850. Ext.Function.defer(resize_box, 1000);
  1851. }
  1852. };
  1853.  
  1854. Ext.apply(me, {
  1855. layout: 'fit',
  1856. border: false,
  1857. autoScroll: me.toplevel ? false : true,
  1858. items: box,
  1859. reloadApplet: function() {
  1860. PVE.Utils.API2Request({
  1861. url: me.url,
  1862. params: me.params,
  1863. method: me.method || 'POST',
  1864. failure: function(response, opts) {
  1865. box.update(gettext('Error') + ' ' + response.htmlStatus);
  1866. },
  1867. success: function(response, opts) {
  1868. start_vnc_viewer(response.result.data);
  1869. }
  1870. });
  1871. }
  1872. });
  1873.  
  1874. me.callParent();
  1875.  
  1876. if (me.toplevel) {
  1877. me.on("render", function() { me.reloadApplet();});
  1878. } else {
  1879. me.on("show", function() { me.reloadApplet();});
  1880. me.on("hide", function() { box.update(""); });
  1881. }
  1882. }
  1883. });
  1884.  
  1885. Ext.define('PVE.KVMConsole', {
  1886. extend: 'PVE.VNCConsole',
  1887. alias: ['widget.pveKVMConsole'],
  1888.  
  1889. initComponent : function() {
  1890. var me = this;
  1891.  
  1892. if (!me.nodename) {
  1893. throw "no node name specified";
  1894. }
  1895.  
  1896. if (!me.vmid) {
  1897. throw "no VM ID specified";
  1898. }
  1899.  
  1900. var vm_command = function(cmd, params, reload_applet) {
  1901. PVE.Utils.API2Request({
  1902. params: params,
  1903. url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/status/" + cmd,
  1904. method: 'POST',
  1905. waitMsgTarget: me,
  1906. failure: function(response, opts) {
  1907. Ext.Msg.alert('Error', response.htmlStatus);
  1908. },
  1909. success: function() {
  1910. if (reload_applet) {
  1911. Ext.Function.defer(me.reloadApplet, 1000, me);
  1912. }
  1913. }
  1914. });
  1915. };
  1916.  
  1917. var tbar = [
  1918. {
  1919. text: gettext('Start'),
  1920. handler: function() {
  1921. vm_command("start", {}, 1);
  1922. }
  1923. },
  1924. {
  1925. text: gettext('Shutdown'),
  1926. handler: function() {
  1927. var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), me.vmid);
  1928. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1929. if (btn !== 'yes') {
  1930. return;
  1931. }
  1932. vm_command('shutdown', {timeout: 30});
  1933. });
  1934. }
  1935. },
  1936. {
  1937. text: gettext('Stop'),
  1938. handler: function() {
  1939. var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), me.vmid);
  1940. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1941. if (btn !== 'yes') {
  1942. return;
  1943. }
  1944. vm_command("stop", { timeout: 30});
  1945. });
  1946. }
  1947. },
  1948. {
  1949. xtype: 'pveQemuSendKeyMenu',
  1950. nodename: me.nodename,
  1951. vmid: me.vmid
  1952. },
  1953. {
  1954. text: gettext('Reset'),
  1955. handler: function() {
  1956. var msg = Ext.String.format(gettext("Do you really want to reset VM {0}?"), me.vmid);
  1957. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1958. if (btn !== 'yes') {
  1959. return;
  1960. }
  1961. vm_command("reset");
  1962. });
  1963. }
  1964. },
  1965. {
  1966. text: gettext('Suspend'),
  1967. handler: function() {
  1968. var msg = Ext.String.format(gettext("Do you really want to suspend VM {0}?"), me.vmid);
  1969. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  1970. if (btn !== 'yes') {
  1971. return;
  1972. }
  1973. vm_command("suspend");
  1974. });
  1975. }
  1976. },
  1977. {
  1978. text: gettext('Resume'),
  1979. handler: function() {
  1980. vm_command("resume");
  1981. }
  1982. },
  1983. // Note: no migrate here, because we can't display migrate log
  1984. {
  1985. text: gettext('Console'),
  1986. handler: function() {
  1987. PVE.Utils.openConoleWindow('kvm', me.vmid, me.nodename, me.vmname);
  1988. }
  1989. },
  1990. '->',
  1991. {
  1992. text: gettext('Refresh'),
  1993. handler: function() {
  1994. var applet = Ext.getDom(me.appletID);
  1995. applet.sendRefreshRequest();
  1996. }
  1997. },
  1998. {
  1999. text: gettext('Reload'),
  2000. handler: function () {
  2001. me.reloadApplet();
  2002. }
  2003. }
  2004. ];
  2005.  
  2006. Ext.apply(me, {
  2007. tbar: tbar,
  2008. url: "/nodes/" + me.nodename + "/qemu/" + me.vmid + "/vncproxy"
  2009. });
  2010.  
  2011. me.callParent();
  2012. }
  2013. });
  2014.  
  2015. Ext.define('PVE.OpenVZConsole', {
  2016. extend: 'PVE.VNCConsole',
  2017. alias: ['widget.pveOpenVZConsole'],
  2018.  
  2019. initComponent : function() {
  2020. var me = this;
  2021.  
  2022. if (!me.nodename) {
  2023. throw "no node name specified";
  2024. }
  2025.  
  2026. if (!me.vmid) {
  2027. throw "no VM ID specified";
  2028. }
  2029.  
  2030. var vm_command = function(cmd, params, reload_applet) {
  2031. PVE.Utils.API2Request({
  2032. params: params,
  2033. url: '/nodes/' + me.nodename + '/openvz/' + me.vmid + "/status/" + cmd,
  2034. waitMsgTarget: me,
  2035. method: 'POST',
  2036. failure: function(response, opts) {
  2037. Ext.Msg.alert('Error', response.htmlStatus);
  2038. },
  2039. success: function() {
  2040. if (reload_applet) {
  2041. Ext.Function.defer(me.reloadApplet, 1000, me);
  2042. }
  2043. }
  2044. });
  2045. };
  2046.  
  2047. var tbar = [
  2048. {
  2049. text: gettext('Start'),
  2050. handler: function() {
  2051. vm_command("start", {}, 1);
  2052. }
  2053. },
  2054. {
  2055. text: gettext('Shutdown'),
  2056. handler: function() {
  2057. var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), me.vmid);
  2058. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  2059. if (btn !== 'yes') {
  2060. return;
  2061. }
  2062. vm_command("shutdown");
  2063. });
  2064. }
  2065. },
  2066. {
  2067. text: gettext('Stop'),
  2068. handler: function() {
  2069. var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), me.vmid);
  2070. Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
  2071. if (btn !== 'yes') {
  2072. return;
  2073. }
  2074. vm_command("stop");
  2075. });
  2076. }
  2077. },
  2078. // Note: no migrate here, because we can't display migrate log
  2079. // and openvz migrate does not work if console is open
  2080. {
  2081. text: gettext('Console'),
  2082. handler: function() {
  2083. PVE.Utils.openConoleWindow('openvz', me.vmid, me.nodename, me.vmname);
  2084. }
  2085. },
  2086. '->',
  2087. {
  2088. text: gettext('Refresh'),
  2089. handler: function() {
  2090. var applet = Ext.getDom(me.appletID);
  2091. applet.sendRefreshRequest();
  2092. }
  2093. },
  2094. {
  2095. text: gettext('Reload'),
  2096. handler: function () {
  2097. me.reloadApplet();
  2098. }
  2099. }
  2100. ];
  2101.  
  2102. Ext.apply(me, {
  2103. tbar: tbar,
  2104. url: "/nodes/" + me.nodename + "/openvz/" + me.vmid + "/vncproxy"
  2105. });
  2106.  
  2107. me.callParent();
  2108. }
  2109. });
  2110.  
  2111. Ext.define('PVE.Shell', {
  2112. extend: 'PVE.VNCConsole',
  2113. alias: ['widget.pveShell'],
  2114.  
  2115. initComponent : function() {
  2116. var me = this;
  2117.  
  2118. if (!me.nodename) {
  2119. throw "no node name specified";
  2120. }
  2121.  
  2122. var tbar = [
  2123. '->',
  2124. {
  2125. text: gettext('Refresh'),
  2126. handler: function() {
  2127. var applet = Ext.getDom(me.appletID);
  2128. applet.sendRefreshRequest();
  2129. }
  2130. },
  2131. {
  2132. text: gettext('Reload'),
  2133. handler: function () { me.reloadApplet(); }
  2134. },
  2135. {
  2136. text: gettext('Shell'),
  2137. handler: function() {
  2138. PVE.Utils.openConoleWindow('shell', undefined, me.nodename);
  2139. }
  2140. }
  2141. ];
  2142.  
  2143. Ext.apply(me, {
  2144. tbar: tbar,
  2145. url: "/nodes/" + me.nodename + "/vncshell"
  2146. });
  2147.  
  2148. me.callParent();
  2149. }
  2150. });Ext.define('PVE.data.TimezoneStore', {
  2151. extend: 'Ext.data.Store',
  2152.  
  2153. statics: {
  2154. timezones: [
  2155. ['Africa/Abidjan'],
  2156. ['Africa/Accra'],
  2157. ['Africa/Addis_Ababa'],
  2158. ['Africa/Algiers'],
  2159. ['Africa/Asmara'],
  2160. ['Africa/Bamako'],
  2161. ['Africa/Bangui'],
  2162. ['Africa/Banjul'],
  2163. ['Africa/Bissau'],
  2164. ['Africa/Blantyre'],
  2165. ['Africa/Brazzaville'],
  2166. ['Africa/Bujumbura'],
  2167. ['Africa/Cairo'],
  2168. ['Africa/Casablanca'],
  2169. ['Africa/Ceuta'],
  2170. ['Africa/Conakry'],
  2171. ['Africa/Dakar'],
  2172. ['Africa/Dar_es_Salaam'],
  2173. ['Africa/Djibouti'],
  2174. ['Africa/Douala'],
  2175. ['Africa/El_Aaiun'],
  2176. ['Africa/Freetown'],
  2177. ['Africa/Gaborone'],
  2178. ['Africa/Harare'],
  2179. ['Africa/Johannesburg'],
  2180. ['Africa/Kampala'],
  2181. ['Africa/Khartoum'],
  2182. ['Africa/Kigali'],
  2183. ['Africa/Kinshasa'],
  2184. ['Africa/Lagos'],
  2185. ['Africa/Libreville'],
  2186. ['Africa/Lome'],
  2187. ['Africa/Luanda'],
  2188. ['Africa/Lubumbashi'],
  2189. ['Africa/Lusaka'],
  2190. ['Africa/Malabo'],
  2191. ['Africa/Maputo'],
  2192. ['Africa/Maseru'],
  2193. ['Africa/Mbabane'],
  2194. ['Africa/Mogadishu'],
  2195. ['Africa/Monrovia'],
  2196. ['Africa/Nairobi'],
  2197. ['Africa/Ndjamena'],
  2198. ['Africa/Niamey'],
  2199. ['Africa/Nouakchott'],
  2200. ['Africa/Ouagadougou'],
  2201. ['Africa/Porto-Novo'],
  2202. ['Africa/Sao_Tome'],
  2203. ['Africa/Tripoli'],
  2204. ['Africa/Tunis'],
  2205. ['Africa/Windhoek'],
  2206. ['America/Adak'],
  2207. ['America/Anchorage'],
  2208. ['America/Anguilla'],
  2209. ['America/Antigua'],
  2210. ['America/Araguaina'],
  2211. ['America/Argentina/Buenos_Aires'],
  2212. ['America/Argentina/Catamarca'],
  2213. ['America/Argentina/Cordoba'],
  2214. ['America/Argentina/Jujuy'],
  2215. ['America/Argentina/La_Rioja'],
  2216. ['America/Argentina/Mendoza'],
  2217. ['America/Argentina/Rio_Gallegos'],
  2218. ['America/Argentina/Salta'],
  2219. ['America/Argentina/San_Juan'],
  2220. ['America/Argentina/San_Luis'],
  2221. ['America/Argentina/Tucuman'],
  2222. ['America/Argentina/Ushuaia'],
  2223. ['America/Aruba'],
  2224. ['America/Asuncion'],
  2225. ['America/Atikokan'],
  2226. ['America/Bahia'],
  2227. ['America/Bahia_Banderas'],
  2228. ['America/Barbados'],
  2229. ['America/Belem'],
  2230. ['America/Belize'],
  2231. ['America/Blanc-Sablon'],
  2232. ['America/Boa_Vista'],
  2233. ['America/Bogota'],
  2234. ['America/Boise'],
  2235. ['America/Cambridge_Bay'],
  2236. ['America/Campo_Grande'],
  2237. ['America/Cancun'],
  2238. ['America/Caracas'],
  2239. ['America/Cayenne'],
  2240. ['America/Cayman'],
  2241. ['America/Chicago'],
  2242. ['America/Chihuahua'],
  2243. ['America/Costa_Rica'],
  2244. ['America/Cuiaba'],
  2245. ['America/Curacao'],
  2246. ['America/Danmarkshavn'],
  2247. ['America/Dawson'],
  2248. ['America/Dawson_Creek'],
  2249. ['America/Denver'],
  2250. ['America/Detroit'],
  2251. ['America/Dominica'],
  2252. ['America/Edmonton'],
  2253. ['America/Eirunepe'],
  2254. ['America/El_Salvador'],
  2255. ['America/Fortaleza'],
  2256. ['America/Glace_Bay'],
  2257. ['America/Godthab'],
  2258. ['America/Goose_Bay'],
  2259. ['America/Grand_Turk'],
  2260. ['America/Grenada'],
  2261. ['America/Guadeloupe'],
  2262. ['America/Guatemala'],
  2263. ['America/Guayaquil'],
  2264. ['America/Guyana'],
  2265. ['America/Halifax'],
  2266. ['America/Havana'],
  2267. ['America/Hermosillo'],
  2268. ['America/Indiana/Indianapolis'],
  2269. ['America/Indiana/Knox'],
  2270. ['America/Indiana/Marengo'],
  2271. ['America/Indiana/Petersburg'],
  2272. ['America/Indiana/Tell_City'],
  2273. ['America/Indiana/Vevay'],
  2274. ['America/Indiana/Vincennes'],
  2275. ['America/Indiana/Winamac'],
  2276. ['America/Inuvik'],
  2277. ['America/Iqaluit'],
  2278. ['America/Jamaica'],
  2279. ['America/Juneau'],
  2280. ['America/Kentucky/Louisville'],
  2281. ['America/Kentucky/Monticello'],
  2282. ['America/La_Paz'],
  2283. ['America/Lima'],
  2284. ['America/Los_Angeles'],
  2285. ['America/Maceio'],
  2286. ['America/Managua'],
  2287. ['America/Manaus'],
  2288. ['America/Marigot'],
  2289. ['America/Martinique'],
  2290. ['America/Matamoros'],
  2291. ['America/Mazatlan'],
  2292. ['America/Menominee'],
  2293. ['America/Merida'],
  2294. ['America/Mexico_City'],
  2295. ['America/Miquelon'],
  2296. ['America/Moncton'],
  2297. ['America/Monterrey'],
  2298. ['America/Montevideo'],
  2299. ['America/Montreal'],
  2300. ['America/Montserrat'],
  2301. ['America/Nassau'],
  2302. ['America/New_York'],
  2303. ['America/Nipigon'],
  2304. ['America/Nome'],
  2305. ['America/Noronha'],
  2306. ['America/North_Dakota/Center'],
  2307. ['America/North_Dakota/New_Salem'],
  2308. ['America/Ojinaga'],
  2309. ['America/Panama'],
  2310. ['America/Pangnirtung'],
  2311. ['America/Paramaribo'],
  2312. ['America/Phoenix'],
  2313. ['America/Port-au-Prince'],
  2314. ['America/Port_of_Spain'],
  2315. ['America/Porto_Velho'],
  2316. ['America/Puerto_Rico'],
  2317. ['America/Rainy_River'],
  2318. ['America/Rankin_Inlet'],
  2319. ['America/Recife'],
  2320. ['America/Regina'],
  2321. ['America/Resolute'],
  2322. ['America/Rio_Branco'],
  2323. ['America/Santa_Isabel'],
  2324. ['America/Santarem'],
  2325. ['America/Santiago'],
  2326. ['America/Santo_Domingo'],
  2327. ['America/Sao_Paulo'],
  2328. ['America/Scoresbysund'],
  2329. ['America/Shiprock'],
  2330. ['America/St_Barthelemy'],
  2331. ['America/St_Johns'],
  2332. ['America/St_Kitts'],
  2333. ['America/St_Lucia'],
  2334. ['America/St_Thomas'],
  2335. ['America/St_Vincent'],
  2336. ['America/Swift_Current'],
  2337. ['America/Tegucigalpa'],
  2338. ['America/Thule'],
  2339. ['America/Thunder_Bay'],
  2340. ['America/Tijuana'],
  2341. ['America/Toronto'],
  2342. ['America/Tortola'],
  2343. ['America/Vancouver'],
  2344. ['America/Whitehorse'],
  2345. ['America/Winnipeg'],
  2346. ['America/Yakutat'],
  2347. ['America/Yellowknife'],
  2348. ['Antarctica/Casey'],
  2349. ['Antarctica/Davis'],
  2350. ['Antarctica/DumontDUrville'],
  2351. ['Antarctica/Macquarie'],
  2352. ['Antarctica/Mawson'],
  2353. ['Antarctica/McMurdo'],
  2354. ['Antarctica/Palmer'],
  2355. ['Antarctica/Rothera'],
  2356. ['Antarctica/South_Pole'],
  2357. ['Antarctica/Syowa'],
  2358. ['Antarctica/Vostok'],
  2359. ['Arctic/Longyearbyen'],
  2360. ['Asia/Aden'],
  2361. ['Asia/Almaty'],
  2362. ['Asia/Amman'],
  2363. ['Asia/Anadyr'],
  2364. ['Asia/Aqtau'],
  2365. ['Asia/Aqtobe'],
  2366. ['Asia/Ashgabat'],
  2367. ['Asia/Baghdad'],
  2368. ['Asia/Bahrain'],
  2369. ['Asia/Baku'],
  2370. ['Asia/Bangkok'],
  2371. ['Asia/Beirut'],
  2372. ['Asia/Bishkek'],
  2373. ['Asia/Brunei'],
  2374. ['Asia/Choibalsan'],
  2375. ['Asia/Chongqing'],
  2376. ['Asia/Colombo'],
  2377. ['Asia/Damascus'],
  2378. ['Asia/Dhaka'],
  2379. ['Asia/Dili'],
  2380. ['Asia/Dubai'],
  2381. ['Asia/Dushanbe'],
  2382. ['Asia/Gaza'],
  2383. ['Asia/Harbin'],
  2384. ['Asia/Ho_Chi_Minh'],
  2385. ['Asia/Hong_Kong'],
  2386. ['Asia/Hovd'],
  2387. ['Asia/Irkutsk'],
  2388. ['Asia/Jakarta'],
  2389. ['Asia/Jayapura'],
  2390. ['Asia/Jerusalem'],
  2391. ['Asia/Kabul'],
  2392. ['Asia/Kamchatka'],
  2393. ['Asia/Karachi'],
  2394. ['Asia/Kashgar'],
  2395. ['Asia/Kathmandu'],
  2396. ['Asia/Kolkata'],
  2397. ['Asia/Krasnoyarsk'],
  2398. ['Asia/Kuala_Lumpur'],
  2399. ['Asia/Kuching'],
  2400. ['Asia/Kuwait'],
  2401. ['Asia/Macau'],
  2402. ['Asia/Magadan'],
  2403. ['Asia/Makassar'],
  2404. ['Asia/Manila'],
  2405. ['Asia/Muscat'],
  2406. ['Asia/Nicosia'],
  2407. ['Asia/Novokuznetsk'],
  2408. ['Asia/Novosibirsk'],
  2409. ['Asia/Omsk'],
  2410. ['Asia/Oral'],
  2411. ['Asia/Phnom_Penh'],
  2412. ['Asia/Pontianak'],
  2413. ['Asia/Pyongyang'],
  2414. ['Asia/Qatar'],
  2415. ['Asia/Qyzylorda'],
  2416. ['Asia/Rangoon'],
  2417. ['Asia/Riyadh'],
  2418. ['Asia/Sakhalin'],
  2419. ['Asia/Samarkand'],
  2420. ['Asia/Seoul'],
  2421. ['Asia/Shanghai'],
  2422. ['Asia/Singapore'],
  2423. ['Asia/Taipei'],
  2424. ['Asia/Tashkent'],
  2425. ['Asia/Tbilisi'],
  2426. ['Asia/Tehran'],
  2427. ['Asia/Thimphu'],
  2428. ['Asia/Tokyo'],
  2429. ['Asia/Ulaanbaatar'],
  2430. ['Asia/Urumqi'],
  2431. ['Asia/Vientiane'],
  2432. ['Asia/Vladivostok'],
  2433. ['Asia/Yakutsk'],
  2434. ['Asia/Yekaterinburg'],
  2435. ['Asia/Yerevan'],
  2436. ['Atlantic/Azores'],
  2437. ['Atlantic/Bermuda'],
  2438. ['Atlantic/Canary'],
  2439. ['Atlantic/Cape_Verde'],
  2440. ['Atlantic/Faroe'],
  2441. ['Atlantic/Madeira'],
  2442. ['Atlantic/Reykjavik'],
  2443. ['Atlantic/South_Georgia'],
  2444. ['Atlantic/St_Helena'],
  2445. ['Atlantic/Stanley'],
  2446. ['Australia/Adelaide'],
  2447. ['Australia/Brisbane'],
  2448. ['Australia/Broken_Hill'],
  2449. ['Australia/Currie'],
  2450. ['Australia/Darwin'],
  2451. ['Australia/Eucla'],
  2452. ['Australia/Hobart'],
  2453. ['Australia/Lindeman'],
  2454. ['Australia/Lord_Howe'],
  2455. ['Australia/Melbourne'],
  2456. ['Australia/Perth'],
  2457. ['Australia/Sydney'],
  2458. ['Europe/Amsterdam'],
  2459. ['Europe/Andorra'],
  2460. ['Europe/Athens'],
  2461. ['Europe/Belgrade'],
  2462. ['Europe/Berlin'],
  2463. ['Europe/Bratislava'],
  2464. ['Europe/Brussels'],
  2465. ['Europe/Bucharest'],
  2466. ['Europe/Budapest'],
  2467. ['Europe/Chisinau'],
  2468. ['Europe/Copenhagen'],
  2469. ['Europe/Dublin'],
  2470. ['Europe/Gibraltar'],
  2471. ['Europe/Guernsey'],
  2472. ['Europe/Helsinki'],
  2473. ['Europe/Isle_of_Man'],
  2474. ['Europe/Istanbul'],
  2475. ['Europe/Jersey'],
  2476. ['Europe/Kaliningrad'],
  2477. ['Europe/Kiev'],
  2478. ['Europe/Lisbon'],
  2479. ['Europe/Ljubljana'],
  2480. ['Europe/London'],
  2481. ['Europe/Luxembourg'],
  2482. ['Europe/Madrid'],
  2483. ['Europe/Malta'],
  2484. ['Europe/Mariehamn'],
  2485. ['Europe/Minsk'],
  2486. ['Europe/Monaco'],
  2487. ['Europe/Moscow'],
  2488. ['Europe/Oslo'],
  2489. ['Europe/Paris'],
  2490. ['Europe/Podgorica'],
  2491. ['Europe/Prague'],
  2492. ['Europe/Riga'],
  2493. ['Europe/Rome'],
  2494. ['Europe/Samara'],
  2495. ['Europe/San_Marino'],
  2496. ['Europe/Sarajevo'],
  2497. ['Europe/Simferopol'],
  2498. ['Europe/Skopje'],
  2499. ['Europe/Sofia'],
  2500. ['Europe/Stockholm'],
  2501. ['Europe/Tallinn'],
  2502. ['Europe/Tirane'],
  2503. ['Europe/Uzhgorod'],
  2504. ['Europe/Vaduz'],
  2505. ['Europe/Vatican'],
  2506. ['Europe/Vienna'],
  2507. ['Europe/Vilnius'],
  2508. ['Europe/Volgograd'],
  2509. ['Europe/Warsaw'],
  2510. ['Europe/Zagreb'],
  2511. ['Europe/Zaporozhye'],
  2512. ['Europe/Zurich'],
  2513. ['Indian/Antananarivo'],
  2514. ['Indian/Chagos'],
  2515. ['Indian/Christmas'],
  2516. ['Indian/Cocos'],
  2517. ['Indian/Comoro'],
  2518. ['Indian/Kerguelen'],
  2519. ['Indian/Mahe'],
  2520. ['Indian/Maldives'],
  2521. ['Indian/Mauritius'],
  2522. ['Indian/Mayotte'],
  2523. ['Indian/Reunion'],
  2524. ['Pacific/Apia'],
  2525. ['Pacific/Auckland'],
  2526. ['Pacific/Chatham'],
  2527. ['Pacific/Chuuk'],
  2528. ['Pacific/Easter'],
  2529. ['Pacific/Efate'],
  2530. ['Pacific/Enderbury'],
  2531. ['Pacific/Fakaofo'],
  2532. ['Pacific/Fiji'],
  2533. ['Pacific/Funafuti'],
  2534. ['Pacific/Galapagos'],
  2535. ['Pacific/Gambier'],
  2536. ['Pacific/Guadalcanal'],
  2537. ['Pacific/Guam'],
  2538. ['Pacific/Honolulu'],
  2539. ['Pacific/Johnston'],
  2540. ['Pacific/Kiritimati'],
  2541. ['Pacific/Kosrae'],
  2542. ['Pacific/Kwajalein'],
  2543. ['Pacific/Majuro'],
  2544. ['Pacific/Marquesas'],
  2545. ['Pacific/Midway'],
  2546. ['Pacific/Nauru'],
  2547. ['Pacific/Niue'],
  2548. ['Pacific/Norfolk'],
  2549. ['Pacific/Noumea'],
  2550. ['Pacific/Pago_Pago'],
  2551. ['Pacific/Palau'],
  2552. ['Pacific/Pitcairn'],
  2553. ['Pacific/Pohnpei'],
  2554. ['Pacific/Port_Moresby'],
  2555. ['Pacific/Rarotonga'],
  2556. ['Pacific/Saipan'],
  2557. ['Pacific/Tahiti'],
  2558. ['Pacific/Tarawa'],
  2559. ['Pacific/Tongatapu'],
  2560. ['Pacific/Wake'],
  2561. ['Pacific/Wallis']
  2562. ]
  2563. },
  2564.  
  2565. constructor: function(config) {
  2566. var me = this;
  2567.  
  2568. config = config || {};
  2569.  
  2570. Ext.regModel('Timezone', {
  2571. fields: ['zone'],
  2572. proxy: {
  2573. type: 'memory',
  2574. reader: 'array'
  2575. }
  2576. });
  2577.  
  2578. Ext.apply(config, {
  2579. model: 'Timezone',
  2580. data: PVE.data.TimezoneStore.timezones
  2581. });
  2582.  
  2583. me.callParent([config]);
  2584. }
  2585. });/* A reader to store a single JSON Object (hash) into a storage.
  2586. * Also accepts an array containing a single hash.
  2587. * So it can read:
  2588. *
  2589. * example1: { data: "xyz" }
  2590. * example2: [ { data: "xyz" } ]
  2591. */
  2592.  
  2593. Ext.define('PVE.data.reader.JsonObject', {
  2594. extend: 'Ext.data.reader.Json',
  2595. alias : 'reader.jsonobject',
  2596.  
  2597. root: 'data',
  2598.  
  2599. constructor: function(config) {
  2600. var me = this;
  2601.  
  2602. Ext.apply(me, config || {});
  2603.  
  2604. me.callParent([config]);
  2605. },
  2606.  
  2607. getResponseData: function(response) {
  2608. var me = this;
  2609.  
  2610. var data = [];
  2611. try {
  2612. var result = Ext.decode(response.responseText);
  2613. var root = me.getRoot(result);
  2614. var org_root = root;
  2615.  
  2616. if (Ext.isArray(org_root)) {
  2617. if (org_root.length == 1) {
  2618. root = org_root[0];
  2619. } else {
  2620. root = {};
  2621. }
  2622. }
  2623.  
  2624. if (me.rows) {
  2625. Ext.Object.each(me.rows, function(key, rowdef) {
  2626. if (Ext.isDefined(root[key])) {
  2627. data.push({key: key, value: root[key]});
  2628. } else if (Ext.isDefined(rowdef.defaultValue)) {
  2629. data.push({key: key, value: rowdef.defaultValue});
  2630. } else if (rowdef.required) {
  2631. data.push({key: key, value: undefined});
  2632. }
  2633. });
  2634. } else {
  2635. Ext.Object.each(root, function(key, value) {
  2636. data.push({key: key, value: value });
  2637. });
  2638. }
  2639. }
  2640. catch (ex) {
  2641. Ext.Error.raise({
  2642. response: response,
  2643. json: response.responseText,
  2644. parseError: ex,
  2645. msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
  2646. });
  2647. }
  2648.  
  2649. return data;
  2650. }
  2651. });
  2652.  
  2653. Ext.define('PVE.RestProxy', {
  2654. extend: 'Ext.data.RestProxy',
  2655. alias : 'proxy.pve',
  2656.  
  2657. constructor: function(config) {
  2658. var me = this;
  2659.  
  2660. config = config || {};
  2661.  
  2662. Ext.applyIf(config, {
  2663. pageParam : null,
  2664. startParam: null,
  2665. limitParam: null,
  2666. groupParam: null,
  2667. sortParam: null,
  2668. filterParam: null,
  2669. noCache : false,
  2670. reader: {
  2671. type: 'json',
  2672. root: config.root || 'data'
  2673. },
  2674. afterRequest: function(request, success) {
  2675. me.fireEvent('afterload', me, request, success);
  2676. return;
  2677. }
  2678. });
  2679.  
  2680. me.callParent([config]);
  2681. }
  2682.  
  2683. }, function() {
  2684.  
  2685. Ext.define('pve-domains', {
  2686. extend: "Ext.data.Model",
  2687. fields: [ 'realm', 'type', 'comment', 'default',
  2688. {
  2689. name: 'descr',
  2690. // Note: We use this in the RealmComboBox.js
  2691. // (see Bug #125)
  2692. convert: function(value, record) {
  2693. var info = record.data;
  2694. var text;
  2695.  
  2696. if (value) {
  2697. return value;
  2698. }
  2699. // return realm if there is no comment
  2700. return info.comment || info.realm;
  2701. }
  2702. }
  2703. ],
  2704. proxy: {
  2705. type: 'pve',
  2706. url: "/api2/json/access/domains"
  2707. }
  2708. });
  2709.  
  2710. Ext.define('KeyValue', {
  2711. extend: "Ext.data.Model",
  2712. fields: [ 'key', 'value' ],
  2713. idProperty: 'key'
  2714. });
  2715.  
  2716. Ext.define('pve-string-list', {
  2717. extend: 'Ext.data.Model',
  2718. fields: [ 'n', 't' ],
  2719. idProperty: 'n'
  2720. });
  2721.  
  2722. Ext.define('pve-tasks', {
  2723. extend: 'Ext.data.Model',
  2724. fields: [
  2725. { name: 'starttime', type : 'date', dateFormat: 'timestamp' },
  2726. { name: 'endtime', type : 'date', dateFormat: 'timestamp' },
  2727. { name: 'pid', type: 'int' },
  2728. 'node', 'upid', 'user', 'status', 'type', 'id'
  2729. ],
  2730. idProperty: 'upid'
  2731. });
  2732.  
  2733. Ext.define('pve-cluster-log', {
  2734. extend: 'Ext.data.Model',
  2735. fields: [
  2736. { name: 'uid' , type: 'int' },
  2737. { name: 'time', type : 'date', dateFormat: 'timestamp' },
  2738. { name: 'pri', type: 'int' },
  2739. { name: 'pid', type: 'int' },
  2740. 'node', 'user', 'tag', 'msg',
  2741. {
  2742. name: 'id',
  2743. convert: function(value, record) {
  2744. var info = record.data;
  2745. var text;
  2746.  
  2747. if (value) {
  2748. return value;
  2749. }
  2750. // compute unique ID
  2751. return info.uid + ':' + info.node;
  2752. }
  2753. }
  2754. ],
  2755. idProperty: 'id'
  2756. });
  2757. });
  2758. // Serialize load (avoid too many parallel connections)
  2759. Ext.define('PVE.data.UpdateQueue', {
  2760. singleton: true,
  2761.  
  2762. constructor : function(){
  2763. var me = this;
  2764.  
  2765. var queue = [];
  2766. var queue_idx = {};
  2767.  
  2768. var idle = true;
  2769.  
  2770. var start_update = function() {
  2771. if (!idle) {
  2772. return;
  2773. }
  2774.  
  2775. var store = queue.shift();
  2776. if (!store) {
  2777. return;
  2778. }
  2779.  
  2780. queue_idx[store.storeid] = null;
  2781.  
  2782. idle = false;
  2783. store.load({
  2784. callback: function(records, operation, success) {
  2785. idle = true;
  2786. start_update();
  2787. }
  2788. });
  2789. };
  2790.  
  2791. Ext.apply(me, {
  2792. queue: function(store) {
  2793. if (!store.storeid) {
  2794. throw "unable to queue store without storeid";
  2795. }
  2796. if (!queue_idx[store.storeid]) {
  2797. queue_idx[store.storeid] = store;
  2798. queue.push(store);
  2799. }
  2800. start_update();
  2801. }
  2802. });
  2803. }
  2804. });
  2805. Ext.define('PVE.data.UpdateStore', {
  2806. extend: 'Ext.data.Store',
  2807.  
  2808. constructor: function(config) {
  2809. var me = this;
  2810.  
  2811. config = config || {};
  2812.  
  2813. if (!config.interval) {
  2814. config.interval = 3000;
  2815. }
  2816.  
  2817. if (!config.storeid) {
  2818. throw "no storeid specified";
  2819. }
  2820.  
  2821. var load_task = new Ext.util.DelayedTask();
  2822.  
  2823. var run_load_task = function() {
  2824. if (PVE.Utils.authOK()) {
  2825. PVE.data.UpdateQueue.queue(me);
  2826. load_task.delay(config.interval, run_load_task);
  2827. } else {
  2828. load_task.delay(200, run_load_task);
  2829. }
  2830. };
  2831.  
  2832. Ext.apply(config, {
  2833. startUpdate: function() {
  2834. run_load_task();
  2835. },
  2836. stopUpdate: function() {
  2837. load_task.cancel();
  2838. }
  2839. });
  2840.  
  2841. me.callParent([config]);
  2842.  
  2843. me.on('destroy', function() {
  2844. load_task.cancel();
  2845. });
  2846. }
  2847. });
  2848. /* Config properties:
  2849. * rstore: A storage to track changes
  2850. * Only works if rstore has a model and use 'idProperty'
  2851. */
  2852. Ext.define('PVE.data.DiffStore', {
  2853. extend: 'Ext.data.Store',
  2854.  
  2855. constructor: function(config) {
  2856. var me = this;
  2857.  
  2858. config = config || {};
  2859.  
  2860. if (!config.rstore) {
  2861. throw "no rstore specified";
  2862. }
  2863.  
  2864. if (!config.rstore.model) {
  2865. throw "no rstore model specified";
  2866. }
  2867.  
  2868. var rstore = config.rstore;
  2869.  
  2870. Ext.apply(config, {
  2871. model: rstore.model,
  2872. proxy: { type: 'memory' }
  2873. });
  2874.  
  2875. me.callParent([config]);
  2876.  
  2877. var first_load = true;
  2878.  
  2879. var cond_add_item = function(data, id) {
  2880. var olditem = me.getById(id);
  2881. if (olditem) {
  2882. olditem.beginEdit();
  2883. me.model.prototype.fields.eachKey(function(field) {
  2884. if (olditem.data[field] !== data[field]) {
  2885. olditem.set(field, data[field]);
  2886. }
  2887. });
  2888. olditem.endEdit(true);
  2889. olditem.commit();
  2890. } else {
  2891. var newrec = Ext.ModelMgr.create(data, me.model, id);
  2892. var pos = (me.appendAtStart && !first_load) ? 0 : me.data.length;
  2893. me.insert(pos, newrec);
  2894. }
  2895. };
  2896.  
  2897. me.mon(rstore, 'load', function(s, records, success) {
  2898.  
  2899. if (!success) {
  2900. return;
  2901. }
  2902.  
  2903. me.suspendEvents();
  2904.  
  2905. // remove vanished items
  2906. (me.snapshot || me.data).each(function(olditem) {
  2907. var item = rstore.getById(olditem.getId());
  2908. if (!item) {
  2909. me.remove(olditem);
  2910. }
  2911. });
  2912.  
  2913. rstore.each(function(item) {
  2914. cond_add_item(item.data, item.getId());
  2915. });
  2916.  
  2917. me.filter();
  2918.  
  2919. first_load = false;
  2920.  
  2921. me.resumeEvents();
  2922. me.fireEvent('datachanged', me);
  2923. });
  2924. }
  2925. });
  2926. Ext.define('PVE.data.ObjectStore', {
  2927. extend: 'PVE.data.UpdateStore',
  2928.  
  2929. constructor: function(config) {
  2930. var me = this;
  2931.  
  2932. config = config || {};
  2933.  
  2934. if (!config.storeid) {
  2935. config.storeid = 'pve-store-' + (++Ext.idSeed);
  2936. }
  2937.  
  2938. Ext.applyIf(config, {
  2939. model: 'KeyValue',
  2940. proxy: {
  2941. type: 'pve',
  2942. url: config.url,
  2943. extraParams: config.extraParams,
  2944. reader: {
  2945. type: 'jsonobject',
  2946. rows: config.rows
  2947. }
  2948. }
  2949. });
  2950.  
  2951. me.callParent([config]);
  2952. }
  2953. });
  2954. Ext.define('PVE.data.ResourceStore', {
  2955. extend: 'PVE.data.UpdateStore',
  2956. singleton: true,
  2957.  
  2958. findNextVMID: function() {
  2959. var me = this, i;
  2960.  
  2961. for (i = 100; i < 10000; i++) {
  2962. if (me.findExact('vmid', i) < 0) {
  2963. return i;
  2964. }
  2965. }
  2966. },
  2967.  
  2968. findVMID: function(vmid) {
  2969. var me = this, i;
  2970.  
  2971. return (me.findExact('vmid', parseInt(vmid, 10)) >= 0);
  2972. },
  2973.  
  2974.  
  2975. constructor: function(config) {
  2976. // fixme: how to avoid those warnings
  2977. /*jslint confusion: true */
  2978.  
  2979. var me = this;
  2980.  
  2981. config = config || {};
  2982.  
  2983. var field_defaults = {
  2984. type: {
  2985. header: gettext('Type'),
  2986. type: 'text',
  2987. renderer: PVE.Utils.render_resource_type,
  2988. sortable: true,
  2989. hideable: false,
  2990. width: 80
  2991. },
  2992. id: {
  2993. header: 'ID',
  2994. type: 'text',
  2995. hidden: true,
  2996. sortable: true,
  2997. width: 80
  2998. },
  2999. running: {
  3000. header: gettext('Online'),
  3001. type: 'boolean',
  3002. hidden: true,
  3003. convert: function(value, record) {
  3004. var info = record.data;
  3005. if (info.type === 'qemu' || info.type === 'openvz' || info.type === 'node') {
  3006. return (Ext.isNumeric(info.uptime) && (info.uptime > 0));
  3007. } else {
  3008. return false;
  3009. }
  3010. }
  3011. },
  3012. text: {
  3013. header: gettext('Description'),
  3014. type: 'text',
  3015. sortable: true,
  3016. width: 200,
  3017. convert: function(value, record) {
  3018. var info = record.data;
  3019. var text;
  3020.  
  3021. if (value) {
  3022. return value;
  3023. }
  3024.  
  3025. if (info.type === 'node') {
  3026. text = info.node;
  3027. } else if (info.type === 'pool') {
  3028. text = info.pool;
  3029. } else if (info.type === 'storage') {
  3030. text = info.storage + ' (' + info.node + ')';
  3031. } else if (info.type === 'qemu' || info.type === 'openvz') {
  3032. text = String(info.vmid);
  3033. if (info.name) {
  3034. text += " (" + info.name + ')';
  3035. }
  3036. } else {
  3037. text = info.id;
  3038. }
  3039. return text;
  3040. }
  3041. },
  3042. vmid: {
  3043. header: 'VMID',
  3044. type: 'integer',
  3045. hidden: true,
  3046. sortable: true,
  3047. width: 80
  3048. },
  3049. name: {
  3050. header: gettext('Name'),
  3051. hidden: true,
  3052. sortable: true,
  3053. type: 'text'
  3054. },
  3055. disk: {
  3056. header: gettext('Disk usage'),
  3057. type: 'integer',
  3058. renderer: PVE.Utils.render_disk_usage,
  3059. sortable: true,
  3060. width: 100
  3061. },
  3062. maxdisk: {
  3063. header: gettext('Disk size'),
  3064. type: 'integer',
  3065. renderer: PVE.Utils.render_size,
  3066. sortable: true,
  3067. hidden: true,
  3068. width: 100
  3069. },
  3070. mem: {
  3071. header: gettext('Memory usage'),
  3072. type: 'integer',
  3073. renderer: PVE.Utils.render_mem_usage,
  3074. sortable: true,
  3075. width: 100
  3076. },
  3077. maxmem: {
  3078. header: gettext('Memory size'),
  3079. type: 'integer',
  3080. renderer: PVE.Utils.render_size,
  3081. hidden: true,
  3082. sortable: true,
  3083. width: 100
  3084. },
  3085. cpu: {
  3086. header: gettext('CPU usage'),
  3087. type: 'float',
  3088. renderer: PVE.Utils.render_cpu,
  3089. sortable: true,
  3090. width: 100
  3091. },
  3092. maxcpu: {
  3093. header: 'maxcpu',
  3094. type: 'integer',
  3095. hidden: true,
  3096. sortable: true,
  3097. width: 60
  3098. },
  3099. diskread: {
  3100. header: 'Total Disk Read',
  3101. type: 'integer',
  3102. hidden: true,
  3103. sortable: true,
  3104. renderer: PVE.Utils.format_size,
  3105. width: 100
  3106. },
  3107. diskwrite: {
  3108. header: 'Total Disk Write',
  3109. type: 'integer',
  3110. hidden: true,
  3111. sortable: true,
  3112. renderer: PVE.Utils.format_size,
  3113. width: 100
  3114. },
  3115. netin: {
  3116. header: 'Total NetIn',
  3117. type: 'integer',
  3118. hidden: true,
  3119. sortable: true,
  3120. renderer: PVE.Utils.format_size,
  3121. width: 100
  3122. },
  3123. netout: {
  3124. header: 'Total NetOut',
  3125. type: 'integer',
  3126. hidden: true,
  3127. sortable: true,
  3128. renderer: PVE.Utils.format_size,
  3129. width: 100
  3130. },
  3131. uptime: {
  3132. header: gettext('Uptime'),
  3133. type: 'integer',
  3134. renderer: PVE.Utils.render_uptime,
  3135. sortable: true,
  3136. width: 110
  3137. },
  3138. node: {
  3139. header: gettext('Node'),
  3140. type: 'text',
  3141. hidden: true,
  3142. sortable: true,
  3143. width: 110
  3144. },
  3145. storage: {
  3146. header: gettext('Storage'),
  3147. type: 'text',
  3148. hidden: true,
  3149. sortable: true,
  3150. width: 110
  3151. },
  3152. pool: {
  3153. header: gettext('Pool'),
  3154. type: 'text',
  3155. hidden: true,
  3156. sortable: true,
  3157. width: 110
  3158. }
  3159. };
  3160.  
  3161. var fields = [];
  3162. var fieldNames = [];
  3163. Ext.Object.each(field_defaults, function(key, value) {
  3164. if (!Ext.isDefined(value.convert)) {
  3165. fields.push({name: key, type: value.type});
  3166. fieldNames.push(key);
  3167. } else if (key === 'text' || key === 'running') {
  3168. fields.push({name: key, type: value.type, convert: value.convert});
  3169. fieldNames.push(key);
  3170. }
  3171. });
  3172.  
  3173. Ext.define('PVEResources', {
  3174. extend: "Ext.data.Model",
  3175. fields: fields,
  3176. proxy: {
  3177. type: 'pve',
  3178. url: '/api2/json/cluster/resources'
  3179. }
  3180. });
  3181.  
  3182. Ext.define('PVETree', {
  3183. extend: "Ext.data.Model",
  3184. fields: fields,
  3185. proxy: { type: 'memory' }
  3186. });
  3187.  
  3188. Ext.apply(config, {
  3189. storeid: 'PVEResources',
  3190. model: 'PVEResources',
  3191. autoDestory: false,
  3192. defaultColums: function() {
  3193. var res = [];
  3194. Ext.Object.each(field_defaults, function(field, info) {
  3195. var fi = Ext.apply({ dataIndex: field }, info);
  3196. res.push(fi);
  3197. });
  3198. return res;
  3199. },
  3200. fieldNames: fieldNames
  3201. });
  3202.  
  3203. me.callParent([config]);
  3204. }
  3205. });
  3206. Ext.define('PVE.form.Checkbox', {
  3207. extend: 'Ext.form.field.Checkbox',
  3208. alias: ['widget.pvecheckbox'],
  3209.  
  3210. defaultValue: undefined,
  3211.  
  3212. deleteDefaultValue: false,
  3213. deleteEmpty: false,
  3214.  
  3215. inputValue: '1',
  3216.  
  3217. height: 22, // hack: set same height as text fields
  3218.  
  3219. getSubmitData: function() {
  3220. var me = this,
  3221. data = null,
  3222. val;
  3223. if (!me.disabled && me.submitValue) {
  3224. val = me.getSubmitValue();
  3225. if (val !== null) {
  3226. data = {};
  3227. if ((val == me.defaultValue) && me.deleteDefaultValue) {
  3228. data['delete'] = me.getName();
  3229. } else {
  3230. data[me.getName()] = val;
  3231. }
  3232. } else if (me.deleteEmpty) {
  3233. data = {};
  3234. data['delete'] = me.getName();
  3235. }
  3236. }
  3237. return data;
  3238. },
  3239.  
  3240. // also accept integer 1 as true
  3241. setRawValue: function(value) {
  3242. var me = this;
  3243.  
  3244. if (value === 1) {
  3245. me.callParent([true]);
  3246. } else {
  3247. me.callParent([value]);
  3248. }
  3249. }
  3250.  
  3251. });Ext.define('PVE.form.Textfield', {
  3252. extend: 'Ext.form.field.Text',
  3253. alias: ['widget.pvetextfield'],
  3254.  
  3255. skipEmptyText: true,
  3256.  
  3257. deleteEmpty: false,
  3258.  
  3259. getSubmitData: function() {
  3260. var me = this,
  3261. data = null,
  3262. val;
  3263. if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  3264. val = me.getSubmitValue();
  3265. if (val !== null) {
  3266. data = {};
  3267. data[me.getName()] = val;
  3268. } else if (me.deleteEmpty) {
  3269. data = {};
  3270. data['delete'] = me.getName();
  3271. }
  3272. }
  3273. return data;
  3274. },
  3275.  
  3276. getSubmitValue: function() {
  3277. var me = this;
  3278.  
  3279. var value = this.processRawValue(this.getRawValue());
  3280. if (value !== '') {
  3281. return value;
  3282. }
  3283.  
  3284. return me.skipEmptyText ? null: value;
  3285. }
  3286. });Ext.define('PVE.form.RRDTypeSelector', {
  3287. extend: 'Ext.form.field.ComboBox',
  3288. alias: ['widget.pveRRDTypeSelector'],
  3289.  
  3290. initComponent: function() {
  3291. var me = this;
  3292.  
  3293. var store = new Ext.data.ArrayStore({
  3294. fields: [ 'id', 'timeframe', 'cf', 'text' ],
  3295. data : [
  3296. [ 'hour', 'hour', 'AVERAGE', "Hour (average)" ],
  3297. [ 'hourmax', 'hour', 'MAX', "Hour (max)" ],
  3298. [ 'day', 'day', 'AVERAGE', "Day (average)" ],
  3299. [ 'daymax', 'day', 'MAX', "Day (max)" ],
  3300. [ 'week', 'week', 'AVERAGE', "Week (average)" ],
  3301. [ 'weekmax', 'week', 'MAX', "Week (max)" ],
  3302. [ 'month', 'month', 'AVERAGE', "Month (average)" ],
  3303. [ 'monthmax', 'month', 'MAX', "Month (max)" ],
  3304. [ 'year', 'year', 'AVERAGE', "Year (average)" ],
  3305. [ 'yearmax', 'year', 'MAX', "Year (max)" ]
  3306. ]
  3307. });
  3308.  
  3309. Ext.apply(me, {
  3310. store: store,
  3311. displayField: 'text',
  3312. valueField: 'id',
  3313. editable: false,
  3314. queryMode: 'local',
  3315. value: 'hour',
  3316. getState: function() {
  3317. var ind = store.findExact('id', me.getValue());
  3318. var rec = store.getAt(ind);
  3319. if (!rec) {
  3320. return;
  3321. }
  3322. return {
  3323. id: rec.data.id,
  3324. timeframe: rec.data.timeframe,
  3325. cf: rec.data.cf
  3326. };
  3327. },
  3328. applyState : function(state) {
  3329. if (state && state.id) {
  3330. me.setValue(state.id);
  3331. }
  3332. },
  3333. stateEvents: [ 'select' ],
  3334. stateful: true,
  3335. id: 'pveRRDTypeSelection'
  3336. });
  3337.  
  3338. me.callParent();
  3339.  
  3340. var statechange = function(sp, key, value) {
  3341. if (key === me.id) {
  3342. me.applyState(value);
  3343. }
  3344. };
  3345.  
  3346. var sp = Ext.state.Manager.getProvider();
  3347. me.mon(sp, 'statechange', statechange, me);
  3348. }
  3349. });
  3350.  
  3351. Ext.define('PVE.form.ComboGrid', {
  3352. extend: 'Ext.form.field.ComboBox',
  3353. alias: ['widget.PVE.form.ComboGrid'],
  3354.  
  3355. computeHeight: function() {
  3356. var me = this;
  3357. var lh = PVE.Utils.gridLineHeigh();
  3358. var count = me.store.getCount();
  3359. return (count > 10) ? 10*lh : 26+count*lh;
  3360. },
  3361.  
  3362. // hack: allow to select empty value
  3363. // seems extjs does not allow that when 'editable == false'
  3364. onKeyUp: function(e, t) {
  3365. var me = this;
  3366. var key = e.getKey();
  3367.  
  3368. if (!me.editable && me.allowBlank && !me.multiSelect &&
  3369. (key == e.BACKSPACE || key == e.DELETE)) {
  3370. me.setValue('');
  3371. }
  3372.  
  3373. me.callParent(arguments);
  3374. },
  3375.  
  3376. // copied from ComboBox
  3377. createPicker: function() {
  3378. var me = this,
  3379. picker,
  3380. menuCls = Ext.baseCSSPrefix + 'menu',
  3381.  
  3382. opts = Ext.apply({
  3383. selModel: {
  3384. mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
  3385. },
  3386. floating: true,
  3387. hidden: true,
  3388. ownerCt: me.ownerCt,
  3389. cls: me.el.up('.' + menuCls) ? menuCls : '',
  3390. store: me.store,
  3391. displayField: me.displayField,
  3392. focusOnToFront: false,
  3393. height: me.computeHeight(),
  3394. pageSize: me.pageSize
  3395. }, me.listConfig, me.defaultListConfig);
  3396.  
  3397. // NOTE: we simply use a grid panel
  3398. //picker = me.picker = Ext.create('Ext.view.BoundList', opts);
  3399. picker = me.picker = Ext.create('Ext.grid.Panel', opts);
  3400.  
  3401. // pass getNode() to the view
  3402. picker.getNode = function() {
  3403. picker.getView().getNode(arguments);
  3404. };
  3405.  
  3406. me.mon(picker, {
  3407. itemclick: me.onItemClick,
  3408. refresh: me.onListRefresh,
  3409. show: function() {
  3410. picker.setHeight(me.computeHeight());
  3411. me.syncSelection();
  3412. },
  3413. scope: me
  3414. });
  3415.  
  3416. me.mon(picker.getSelectionModel(), 'selectionchange', me.onListSelectionChange, me);
  3417.  
  3418. return picker;
  3419. },
  3420.  
  3421. initComponent: function() {
  3422. var me = this;
  3423.  
  3424. Ext.apply(me, {
  3425. queryMode: 'local',
  3426. editable: false,
  3427. matchFieldWidth: false
  3428. });
  3429.  
  3430. Ext.applyIf(me, { value: ''}); // hack: avoid ExtJS validate() bug
  3431.  
  3432. Ext.applyIf(me.listConfig, { width: 400 });
  3433.  
  3434. me.callParent();
  3435.  
  3436. me.store.on('beforeload', function() {
  3437. if (!me.isDisabled()) {
  3438. me.setDisabled(true);
  3439. me.enableAfterLoad = true;
  3440. }
  3441. });
  3442.  
  3443. // hack: autoSelect does not work
  3444. me.store.on('load', function(store, r, success, o) {
  3445. if (success) {
  3446. me.clearInvalid();
  3447.  
  3448. if (me.enableAfterLoad) {
  3449. delete me.enableAfterLoad;
  3450. me.setDisabled(false);
  3451. }
  3452.  
  3453. var def = me.getValue();
  3454. if (def) {
  3455. me.setValue(def, true); // sync with grid
  3456. }
  3457. var found = false;
  3458. if (def) {
  3459. if (Ext.isArray(def)) {
  3460. Ext.Array.each(def, function(v) {
  3461. if (store.findRecord(me.valueField, v)) {
  3462. found = true;
  3463. return false; // break
  3464. }
  3465. });
  3466. } else {
  3467. found = store.findRecord(me.valueField, def);
  3468. }
  3469. }
  3470.  
  3471. if (!found) {
  3472. var rec = me.store.first();
  3473. if (me.autoSelect && rec && rec.data) {
  3474. def = rec.data[me.valueField];
  3475. me.setValue(def, true);
  3476. } else {
  3477. me.setValue('', true);
  3478. }
  3479. }
  3480. }
  3481. });
  3482. }
  3483. });
  3484. Ext.define('PVE.form.KVComboBox', {
  3485. extend: 'Ext.form.field.ComboBox',
  3486. alias: 'widget.pveKVComboBox',
  3487.  
  3488. deleteEmpty: true,
  3489.  
  3490. getSubmitData: function() {
  3491. var me = this,
  3492. data = null,
  3493. val;
  3494. if (!me.disabled && me.submitValue) {
  3495. val = me.getSubmitValue();
  3496. if (val !== null && val !== '') {
  3497. data = {};
  3498. data[me.getName()] = val;
  3499. } else if (me.deleteEmpty) {
  3500. data = {};
  3501. data['delete'] = me.getName();
  3502. }
  3503. }
  3504. return data;
  3505. },
  3506.  
  3507. initComponent: function() {
  3508. var me = this;
  3509.  
  3510. me.store = Ext.create('Ext.data.ArrayStore', {
  3511. model: 'KeyValue',
  3512. data : me.data
  3513. });
  3514.  
  3515. Ext.apply(me, {
  3516. displayField: 'value',
  3517. valueField: 'key',
  3518. queryMode: 'local',
  3519. editable: false
  3520. });
  3521.  
  3522. me.callParent();
  3523. }
  3524. });
  3525. // boolean type including 'Default' (delete property from file)
  3526. Ext.define('PVE.form.Boolean', {
  3527. extend: 'PVE.form.KVComboBox',
  3528. alias: ['widget.booleanfield'],
  3529.  
  3530. initComponent: function() {
  3531. var me = this;
  3532.  
  3533. me.data = [
  3534. ['', gettext('Default')],
  3535. [1, gettext('Yes')],
  3536. [0, gettext('No')]
  3537. ];
  3538.  
  3539. me.callParent();
  3540. }
  3541. });
  3542. Ext.define('PVE.form.CompressionSelector', {
  3543. extend: 'PVE.form.KVComboBox',
  3544. alias: ['widget.pveCompressionSelector'],
  3545.  
  3546. initComponent: function() {
  3547. var me = this;
  3548.  
  3549. me.data = [
  3550. ['', gettext('none')],
  3551. ['lzo', 'LZO (' + gettext('fast') + ')'],
  3552. ['gzip', 'GZIP (' + gettext('good') + ')']
  3553. ];
  3554.  
  3555. me.callParent();
  3556. }
  3557. });
  3558. Ext.define('PVE.form.PoolSelector', {
  3559. extend: 'PVE.form.ComboGrid',
  3560. alias: ['widget.pvePoolSelector'],
  3561.  
  3562. allowBlank: false,
  3563.  
  3564. initComponent: function() {
  3565. var me = this;
  3566.  
  3567. var store = new Ext.data.Store({
  3568. model: 'pve-pools'
  3569. });
  3570.  
  3571. Ext.apply(me, {
  3572. store: store,
  3573. autoSelect: false,
  3574. valueField: 'poolid',
  3575. displayField: 'poolid',
  3576. listConfig: {
  3577. columns: [
  3578. {
  3579. header: gettext('Pool'),
  3580. sortable: true,
  3581. dataIndex: 'poolid',
  3582. flex: 1
  3583. },
  3584. {
  3585. id: 'comment',
  3586. header: 'Comment',
  3587. sortable: false,
  3588. dataIndex: 'comment',
  3589. flex: 1
  3590. }
  3591. ]
  3592. }
  3593. });
  3594.  
  3595. me.callParent();
  3596.  
  3597. store.load();
  3598. }
  3599.  
  3600. }, function() {
  3601.  
  3602. Ext.define('pve-pools', {
  3603. extend: 'Ext.data.Model',
  3604. fields: [ 'poolid', 'comment' ],
  3605. proxy: {
  3606. type: 'pve',
  3607. url: "/api2/json/pools"
  3608. },
  3609. idProperty: 'poolid'
  3610. });
  3611.  
  3612. });
  3613. Ext.define('PVE.form.GroupSelector', {
  3614. extend: 'PVE.form.ComboGrid',
  3615. alias: ['widget.pveGroupSelector'],
  3616.  
  3617. allowBlank: false,
  3618.  
  3619. initComponent: function() {
  3620. var me = this;
  3621.  
  3622. var store = new Ext.data.Store({
  3623. model: 'pve-groups'
  3624. });
  3625.  
  3626. Ext.apply(me, {
  3627. store: store,
  3628. autoSelect: false,
  3629. valueField: 'groupid',
  3630. displayField: 'groupid',
  3631. listConfig: {
  3632. columns: [
  3633. {
  3634. header: gettext('Group'),
  3635. sortable: true,
  3636. dataIndex: 'groupid',
  3637. flex: 1
  3638. },
  3639. {
  3640. id: 'comment',
  3641. header: 'Comment',
  3642. sortable: false,
  3643. dataIndex: 'comment',
  3644. flex: 1
  3645. }
  3646. ]
  3647. }
  3648. });
  3649.  
  3650. me.callParent();
  3651.  
  3652. store.load();
  3653. }
  3654.  
  3655. }, function() {
  3656.  
  3657. Ext.define('pve-groups', {
  3658. extend: 'Ext.data.Model',
  3659. fields: [ 'groupid', 'comment' ],
  3660. proxy: {
  3661. type: 'pve',
  3662. url: "/api2/json/access/groups"
  3663. },
  3664. idProperty: 'groupid'
  3665. });
  3666.  
  3667. });
  3668. Ext.define('PVE.form.UserSelector', {
  3669. extend: 'PVE.form.ComboGrid',
  3670. alias: ['widget.pveUserSelector'],
  3671.  
  3672. initComponent: function() {
  3673. var me = this;
  3674.  
  3675. var store = new Ext.data.Store({
  3676. model: 'pve-users'
  3677. });
  3678.  
  3679. var render_full_name = function(firstname, metaData, record) {
  3680.  
  3681. var first = firstname || '';
  3682. var last = record.data.lastname || '';
  3683. return first + " " + last;
  3684. };
  3685.  
  3686. Ext.apply(me, {
  3687. store: store,
  3688. allowBlank: false,
  3689. autoSelect: false,
  3690. valueField: 'userid',
  3691. displayField: 'userid',
  3692. listConfig: {
  3693. columns: [
  3694. {
  3695. header: gettext('User'),
  3696. sortable: true,
  3697. dataIndex: 'userid',
  3698. flex: 1
  3699. },
  3700. {
  3701. header: 'Name',
  3702. sortable: true,
  3703. renderer: render_full_name,
  3704. dataIndex: 'firstname',
  3705. flex: 1
  3706. },
  3707. {
  3708. id: 'comment',
  3709. header: 'Comment',
  3710. sortable: false,
  3711. dataIndex: 'comment',
  3712. flex: 1
  3713. }
  3714. ]
  3715. }
  3716. });
  3717.  
  3718. me.callParent();
  3719.  
  3720. store.load({ params: { enabled: 1 }});
  3721. }
  3722.  
  3723. }, function() {
  3724.  
  3725. Ext.define('pve-users', {
  3726. extend: 'Ext.data.Model',
  3727. fields: [
  3728. 'userid', 'firstname', 'lastname' , 'email', 'comment',
  3729. { type: 'boolean', name: 'enable' },
  3730. { type: 'date', dateFormat: 'timestamp', name: 'expire' }
  3731. ],
  3732. proxy: {
  3733. type: 'pve',
  3734. url: "/api2/json/access/users"
  3735. },
  3736. idProperty: 'userid'
  3737. });
  3738.  
  3739. });
  3740.  
  3741.  
  3742. Ext.define('PVE.form.RoleSelector', {
  3743. extend: 'PVE.form.ComboGrid',
  3744. alias: ['widget.pveRoleSelector'],
  3745.  
  3746. initComponent: function() {
  3747. var me = this;
  3748.  
  3749. var store = new Ext.data.Store({
  3750. model: 'pve-roles'
  3751. });
  3752.  
  3753. Ext.apply(me, {
  3754. store: store,
  3755. allowBlank: false,
  3756. autoSelect: false,
  3757. valueField: 'roleid',
  3758. displayField: 'roleid',
  3759. listConfig: {
  3760. columns: [
  3761. {
  3762. header: gettext('Role'),
  3763. sortable: true,
  3764. dataIndex: 'roleid',
  3765. flex: 1
  3766. }
  3767. ]
  3768. }
  3769. });
  3770.  
  3771. me.callParent();
  3772.  
  3773. store.load();
  3774. }
  3775.  
  3776. }, function() {
  3777.  
  3778. Ext.define('pve-roles', {
  3779. extend: 'Ext.data.Model',
  3780. fields: [ 'roleid', 'privs' ],
  3781. proxy: {
  3782. type: 'pve',
  3783. url: "/api2/json/access/roles"
  3784. },
  3785. idProperty: 'roleid'
  3786. });
  3787.  
  3788. });
  3789. Ext.define('PVE.form.VMIDSelector', {
  3790. extend: 'Ext.form.field.Number',
  3791. alias: 'widget.pveVMIDSelector',
  3792.  
  3793. allowBlank: false,
  3794.  
  3795. minValue: 100,
  3796.  
  3797. maxValue: 999999999,
  3798.  
  3799. validateExists: undefined,
  3800.  
  3801. validator: function(value) {
  3802. /*jslint confusion: true */
  3803. var me = this;
  3804.  
  3805. if (!Ext.isDefined(me.validateExists)) {
  3806. return true;
  3807. }
  3808. if (PVE.data.ResourceStore.findVMID(value)) {
  3809. if (me.validateExists === true) {
  3810. return true;
  3811. }
  3812. return "This VM ID is already in use.";
  3813. } else {
  3814. if (me.validateExists === false) {
  3815. return true;
  3816. }
  3817. return "This VM ID does not exists.";
  3818. }
  3819. },
  3820.  
  3821. initComponent: function() {
  3822. var me = this;
  3823.  
  3824. Ext.applyIf(me, {
  3825. fieldLabel: 'VM ID'
  3826. });
  3827.  
  3828. me.callParent();
  3829. }
  3830. });
  3831. Ext.define('PVE.form.NetworkCardSelector', {
  3832. extend: 'PVE.form.KVComboBox',
  3833. alias: ['widget.PVE.form.NetworkCardSelector'],
  3834.  
  3835. initComponent: function() {
  3836. var me = this;
  3837.  
  3838. me.data = [
  3839. ['rtl8139', 'Realtec RTL8139'],
  3840. ['e1000', 'Intel E1000'],
  3841. ['virtio', 'VirtIO (paravirtualized)']
  3842. ];
  3843.  
  3844. me.callParent();
  3845. }
  3846. });
  3847. Ext.define('PVE.form.DiskFormatSelector', {
  3848. extend: 'PVE.form.KVComboBox',
  3849. alias: ['widget.PVE.form.DiskFormatSelector'],
  3850.  
  3851. initComponent: function() {
  3852. var me = this;
  3853.  
  3854. me.data = [
  3855. ['raw', 'Raw disk image (raw)'],
  3856. ['qcow2', 'QEMU image format (qcow2)'],
  3857. ['vmdk', 'VMware image format (vmdk)']
  3858. ];
  3859.  
  3860. me.callParent();
  3861. }
  3862. });
  3863. Ext.define('PVE.form.BusTypeSelector', {
  3864. extend: 'PVE.form.KVComboBox',
  3865. alias: ['widget.PVE.form.BusTypeSelector'],
  3866.  
  3867. noVirtIO: false,
  3868.  
  3869. noScsi: false,
  3870.  
  3871. initComponent: function() {
  3872. var me = this;
  3873.  
  3874. me.data = [['ide', 'IDE']];
  3875.  
  3876. if (!me.noVirtIO) {
  3877. me.data.push(['virtio', 'VIRTIO']);
  3878. }
  3879.  
  3880. if (!me.noScsi) {
  3881. me.data.push(['scsi', 'SCSI']);
  3882. }
  3883.  
  3884. me.callParent();
  3885. }
  3886. });
  3887. Ext.define('PVE.form.ControllerSelector', {
  3888. extend: 'Ext.form.FieldContainer',
  3889. alias: ['widget.PVE.form.ControllerSelector'],
  3890.  
  3891. statics: {
  3892. maxIds: {
  3893. ide: 3,
  3894. virtio: 5,
  3895. scsi: 13
  3896. }
  3897. },
  3898.  
  3899. noVirtIO: false,
  3900.  
  3901. noScsi: false,
  3902.  
  3903. vmconfig: {}, // used to check for existing devices
  3904.  
  3905. setVMConfig: function(vmconfig, autoSelect) {
  3906. var me = this;
  3907.  
  3908. me.vmconfig = Ext.apply({}, vmconfig);
  3909. if (autoSelect) {
  3910. var clist = ['ide', 'virtio', 'scsi'];
  3911. if (autoSelect === 'cdrom') {
  3912. clist = ['ide', 'scsi'];
  3913. if (!Ext.isDefined(me.vmconfig.ide2)) {
  3914. me.down('field[name=controller]').setValue('ide');
  3915. me.down('field[name=deviceid]').setValue(2);
  3916. return;
  3917. }
  3918. } else if (me.vmconfig.ostype === 'l26') {
  3919. clist = ['virtio', 'ide', 'scsi'];
  3920. }
  3921.  
  3922. Ext.Array.each(clist, function(controller) {
  3923. var confid, i;
  3924. if ((controller === 'virtio' && me.noVirtIO) ||
  3925. (controller === 'scsi' && me.noScsi)) {
  3926. return; //continue
  3927. }
  3928. me.down('field[name=controller]').setValue(controller);
  3929. for (i = 0; i <= PVE.form.ControllerSelector.maxIds[controller]; i++) {
  3930. confid = controller + i.toString();
  3931. if (!Ext.isDefined(me.vmconfig[confid])) {
  3932. me.down('field[name=deviceid]').setValue(i);
  3933. return false; // break
  3934. }
  3935. }
  3936. });
  3937. }
  3938. me.down('field[name=deviceid]').validate();
  3939. },
  3940.  
  3941. initComponent: function() {
  3942. var me = this;
  3943.  
  3944. Ext.apply(me, {
  3945. fieldLabel: 'Bus/Device',
  3946. layout: 'hbox',
  3947. height: 22, // hack: set to same height as other fields
  3948. defaults: {
  3949. flex: 1,
  3950. hideLabel: true
  3951. },
  3952. items: [
  3953. {
  3954. xtype: 'PVE.form.BusTypeSelector',
  3955. name: 'controller',
  3956. value: 'ide',
  3957. noVirtIO: me.noVirtIO,
  3958. noScsi: me.noScsi,
  3959. allowBlank: false,
  3960. listeners: {
  3961. change: function(t, value) {
  3962. if (!me.rendered || !value) {
  3963. return;
  3964. }
  3965. var field = me.down('field[name=deviceid]');
  3966. field.setMaxValue(PVE.form.ControllerSelector.maxIds[value]);
  3967. field.validate();
  3968. }
  3969. }
  3970. },
  3971. {
  3972. xtype: 'numberfield',
  3973. name: 'deviceid',
  3974. minValue: 0,
  3975. maxValue: PVE.form.ControllerSelector.maxIds.ide,
  3976. value: '0',
  3977. validator: function(value) {
  3978. /*jslint confusion: true */
  3979. if (!me.rendered) {
  3980. return;
  3981. }
  3982. var field = me.down('field[name=controller]');
  3983. var controller = field.getValue();
  3984. var confid = controller + value;
  3985. if (Ext.isDefined(me.vmconfig[confid])) {
  3986. return "This device is already in use.";
  3987. }
  3988. return true;
  3989. }
  3990. }
  3991. ]
  3992. });
  3993.  
  3994. me.callParent();
  3995. }
  3996. }); Ext.define('PVE.form.RealmComboBox', {
  3997. extend: 'Ext.form.field.ComboBox',
  3998. alias: ['widget.pveRealmComboBox'],
  3999.  
  4000. initComponent: function() {
  4001. var me = this;
  4002.  
  4003. var stateid = 'pveloginrealm';
  4004.  
  4005. var realmstore = Ext.create('Ext.data.Store', {
  4006. model: 'pve-domains',
  4007. autoDestory: true
  4008. });
  4009.  
  4010. Ext.apply(me, {
  4011. fieldLabel: 'Realm',
  4012. name: 'realm',
  4013. store: realmstore,
  4014. queryMode: 'local',
  4015. allowBlank: false,
  4016. forceSelection: true,
  4017. autoSelect: false,
  4018. triggerAction: 'all',
  4019. valueField: 'realm',
  4020. displayField: 'descr',
  4021. getState: function() {
  4022. return { value: this.getValue() };
  4023. },
  4024. applyState : function(state) {
  4025. if (state && state.value) {
  4026. this.setValue(state.value);
  4027. }
  4028. },
  4029. stateEvents: [ 'select' ],
  4030. stateful: true,
  4031. id: stateid, // fixme: remove (Stateful does not work without)
  4032. stateID: stateid
  4033. });
  4034.  
  4035. me.callParent();
  4036.  
  4037. realmstore.load({
  4038. callback: function(r, o, success) {
  4039. if (success) {
  4040. var def = me.getValue();
  4041. if (!def || !realmstore.findRecord('realm', def)) {
  4042. def = 'pam';
  4043. Ext.each(r, function(record) {
  4044. if (record.data && record.data["default"]) {
  4045. def = record.data.realm;
  4046. }
  4047. });
  4048. }
  4049. if (def) {
  4050. me.setValue(def);
  4051. }
  4052. }
  4053. }
  4054. });
  4055. }
  4056. });Ext.define('PVE.form.BondModeSelector', {
  4057. extend: 'PVE.form.KVComboBox',
  4058. alias: ['widget.bondModeSelector'],
  4059.  
  4060. initComponent: function() {
  4061. var me = this;
  4062.  
  4063. me.data = [
  4064. ['balance-rr', 'balance-rr'],
  4065. ['active-backup', 'active-backup'],
  4066. ['balance-xor', 'balance-xor'],
  4067. ['broadcast', 'broadcast'],
  4068. ['802.3ad', '802.3ad'],
  4069. ['balance-tlb', 'balance-tlb'],
  4070. ['balance-alb', 'balance-alb']
  4071. ];
  4072.  
  4073. me.callParent();
  4074. }
  4075. });
  4076. Ext.define('PVE.form.ViewSelector', {
  4077. extend: 'Ext.form.field.ComboBox',
  4078. alias: ['widget.pveViewSelector'],
  4079.  
  4080. initComponent: function() {
  4081. var me = this;
  4082.  
  4083. var default_views = {
  4084. server: {
  4085. text: gettext('Server View'),
  4086. groups: ['node']
  4087. },
  4088. folder: {
  4089. text: gettext('Folder View'),
  4090. groups: ['type']
  4091. },
  4092. storage: {
  4093. text: gettext('Storage View'),
  4094. groups: ['node'],
  4095. filterfn: function(node) {
  4096. return node.data.type === 'storage';
  4097. }
  4098. }
  4099. };
  4100.  
  4101. var groupdef = [];
  4102. Ext.Object.each(default_views, function(viewname, value) {
  4103. groupdef.push([viewname, value.text]);
  4104. });
  4105.  
  4106. var store = Ext.create('Ext.data.Store', {
  4107. model: 'KeyValue',
  4108. proxy: {
  4109. type: 'memory',
  4110. reader: 'array'
  4111. },
  4112. data: groupdef,
  4113. autoload: true,
  4114. autoDestory: true
  4115. });
  4116.  
  4117. Ext.apply(me, {
  4118. hideLabel: true,
  4119. store: store,
  4120. value: groupdef[0][0],
  4121. editable: false,
  4122. queryMode: 'local',
  4123. allowBlank: false,
  4124. forceSelection: true,
  4125. autoSelect: false,
  4126. triggerAction: 'all',
  4127. valueField: 'key',
  4128. displayField: 'value',
  4129.  
  4130. getViewFilter: function() {
  4131. var view = me.getValue();
  4132. return Ext.apply({ id: view }, default_views[view] || default_views.server);
  4133. },
  4134.  
  4135. getState: function() {
  4136. return { value: me.getValue() };
  4137. },
  4138.  
  4139. applyState : function(state, doSelect) {
  4140. var view = me.getValue();
  4141. if (state && state.value && (view != state.value)) {
  4142. var record = store.findRecord('key', state.value);
  4143. if (record) {
  4144. me.setValue(state.value, true);
  4145. if (doSelect) {
  4146. me.fireEvent('select', me, [record]);
  4147. }
  4148. }
  4149. }
  4150. },
  4151. stateEvents: [ 'select' ],
  4152. stateful: true,
  4153. id: 'view'
  4154. });
  4155.  
  4156. me.callParent();
  4157.  
  4158. var statechange = function(sp, key, value) {
  4159. if (key === me.id) {
  4160. me.applyState(value, true);
  4161. }
  4162. };
  4163.  
  4164. var sp = Ext.state.Manager.getProvider();
  4165.  
  4166. me.mon(sp, 'statechange', statechange, me);
  4167. }
  4168. });Ext.define('PVE.form.NodeSelector', {
  4169. extend: 'PVE.form.ComboGrid',
  4170. alias: ['widget.PVE.form.NodeSelector'],
  4171.  
  4172. // invalidate nodes which are offline
  4173. onlineValidator: false,
  4174.  
  4175. initComponent: function() {
  4176. var me = this;
  4177.  
  4178. var store = Ext.create('Ext.data.Store', {
  4179. fields: [ 'node', 'cpu', 'maxcpu', 'mem', 'maxmem', 'uptime' ],
  4180. autoLoad: true,
  4181. proxy: {
  4182. type: 'pve',
  4183. url: '/api2/json/nodes'
  4184. },
  4185. autoDestory: true,
  4186. sorters: [
  4187. {
  4188. property : 'mem',
  4189. direction: 'DESC'
  4190. },
  4191. {
  4192. property : 'node',
  4193. direction: 'ASC'
  4194. }
  4195. ]
  4196. });
  4197.  
  4198. Ext.apply(me, {
  4199. store: store,
  4200. valueField: 'node',
  4201. displayField: 'node',
  4202. listConfig: {
  4203. columns: [
  4204. {
  4205. header: 'Node',
  4206. dataIndex: 'node',
  4207. hideable: false,
  4208. flex: 1
  4209. },
  4210. {
  4211. header: 'Memory usage',
  4212. renderer: PVE.Utils.render_mem_usage,
  4213. width: 100,
  4214. dataIndex: 'mem'
  4215. },
  4216. {
  4217. header: 'CPU usage',
  4218. renderer: PVE.Utils.render_cpu,
  4219. sortable: true,
  4220. width: 100,
  4221. dataIndex: 'cpu'
  4222. }
  4223. ]
  4224. },
  4225. validator: function(value) {
  4226. /*jslint confusion: true */
  4227. if (!me.onlineValidator || (me.allowBlank && !value)) {
  4228. return true;
  4229. }
  4230.  
  4231. var offline = [];
  4232. Ext.Array.each(value.split(/\s*,\s*/), function(node) {
  4233. var rec = me.store.findRecord(me.valueField, node);
  4234. if (!(rec && rec.data) || !Ext.isNumeric(rec.data.mem)) {
  4235. offline.push(node);
  4236. }
  4237. });
  4238.  
  4239. if (offline.length == 0) {
  4240. return true;
  4241. }
  4242.  
  4243. return "Node " + offline.join(', ') + " seems to be offline!";
  4244. }
  4245. });
  4246.  
  4247. me.callParent();
  4248. }
  4249. });Ext.define('PVE.form.FileSelector', {
  4250. extend: 'PVE.form.ComboGrid',
  4251. alias: ['widget.pveFileSelector'],
  4252.  
  4253. setStorage: function(storage, nodename) {
  4254. var me = this;
  4255.  
  4256. var change = false;
  4257. if (storage && (me.storage !== storage)) {
  4258. me.storage = storage;
  4259. change = true;
  4260. }
  4261.  
  4262. if (nodename && (me.nodename !== nodename)) {
  4263. me.nodename = nodename;
  4264. change = true;
  4265. }
  4266.  
  4267. if (!(me.storage && me.nodename && change)) {
  4268. return;
  4269. }
  4270.  
  4271. var url = '/api2/json/nodes/' + me.nodename + '/storage/' + me.storage + '/content';
  4272. if (me.storageContent) {
  4273. url += '?content=' + me.storageContent;
  4274. }
  4275.  
  4276. me.store.setProxy({
  4277. type: 'pve',
  4278. url: url
  4279. });
  4280.  
  4281. me.store.load();
  4282. },
  4283.  
  4284. initComponent: function() {
  4285. var me = this;
  4286.  
  4287. var store = Ext.create('Ext.data.Store', {
  4288. model: 'pve-storage-content'
  4289. });
  4290.  
  4291. Ext.apply(me, {
  4292. store: store,
  4293. allowBlank: false,
  4294. autoSelect: false,
  4295. valueField: 'volid',
  4296. displayField: 'text',
  4297. listConfig: {
  4298. columns: [
  4299. {
  4300. header: 'Name',
  4301. dataIndex: 'text',
  4302. hideable: false,
  4303. flex: 1
  4304. },
  4305. {
  4306. header: 'Format',
  4307. width: 60,
  4308. dataIndex: 'format'
  4309. },
  4310. {
  4311. header: 'Size',
  4312. width: 60,
  4313. dataIndex: 'size',
  4314. renderer: PVE.Utils.format_size
  4315. }
  4316. ]
  4317. }
  4318. });
  4319.  
  4320. me.callParent();
  4321.  
  4322. me.setStorage(me.storage, me.nodename);
  4323. }
  4324. });Ext.define('PVE.form.StorageSelector', {
  4325. extend: 'PVE.form.ComboGrid',
  4326. alias: ['widget.PVE.form.StorageSelector'],
  4327.  
  4328. setNodename: function(nodename) {
  4329. var me = this;
  4330.  
  4331. if (!nodename || (me.nodename === nodename)) {
  4332. return;
  4333. }
  4334.  
  4335. me.nodename = nodename;
  4336.  
  4337. var url = '/api2/json/nodes/' + me.nodename + '/storage';
  4338. if (me.storageContent) {
  4339. url += '?content=' + me.storageContent;
  4340. }
  4341.  
  4342. me.store.setProxy({
  4343. type: 'pve',
  4344. url: url
  4345. });
  4346.  
  4347. me.store.load();
  4348. },
  4349.  
  4350. initComponent: function() {
  4351. var me = this;
  4352.  
  4353. var nodename = me.nodename;
  4354. me.nodename = undefined;
  4355.  
  4356. var store = Ext.create('Ext.data.Store', {
  4357. model: 'pve-storage-status',
  4358. sorters: {
  4359. property: 'storage',
  4360. order: 'DESC'
  4361. }
  4362. });
  4363.  
  4364. Ext.apply(me, {
  4365. store: store,
  4366. allowBlank: false,
  4367. valueField: 'storage',
  4368. displayField: 'storage',
  4369. listConfig: {
  4370. columns: [
  4371. {
  4372. header: 'Name',
  4373. dataIndex: 'storage',
  4374. hideable: false,
  4375. flex: 1
  4376. },
  4377. {
  4378. header: 'Type',
  4379. width: 60,
  4380. dataIndex: 'type'
  4381. },
  4382. {
  4383. header: 'Avail',
  4384. width: 80,
  4385. dataIndex: 'avail',
  4386. renderer: PVE.Utils.format_size
  4387. },
  4388. {
  4389. header: 'Capacity',
  4390. width: 80,
  4391. dataIndex: 'total',
  4392. renderer: PVE.Utils.format_size
  4393. }
  4394. ]
  4395. }
  4396. });
  4397.  
  4398. me.callParent();
  4399.  
  4400. if (nodename) {
  4401. me.setNodename(nodename);
  4402. }
  4403. }
  4404. }, function() {
  4405.  
  4406. Ext.define('pve-storage-status', {
  4407. extend: 'Ext.data.Model',
  4408. fields: [ 'storage', 'active', 'type', 'avail', 'total' ],
  4409. idProperty: 'storage'
  4410. });
  4411.  
  4412. });
  4413. Ext.define('PVE.form.BridgeSelector', {
  4414. extend: 'PVE.form.ComboGrid',
  4415. alias: ['widget.PVE.form.BridgeSelector'],
  4416.  
  4417. setNodename: function(nodename) {
  4418. var me = this;
  4419.  
  4420. if (!nodename || (me.nodename === nodename)) {
  4421. return;
  4422. }
  4423.  
  4424. me.nodename = nodename;
  4425.  
  4426. me.store.setProxy({
  4427. type: 'pve',
  4428. url: '/api2/json/nodes/' + me.nodename + '/network?type=bridge'
  4429. });
  4430.  
  4431. me.store.load();
  4432. },
  4433.  
  4434. initComponent: function() {
  4435. var me = this;
  4436.  
  4437. var nodename = me.nodename;
  4438. me.nodename = undefined;
  4439.  
  4440. var store = Ext.create('Ext.data.Store', {
  4441. fields: [ 'iface', 'active', 'type' ],
  4442. filterOnLoad: true
  4443. });
  4444.  
  4445. Ext.apply(me, {
  4446. store: store,
  4447. valueField: 'iface',
  4448. displayField: 'iface',
  4449. listConfig: {
  4450. columns: [
  4451. {
  4452. header: 'Bridge',
  4453. dataIndex: 'iface',
  4454. hideable: false,
  4455. flex: 1
  4456. },
  4457. {
  4458. header: gettext('Active'),
  4459. width: 60,
  4460. dataIndex: 'active',
  4461. renderer: PVE.Utils.format_boolean
  4462. }
  4463. ]
  4464. }
  4465. });
  4466.  
  4467. me.callParent();
  4468.  
  4469. if (nodename) {
  4470. me.setNodename(nodename);
  4471. }
  4472. }
  4473. });
  4474.  
  4475. Ext.define('PVE.form.CPUModelSelector', {
  4476. extend: 'PVE.form.KVComboBox',
  4477. alias: ['widget.CPUModelSelector'],
  4478.  
  4479. initComponent: function() {
  4480. var me = this;
  4481.  
  4482. me.data = [
  4483. ['', 'Default (qemu64)'],
  4484. ['486', '486'],
  4485. ['athlon', 'athlon'],
  4486. ['core2duo', 'core2duo'],
  4487. ['coreduo', 'coreduo'],
  4488. ['kvm32', 'kvm32'],
  4489. ['kvm64', 'kvm64'],
  4490. ['pentium', 'pentium'],
  4491. ['pentium2', 'pentium2'],
  4492. ['pentium3', 'pentium3'],
  4493. ['phenom', 'phenom'],
  4494. ['qemu32', 'qemu32'],
  4495. ['qemu64', 'qemu64'],
  4496. ['cpu64-rhel6', 'cpu64-rhel6'],
  4497. ['cpu64-rhel5', 'cpu64-rhel5'],
  4498. ['Conroe', 'Conroe'],
  4499. ['Penryn', 'Penryn'],
  4500. ['Nehalem', 'Nehalem'],
  4501. ['Westmere', 'Westmere'],
  4502. ['Opteron_G1', 'Opteron_G1'],
  4503. ['Opteron_G2', 'Opteron_G2'],
  4504. ['Opteron_G3', 'Opteron_G3'],
  4505. ['host', 'host']
  4506. ];
  4507.  
  4508. me.callParent();
  4509. }
  4510. });
  4511. Ext.define('PVE.form.VNCKeyboardSelector', {
  4512. extend: 'PVE.form.KVComboBox',
  4513. alias: ['widget.VNCKeyboardSelector'],
  4514.  
  4515. initComponent: function() {
  4516. var me = this;
  4517. me.data = PVE.Utils.kvm_keymap_array();
  4518. me.callParent();
  4519. }
  4520. });
  4521. Ext.define('PVE.form.LanguageSelector', {
  4522. extend: 'PVE.form.KVComboBox',
  4523. alias: ['widget.pveLanguageSelector'],
  4524.  
  4525. initComponent: function() {
  4526. var me = this;
  4527. me.data = PVE.Utils.language_array();
  4528. me.callParent();
  4529. }
  4530. });
  4531. Ext.define('PVE.form.DisplaySelector', {
  4532. extend: 'PVE.form.KVComboBox',
  4533. alias: ['widget.DisplaySelector'],
  4534.  
  4535. initComponent: function() {
  4536. var me = this;
  4537.  
  4538. me.data = PVE.Utils.kvm_vga_driver_array();
  4539. me.callParent();
  4540. }
  4541. });
  4542. Ext.define('PVE.form.CacheTypeSelector', {
  4543. extend: 'PVE.form.KVComboBox',
  4544. alias: ['widget.CacheTypeSelector'],
  4545.  
  4546. initComponent: function() {
  4547. var me = this;
  4548.  
  4549. me.data = [
  4550. ['', 'Default (no cache)'],
  4551. ['directsync', 'Direct sync'],
  4552. ['writethrough', 'Write through'],
  4553. ['writeback', 'Write back'],
  4554. ['unsafe', 'Write back (unsafe)'],
  4555. ['none', 'No cache']
  4556. ];
  4557.  
  4558. me.callParent();
  4559. }
  4560. });
  4561. Ext.define('PVE.form.ContentTypeSelector', {
  4562. extend: 'PVE.form.KVComboBox',
  4563. alias: ['widget.pveContentTypeSelector'],
  4564.  
  4565. initComponent: function() {
  4566. var me = this;
  4567.  
  4568. me.data = [];
  4569.  
  4570. var cts = ['images', 'iso', 'vztmpl', 'backup', 'rootdir'];
  4571. Ext.Array.each(cts, function(ct) {
  4572. me.data.push([ct, PVE.Utils.format_content_types(ct)]);
  4573. });
  4574.  
  4575. me.callParent();
  4576. }
  4577. });
  4578. Ext.define('PVE.form.DayOfWeekSelector', {
  4579. extend: 'PVE.form.KVComboBox',
  4580. alias: ['widget.pveDayOfWeekSelector'],
  4581.  
  4582. initComponent: function() {
  4583. var me = this;
  4584.  
  4585. me.data = [
  4586. ['sun', Ext.Date.dayNames[0]],
  4587. ['mon', Ext.Date.dayNames[1]],
  4588. ['tue', Ext.Date.dayNames[2]],
  4589. ['wed', Ext.Date.dayNames[3]],
  4590. ['thu', Ext.Date.dayNames[4]],
  4591. ['fri', Ext.Date.dayNames[5]],
  4592. ['sat', Ext.Date.dayNames[6]]
  4593. ];
  4594.  
  4595. me.callParent();
  4596. }
  4597. });
  4598. Ext.define('PVE.form.BackupModeSelector', {
  4599. extend: 'PVE.form.KVComboBox',
  4600. alias: ['widget.pveBackupModeSelector'],
  4601.  
  4602. initComponent: function() {
  4603. var me = this;
  4604.  
  4605. me.data = [
  4606. ['snapshot', 'Snapshot'],
  4607. ['suspend', 'Suspend'],
  4608. ['stop', 'Stop']
  4609. ];
  4610.  
  4611. me.callParent();
  4612. }
  4613. });
  4614. Ext.define('PVE.dc.Tasks', {
  4615. extend: 'Ext.grid.GridPanel',
  4616.  
  4617. alias: ['widget.pveClusterTasks'],
  4618.  
  4619. initComponent : function() {
  4620. var me = this;
  4621.  
  4622. var taskstore = new PVE.data.UpdateStore({
  4623. storeid: 'pve-cluster-tasks',
  4624. model: 'pve-tasks',
  4625. proxy: {
  4626. type: 'pve',
  4627. url: '/api2/json/cluster/tasks'
  4628. },
  4629. sorters: [
  4630. {
  4631. property : 'starttime',
  4632. direction: 'DESC'
  4633. }
  4634. ]
  4635. });
  4636.  
  4637. var store = Ext.create('PVE.data.DiffStore', {
  4638. rstore: taskstore,
  4639. appendAtStart: true
  4640. });
  4641.  
  4642. var run_task_viewer = function() {
  4643. var sm = me.getSelectionModel();
  4644. var rec = sm.getSelection()[0];
  4645. if (!rec) {
  4646. return;
  4647. }
  4648.  
  4649. var win = Ext.create('PVE.window.TaskViewer', {
  4650. upid: rec.data.upid
  4651. });
  4652. win.show();
  4653. };
  4654.  
  4655. Ext.apply(me, {
  4656. store: store,
  4657. stateful: false,
  4658.  
  4659. viewConfig: {
  4660. trackOver: false,
  4661. stripeRows: false, // does not work with getRowClass()
  4662.  
  4663. getRowClass: function(record, index) {
  4664. var status = record.get('status');
  4665.  
  4666. if (status && status != 'OK') {
  4667. return "x-form-invalid-field";
  4668. }
  4669. }
  4670. },
  4671. sortableColumns: false,
  4672. columns: [
  4673. {
  4674. header: gettext("Start Time"),
  4675. dataIndex: 'starttime',
  4676. width: 100,
  4677. renderer: function(value) {
  4678. return Ext.Date.format(value, "M d H:i:s");
  4679. }
  4680. },
  4681. {
  4682. header: gettext("End Time"),
  4683. dataIndex: 'endtime',
  4684. width: 100,
  4685. renderer: function(value, metaData, record) {
  4686. if (record.data.pid) {
  4687. metaData.tdCls = "x-grid-row-loading";
  4688. return "";
  4689. }
  4690. return Ext.Date.format(value, "M d H:i:s");
  4691. }
  4692. },
  4693. {
  4694. header: gettext("Node"),
  4695. dataIndex: 'node',
  4696. width: 100
  4697. },
  4698. {
  4699. header: gettext("User name"),
  4700. dataIndex: 'user',
  4701. width: 150
  4702. },
  4703. {
  4704. header: gettext("Description"),
  4705. dataIndex: 'upid',
  4706. flex: 1,
  4707. renderer: PVE.Utils.render_upid
  4708. },
  4709. {
  4710. header: gettext("Status"),
  4711. dataIndex: 'status',
  4712. width: 200,
  4713. renderer: function(value, metaData, record) {
  4714. if (record.data.pid) {
  4715. metaData.tdCls = "x-grid-row-loading";
  4716. return "";
  4717. }
  4718. if (value == 'OK') {
  4719. return 'OK';
  4720. }
  4721. // metaData.attr = 'style="color:red;"';
  4722. return PVE.Utils.errorText + ': ' + value;
  4723. }
  4724. }
  4725. ],
  4726. listeners: {
  4727. itemdblclick: run_task_viewer,
  4728. show: taskstore.startUpdate,
  4729. hide: taskstore.stopUpdate,
  4730. destroy: taskstore.stopUpdate
  4731. }
  4732. });
  4733.  
  4734. me.callParent();
  4735. }
  4736. });Ext.define('PVE.dc.Log', {
  4737. extend: 'Ext.grid.GridPanel',
  4738.  
  4739. alias: ['widget.pveClusterLog'],
  4740.  
  4741. initComponent : function() {
  4742. var me = this;
  4743.  
  4744. var logstore = new PVE.data.UpdateStore({
  4745. storeid: 'pve-cluster-log',
  4746. model: 'pve-cluster-log',
  4747. proxy: {
  4748. type: 'pve',
  4749. url: '/api2/json/cluster/log'
  4750. }
  4751. });
  4752.  
  4753. var store = Ext.create('PVE.data.DiffStore', {
  4754. rstore: logstore,
  4755. appendAtStart: true
  4756. });
  4757.  
  4758. Ext.apply(me, {
  4759. store: store,
  4760. stateful: false,
  4761.  
  4762. viewConfig: {
  4763. trackOver: false,
  4764. stripeRows: false, // does not work with getRowClass()
  4765.  
  4766. getRowClass: function(record, index) {
  4767. var pri = record.get('pri');
  4768.  
  4769. if (pri && pri <= 3) {
  4770. return "x-form-invalid-field";
  4771. }
  4772. }
  4773. },
  4774. sortableColumns: false,
  4775. columns: [
  4776. {
  4777. header: gettext("Time"),
  4778. dataIndex: 'time',
  4779. width: 100,
  4780. renderer: function(value) {
  4781. return Ext.Date.format(value, "M d H:i:s");
  4782. }
  4783. },
  4784. {
  4785. header: gettext("Node"),
  4786. dataIndex: 'node',
  4787. width: 100
  4788. },
  4789. {
  4790. header: gettext("Service"),
  4791. dataIndex: 'tag',
  4792. width: 100
  4793. },
  4794. {
  4795. header: "PID",
  4796. dataIndex: 'pid',
  4797. width: 100
  4798. },
  4799. {
  4800. header: gettext("User name"),
  4801. dataIndex: 'user',
  4802. width: 150
  4803. },
  4804. {
  4805. header: gettext("Severity"),
  4806. dataIndex: 'pri',
  4807. renderer: PVE.Utils.render_serverity,
  4808. width: 100
  4809. },
  4810. {
  4811. header: gettext("Message"),
  4812. dataIndex: 'msg',
  4813. flex: 1
  4814. }
  4815. ],
  4816. listeners: {
  4817. show: logstore.startUpdate,
  4818. hide: logstore.stopUpdate,
  4819. destroy: logstore.stopUpdate
  4820. }
  4821. });
  4822.  
  4823. me.callParent();
  4824. }
  4825. });Ext.define('PVE.panel.StatusPanel', {
  4826. extend: 'Ext.tab.Panel',
  4827. alias: 'widget.pveStatusPanel',
  4828.  
  4829.  
  4830. //title: "Logs",
  4831. //tabPosition: 'bottom',
  4832.  
  4833. initComponent: function() {
  4834. var me = this;
  4835.  
  4836. var stateid = 'ltab';
  4837. var sp = Ext.state.Manager.getProvider();
  4838.  
  4839. var state = sp.get(stateid);
  4840. if (state && state.value) {
  4841. me.activeTab = state.value;
  4842. }
  4843.  
  4844. Ext.apply(me, {
  4845. listeners: {
  4846. tabchange: function() {
  4847. var atab = me.getActiveTab().itemId;
  4848. var state = { value: atab };
  4849. sp.set(stateid, state);
  4850. }
  4851. },
  4852. items: [
  4853. {
  4854. itemId: 'tasks',
  4855. title: gettext('Tasks'),
  4856. xtype: 'pveClusterTasks'
  4857. },
  4858. {
  4859. itemId: 'clog',
  4860. title: gettext('Cluster log'),
  4861. xtype: 'pveClusterLog'
  4862. }
  4863. ]
  4864. });
  4865.  
  4866. me.callParent();
  4867.  
  4868. me.items.get(0).fireEvent('show', me.items.get(0));
  4869.  
  4870. var statechange = function(sp, key, state) {
  4871. if (key === stateid) {
  4872. var atab = me.getActiveTab().itemId;
  4873. var ntab = state.value;
  4874. if (state && ntab && (atab != ntab)) {
  4875. me.setActiveTab(ntab);
  4876. }
  4877. }
  4878. };
  4879.  
  4880. sp.on('statechange', statechange);
  4881. me.on('destroy', function() {
  4882. sp.un('statechange', statechange);
  4883. });
  4884.  
  4885. }
  4886. });
  4887. Ext.define('PVE.panel.RRDView', {
  4888. extend: 'Ext.panel.Panel',
  4889. alias: 'widget.pveRRDView',
  4890.  
  4891. initComponent : function() {
  4892. var me = this;
  4893.  
  4894. if (!me.timeframe) {
  4895. me.timeframe = 'hour';
  4896. }
  4897.  
  4898. if (!me.rrdcffn) {
  4899. me.rrdcffn = 'AVERAGE';
  4900. }
  4901.  
  4902. if (!me.datasource) {
  4903. throw "no datasource specified";
  4904. }
  4905.  
  4906. if (!me.rrdurl) {
  4907. throw "no rrdurl specified";
  4908. }
  4909.  
  4910. var datasource = me.datasource;
  4911.  
  4912. // fixme: dcindex??
  4913. var dcindex = 0;
  4914. var create_url = function() {
  4915. var url = me.rrdurl + "?ds=" + datasource +
  4916. "&timeframe=" + me.timeframe + "&cf=" + me.rrdcffn +
  4917. "&_dc=" + dcindex.toString();
  4918. dcindex++;
  4919. return url;
  4920. };
  4921.  
  4922. var stateid = 'pveRRDTypeSelection';
  4923.  
  4924. Ext.apply(me, {
  4925. layout: 'fit',
  4926. html: {
  4927. tag: 'img',
  4928. width: 800,
  4929. height: 200,
  4930. src: create_url()
  4931. },
  4932. applyState : function(state) {
  4933. if (state && state.id) {
  4934. me.timeframe = state.timeframe;
  4935. me.rrdcffn = state.cf;
  4936. me.reload_task.delay(10);
  4937. }
  4938. }
  4939. });
  4940.  
  4941. me.callParent();
  4942.  
  4943. me.reload_task = new Ext.util.DelayedTask(function() {
  4944. if (me.rendered) {
  4945. try {
  4946. var html = {
  4947. tag: 'img',
  4948. width: 800,
  4949. height: 200,
  4950. src: create_url()
  4951. };
  4952. me.update(html);
  4953. } catch (e) {
  4954. // fixme:
  4955. console.log(e);
  4956. }
  4957. me.reload_task.delay(30000);
  4958. } else {
  4959. me.reload_task.delay(1000);
  4960. }
  4961. });
  4962.  
  4963. me.reload_task.delay(30000);
  4964.  
  4965. me.on('destroy', function() {
  4966. me.reload_task.cancel();
  4967. });
  4968.  
  4969. var sp = Ext.state.Manager.getProvider();
  4970. me.applyState(sp.get(stateid));
  4971.  
  4972. var state_change_fn = function(prov, key, value) {
  4973. if (key == stateid) {
  4974. me.applyState(value);
  4975. }
  4976. };
  4977.  
  4978. me.mon(sp, 'statechange', state_change_fn);
  4979. }
  4980. });
  4981. Ext.define('PVE.panel.InputPanel', {
  4982. extend: 'Ext.panel.Panel',
  4983. alias: ['widget.inputpanel'],
  4984.  
  4985. border: false,
  4986.  
  4987. // overwrite this to modify submit data
  4988. onGetValues: function(values) {
  4989. return values;
  4990. },
  4991.  
  4992. getValues: function(dirtyOnly) {
  4993. var me = this;
  4994.  
  4995. if (Ext.isFunction(me.onGetValues)) {
  4996. dirtyOnly = false;
  4997. }
  4998.  
  4999. var values = {};
  5000.  
  5001. Ext.Array.each(me.query('[isFormField]'), function(field) {
  5002. if (!dirtyOnly || field.isDirty()) {
  5003. PVE.Utils.assemble_field_data(values, field.getSubmitData());
  5004. }
  5005. });
  5006.  
  5007. return me.onGetValues(values);
  5008. },
  5009.  
  5010. setValues: function(values) {
  5011. var me = this;
  5012.  
  5013. var form = me.up('form');
  5014.  
  5015. Ext.iterate(values, function(fieldId, val) {
  5016. var field = me.query('[isFormField][name=' + fieldId + ']')[0];
  5017. if (field) {
  5018. field.setValue(val);
  5019. if (form.trackResetOnLoad) {
  5020. field.resetOriginalValue();
  5021. }
  5022. }
  5023. });
  5024. },
  5025.  
  5026. initComponent: function() {
  5027. var me = this;
  5028.  
  5029. var items;
  5030.  
  5031. if (me.items) {
  5032. me.columns = 1;
  5033. items = [
  5034. {
  5035. columnWidth: 1,
  5036. layout: 'anchor',
  5037. items: me.items
  5038. }
  5039. ];
  5040. me.items = undefined;
  5041. } else if (me.column1) {
  5042. me.columns = 2;
  5043. items = [
  5044. {
  5045. columnWidth: 0.5,
  5046. padding: '0 10 0 0',
  5047. layout: 'anchor',
  5048. items: me.column1
  5049. },
  5050. {
  5051. columnWidth: 0.5,
  5052. padding: '0 0 0 10',
  5053. layout: 'anchor',
  5054. items: me.column2 || [] // allow empty column
  5055. }
  5056. ];
  5057. } else {
  5058. throw "unsupported config";
  5059. }
  5060.  
  5061. if (me.useFieldContainer) {
  5062. Ext.apply(me, {
  5063. layout: 'fit',
  5064. items: Ext.apply(me.useFieldContainer, {
  5065. layout: 'column',
  5066. defaultType: 'container',
  5067. items: items
  5068. })
  5069. });
  5070. } else {
  5071. Ext.apply(me, {
  5072. layout: 'column',
  5073. defaultType: 'container',
  5074. items: items
  5075. });
  5076. }
  5077.  
  5078. me.callParent();
  5079. }
  5080. });
  5081. // fixme: how can we avoid those lint errors?
  5082. /*jslint confusion: true */
  5083. Ext.define('PVE.window.Edit', {
  5084. extend: 'Ext.window.Window',
  5085. alias: 'widget.pveWindowEdit',
  5086.  
  5087. resizable: false,
  5088.  
  5089. // use this tio atimatically generate a title like
  5090. // Create: <subject>
  5091. subject: undefined,
  5092.  
  5093. // set create to true if you want a Create button (instead
  5094. // OK and RESET)
  5095. create: false,
  5096.  
  5097. // set to true if you want an Add button (instead of Create)
  5098. isAdd: false,
  5099.  
  5100. isValid: function() {
  5101. var me = this;
  5102.  
  5103. var form = me.formPanel.getForm();
  5104. return form.isValid();
  5105. },
  5106.  
  5107. getValues: function(dirtyOnly) {
  5108. var me = this;
  5109.  
  5110. var values = {};
  5111.  
  5112. var form = me.formPanel.getForm();
  5113.  
  5114. form.getFields().each(function(field) {
  5115. if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
  5116. PVE.Utils.assemble_field_data(values, field.getSubmitData());
  5117. }
  5118. });
  5119.  
  5120. Ext.Array.each(me.query('inputpanel'), function(panel) {
  5121. PVE.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
  5122. });
  5123.  
  5124. return values;
  5125. },
  5126.  
  5127. setValues: function(values) {
  5128. var me = this;
  5129.  
  5130. var form = me.formPanel.getForm();
  5131.  
  5132. Ext.iterate(values, function(fieldId, val) {
  5133. var field = form.findField(fieldId);
  5134. if (field && !field.up('inputpanel')) {
  5135. field.setValue(val);
  5136. if (form.trackResetOnLoad) {
  5137. field.resetOriginalValue();
  5138. }
  5139. }
  5140. });
  5141.  
  5142. Ext.Array.each(me.query('inputpanel'), function(panel) {
  5143. panel.setValues(values);
  5144. });
  5145. },
  5146.  
  5147. submit: function() {
  5148. var me = this;
  5149.  
  5150. var form = me.formPanel.getForm();
  5151.  
  5152. var values = me.getValues();
  5153. Ext.Object.each(values, function(name, val) {
  5154. if (values.hasOwnProperty(name)) {
  5155. if (Ext.isArray(val) && !val.length) {
  5156. values[name] = '';
  5157. }
  5158. }
  5159. });
  5160.  
  5161. if (me.digest) {
  5162. values.digest = me.digest;
  5163. }
  5164.  
  5165. PVE.Utils.API2Request({
  5166. url: me.url,
  5167. waitMsgTarget: me,
  5168. method: me.method || 'PUT',
  5169. params: values,
  5170. failure: function(response, options) {
  5171. if (response.result.errors) {
  5172. form.markInvalid(response.result.errors);
  5173. }
  5174. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  5175. },
  5176. success: function(response, options) {
  5177. me.close();
  5178. }
  5179. });
  5180. },
  5181.  
  5182. load: function(options) {
  5183. var me = this;
  5184.  
  5185. var form = me.formPanel.getForm();
  5186.  
  5187. options = options || {};
  5188.  
  5189. var newopts = Ext.apply({
  5190. waitMsgTarget: me
  5191. }, options);
  5192.  
  5193. var createWrapper = function(successFn) {
  5194. Ext.apply(newopts, {
  5195. url: me.url,
  5196. method: 'GET',
  5197. success: function(response, opts) {
  5198. form.clearInvalid();
  5199. me.digest = response.result.data.digest;
  5200. if (successFn) {
  5201. successFn(response, opts);
  5202. } else {
  5203. me.setValues(response.result.data);
  5204. }
  5205. // hack: fix ExtJS bug
  5206. Ext.Array.each(me.query('radiofield'), function(f) {
  5207. f.resetOriginalValue();
  5208. });
  5209. },
  5210. failure: function(response, opts) {
  5211. Ext.Msg.alert(gettext('Error'), response.htmlStatus, function() {
  5212. me.close();
  5213. });
  5214. }
  5215. });
  5216. };
  5217.  
  5218. createWrapper(options.success);
  5219.  
  5220. PVE.Utils.API2Request(newopts);
  5221. },
  5222.  
  5223. initComponent : function() {
  5224. var me = this;
  5225.  
  5226. if (!me.url) {
  5227. throw "no url specified";
  5228. }
  5229.  
  5230. var items = Ext.isArray(me.items) ? me.items : [ me.items ];
  5231.  
  5232. me.items = undefined;
  5233.  
  5234. me.formPanel = Ext.create('Ext.form.Panel', {
  5235. url: me.url,
  5236. method: me.method || 'PUT',
  5237. trackResetOnLoad: true,
  5238. bodyPadding: 10,
  5239. border: false,
  5240. defaults: {
  5241. border: false
  5242. },
  5243. fieldDefaults: Ext.apply({}, me.fieldDefaults, {
  5244. labelWidth: 100,
  5245. anchor: '100%'
  5246. }),
  5247. items: items
  5248. });
  5249.  
  5250. var form = me.formPanel.getForm();
  5251.  
  5252. var submitBtn = Ext.create('Ext.Button', {
  5253. text: me.create ? (me.isAdd ? gettext('Add') : gettext('Create')) : gettext('OK'),
  5254. disabled: !me.create,
  5255. handler: function() {
  5256. me.submit();
  5257. }
  5258. });
  5259.  
  5260. var resetBtn = Ext.create('Ext.Button', {
  5261. text: 'Reset',
  5262. disabled: true,
  5263. handler: function(){
  5264. form.reset();
  5265. }
  5266. });
  5267.  
  5268. var set_button_status = function() {
  5269. var valid = form.isValid();
  5270. var dirty = form.isDirty();
  5271. submitBtn.setDisabled(!valid || !(dirty || me.create));
  5272. resetBtn.setDisabled(!dirty);
  5273. };
  5274.  
  5275. form.on('dirtychange', set_button_status);
  5276. form.on('validitychange', set_button_status);
  5277.  
  5278. var colwidth = 300;
  5279. if (me.fieldDefaults && me.fieldDefaults.labelWidth) {
  5280. colwidth += me.fieldDefaults.labelWidth - 100;
  5281. }
  5282.  
  5283.  
  5284. var twoColumn = items[0].column1 || items[0].column2;
  5285.  
  5286. if (me.subject && !me.title) {
  5287. me.title = PVE.Utils.dialog_title(me.subject, me.create, me.isAdd);
  5288. }
  5289.  
  5290. Ext.applyIf(me, {
  5291. modal: true,
  5292. layout: 'auto',
  5293. width: twoColumn ? colwidth*2 : colwidth,
  5294. border: false,
  5295. items: [ me.formPanel ],
  5296. buttons: me.create ? [ submitBtn ] : [ submitBtn, resetBtn ]
  5297. });
  5298.  
  5299. me.callParent();
  5300.  
  5301. // always mark invalid fields
  5302. me.on('afterlayout', function() {
  5303. me.isValid();
  5304. });
  5305. }
  5306. });
  5307. Ext.define('PVE.window.LoginWindow', {
  5308. extend: 'Ext.window.Window',
  5309.  
  5310. // private
  5311. onLogon: function() {
  5312. var me = this;
  5313.  
  5314. var form = me.getComponent(0).getForm();
  5315.  
  5316. if(form.isValid()){
  5317. me.el.mask(gettext('Please wait...'), 'x-mask-loading');
  5318.  
  5319. form.submit({
  5320. failure: function(f, resp){
  5321. me.el.unmask();
  5322. Ext.MessageBox.alert(gettext('Error'),
  5323. gettext("Login failed. Please try again"),
  5324. function() {
  5325. var uf = form.findField('username');
  5326. uf.focus(true, true);
  5327. });
  5328. },
  5329. success: function(f, resp){
  5330. me.el.unmask();
  5331.  
  5332. var handler = me.handler || Ext.emptyFn;
  5333. handler.call(me, resp.result.data);
  5334. me.close();
  5335. }
  5336. });
  5337. }
  5338. },
  5339.  
  5340. initComponent: function() {
  5341. var me = this;
  5342.  
  5343. Ext.apply(me, {
  5344. width: 400,
  5345. modal: true,
  5346. border: false,
  5347. draggable: true,
  5348. closable: false,
  5349. resizable: false,
  5350. layout: 'auto',
  5351. title: gettext('Proxmox VE Login'),
  5352.  
  5353. items: [{
  5354. xtype: 'form',
  5355. frame: true,
  5356. url: '/api2/extjs/access/ticket',
  5357.  
  5358. fieldDefaults: {
  5359. labelAlign: 'right'
  5360. },
  5361.  
  5362. defaults: {
  5363. anchor: '-5',
  5364. allowBlank: false
  5365. },
  5366.  
  5367. items: [
  5368. {
  5369. xtype: 'textfield',
  5370. fieldLabel: gettext('User name'),
  5371. name: 'username',
  5372. inputId: 'loginform-username',
  5373. value: document.hiddenlogin.username.value,
  5374. blankText: gettext("Enter your user name"),
  5375. listeners: {
  5376. afterrender: function(f) {
  5377. // Note: only works if we pass delay 1000
  5378. f.focus(true, 1000);
  5379. Ext.Function.defer(function() { document.getElementById('loginform-username').value=document.hiddenlogin.username.value; document.getElementById('loginform-password').value=document.hiddenlogin.password.value; }, 1000);
  5380. },
  5381. specialkey: function(f, e) {
  5382. if (e.getKey() === e.ENTER) {
  5383. var pf = me.query('textfield[name="password"]')[0];
  5384. if (pf.getValue()) {
  5385. me.onLogon();
  5386. } else {
  5387. pf.focus(false);
  5388. }
  5389. }
  5390. }
  5391. }
  5392. },
  5393. {
  5394. xtype: 'textfield',
  5395. inputType: 'password',
  5396. fieldLabel: gettext('Password'),
  5397. name: 'password',
  5398. inputId: 'loginform-password',
  5399. value: document.hiddenlogin.password.value,
  5400. blankText: gettext("Enter your password"),
  5401. listeners: {
  5402. specialkey: function(field, e) {
  5403. if (e.getKey() === e.ENTER) {
  5404. me.onLogon();
  5405. }
  5406. }
  5407. }
  5408. },
  5409. {
  5410. xtype: 'pveRealmComboBox',
  5411. name: 'realm'
  5412. },
  5413. {
  5414. xtype: 'pveLanguageSelector',
  5415. fieldLabel: gettext('Language'),
  5416. value: Ext.util.Cookies.get('PVELangCookie') || 'en',
  5417. name: 'lang',
  5418. submitValue: false,
  5419. listeners: {
  5420. change: function(t, value) {
  5421. var dt = Ext.Date.add(new Date(), Ext.Date.YEAR, 10);
  5422. Ext.util.Cookies.set('PVELangCookie', value, dt);
  5423. me.el.mask(gettext('Please wait...'), 'x-mask-loading');
  5424. window.location.reload();
  5425. }
  5426. }
  5427. }
  5428. ],
  5429. buttons: [
  5430. {
  5431. text: gettext('Login'),
  5432. handler: function(){
  5433. document.hiddenlogin.username.value=document.getElementById('loginform-username').value;
  5434. document.hiddenlogin.password.value=document.getElementById('loginform-password').value;
  5435. document.hiddenlogin.submit();
  5436. me.onLogon();
  5437. }
  5438. }
  5439. ]
  5440. }]
  5441. });
  5442.  
  5443. me.callParent();
  5444. }
  5445. });
  5446. // fixme: how can we avoid those lint errors?
  5447. /*jslint confusion: true */
  5448.  
  5449. Ext.define('PVE.window.TaskViewer', {
  5450. extend: 'Ext.window.Window',
  5451. alias: 'widget.pveTaskViewer',
  5452.  
  5453. initComponent: function() {
  5454. var me = this;
  5455.  
  5456. if (!me.upid) {
  5457. throw "no task specified";
  5458. }
  5459.  
  5460. var task = PVE.Utils.parse_task_upid(me.upid);
  5461.  
  5462. var rows = {
  5463. status: {
  5464. header: gettext('Status'),
  5465. defaultValue: 'unknown'
  5466. },
  5467. type: {
  5468. header: 'Task type',
  5469. required: true
  5470. },
  5471. user: {
  5472. header: gettext('User name'),
  5473. required: true
  5474. },
  5475. node: {
  5476. header: gettext('Node'),
  5477. required: true
  5478. },
  5479. pid: {
  5480. header: 'Process ID',
  5481. required: true
  5482. },
  5483. starttime: {
  5484. header: gettext('Start Time'),
  5485. required: true,
  5486. renderer: PVE.Utils.render_timestamp
  5487. },
  5488. upid: {
  5489. header: 'Unique task ID'
  5490. }
  5491. };
  5492.  
  5493. var statstore = Ext.create('PVE.data.ObjectStore', {
  5494. url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
  5495. interval: 1000,
  5496. rows: rows
  5497. });
  5498.  
  5499. me.on('destroy', statstore.stopUpdate);
  5500.  
  5501. var stop_task = function() {
  5502. PVE.Utils.API2Request({
  5503. url: "/nodes/" + task.node + "/tasks/" + me.upid,
  5504. waitMsgTarget: me,
  5505. method: 'DELETE',
  5506. failure: function(response, opts) {
  5507. Ext.Msg.alert('Error', response.htmlStatus);
  5508. }
  5509. });
  5510. };
  5511.  
  5512. var stop_btn1 = new Ext.Button({
  5513. text: gettext('Stop'),
  5514. disabled: true,
  5515. handler: stop_task
  5516. });
  5517.  
  5518. var stop_btn2 = new Ext.Button({
  5519. text: gettext('Stop'),
  5520. disabled: true,
  5521. handler: stop_task
  5522. });
  5523.  
  5524. var statgrid = Ext.create('PVE.grid.ObjectGrid', {
  5525. title: gettext('Status'),
  5526. layout: 'fit',
  5527. tbar: [ stop_btn1 ],
  5528. rstore: statstore,
  5529. rows: rows,
  5530. border: false
  5531. });
  5532.  
  5533. var logView = Ext.create('PVE.panel.LogView', {
  5534. title: gettext('Output'),
  5535. tbar: [ stop_btn2 ],
  5536. border: false,
  5537. url: "/api2/extjs/nodes/" + task.node + "/tasks/" + me.upid + "/log"
  5538. });
  5539.  
  5540. me.mon(statstore, 'load', function() {
  5541. var status = statgrid.getObjectValue('status');
  5542.  
  5543. if (status === 'stopped') {
  5544. logView.requestUpdate(undefined, true);
  5545. logView.scrollToEnd = false;
  5546. statstore.stopUpdate();
  5547. }
  5548.  
  5549. stop_btn1.setDisabled(status !== 'running');
  5550. stop_btn2.setDisabled(status !== 'running');
  5551. });
  5552.  
  5553. statstore.startUpdate();
  5554.  
  5555. Ext.applyIf(me, {
  5556. title: "Task viewer: " + task.desc,
  5557. width: 800,
  5558. height: 400,
  5559. layout: 'fit',
  5560. modal: true,
  5561. bodyPadding: 5,
  5562. items: [{
  5563. xtype: 'tabpanel',
  5564. region: 'center',
  5565. items: [ logView, statgrid ]
  5566. }]
  5567. });
  5568.  
  5569. me.callParent();
  5570.  
  5571. logView.fireEvent('show', logView);
  5572. }
  5573. });
  5574.  
  5575. Ext.define('PVE.window.Wizard', {
  5576. extend: 'Ext.window.Window',
  5577.  
  5578. getValues: function(dirtyOnly) {
  5579. var me = this;
  5580.  
  5581. var values = {};
  5582.  
  5583. var form = me.down('form').getForm();
  5584.  
  5585. form.getFields().each(function(field) {
  5586. if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
  5587. PVE.Utils.assemble_field_data(values, field.getSubmitData());
  5588. }
  5589. });
  5590.  
  5591. Ext.Array.each(me.query('inputpanel'), function(panel) {
  5592. PVE.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
  5593. });
  5594.  
  5595. return values;
  5596. },
  5597.  
  5598. initComponent: function() {
  5599. var me = this;
  5600.  
  5601. var tabs = me.items || [];
  5602. delete me.items;
  5603.  
  5604. /*
  5605. * Items may have the following functions:
  5606. * validator(): per tab custom validation
  5607. * onSubmit(): submit handler
  5608. * onGetValues(): overwrite getValues results
  5609. */
  5610.  
  5611. Ext.Array.each(tabs, function(tab) {
  5612. tab.disabled = true;
  5613. });
  5614. tabs[0].disabled = false;
  5615.  
  5616. var check_card = function(card) {
  5617. var valid = true;
  5618. var fields = card.query('field, fieldcontainer');
  5619. if (card.isXType('fieldcontainer')) {
  5620. fields.unshift(card);
  5621. }
  5622. Ext.Array.each(fields, function(field) {
  5623. // Note: not all fielcontainer have isValid()
  5624. if (Ext.isFunction(field.isValid) && !field.isValid()) {
  5625. valid = false;
  5626. }
  5627. });
  5628.  
  5629. if (Ext.isFunction(card.validator)) {
  5630. return card.validator();
  5631. }
  5632.  
  5633. return valid;
  5634. };
  5635.  
  5636.  
  5637. var tbar = Ext.create('Ext.toolbar.Toolbar', {
  5638. ui: 'footer',
  5639. region: 'south',
  5640. margins: '0 5 5 5',
  5641. items: [
  5642. '->',
  5643. {
  5644. text: gettext('Back'),
  5645. disabled: true,
  5646. itemId: 'back',
  5647. minWidth: 60,
  5648. handler: function() {
  5649. var tp = me.down('#wizcontent');
  5650. var atab = tp.getActiveTab();
  5651. var prev = tp.items.indexOf(atab) - 1;
  5652. if (prev < 0) {
  5653. return;
  5654. }
  5655. var ntab = tp.items.getAt(prev);
  5656. if (ntab) {
  5657. tp.setActiveTab(ntab);
  5658. }
  5659.  
  5660.  
  5661. }
  5662. },
  5663. {
  5664. text: gettext('Next'),
  5665. disabled: true,
  5666. itemId: 'next',
  5667. minWidth: 60,
  5668. handler: function() {
  5669.  
  5670. var form = me.down('form').getForm();
  5671.  
  5672. var tp = me.down('#wizcontent');
  5673. var atab = tp.getActiveTab();
  5674. if (!check_card(atab)) {
  5675. return;
  5676. }
  5677.  
  5678. var next = tp.items.indexOf(atab) + 1;
  5679. var ntab = tp.items.getAt(next);
  5680. if (ntab) {
  5681. ntab.enable();
  5682. tp.setActiveTab(ntab);
  5683. }
  5684.  
  5685. }
  5686. },
  5687. {
  5688. text: gettext('Finish'),
  5689. minWidth: 60,
  5690. hidden: true,
  5691. itemId: 'submit',
  5692. handler: function() {
  5693. var tp = me.down('#wizcontent');
  5694. var atab = tp.getActiveTab();
  5695. atab.onSubmit();
  5696. }
  5697. }
  5698. ]
  5699. });
  5700.  
  5701. var display_header = function(newcard) {
  5702. var html = '<h1>' + newcard.title + '</h1>';
  5703. if (newcard.descr) {
  5704. html += newcard.descr;
  5705. }
  5706. me.down('#header').update(html);
  5707. };
  5708.  
  5709. var disable_at = function(card) {
  5710. var tp = me.down('#wizcontent');
  5711. var idx = tp.items.indexOf(card);
  5712. for(;idx < tp.items.getCount();idx++) {
  5713. var nc = tp.items.getAt(idx);
  5714. if (nc) {
  5715. nc.disable();
  5716. }
  5717. }
  5718. };
  5719.  
  5720. var tabchange = function(tp, newcard, oldcard) {
  5721. if (newcard.onSubmit) {
  5722. me.down('#next').setVisible(false);
  5723. me.down('#submit').setVisible(true);
  5724. } else {
  5725. me.down('#next').setVisible(true);
  5726. me.down('#submit').setVisible(false);
  5727. }
  5728. var valid = check_card(newcard);
  5729. me.down('#next').setDisabled(!valid);
  5730. me.down('#submit').setDisabled(!valid);
  5731. me.down('#back').setDisabled(tp.items.indexOf(newcard) == 0);
  5732.  
  5733. if (oldcard && !check_card(oldcard)) {
  5734. disable_at(oldcard);
  5735. }
  5736.  
  5737. var next = tp.items.indexOf(newcard) + 1;
  5738. var ntab = tp.items.getAt(next);
  5739. if (valid && ntab && !newcard.onSubmit) {
  5740. ntab.enable();
  5741. }
  5742. };
  5743.  
  5744. if (me.subject && !me.title) {
  5745. me.title = PVE.Utils.dialog_title(me.subject, true, false);
  5746. }
  5747.  
  5748. Ext.applyIf(me, {
  5749. width: 620,
  5750. height: 400,
  5751. modal: true,
  5752. border: false,
  5753. draggable: true,
  5754. closable: true,
  5755. resizable: false,
  5756. layout: 'border',
  5757. items: [
  5758. {
  5759. // disabled for now - not really needed
  5760. hidden: true,
  5761. region: 'north',
  5762. itemId: 'header',
  5763. layout: 'fit',
  5764. margins: '5 5 0 5',
  5765. bodyPadding: 10,
  5766. html: ''
  5767. },
  5768. {
  5769. xtype: 'form',
  5770. region: 'center',
  5771. layout: 'fit',
  5772. border: false,
  5773. margins: '5 5 0 5',
  5774. fieldDefaults: {
  5775. labelWidth: 100,
  5776. anchor: '100%'
  5777. },
  5778. items: [{
  5779. itemId: 'wizcontent',
  5780. xtype: 'tabpanel',
  5781. activeItem: 0,
  5782. bodyPadding: 10,
  5783. listeners: {
  5784. afterrender: function(tp) {
  5785. var atab = this.getActiveTab();
  5786. tabchange(tp, atab);
  5787. },
  5788. tabchange: function(tp, newcard, oldcard) {
  5789. display_header(newcard);
  5790. tabchange(tp, newcard, oldcard);
  5791. }
  5792. },
  5793. items: tabs
  5794. }]
  5795. },
  5796. tbar
  5797. ]
  5798. });
  5799. me.callParent();
  5800. display_header(tabs[0]);
  5801.  
  5802. Ext.Array.each(me.query('field'), function(field) {
  5803. field.on('validitychange', function(f) {
  5804. var tp = me.down('#wizcontent');
  5805. var atab = tp.getActiveTab();
  5806. var valid = check_card(atab);
  5807. me.down('#next').setDisabled(!valid);
  5808. me.down('#submit').setDisabled(!valid);
  5809. var next = tp.items.indexOf(atab) + 1;
  5810. var ntab = tp.items.getAt(next);
  5811. if (!valid) {
  5812. disable_at(ntab);
  5813. } else if (ntab && !atab.onSubmit) {
  5814. ntab.enable();
  5815. }
  5816. });
  5817. });
  5818. }
  5819. });
  5820. Ext.define('PVE.window.NotesEdit', {
  5821. extend: 'PVE.window.Edit',
  5822.  
  5823. initComponent : function() {
  5824. var me = this;
  5825.  
  5826. Ext.apply(me, {
  5827. title: gettext('Notes'),
  5828. width: 600,
  5829. layout: 'fit',
  5830. items: {
  5831. xtype: 'textarea',
  5832. name: 'description',
  5833. rows: 7,
  5834. value: '',
  5835. hideLabel: true
  5836. }
  5837. });
  5838.  
  5839. me.callParent();
  5840.  
  5841. me.load();
  5842. }
  5843. });
  5844. Ext.define('PVE.window.Backup', {
  5845. extend: 'Ext.window.Window',
  5846.  
  5847. resizable: false,
  5848.  
  5849. initComponent : function() {
  5850. var me = this;
  5851.  
  5852. if (!me.nodename) {
  5853. throw "no node name specified";
  5854. }
  5855.  
  5856. if (!me.vmid) {
  5857. throw "no VM ID specified";
  5858. }
  5859.  
  5860. if (!me.vmtype) {
  5861. throw "no VM type specified";
  5862. }
  5863.  
  5864. var storagesel = Ext.create('PVE.form.StorageSelector', {
  5865. nodename: me.nodename,
  5866. name: 'storage',
  5867. value: me.storage,
  5868. fieldLabel: gettext('Storage'),
  5869. storageContent: 'backup',
  5870. allowBlank: false
  5871. });
  5872.  
  5873. me.formPanel = Ext.create('Ext.form.Panel', {
  5874. bodyPadding: 10,
  5875. border: false,
  5876. fieldDefaults: {
  5877. labelWidth: 100,
  5878. anchor: '100%'
  5879. },
  5880. items: [
  5881. storagesel,
  5882. {
  5883. xtype: 'pveBackupModeSelector',
  5884. fieldLabel: gettext('Mode'),
  5885. value: 'snapshot',
  5886. name: 'mode'
  5887. },
  5888. {
  5889. xtype: 'pveCompressionSelector',
  5890. name: 'compress',
  5891. value: 'lzo',
  5892. fieldLabel: gettext('Compression')
  5893. }
  5894. ]
  5895. });
  5896.  
  5897. var form = me.formPanel.getForm();
  5898.  
  5899. var submitBtn = Ext.create('Ext.Button', {
  5900. text: gettext('Backup'),
  5901. handler: function(){
  5902. var storage = storagesel.getValue();
  5903. var values = form.getValues();
  5904. var params = {
  5905. storage: storage,
  5906. vmid: me.vmid,
  5907. mode: values.mode,
  5908. remove: 0
  5909. };
  5910. if (values.compress) {
  5911. params.compress = values.compress;
  5912. }
  5913.  
  5914. PVE.Utils.API2Request({
  5915. url: '/nodes/' + me.nodename + '/vzdump',
  5916. params: params,
  5917. method: 'POST',
  5918. failure: function (response, opts) {
  5919. Ext.Msg.alert('Error',response.htmlStatus);
  5920. },
  5921. success: function(response, options) {
  5922. var upid = response.result.data;
  5923.  
  5924. var win = Ext.create('PVE.window.TaskViewer', {
  5925. upid: upid
  5926. });
  5927. win.show();
  5928. me.close();
  5929. }
  5930. });
  5931. }
  5932. });
  5933.  
  5934. var title = gettext('Backup') + " " +
  5935. ((me.vmtype === 'openvz') ? "CT" : "VM") +
  5936. " " + me.vmid;
  5937.  
  5938. Ext.apply(me, {
  5939. title: title,
  5940. width: 350,
  5941. modal: true,
  5942. layout: 'auto',
  5943. border: false,
  5944. items: [ me.formPanel ],
  5945. buttons: [ submitBtn ]
  5946. });
  5947.  
  5948. me.callParent();
  5949. }
  5950. });
  5951. Ext.define('PVE.window.Restore', {
  5952. extend: 'Ext.window.Window', // fixme: PVE.window.Edit?
  5953.  
  5954. resizable: false,
  5955.  
  5956. initComponent : function() {
  5957. var me = this;
  5958.  
  5959. if (!me.nodename) {
  5960. throw "no node name specified";
  5961. }
  5962.  
  5963. if (!me.volid) {
  5964. throw "no volume ID specified";
  5965. }
  5966.  
  5967. if (!me.vmtype) {
  5968. throw "no vmtype specified";
  5969. }
  5970.  
  5971. var storagesel = Ext.create('PVE.form.StorageSelector', {
  5972. nodename: me.nodename,
  5973. name: 'storage',
  5974. value: '',
  5975. fieldLabel: gettext('Storage'),
  5976. storageContent: (me.vmtype === 'openvz') ? 'rootdir' : 'images',
  5977. allowBlank: true
  5978. });
  5979.  
  5980. me.formPanel = Ext.create('Ext.form.Panel', {
  5981. bodyPadding: 10,
  5982. border: false,
  5983. fieldDefaults: {
  5984. labelWidth: 60,
  5985. anchor: '100%'
  5986. },
  5987. items: [
  5988. {
  5989. xtype: 'displayfield',
  5990. value: me.volidText || me.volid,
  5991. fieldLabel: gettext('Source')
  5992. },
  5993. storagesel,
  5994. {
  5995. xtype: me.vmid ? 'displayfield' : 'pveVMIDSelector',
  5996. name: 'vmid',
  5997. fieldLabel: 'VM ID',
  5998. value: me.vmid || PVE.data.ResourceStore.findNextVMID(),
  5999. validateExists: false
  6000. }
  6001. ]
  6002. });
  6003.  
  6004. var form = me.formPanel.getForm();
  6005.  
  6006. var doRestore = function(url, params) {
  6007. PVE.Utils.API2Request({
  6008. url: url,
  6009. params: params,
  6010. method: 'POST',
  6011. waitMsgTarget: me,
  6012. failure: function (response, opts) {
  6013. Ext.Msg.alert('Error', response.htmlStatus);
  6014. },
  6015. success: function(response, options) {
  6016. var upid = response.result.data;
  6017.  
  6018. var win = Ext.create('PVE.window.TaskViewer', {
  6019. upid: upid
  6020. });
  6021. win.show();
  6022. me.close();
  6023. }
  6024. });
  6025. };
  6026.  
  6027. var submitBtn = Ext.create('Ext.Button', {
  6028. text: gettext('Restore'),
  6029. handler: function(){
  6030. var storage = storagesel.getValue();
  6031. var values = form.getValues();
  6032.  
  6033. var params = {
  6034. storage: storage,
  6035. vmid: me.vmid || values.vmid,
  6036. force: me.vmid ? 1 : 0
  6037. };
  6038.  
  6039. var url;
  6040. if (me.vmtype === 'openvz') {
  6041. url = '/nodes/' + me.nodename + '/openvz';
  6042. params.ostemplate = me.volid;
  6043. params.restore = 1;
  6044. } else if (me.vmtype === 'qemu') {
  6045. url = '/nodes/' + me.nodename + '/qemu';
  6046. params.archive = me.volid;
  6047. } else {
  6048. throw 'unknown VM type';
  6049. }
  6050.  
  6051. if (me.vmid) {
  6052. var msg = gettext('Are you sure you want to restore this VM?') + ' ' +
  6053. gettext('This will permanently erase current VM data.');
  6054. Ext.Msg.confirm('Confirmation', msg, function(btn) {
  6055. if (btn !== 'yes') {
  6056. return;
  6057. }
  6058. doRestore(url, params);
  6059. });
  6060. } else {
  6061. doRestore(url, params);
  6062. }
  6063. }
  6064. });
  6065.  
  6066. form.on('validitychange', function(f, valid) {
  6067. submitBtn.setDisabled(!valid);
  6068. });
  6069.  
  6070. var title = (me.vmtype === 'openvz') ? "Restore CT" : "Restore VM";
  6071.  
  6072. Ext.apply(me, {
  6073. title: title,
  6074. width: 450,
  6075. modal: true,
  6076. layout: 'auto',
  6077. border: false,
  6078. items: [ me.formPanel ],
  6079. buttons: [ submitBtn ]
  6080. });
  6081.  
  6082. me.callParent();
  6083. }
  6084. });
  6085. Ext.define('PVE.panel.NotesView', {
  6086. extend: 'Ext.panel.Panel',
  6087.  
  6088. load: function() {
  6089. var me = this;
  6090.  
  6091. PVE.Utils.API2Request({
  6092. url: me.url,
  6093. waitMsgTarget: me,
  6094. failure: function(response, opts) {
  6095. me.update("Error " + response.htmlStatus);
  6096. },
  6097. success: function(response, opts) {
  6098. var data = response.result.data.description || '';
  6099. me.update(Ext.htmlEncode(data));
  6100. }
  6101. });
  6102. },
  6103.  
  6104. initComponent : function() {
  6105. var me = this;
  6106.  
  6107. var nodename = me.pveSelNode.data.node;
  6108. if (!nodename) {
  6109. throw "no node name specified";
  6110. }
  6111.  
  6112. var vmid = me.pveSelNode.data.vmid;
  6113. if (!vmid) {
  6114. throw "no VM ID specified";
  6115. }
  6116.  
  6117. var vmtype = me.pveSelNode.data.type;
  6118. var url;
  6119.  
  6120. if (vmtype === 'qemu') {
  6121. me.url = '/api2/extjs/nodes/' + nodename + '/qemu/' + vmid + '/config';
  6122. } else if (vmtype === 'openvz') {
  6123. me.url = '/api2/extjs/nodes/' + nodename + '/openvz/' + vmid + '/config';
  6124. } else {
  6125. throw "unknown vm type '" + vmtype + "'";
  6126. }
  6127.  
  6128. Ext.apply(me, {
  6129. title: gettext("Notes"),
  6130. style: 'padding-left:10px',
  6131. bodyStyle: 'white-space:pre',
  6132. bodyPadding: 10,
  6133. autoScroll: true,
  6134. listeners: {
  6135. render: function(c) {
  6136. c.el.on('dblclick', function() {
  6137. var win = Ext.create('PVE.window.NotesEdit', {
  6138. pveSelNode: me.pveSelNode,
  6139. url: me.url
  6140. });
  6141. win.show();
  6142. win.on('destroy', me.load, me);
  6143. });
  6144. }
  6145. }
  6146. });
  6147.  
  6148. me.callParent();
  6149. }
  6150. });
  6151. Ext.override(Ext.view.Table, {
  6152. afterRender: function() {
  6153. var me = this;
  6154.  
  6155. me.callParent();
  6156. me.mon(me.el, {
  6157. scroll: me.fireBodyScroll,
  6158. scope: me
  6159. });
  6160. if (!me.featuresMC ||
  6161. (me.featuresMC.findIndex('ftype', 'selectable') < 0)) {
  6162. me.el.unselectable();
  6163. }
  6164.  
  6165. me.attachEventsForFeatures();
  6166. }
  6167. });
  6168.  
  6169. Ext.define('PVE.grid.SelectFeature', {
  6170. extend: 'Ext.grid.feature.Feature',
  6171. alias: 'feature.selectable',
  6172.  
  6173. mutateMetaRowTpl: function(metaRowTpl) {
  6174. var tpl, i,
  6175. ln = metaRowTpl.length;
  6176.  
  6177. for (i = 0; i < ln; i++) {
  6178. tpl = metaRowTpl[i];
  6179. tpl = tpl.replace(/x-grid-row/, 'x-grid-row x-selectable');
  6180. tpl = tpl.replace(/x-grid-cell-inner x-unselectable/g, 'x-grid-cell-inner');
  6181. tpl = tpl.replace(/unselectable="on"/g, '');
  6182. metaRowTpl[i] = tpl;
  6183. }
  6184. }
  6185. });
  6186. Ext.define('PVE.grid.ObjectGrid', {
  6187. extend: 'Ext.grid.GridPanel',
  6188. alias: ['widget.pveObjectGrid'],
  6189.  
  6190. getObjectValue: function(key, defaultValue) {
  6191. var me = this;
  6192. var rec = me.store.getById(key);
  6193. if (rec) {
  6194. return rec.data.value;
  6195. }
  6196. return defaultValue;
  6197. },
  6198.  
  6199. renderKey: function(key, metaData, record, rowIndex, colIndex, store) {
  6200. var me = this;
  6201. var rows = me.rows;
  6202. var rowdef = (rows && rows[key]) ? rows[key] : {};
  6203. return rowdef.header || key;
  6204. },
  6205.  
  6206. renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
  6207. var me = this;
  6208. var rows = me.rows;
  6209. var key = record.data.key;
  6210. var rowdef = (rows && rows[key]) ? rows[key] : {};
  6211.  
  6212. var renderer = rowdef.renderer;
  6213. if (renderer) {
  6214. return renderer(value, metaData, record, rowIndex, colIndex, store);
  6215. }
  6216.  
  6217. return value;
  6218. },
  6219.  
  6220. initComponent : function() {
  6221. var me = this;
  6222.  
  6223. var rows = me.rows;
  6224.  
  6225. if (!me.rstore) {
  6226. if (!me.url) {
  6227. throw "no url specified";
  6228. }
  6229.  
  6230. me.rstore = Ext.create('PVE.data.ObjectStore', {
  6231. url: me.url,
  6232. interval: me.interval,
  6233. extraParams: me.extraParams,
  6234. rows: me.rows
  6235. });
  6236. }
  6237.  
  6238. var rstore = me.rstore;
  6239.  
  6240. var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  6241.  
  6242. if (rows) {
  6243. Ext.Object.each(rows, function(key, rowdef) {
  6244. if (Ext.isDefined(rowdef.defaultValue)) {
  6245. store.add({ key: key, value: rowdef.defaultValue });
  6246. } else if (rowdef.required) {
  6247. store.add({ key: key, value: undefined });
  6248. }
  6249. });
  6250. }
  6251.  
  6252. if (me.sorterFn) {
  6253. store.sorters.add(new Ext.util.Sorter({
  6254. sorterFn: me.sorterFn
  6255. }));
  6256. }
  6257.  
  6258. store.filters.add(new Ext.util.Filter({
  6259. filterFn: function(item) {
  6260. if (rows) {
  6261. var rowdef = rows[item.data.key];
  6262. if (!rowdef || (rowdef.visible === false)) {
  6263. return false;
  6264. }
  6265. }
  6266. return true;
  6267. }
  6268. }));
  6269.  
  6270. PVE.Utils.monStoreErrors(me, rstore);
  6271.  
  6272. Ext.applyIf(me, {
  6273. store: store,
  6274. hideHeaders: true,
  6275. stateful: false,
  6276. columns: [
  6277. {
  6278. header: 'Name',
  6279. width: me.cwidth1 || 100,
  6280. dataIndex: 'key',
  6281. renderer: me.renderKey
  6282. },
  6283. {
  6284. flex: 1,
  6285. header: 'Value',
  6286. dataIndex: 'value',
  6287. renderer: me.renderValue
  6288. }
  6289. ]
  6290. });
  6291.  
  6292. me.callParent();
  6293. }
  6294. });
  6295. // fixme: remove this fix
  6296. // this hack is required for ExtJS 4.0.0
  6297. Ext.override(Ext.grid.feature.Chunking, {
  6298. attachEvents: function() {
  6299. var grid = this.view.up('gridpanel'),
  6300. scroller = grid.down('gridscroller[dock=right]');
  6301. if (scroller === null ) {
  6302. grid.on("afterlayout", this.attachEvents, this);
  6303. return;
  6304. }
  6305. scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
  6306. },
  6307. rowHeight: PVE.Utils.gridLineHeigh()
  6308. });
  6309.  
  6310. Ext.define('PVE.grid.ResourceGrid', {
  6311. extend: 'Ext.grid.GridPanel',
  6312. alias: ['widget.pveResourceGrid'],
  6313.  
  6314. //fixme: this makes still problems with the scrollbar
  6315. //features: [ {ftype: 'chunking'}],
  6316.  
  6317. initComponent : function() {
  6318. var me = this;
  6319.  
  6320. var rstore = PVE.data.ResourceStore;
  6321. var sp = Ext.state.Manager.getProvider();
  6322.  
  6323. var coldef = rstore.defaultColums();
  6324.  
  6325. var store = Ext.create('Ext.data.Store', {
  6326. model: 'PVEResources',
  6327. sorters: [
  6328. {
  6329. property : 'type',
  6330. direction: 'ASC'
  6331. }
  6332. ],
  6333. proxy: { type: 'memory' }
  6334. });
  6335.  
  6336. var textfilter = '';
  6337.  
  6338. var textfilter_match = function(item) {
  6339. var match = false;
  6340. Ext.each(['name', 'storage', 'node', 'type', 'text'], function(field) {
  6341. var v = item.data[field];
  6342. if (v !== undefined) {
  6343. v = v.toLowerCase();
  6344. if (v.indexOf(textfilter) >= 0) {
  6345. match = true;
  6346. return false;
  6347. }
  6348. }
  6349. });
  6350. return match;
  6351. };
  6352.  
  6353. var updateGrid = function() {
  6354.  
  6355. var filterfn = me.viewFilter ? me.viewFilter.filterfn : null;
  6356.  
  6357. //console.log("START GRID UPDATE " + me.viewFilter);
  6358.  
  6359. store.suspendEvents();
  6360.  
  6361. var nodeidx = {};
  6362. var gather_child_nodes = function(cn) {
  6363. if (!cn) {
  6364. return;
  6365. }
  6366. var cs = cn.childNodes;
  6367. if (!cs) {
  6368. return;
  6369. }
  6370. var len = cs.length, i = 0, n, res;
  6371.  
  6372. for (; i < len; i++) {
  6373. var child = cs[i];
  6374. var orgnode = rstore.data.get(child.data.id);
  6375. if (orgnode) {
  6376. if ((!filterfn || filterfn(child)) &&
  6377. (!textfilter || textfilter_match(child))) {
  6378. nodeidx[child.data.id] = orgnode;
  6379. }
  6380. }
  6381. gather_child_nodes(child);
  6382. }
  6383. };
  6384. gather_child_nodes(me.pveSelNode);
  6385.  
  6386. // remove vanished items
  6387. var rmlist = [];
  6388. store.each(function(olditem) {
  6389. var item = nodeidx[olditem.data.id];
  6390. if (!item) {
  6391. //console.log("GRID REM UID: " + olditem.data.id);
  6392. rmlist.push(olditem);
  6393. }
  6394. });
  6395.  
  6396. if (rmlist.length) {
  6397. store.remove(rmlist);
  6398. }
  6399.  
  6400. // add new items
  6401. var addlist = [];
  6402. var key;
  6403. for (key in nodeidx) {
  6404. if (nodeidx.hasOwnProperty(key)) {
  6405. var item = nodeidx[key];
  6406.  
  6407. // getById() use find(), which is slow (ExtJS4 DP5)
  6408. //var olditem = store.getById(item.data.id);
  6409. var olditem = store.data.get(item.data.id);
  6410.  
  6411. if (!olditem) {
  6412. //console.log("GRID ADD UID: " + item.data.id);
  6413. var info = Ext.apply({}, item.data);
  6414. var child = Ext.ModelMgr.create(info, store.model, info.id);
  6415. addlist.push(item);
  6416. continue;
  6417. }
  6418. // try to detect changes
  6419. var changes = false;
  6420. var fieldkeys = PVE.data.ResourceStore.fieldNames;
  6421. var fieldcount = fieldkeys.length;
  6422. var fieldind;
  6423. for (fieldind = 0; fieldind < fieldcount; fieldind++) {
  6424. var field = fieldkeys[fieldind];
  6425. if (field != 'id' && item.data[field] != olditem.data[field]) {
  6426. changes = true;
  6427. //console.log("changed item " + item.id + " " + field + " " + item.data[field] + " != " + olditem.data[field]);
  6428. olditem.beginEdit();
  6429. olditem.set(field, item.data[field]);
  6430. }
  6431. }
  6432. if (changes) {
  6433. olditem.endEdit(true);
  6434. olditem.commit(true);
  6435. }
  6436. }
  6437. }
  6438.  
  6439. if (addlist.length) {
  6440. store.add(addlist);
  6441. }
  6442.  
  6443. store.sort();
  6444.  
  6445. store.resumeEvents();
  6446.  
  6447. store.fireEvent('datachanged', store);
  6448.  
  6449. //console.log("END GRID UPDATE");
  6450. };
  6451.  
  6452. var filter_task = new Ext.util.DelayedTask(function(){
  6453. updateGrid();
  6454. });
  6455.  
  6456. var load_cb = function() {
  6457. updateGrid();
  6458. };
  6459.  
  6460. Ext.applyIf(me, {
  6461. title: gettext('Search')
  6462. });
  6463.  
  6464. Ext.apply(me, {
  6465. store: store,
  6466. tbar: [
  6467. '->',
  6468. gettext('Search') + ':', ' ',
  6469. {
  6470. xtype: 'textfield',
  6471. width: 200,
  6472. value: textfilter,
  6473. enableKeyEvents: true,
  6474. listeners: {
  6475. keyup: function(field, e) {
  6476. var v = field.getValue();
  6477. textfilter = v;
  6478. filter_task.delay(500);
  6479. }
  6480. }
  6481. }
  6482. ],
  6483. viewConfig: {
  6484. stripeRows: true
  6485. },
  6486. listeners: {
  6487. itemcontextmenu: function(v, record, item, index, event) {
  6488. event.stopEvent();
  6489. v.select(record);
  6490. var menu;
  6491.  
  6492. if (record.data.type === 'qemu') {
  6493. menu = Ext.create('PVE.qemu.CmdMenu', {
  6494. pveSelNode: record
  6495. });
  6496. } else if (record.data.type === 'openvz') {
  6497. menu = Ext.create('PVE.openvz.CmdMenu', {
  6498. pveSelNode: record
  6499. });
  6500. } else {
  6501. return;
  6502. }
  6503.  
  6504. menu.showAt(event.getXY());
  6505. },
  6506. itemdblclick: function(v, record) {
  6507. var ws = me.up('pveStdWorkspace');
  6508. ws.selectById(record.data.id);
  6509. },
  6510. destroy: function() {
  6511. rstore.un("load", load_cb);
  6512. }
  6513. },
  6514. columns: coldef
  6515. });
  6516.  
  6517. me.callParent();
  6518.  
  6519. updateGrid();
  6520. rstore.on("load", load_cb);
  6521. }
  6522. });Ext.define('PVE.pool.AddVM', {
  6523. extend: 'PVE.window.Edit',
  6524.  
  6525. initComponent : function() {
  6526. /*jslint confusion: true */
  6527. var me = this;
  6528.  
  6529. if (!me.pool) {
  6530. throw "no pool specified";
  6531. }
  6532.  
  6533. me.create = true;
  6534. me.isAdd = true;
  6535. me.url = "/pools/" + me.pool;
  6536. me.method = 'PUT';
  6537.  
  6538. Ext.apply(me, {
  6539. subject: gettext('Virtual Machine'),
  6540. width: 350,
  6541. items: [
  6542. {
  6543. xtype: 'pveVMIDSelector',
  6544. name: 'vms',
  6545. validateExists: true,
  6546. value: '',
  6547. fieldLabel: "VM ID"
  6548. }
  6549. ]
  6550. });
  6551.  
  6552. me.callParent();
  6553. }
  6554. });
  6555.  
  6556. Ext.define('PVE.pool.AddStorage', {
  6557. extend: 'PVE.window.Edit',
  6558.  
  6559. initComponent : function() {
  6560. /*jslint confusion: true */
  6561. var me = this;
  6562.  
  6563. if (!me.pool) {
  6564. throw "no pool specified";
  6565. }
  6566.  
  6567. me.create = true;
  6568. me.isAdd = true;
  6569. me.url = "/pools/" + me.pool;
  6570. me.method = 'PUT';
  6571.  
  6572. Ext.apply(me, {
  6573. subject: gettext('Storage'),
  6574. width: 350,
  6575. items: [
  6576. {
  6577. xtype: 'PVE.form.StorageSelector',
  6578. name: 'storage',
  6579. nodename: 'localhost',
  6580. autoSelect: false,
  6581. value: '',
  6582. fieldLabel: gettext("Storage")
  6583. }
  6584. ]
  6585. });
  6586.  
  6587. me.callParent();
  6588. }
  6589. });
  6590.  
  6591. Ext.define('PVE.grid.PoolMembers', {
  6592. extend: 'Ext.grid.GridPanel',
  6593. alias: ['widget.pvePoolMembers'],
  6594.  
  6595. // fixme: dynamic status update ?
  6596.  
  6597. initComponent : function() {
  6598. var me = this;
  6599.  
  6600. if (!me.pool) {
  6601. throw "no pool specified";
  6602. }
  6603.  
  6604. var store = Ext.create('Ext.data.Store', {
  6605. model: 'PVEResources',
  6606. sorters: [
  6607. {
  6608. property : 'type',
  6609. direction: 'ASC'
  6610. }
  6611. ],
  6612. proxy: {
  6613. type: 'pve',
  6614. root: 'data.members',
  6615. url: "/api2/json/pools/" + me.pool
  6616. }
  6617. });
  6618.  
  6619. var coldef = PVE.data.ResourceStore.defaultColums();
  6620.  
  6621. var reload = function() {
  6622. store.load();
  6623. };
  6624.  
  6625. var sm = Ext.create('Ext.selection.RowModel', {});
  6626.  
  6627. var remove_btn = new PVE.button.Button({
  6628. text: gettext('Remove'),
  6629. disabled: true,
  6630. selModel: sm,
  6631. confirmMsg: function (rec) {
  6632. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  6633. "'" + rec.data.id + "'");
  6634. },
  6635. handler: function(btn, event, rec) {
  6636. var params = { 'delete': 1 };
  6637. if (rec.data.type === 'storage') {
  6638. params.storage = rec.data.storage;
  6639. } else if (rec.data.type === 'qemu' || rec.data.type === 'openvz') {
  6640. params.vms = rec.data.vmid;
  6641. } else {
  6642. throw "unknown resource type";
  6643. }
  6644.  
  6645. PVE.Utils.API2Request({
  6646. url: '/pools/' + me.pool,
  6647. method: 'PUT',
  6648. params: params,
  6649. waitMsgTarget: me,
  6650. callback: function() {
  6651. reload();
  6652. },
  6653. failure: function (response, opts) {
  6654. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  6655. }
  6656. });
  6657. }
  6658. });
  6659.  
  6660. Ext.apply(me, {
  6661. store: store,
  6662. selModel: sm,
  6663. tbar: [
  6664. {
  6665. text: gettext('Add'),
  6666. menu: new Ext.menu.Menu({
  6667. items: [
  6668. {
  6669. text: gettext('Virtual Machine'),
  6670. iconCls: 'pve-itype-icon-qemu',
  6671. handler: function() {
  6672. var win = Ext.create('PVE.pool.AddVM', { pool: me.pool });
  6673. win.on('destroy', reload);
  6674. win.show();
  6675. }
  6676. },
  6677. {
  6678. text: gettext('Storage'),
  6679. iconCls: 'pve-itype-icon-storage',
  6680. handler: function() {
  6681. var win = Ext.create('PVE.pool.AddStorage', { pool: me.pool });
  6682. win.on('destroy', reload);
  6683. win.show();
  6684. }
  6685. }
  6686. ]
  6687. })
  6688. },
  6689. remove_btn
  6690. ],
  6691. viewConfig: {
  6692. stripeRows: true
  6693. },
  6694. columns: coldef,
  6695. listeners: {
  6696. show: reload
  6697. }
  6698. });
  6699.  
  6700. me.callParent();
  6701. }
  6702. });Ext.define('PVE.tree.ResourceTree', {
  6703. extend: 'Ext.tree.TreePanel',
  6704. alias: ['widget.pveResourceTree'],
  6705.  
  6706. statics: {
  6707. typeDefaults: {
  6708. node: {
  6709. iconCls: 'x-tree-node-server',
  6710. text: gettext('Node list')
  6711. },
  6712. pool: {
  6713. iconCls: 'x-tree-node-pool',
  6714. text: gettext('Resource Pool')
  6715. },
  6716. storage: {
  6717. iconCls: 'x-tree-node-harddisk',
  6718. text: gettext('Storage list')
  6719. },
  6720. qemu: {
  6721. iconCls: 'x-tree-node-computer',
  6722. text: gettext('Virtual Machine')
  6723. },
  6724. openvz: {
  6725. iconCls: 'x-tree-node-openvz',
  6726. text: gettext('OpenVZ Container')
  6727. }
  6728. }
  6729. },
  6730.  
  6731. // private
  6732. nodeSortFn: function(node1, node2) {
  6733. var n1 = node1.data;
  6734. var n2 = node2.data;
  6735.  
  6736. if ((n1.groupbyid && n2.groupbyid) ||
  6737. !(n1.groupbyid || n2.groupbyid)) {
  6738.  
  6739. var tcmp;
  6740.  
  6741. var v1 = n1.type;
  6742. var v2 = n2.type;
  6743.  
  6744. if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
  6745. return tcmp;
  6746. }
  6747.  
  6748. // numeric compare for VM IDs
  6749. if (v1 === 'qemu' || v1 === 'openvz') {
  6750. v1 = n1.vmid;
  6751. v2 = n2.vmid;
  6752. if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
  6753. return tcmp;
  6754. }
  6755. }
  6756.  
  6757. return n1.text > n2.text ? 1 : (n1.text < n2.text ? -1 : 0);
  6758. } else if (n1.groupbyid) {
  6759. return -1;
  6760. } else if (n2.groupbyid) {
  6761. return 1;
  6762. }
  6763. },
  6764.  
  6765. // private: fast binary search
  6766. findInsertIndex: function(node, child, start, end) {
  6767. var me = this;
  6768.  
  6769. var diff = end - start;
  6770.  
  6771. var mid = start + (diff>>1);
  6772.  
  6773. if (diff <= 0) {
  6774. return start;
  6775. }
  6776.  
  6777. var res = me.nodeSortFn(child, node.childNodes[mid]);
  6778. if (res <= 0) {
  6779. return me.findInsertIndex(node, child, start, mid);
  6780. } else {
  6781. return me.findInsertIndex(node, child, mid + 1, end);
  6782. }
  6783. },
  6784.  
  6785. setIconCls: function(info) {
  6786. var me = this;
  6787.  
  6788. var defaults = PVE.tree.ResourceTree.typeDefaults[info.type];
  6789. if (defaults && defaults.iconCls) {
  6790. if (info.running) {
  6791. info.iconCls = defaults.iconCls + "-running";
  6792. } else {
  6793. info.iconCls = defaults.iconCls;
  6794. }
  6795. }
  6796. },
  6797.  
  6798. // private
  6799. addChildSorted: function(node, info) {
  6800. var me = this;
  6801.  
  6802. me.setIconCls(info);
  6803.  
  6804. var defaults;
  6805. if (info.groupbyid) {
  6806. info.text = info.groupbyid;
  6807. if (info.type === 'type') {
  6808. defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
  6809. if (defaults && defaults.text) {
  6810. info.text = defaults.text;
  6811. }
  6812. }
  6813. }
  6814. var child = Ext.ModelMgr.create(info, 'PVETree', info.id);
  6815.  
  6816. var cs = node.childNodes;
  6817. var pos;
  6818. if (cs) {
  6819. pos = cs[me.findInsertIndex(node, child, 0, cs.length)];
  6820. }
  6821.  
  6822. node.insertBefore(child, pos);
  6823.  
  6824. return child;
  6825. },
  6826.  
  6827. // private
  6828. groupChild: function(node, info, groups, level) {
  6829. var me = this;
  6830.  
  6831. var groupby = groups[level];
  6832. var v = info[groupby];
  6833.  
  6834. if (v) {
  6835. var group = node.findChild('groupbyid', v);
  6836. if (!group) {
  6837. var groupinfo;
  6838. if (info.type === groupby) {
  6839. groupinfo = info;
  6840. } else {
  6841. groupinfo = {
  6842. type: groupby,
  6843. id : groupby + "/" + v
  6844. };
  6845. if (groupby !== 'type') {
  6846. groupinfo[groupby] = v;
  6847. }
  6848. }
  6849. groupinfo.leaf = false;
  6850. groupinfo.groupbyid = v;
  6851. group = me.addChildSorted(node, groupinfo);
  6852. // fixme: remove when EXTJS has fixed those bugs?!
  6853. group.expand(); group.collapse();
  6854. }
  6855. if (info.type === groupby) {
  6856. return group;
  6857. }
  6858. if (group) {
  6859. return me.groupChild(group, info, groups, level + 1);
  6860. }
  6861. }
  6862.  
  6863. return me.addChildSorted(node, info);
  6864. },
  6865.  
  6866. initComponent : function() {
  6867. var me = this;
  6868.  
  6869. var rstore = PVE.data.ResourceStore;
  6870. var sp = Ext.state.Manager.getProvider();
  6871.  
  6872. if (!me.viewFilter) {
  6873. me.viewFilter = {};
  6874. }
  6875.  
  6876. var pdata = {
  6877. dataIndex: {},
  6878. updateCount: 0
  6879. };
  6880.  
  6881. var store = Ext.create('Ext.data.TreeStore', {
  6882. model: 'PVETree',
  6883. root: {
  6884. expanded: true,
  6885. id: 'root',
  6886. text: gettext('Datacenter')
  6887. }
  6888. });
  6889.  
  6890. var stateid = 'rid';
  6891.  
  6892. var updateTree = function() {
  6893. var tmp;
  6894.  
  6895. // fixme: suspend events ?
  6896.  
  6897. var rootnode = me.store.getRootNode();
  6898.  
  6899. // remember selected node (and all parents)
  6900. var sm = me.getSelectionModel();
  6901.  
  6902. var lastsel = sm.getSelection()[0];
  6903. var parents = [];
  6904. var p = lastsel;
  6905. while (p && !!(p = p.parentNode)) {
  6906. parents.push(p);
  6907. }
  6908.  
  6909. var index = pdata.dataIndex;
  6910.  
  6911. var groups = me.viewFilter.groups || [];
  6912. var filterfn = me.viewFilter.filterfn;
  6913.  
  6914. // remove vanished or changed items
  6915. var key;
  6916. for (key in index) {
  6917. if (index.hasOwnProperty(key)) {
  6918. var olditem = index[key];
  6919.  
  6920. // getById() use find(), which is slow (ExtJS4 DP5)
  6921. //var item = rstore.getById(olditem.data.id);
  6922. var item = rstore.data.get(olditem.data.id);
  6923.  
  6924. var changed = false;
  6925. if (item) {
  6926. // test if any grouping attributes changed
  6927. var i, len;
  6928. for (i = 0, len = groups.length; i < len; i++) {
  6929. var attr = groups[i];
  6930. if (item.data[attr] != olditem.data[attr]) {
  6931. //console.log("changed " + attr);
  6932. changed = true;
  6933. break;
  6934. }
  6935. }
  6936. if ((item.data.text !== olditem.data.text) ||
  6937. (item.data.node !== olditem.data.node) ||
  6938. (item.data.running !== olditem.data.running)) {
  6939. //console.log("changed node/text/running " + olditem.data.id);
  6940. changed = true;
  6941. }
  6942.  
  6943. // fixme: also test filterfn()?
  6944. }
  6945.  
  6946. if (!item || changed) {
  6947. //console.log("REM UID: " + key + " ITEM " + olditem.data.id);
  6948. if (olditem.isLeaf()) {
  6949. delete index[key];
  6950. var parentNode = olditem.parentNode;
  6951. parentNode.removeChild(olditem, true);
  6952. } else {
  6953. if (item && changed) {
  6954. olditem.beginEdit();
  6955. //console.log("REM UPDATE UID: " + key + " ITEM " + item.data.running);
  6956. var info = olditem.data;
  6957. Ext.apply(info, item.data);
  6958. me.setIconCls(info);
  6959. olditem.commit();
  6960. }
  6961. }
  6962. }
  6963. }
  6964. }
  6965.  
  6966. // add new items
  6967. rstore.each(function(item) {
  6968. var olditem = index[item.data.id];
  6969. if (olditem) {
  6970. return;
  6971. }
  6972.  
  6973. if (filterfn && !filterfn(item)) {
  6974. return;
  6975. }
  6976.  
  6977. //console.log("ADD UID: " + item.data.id);
  6978.  
  6979. var info = Ext.apply({ leaf: true }, item.data);
  6980.  
  6981. var child = me.groupChild(rootnode, info, groups, 0);
  6982. if (child) {
  6983. index[item.data.id] = child;
  6984. }
  6985. });
  6986.  
  6987. // select parent node is selection vanished
  6988. if (lastsel && !rootnode.findChild('id', lastsel.data.id, true)) {
  6989. lastsel = rootnode;
  6990. while (!!(p = parents.shift())) {
  6991. if (!!(tmp = rootnode.findChild('id', p.data.id, true))) {
  6992. lastsel = tmp;
  6993. break;
  6994. }
  6995. }
  6996. me.selectById(lastsel.data.id);
  6997. }
  6998.  
  6999. if (!pdata.updateCount) {
  7000. rootnode.collapse();
  7001. rootnode.expand();
  7002. me.applyState(sp.get(stateid));
  7003. }
  7004.  
  7005. pdata.updateCount++;
  7006. };
  7007.  
  7008. var statechange = function(sp, key, value) {
  7009. if (key === stateid) {
  7010. me.applyState(value);
  7011. }
  7012. };
  7013.  
  7014. sp.on('statechange', statechange);
  7015.  
  7016. Ext.apply(me, {
  7017. store: store,
  7018. viewConfig: {
  7019. // note: animate cause problems with applyState
  7020. animate: false
  7021. },
  7022. //useArrows: true,
  7023. //rootVisible: false,
  7024. //title: 'Resource Tree',
  7025. listeners: {
  7026. itemcontextmenu: function(v, record, item, index, event) {
  7027. event.stopEvent();
  7028. //v.select(record);
  7029. var menu;
  7030.  
  7031. if (record.data.type === 'qemu') {
  7032. menu = Ext.create('PVE.qemu.CmdMenu', {
  7033. pveSelNode: record
  7034. });
  7035. } else if (record.data.type === 'openvz') {
  7036. menu = Ext.create('PVE.openvz.CmdMenu', {
  7037. pveSelNode: record
  7038. });
  7039. } else {
  7040. return;
  7041. }
  7042.  
  7043. menu.showAt(event.getXY());
  7044. },
  7045. destroy: function() {
  7046. rstore.un("load", updateTree);
  7047. }
  7048. },
  7049. setViewFilter: function(view) {
  7050. me.viewFilter = view;
  7051. me.clearTree();
  7052. updateTree();
  7053. },
  7054. clearTree: function() {
  7055. pdata.updateCount = 0;
  7056. var rootnode = me.store.getRootNode();
  7057. rootnode.collapse();
  7058. rootnode.removeAll(true);
  7059. pdata.dataIndex = {};
  7060. me.getSelectionModel().deselectAll();
  7061. },
  7062. selectExpand: function(node) {
  7063. var sm = me.getSelectionModel();
  7064. if (!sm.isSelected(node)) {
  7065. sm.select(node);
  7066. var cn = node;
  7067. while (!!(cn = cn.parentNode)) {
  7068. if (!cn.isExpanded()) {
  7069. cn.expand();
  7070. }
  7071. }
  7072. }
  7073. },
  7074. selectById: function(nodeid) {
  7075. var rootnode = me.store.getRootNode();
  7076. var sm = me.getSelectionModel();
  7077. var node;
  7078. if (nodeid === 'root') {
  7079. node = rootnode;
  7080. } else {
  7081. node = rootnode.findChild('id', nodeid, true);
  7082. }
  7083. if (node) {
  7084. me.selectExpand(node);
  7085. }
  7086. },
  7087. checkVmMigration: function(record) {
  7088. if (!(record.data.type === 'qemu' || record.data.type === 'openvz')) {
  7089. throw "not a vm type";
  7090. }
  7091.  
  7092. var rootnode = me.store.getRootNode();
  7093. var node = rootnode.findChild('id', record.data.id, true);
  7094.  
  7095. if (node && node.data.type === record.data.type &&
  7096. node.data.node !== record.data.node) {
  7097. // defer select (else we get strange errors)
  7098. Ext.defer(function() { me.selectExpand(node); }, 100, me);
  7099. }
  7100. },
  7101. applyState : function(state) {
  7102. var sm = me.getSelectionModel();
  7103. if (state && state.value) {
  7104. me.selectById(state.value);
  7105. } else {
  7106. sm.deselectAll();
  7107. }
  7108. }
  7109. });
  7110.  
  7111. me.callParent();
  7112.  
  7113. var sm = me.getSelectionModel();
  7114. sm.on('select', function(sm, n) {
  7115. sp.set(stateid, { value: n.data.id});
  7116. });
  7117.  
  7118. rstore.on("load", updateTree);
  7119. rstore.startUpdate();
  7120. //rstore.stopUpdate();
  7121. }
  7122.  
  7123. });
  7124. Ext.define('PVE.panel.Config', {
  7125. extend: 'Ext.panel.Panel',
  7126. alias: 'widget.pvePanelConfig',
  7127.  
  7128. initComponent: function() {
  7129. var me = this;
  7130.  
  7131. var stateid = me.hstateid;
  7132.  
  7133. var sp = Ext.state.Manager.getProvider();
  7134.  
  7135. var activeTab;
  7136.  
  7137. if (stateid) {
  7138. var state = sp.get(stateid);
  7139. if (state && state.value) {
  7140. activeTab = state.value;
  7141. }
  7142. }
  7143.  
  7144. var items = me.items || [];
  7145. me.items = undefined;
  7146.  
  7147. var tbar = me.tbar || [];
  7148. me.tbar = undefined;
  7149.  
  7150. var title = me.title || me.pveSelNode.data.text;
  7151. me.title = undefined;
  7152.  
  7153. tbar.unshift('->');
  7154. tbar.unshift({
  7155. xtype: 'tbtext',
  7156. text: title,
  7157. baseCls: 'x-panel-header-text',
  7158. padding: '0 0 5 0'
  7159. });
  7160.  
  7161. Ext.applyIf(me, { showSearch: true });
  7162.  
  7163. if (me.showSearch) {
  7164. items.unshift({
  7165. itemId: 'search',
  7166. xtype: 'pveResourceGrid'
  7167. });
  7168. }
  7169.  
  7170. var toolbar = Ext.create('Ext.toolbar.Toolbar', {
  7171. items: tbar,
  7172. style: 'border:0px;',
  7173. height: 28
  7174. });
  7175.  
  7176. var tab = Ext.create('Ext.tab.Panel', {
  7177. flex: 1,
  7178. border: true,
  7179. activeTab: activeTab,
  7180. defaults: Ext.apply(me.defaults || {}, {
  7181. pveSelNode: me.pveSelNode,
  7182. viewFilter: me.viewFilter,
  7183. workspace: me.workspace,
  7184. border: false
  7185. }),
  7186. items: items,
  7187. listeners: {
  7188. afterrender: function(tp) {
  7189. var first = tp.items.get(0);
  7190. if (first) {
  7191. first.fireEvent('show', first);
  7192. }
  7193. },
  7194. tabchange: function(tp, newcard, oldcard) {
  7195. var ntab = newcard.itemId;
  7196. // Note: '' is alias for first tab.
  7197. // First tab can be 'search' or something else
  7198. if (newcard.itemId === items[0].itemId) {
  7199. ntab = '';
  7200. }
  7201. var state = { value: ntab };
  7202. if (stateid) {
  7203. sp.set(stateid, state);
  7204. }
  7205. }
  7206. }
  7207. });
  7208.  
  7209. Ext.apply(me, {
  7210. layout: { type: 'vbox', align: 'stretch' },
  7211. items: [ toolbar, tab]
  7212. });
  7213.  
  7214. me.callParent();
  7215.  
  7216. var statechange = function(sp, key, state) {
  7217. if (stateid && key === stateid) {
  7218. var atab = tab.getActiveTab().itemId;
  7219. var ntab = state.value || items[0].itemId;
  7220. if (state && ntab && (atab != ntab)) {
  7221. tab.setActiveTab(ntab);
  7222. }
  7223. }
  7224. };
  7225.  
  7226. if (stateid) {
  7227. me.mon(sp, 'statechange', statechange);
  7228. }
  7229. }
  7230. });
  7231. Ext.define('PVE.grid.BackupView', {
  7232. extend: 'Ext.grid.GridPanel',
  7233.  
  7234. alias: ['widget.pveBackupView'],
  7235.  
  7236.  
  7237. initComponent : function() {
  7238. var me = this;
  7239.  
  7240. var nodename = me.pveSelNode.data.node;
  7241. if (!nodename) {
  7242. throw "no node name specified";
  7243. }
  7244.  
  7245. var vmid = me.pveSelNode.data.vmid;
  7246. if (!vmid) {
  7247. throw "no VM ID specified";
  7248. }
  7249.  
  7250. var vmtype = me.pveSelNode.data.type;
  7251. if (!vmtype) {
  7252. throw "no VM type specified";
  7253. }
  7254.  
  7255. var filterFn;
  7256. if (vmtype === 'openvz') {
  7257. filterFn = function(item) {
  7258. return item.data.volid.match(':backup/vzdump-openvz-');
  7259. };
  7260. } else if (vmtype === 'qemu') {
  7261. filterFn = function(item) {
  7262. return item.data.volid.match(':backup/vzdump-qemu-');
  7263. };
  7264. } else {
  7265. throw "unsupported VM type '" + vmtype + "'";
  7266. }
  7267.  
  7268. me.store = Ext.create('Ext.data.Store', {
  7269. model: 'pve-storage-content',
  7270. sorters: {
  7271. property: 'volid',
  7272. order: 'DESC'
  7273. },
  7274. filters: { filterFn: filterFn }
  7275. });
  7276.  
  7277. var reload = Ext.Function.createBuffered(function() {
  7278. if (me.store.proxy.url) {
  7279. me.store.load();
  7280. }
  7281. }, 100);
  7282.  
  7283. var setStorage = function(storage) {
  7284. var url = '/api2/json/nodes/' + nodename + '/storage/' + storage + '/content';
  7285. url += '?content=backup';
  7286.  
  7287. me.store.setProxy({
  7288. type: 'pve',
  7289. url: url
  7290. });
  7291.  
  7292. reload();
  7293. };
  7294.  
  7295. var storagesel = Ext.create('PVE.form.StorageSelector', {
  7296. nodename: nodename,
  7297. fieldLabel: gettext('Storage'),
  7298. labelAlign: 'right',
  7299. storageContent: 'backup',
  7300. allowBlank: false,
  7301. listeners: {
  7302. change: function(f, value) {
  7303. setStorage(value);
  7304. }
  7305. }
  7306. });
  7307.  
  7308. var sm = Ext.create('Ext.selection.RowModel', {});
  7309.  
  7310. var backup_btn = Ext.create('Ext.button.Button', {
  7311. text: gettext('Backup now'),
  7312. handler: function() {
  7313. var win = Ext.create('PVE.window.Backup', {
  7314. nodename: nodename,
  7315. vmid: vmid,
  7316. vmtype: vmtype,
  7317. storage: storagesel.getValue()
  7318. });
  7319. win.show();
  7320. }
  7321. });
  7322.  
  7323. var restore_btn = Ext.create('PVE.button.Button', {
  7324. text: gettext('Restore'),
  7325. disabled: true,
  7326. selModel: sm,
  7327. enableFn: function(rec) {
  7328. return !!rec;
  7329. },
  7330. handler: function(b, e, rec) {
  7331. var volid = rec.data.volid;
  7332.  
  7333. var win = Ext.create('PVE.window.Restore', {
  7334. nodename: nodename,
  7335. vmid: vmid,
  7336. volid: rec.data.volid,
  7337. volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
  7338. vmtype: vmtype
  7339. });
  7340. win.show();
  7341. win.on('destroy', reload);
  7342. }
  7343. });
  7344.  
  7345. var delete_btn = Ext.create('PVE.button.Button', {
  7346. text: gettext('Remove'),
  7347. disabled: true,
  7348. selModel: sm,
  7349. dangerous: true,
  7350. confirmMsg: function(rec) {
  7351. var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  7352. "'" + rec.data.volid + "'");
  7353. msg += " " + gettext('This will permanently erase all image data.');
  7354.  
  7355. return msg;
  7356. },
  7357. enableFn: function(rec) {
  7358. return !!rec;
  7359. },
  7360. handler: function(b, e, rec){
  7361. var storage = storagesel.getValue();
  7362. if (!storage) {
  7363. return;
  7364. }
  7365.  
  7366. var volid = rec.data.volid;
  7367. PVE.Utils.API2Request({
  7368. url: "/nodes/" + nodename + "/storage/" + storage + "/content/" + volid,
  7369. method: 'DELETE',
  7370. waitMsgTarget: me,
  7371. failure: function(response, opts) {
  7372. Ext.Msg.alert('Error', response.htmlStatus);
  7373. },
  7374. success: function(response, options) {
  7375. reload();
  7376. }
  7377. });
  7378. }
  7379. });
  7380.  
  7381. Ext.apply(me, {
  7382. stateful: false,
  7383. selModel: sm,
  7384. tbar: [ backup_btn, restore_btn, delete_btn, '->', storagesel ],
  7385. columns: [
  7386. {
  7387. header: gettext('Name'),
  7388. flex: 1,
  7389. sortable: true,
  7390. renderer: PVE.Utils.render_storage_content,
  7391. dataIndex: 'volid'
  7392. },
  7393. {
  7394. header: gettext('Format'),
  7395. width: 100,
  7396. dataIndex: 'format'
  7397. },
  7398. {
  7399. header: gettext('Size'),
  7400. width: 100,
  7401. renderer: PVE.Utils.format_size,
  7402. dataIndex: 'size'
  7403. }
  7404. ],
  7405. listeners: {
  7406. show: reload
  7407. }
  7408. });
  7409.  
  7410. me.callParent();
  7411. }
  7412. });
  7413. Ext.define('PVE.panel.LogView', {
  7414. extend: 'Ext.panel.Panel',
  7415.  
  7416. alias: ['widget.pveLogView'],
  7417.  
  7418. pageSize: 500,
  7419.  
  7420. lineHeight: 16,
  7421.  
  7422. viewInfo: undefined,
  7423.  
  7424. scrollToEnd: true,
  7425.  
  7426. getMaxDown: function(scrollToEnd) {
  7427. var me = this;
  7428.  
  7429. var target = me.getTargetEl();
  7430. var dom = target.dom;
  7431. if (scrollToEnd) {
  7432. dom.scrollTop = dom.scrollHeight - dom.clientHeight;
  7433. }
  7434.  
  7435. var maxDown = dom.scrollHeight - dom.clientHeight -
  7436. dom.scrollTop;
  7437.  
  7438. return maxDown;
  7439. },
  7440.  
  7441. updateView: function(start, end, total, text) {
  7442. var me = this;
  7443. var el = me.dataCmp.el;
  7444.  
  7445. if (me.viewInfo && me.viewInfo.start === start &&
  7446. me.viewInfo.end === end && me.viewInfo.total === total &&
  7447. me.viewInfo.textLength === text.length) {
  7448. return; // same content
  7449. }
  7450.  
  7451. var maxDown = me.getMaxDown();
  7452. var scrollToEnd = (maxDown <= 0) && me.scrollToEnd;
  7453.  
  7454. el.setStyle('padding-top', start*me.lineHeight);
  7455. el.update(text);
  7456. me.dataCmp.setHeight(total*me.lineHeight);
  7457.  
  7458. if (scrollToEnd) {
  7459. me.getMaxDown(true);
  7460. }
  7461.  
  7462. me.viewInfo = {
  7463. start: start,
  7464. end: end,
  7465. total: total,
  7466. textLength: text.length
  7467. };
  7468. },
  7469.  
  7470. doAttemptLoad: function(start) {
  7471. var me = this;
  7472.  
  7473. PVE.Utils.API2Request({
  7474. url: me.url,
  7475. params: {
  7476. start: start,
  7477. limit: me.pageSize
  7478. },
  7479. method: 'GET',
  7480. success: function(response) {
  7481. PVE.Utils.setErrorMask(me, false);
  7482. var list = response.result.data;
  7483. var total = response.result.total;
  7484. var first = 0, last = 0;
  7485. var text = '';
  7486. Ext.Array.each(list, function(item) {
  7487. if (!first|| item.n < first) {
  7488. first = item.n;
  7489. }
  7490. if (!last || item.n > last) {
  7491. last = item.n;
  7492. }
  7493. text = text + Ext.htmlEncode(item.t) + "<br>";
  7494. });
  7495.  
  7496. if (first && last && total) {
  7497. me.updateView(first -1 , last -1, total, text);
  7498. } else {
  7499. me.updateView(0, 0, 0, '');
  7500. }
  7501. },
  7502. failure: function(response) {
  7503. var msg = response.htmlStatus;
  7504. PVE.Utils.setErrorMask(me, msg);
  7505. }
  7506. });
  7507. },
  7508.  
  7509. attemptLoad: function(start) {
  7510. var me = this;
  7511. if (!me.loadTask) {
  7512. me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
  7513. }
  7514. me.loadTask.delay(200, me.doAttemptLoad, me, [start]);
  7515. },
  7516.  
  7517. requestUpdate: function(top, force) {
  7518. var me = this;
  7519.  
  7520. if (top === undefined) {
  7521. var target = me.getTargetEl();
  7522. top = target.dom.scrollTop;
  7523. }
  7524.  
  7525. var viewStart = parseInt((top / me.lineHeight) - 1, 10);
  7526. if (viewStart < 0) {
  7527. viewStart = 0;
  7528. }
  7529. var viewEnd = parseInt(((top + me.getHeight())/ me.lineHeight) + 1, 10);
  7530. var info = me.viewInfo;
  7531.  
  7532. if (info && !force) {
  7533. if (viewStart >= info.start && viewEnd <= info.end) {
  7534. return;
  7535. }
  7536. }
  7537.  
  7538. var line = parseInt((top / me.lineHeight) - (me.pageSize / 2) + 10, 10);
  7539. if (line < 0) {
  7540. line = 0;
  7541. }
  7542.  
  7543. me.attemptLoad(line);
  7544. },
  7545.  
  7546. afterRender: function() {
  7547. var me = this;
  7548.  
  7549. me.callParent(arguments);
  7550.  
  7551. Ext.Function.defer(function() {
  7552. var target = me.getTargetEl();
  7553. target.on('scroll', function(e) {
  7554. me.requestUpdate();
  7555. });
  7556. me.requestUpdate(0);
  7557. }, 20);
  7558. },
  7559.  
  7560. initComponent : function() {
  7561. /*jslint confusion: true */
  7562.  
  7563. var me = this;
  7564.  
  7565. if (!me.url) {
  7566. throw "no url specified";
  7567. }
  7568.  
  7569. me.dataCmp = Ext.create('Ext.Component', {
  7570. style: 'font:normal 11px tahoma, arial, verdana, sans-serif;' +
  7571. 'line-height: ' + me.lineHeight.toString() + 'px; white-space: pre;'
  7572. });
  7573.  
  7574. me.task = Ext.TaskManager.start({
  7575. run: function() {
  7576. if (!me.isVisible() || !me.scrollToEnd || !me.viewInfo) {
  7577. return;
  7578. }
  7579.  
  7580. var maxDown = me.getMaxDown();
  7581. if (maxDown > 0) {
  7582. return;
  7583. }
  7584.  
  7585. me.requestUpdate(undefined, true);
  7586. },
  7587. interval: 1000
  7588. });
  7589.  
  7590. Ext.apply(me, {
  7591. autoScroll: true,
  7592. layout: 'auto',
  7593. items: me.dataCmp,
  7594. bodyStyle: 'padding: 5px;',
  7595. listeners: {
  7596. show: function() {
  7597. var target = me.getTargetEl();
  7598. if (target && target.dom) {
  7599. target.dom.scrollTop = me.savedScrollTop;
  7600. }
  7601. },
  7602. beforehide: function() {
  7603. // Hack: chrome reset scrollTop to 0, so we save/restore
  7604. var target = me.getTargetEl();
  7605. if (target && target.dom) {
  7606. me.savedScrollTop = target.dom.scrollTop;
  7607. }
  7608. },
  7609. destroy: function() {
  7610. Ext.TaskManager.stop(me.task);
  7611. }
  7612. }
  7613. });
  7614.  
  7615. me.callParent();
  7616. }
  7617. });
  7618. Ext.define('PVE.node.DNSEdit', {
  7619. extend: 'PVE.window.Edit',
  7620. alias: ['widget.pveNodeDNSEdit'],
  7621.  
  7622. initComponent : function() {
  7623. var me = this;
  7624.  
  7625. var nodename = me.pveSelNode.data.node;
  7626. if (!nodename) {
  7627. throw "no node name specified";
  7628. }
  7629.  
  7630. me.items = [
  7631. {
  7632. xtype: 'textfield',
  7633. fieldLabel: 'Search domain',
  7634. name: 'search',
  7635. allowBlank: false
  7636. },
  7637. {
  7638. xtype: 'pvetextfield',
  7639. fieldLabel: gettext('DNS server') + " 1",
  7640. vtype: 'IPAddress',
  7641. skipEmptyText: true,
  7642. name: 'dns1'
  7643. },
  7644. {
  7645. xtype: 'pvetextfield',
  7646. fieldLabel: gettext('DNS server') + " 2",
  7647. vtype: 'IPAddress',
  7648. skipEmptyText: true,
  7649. name: 'dns2'
  7650. },
  7651. {
  7652. xtype: 'pvetextfield',
  7653. fieldLabel: gettext('DNS server') + " 3",
  7654. vtype: 'IPAddress',
  7655. skipEmptyText: true,
  7656. name: 'dns3'
  7657. }
  7658. ];
  7659.  
  7660. Ext.applyIf(me, {
  7661. subject: 'DNS',
  7662. url: "/api2/extjs/nodes/" + nodename + "/dns",
  7663. fieldDefaults: {
  7664. labelWidth: 120
  7665. }
  7666. });
  7667.  
  7668. me.callParent();
  7669.  
  7670. me.load();
  7671. }
  7672. });
  7673. Ext.define('PVE.node.DNSView', {
  7674. extend: 'PVE.grid.ObjectGrid',
  7675. alias: ['widget.pveNodeDNSView'],
  7676.  
  7677. initComponent : function() {
  7678. var me = this;
  7679.  
  7680. var nodename = me.pveSelNode.data.node;
  7681. if (!nodename) {
  7682. throw "no node name specified";
  7683. }
  7684.  
  7685. var run_editor = function() {
  7686. var win = Ext.create('PVE.node.DNSEdit', {
  7687. pveSelNode: me.pveSelNode
  7688. });
  7689. win.show();
  7690. };
  7691.  
  7692. Ext.applyIf(me, {
  7693. url: "/api2/json/nodes/" + nodename + "/dns",
  7694. cwidth1: 130,
  7695. interval: 1000,
  7696. rows: {
  7697. search: { header: 'Search domain', required: true },
  7698. dns1: { header: gettext('DNS server') + " 1", required: true },
  7699. dns2: { header: gettext('DNS server') + " 2" },
  7700. dns3: { header: gettext('DNS server') + " 3" }
  7701. },
  7702. tbar: [
  7703. {
  7704. text: gettext("Edit"),
  7705. handler: run_editor
  7706. }
  7707. ],
  7708. listeners: {
  7709. itemdblclick: run_editor
  7710. }
  7711. });
  7712.  
  7713. me.callParent();
  7714.  
  7715. me.on('show', me.rstore.startUpdate);
  7716. me.on('hide', me.rstore.stopUpdate);
  7717. me.on('destroy', me.rstore.stopUpdate);
  7718. }
  7719. });
  7720. Ext.define('PVE.node.TimeView', {
  7721. extend: 'PVE.grid.ObjectGrid',
  7722. alias: ['widget.pveNodeTimeView'],
  7723.  
  7724. initComponent : function() {
  7725. var me = this;
  7726.  
  7727. var nodename = me.pveSelNode.data.node;
  7728. if (!nodename) {
  7729. throw "no node name specified";
  7730. }
  7731.  
  7732. var tzoffset = (new Date()).getTimezoneOffset()*60000;
  7733. var renderlocaltime = function(value) {
  7734. var servertime = new Date((value * 1000) + tzoffset);
  7735. return Ext.Date.format(servertime, 'Y-m-d H:i:s');
  7736. };
  7737.  
  7738. var run_editor = function() {
  7739. var win = Ext.create('PVE.node.TimeEdit', {
  7740. pveSelNode: me.pveSelNode
  7741. });
  7742. win.show();
  7743. };
  7744.  
  7745. Ext.applyIf(me, {
  7746. url: "/api2/json/nodes/" + nodename + "/time",
  7747. cwidth1: 150,
  7748. interval: 1000,
  7749. rows: {
  7750. timezone: {
  7751. header: gettext('Time zone'),
  7752. required: true
  7753. },
  7754. localtime: {
  7755. header: gettext('Server time'),
  7756. required: true,
  7757. renderer: renderlocaltime
  7758. }
  7759. },
  7760. tbar: [
  7761. {
  7762. text: gettext("Edit"),
  7763. handler: run_editor
  7764. }
  7765. ],
  7766. listeners: {
  7767. itemdblclick: run_editor
  7768. }
  7769. });
  7770.  
  7771. me.callParent();
  7772.  
  7773. me.on('show', me.rstore.startUpdate);
  7774. me.on('hide', me.rstore.stopUpdate);
  7775. me.on('destroy', me.rstore.stopUpdate);
  7776. }
  7777. });
  7778. Ext.define('PVE.node.TimeEdit', {
  7779. extend: 'PVE.window.Edit',
  7780. alias: ['widget.pveNodeTimeEdit'],
  7781.  
  7782. initComponent : function() {
  7783. var me = this;
  7784.  
  7785. var nodename = me.pveSelNode.data.node;
  7786. if (!nodename) {
  7787. throw "no node name specified";
  7788. }
  7789.  
  7790. Ext.applyIf(me, {
  7791. subject: gettext('Time zone'),
  7792. url: "/api2/extjs/nodes/" + nodename + "/time",
  7793. fieldDefaults: {
  7794. labelWidth: 70
  7795. },
  7796. width: 400,
  7797. items: {
  7798. xtype: 'combo',
  7799. fieldLabel: gettext('Time zone'),
  7800. name: 'timezone',
  7801. queryMode: 'local',
  7802. store: new PVE.data.TimezoneStore({autoDestory: true}),
  7803. valueField: 'zone',
  7804. displayField: 'zone',
  7805. triggerAction: 'all',
  7806. forceSelection: true,
  7807. editable: false,
  7808. allowBlank: false
  7809. }
  7810. });
  7811.  
  7812. me.callParent();
  7813.  
  7814. me.load();
  7815. }
  7816. });
  7817. Ext.define('PVE.node.StatusView', {
  7818. extend: 'PVE.grid.ObjectGrid',
  7819. alias: ['widget.pveNodeStatusView'],
  7820.  
  7821. initComponent : function() {
  7822. var me = this;
  7823.  
  7824. var nodename = me.pveSelNode.data.node;
  7825. if (!nodename) {
  7826. throw "no node name specified";
  7827. }
  7828.  
  7829. var render_cpuinfo = function(value) {
  7830. return value.cpus + " x " + value.model;
  7831. };
  7832.  
  7833. var render_loadavg = function(value) {
  7834. return value[0] + ", " + value[1] + ", " + value[2];
  7835. };
  7836.  
  7837. var render_cpu = function(value) {
  7838. var per = value * 100;
  7839. return per.toFixed(2) + "%";
  7840. };
  7841.  
  7842. var render_meminfo = function(value) {
  7843. var per = (value.used / value.total)*100;
  7844. var text = "<div>Total: " + PVE.Utils.format_size(value.total) + "</div>" +
  7845. "<div>Used: " + PVE.Utils.format_size(value.used) + "</div>";
  7846. return text;
  7847. };
  7848.  
  7849. var rows = {
  7850. uptime: { header: 'Uptime', required: true, renderer: PVE.Utils.format_duration_long },
  7851. loadavg: { header: 'Load average', required: true, renderer: render_loadavg },
  7852. cpuinfo: { header: 'CPUs', required: true, renderer: render_cpuinfo },
  7853. cpu: { header: 'CPU usage',required: true, renderer: render_cpu },
  7854. wait: { header: 'IO delay', required: true, renderer: render_cpu },
  7855. memory: { header: 'RAM usage', required: true, renderer: render_meminfo },
  7856. swap: { header: 'SWAP usage', required: true, renderer: render_meminfo },
  7857. rootfs: { header: 'HD space (root)', required: true, renderer: render_meminfo },
  7858. pveversion: { header: 'PVE Manager version', required: true },
  7859. kversion: { header: 'Kernel version', required: true }
  7860. };
  7861.  
  7862. Ext.applyIf(me, {
  7863. cwidth1: 150,
  7864. //height: 276,
  7865. rows: rows
  7866. });
  7867.  
  7868. me.callParent();
  7869. }
  7870. });
  7871. /*jslint confusion: true */
  7872. Ext.define('PVE.node.BCFailCnt', {
  7873. extend: 'Ext.grid.GridPanel',
  7874. alias: ['widget.pveNodeBCFailCnt'],
  7875.  
  7876. initComponent : function() {
  7877. var me = this;
  7878.  
  7879. var nodename = me.pveSelNode.data.node;
  7880. if (!nodename) {
  7881. throw "no node name specified";
  7882. }
  7883.  
  7884. var store = new Ext.data.Store({
  7885. model: 'pve-openvz-ubc',
  7886. proxy: {
  7887. type: 'pve',
  7888. url: '/api2/json/nodes/' + nodename + '/ubcfailcnt'
  7889. },
  7890. sorters: [
  7891. {
  7892. property : 'id',
  7893. direction: 'ASC'
  7894. }
  7895. ]
  7896. });
  7897.  
  7898. var reload = function() {
  7899. store.load();
  7900. };
  7901.  
  7902. Ext.applyIf(me, {
  7903. store: store,
  7904. stateful: false,
  7905. columns: [
  7906. {
  7907. header: 'Container',
  7908. width: 100,
  7909. dataIndex: 'id'
  7910. },
  7911. {
  7912. header: 'failcnt',
  7913. flex: 1,
  7914. dataIndex: 'failcnt'
  7915. }
  7916. ],
  7917. listeners: {
  7918. show: reload,
  7919. itemdblclick: function(v, record) {
  7920. var ws = me.up('pveStdWorkspace');
  7921. ws.selectById('openvz/' + record.data.id);
  7922. }
  7923. }
  7924. });
  7925.  
  7926. me.callParent();
  7927.  
  7928. }
  7929. }, function() {
  7930.  
  7931. Ext.define('pve-openvz-ubc', {
  7932. extend: "Ext.data.Model",
  7933. fields: [ 'id', { name: 'failcnt', type: 'number' } ]
  7934. });
  7935.  
  7936. });
  7937. Ext.define('PVE.node.Summary', {
  7938. extend: 'Ext.panel.Panel',
  7939. alias: 'widget.pveNodeSummary',
  7940.  
  7941. initComponent: function() {
  7942. var me = this;
  7943.  
  7944. var nodename = me.pveSelNode.data.node;
  7945. if (!nodename) {
  7946. throw "no node name specified";
  7947. }
  7948.  
  7949. if (!me.statusStore) {
  7950. throw "no status storage specified";
  7951. }
  7952.  
  7953. var rstore = me.statusStore;
  7954.  
  7955. var statusview = Ext.create('PVE.node.StatusView', {
  7956. title: 'Status',
  7957. pveSelNode: me.pveSelNode,
  7958. style: 'padding-top:0px',
  7959. rstore: rstore
  7960. });
  7961.  
  7962. var rrdurl = "/api2/png/nodes/" + nodename + "/rrd";
  7963.  
  7964. Ext.apply(me, {
  7965. autoScroll: true,
  7966. bodyStyle: 'padding:10px',
  7967. defaults: {
  7968. width: 800,
  7969. style: 'padding-top:10px'
  7970. },
  7971. tbar: [ '->', { xtype: 'pveRRDTypeSelector' } ],
  7972. items: [
  7973. statusview,
  7974. {
  7975. xtype: 'pveRRDView',
  7976. title: "CPU usage %",
  7977. datasource: 'cpu,iowait',
  7978. rrdurl: rrdurl
  7979. },
  7980. {
  7981. xtype: 'pveRRDView',
  7982. title: "Server load",
  7983. datasource: 'loadavg',
  7984. rrdurl: rrdurl
  7985. },
  7986. {
  7987. xtype: 'pveRRDView',
  7988. title: "Memory usage",
  7989. datasource: 'memtotal,memused',
  7990. rrdurl: rrdurl
  7991. },
  7992. {
  7993. xtype: 'pveRRDView',
  7994. title: "Network traffic",
  7995. datasource: 'netin,netout',
  7996. rrdurl: rrdurl
  7997. }
  7998. ],
  7999. listeners: {
  8000. show: rstore.startUpdate,
  8001. hide: rstore.stopUpdate,
  8002. destroy: rstore.stopUpdate
  8003. }
  8004. });
  8005.  
  8006. me.callParent();
  8007. }
  8008. });
  8009. Ext.define('PVE.node.ServiceView', {
  8010. extend: 'Ext.grid.GridPanel',
  8011.  
  8012. alias: ['widget.pveNodeServiceView'],
  8013.  
  8014. initComponent : function() {
  8015. var me = this;
  8016.  
  8017. var nodename = me.pveSelNode.data.node;
  8018. if (!nodename) {
  8019. throw "no node name specified";
  8020. }
  8021.  
  8022. var rstore = Ext.create('PVE.data.UpdateStore', {
  8023. interval: 1000,
  8024. storeid: 'pve-services',
  8025. model: 'pve-services',
  8026. proxy: {
  8027. type: 'pve',
  8028. url: "/api2/json/nodes/" + nodename + "/services"
  8029. }
  8030. });
  8031.  
  8032. var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  8033.  
  8034. var service_cmd = function(cmd) {
  8035. var sm = me.getSelectionModel();
  8036. var rec = sm.getSelection()[0];
  8037. PVE.Utils.API2Request({
  8038. url: "/nodes/" + nodename + "/services/" + rec.data.service + "/" + cmd,
  8039. method: 'POST',
  8040. failure: function(response, opts) {
  8041. Ext.Msg.alert('Error', response.htmlStatus);
  8042. me.loading = true;
  8043. },
  8044. success: function(response, opts) {
  8045. rstore.startUpdate();
  8046. var upid = response.result.data;
  8047.  
  8048. var win = Ext.create('PVE.window.TaskViewer', {
  8049. upid: upid
  8050. });
  8051. win.show();
  8052. }
  8053. });
  8054. };
  8055.  
  8056. var start_btn = new Ext.Button({
  8057. text: gettext('Start'),
  8058. disabled: true,
  8059. handler: function(){
  8060. service_cmd("start");
  8061. }
  8062. });
  8063.  
  8064. var stop_btn = new Ext.Button({
  8065. text: gettext('Stop'),
  8066. disabled: true,
  8067. handler: function(){
  8068. service_cmd("stop");
  8069. }
  8070. });
  8071.  
  8072. var restart_btn = new Ext.Button({
  8073. text: gettext('Restart'),
  8074. disabled: true,
  8075. handler: function(){
  8076. service_cmd("restart");
  8077. }
  8078. });
  8079.  
  8080. var set_button_status = function() {
  8081. var sm = me.getSelectionModel();
  8082. var rec = sm.getSelection()[0];
  8083.  
  8084. if (!rec) {
  8085. start_btn.disable();
  8086. stop_btn.disable();
  8087. restart_btn.disable();
  8088. return;
  8089. }
  8090. var service = rec.data.service;
  8091. var state = rec.data.state;
  8092. if (service == 'apache' ||
  8093. service == 'pvecluster' ||
  8094. service == 'pvedaemon') {
  8095. if (state == 'running') {
  8096. start_btn.disable();
  8097. restart_btn.enable();
  8098. } else {
  8099. start_btn.enable();
  8100. restart_btn.disable();
  8101. }
  8102. stop_btn.disable();
  8103. } else {
  8104. if (state == 'running') {
  8105. start_btn.disable();
  8106. restart_btn.enable();
  8107. stop_btn.enable();
  8108. } else {
  8109. start_btn.enable();
  8110. restart_btn.disable();
  8111. stop_btn.disable();
  8112. }
  8113. }
  8114. };
  8115.  
  8116. me.mon(store, 'datachanged', set_button_status);
  8117.  
  8118. PVE.Utils.monStoreErrors(me, rstore);
  8119.  
  8120. Ext.apply(me, {
  8121. store: store,
  8122. stateful: false,
  8123. tbar: [ start_btn, stop_btn, restart_btn ],
  8124. columns: [
  8125. {
  8126. header: gettext('Name'),
  8127. width: 100,
  8128. sortable: true,
  8129. dataIndex: 'name'
  8130. },
  8131. {
  8132. header: gettext('Status'),
  8133. width: 100,
  8134. sortable: true,
  8135. dataIndex: 'state'
  8136. },
  8137. {
  8138. header: gettext('Description'),
  8139. dataIndex: 'desc',
  8140. flex: 1
  8141. }
  8142. ],
  8143. listeners: {
  8144. selectionchange: set_button_status,
  8145. show: rstore.startUpdate,
  8146. hide: rstore.stopUpdate,
  8147. destroy: rstore.stopUpdate
  8148. }
  8149. });
  8150.  
  8151. me.callParent();
  8152. }
  8153. }, function() {
  8154.  
  8155. Ext.define('pve-services', {
  8156. extend: 'Ext.data.Model',
  8157. fields: [ 'service', 'name', 'desc', 'state' ],
  8158. idProperty: 'service'
  8159. });
  8160.  
  8161. });
  8162. Ext.define('PVE.node.NetworkEdit', {
  8163. extend: 'PVE.window.Edit',
  8164. alias: ['widget.pveNodeNetworkEdit'],
  8165.  
  8166. initComponent : function() {
  8167. var me = this;
  8168.  
  8169. var nodename = me.pveSelNode.data.node;
  8170. if (!nodename) {
  8171. throw "no node name specified";
  8172. }
  8173.  
  8174. if (!me.iftype) {
  8175. throw "no network device type specified";
  8176. }
  8177.  
  8178. me.create = !me.iface;
  8179.  
  8180. var iface_vtype;
  8181.  
  8182. if (me.iftype === 'bridge') {
  8183. me.subject = "Bridge";
  8184. iface_vtype = 'BridgeName';
  8185. } else if (me.iftype === 'bond') {
  8186. me.subject = "Bond";
  8187. iface_vtype = 'BondName';
  8188. } else if (me.iftype === 'eth' && !me.create) {
  8189. me.subject = gettext("Network Device");
  8190. } else {
  8191. throw "no known network device type specified";
  8192. }
  8193.  
  8194. var column2 = [
  8195. {
  8196. xtype: 'pvecheckbox',
  8197. fieldLabel: 'Autostart',
  8198. name: 'autostart',
  8199. uncheckedValue: 0,
  8200. checked: me.create ? true : undefined
  8201. }
  8202. ];
  8203.  
  8204. if (me.iftype === 'bridge') {
  8205. column2.push({
  8206. xtype: 'textfield',
  8207. fieldLabel: 'Bridge ports',
  8208. name: 'bridge_ports'
  8209. });
  8210. } else if (me.iftype === 'bond') {
  8211. column2.push({
  8212. xtype: 'textfield',
  8213. fieldLabel: 'Slaves',
  8214. name: 'slaves'
  8215. });
  8216. column2.push({
  8217. xtype: 'bondModeSelector',
  8218. fieldLabel: 'Mode',
  8219. name: 'bond_mode',
  8220. value: me.create ? 'balance-rr' : undefined,
  8221. allowBlank: false
  8222. });
  8223. }
  8224.  
  8225. var url;
  8226. var method;
  8227.  
  8228. if (me.create) {
  8229. url = "/api2/extjs/nodes/" + nodename + "/network";
  8230. method = 'POST';
  8231. } else {
  8232. url = "/api2/extjs/nodes/" + nodename + "/network/" + me.iface;
  8233. method = 'PUT';
  8234. }
  8235.  
  8236. var column1 = [
  8237. {
  8238. xtype: me.create ? 'textfield' : 'displayfield',
  8239. fieldLabel: gettext('Name'),
  8240. height: 22, // hack: set same height as text fields
  8241. name: 'iface',
  8242. value: me.iface,
  8243. vtype: iface_vtype,
  8244. allowBlank: false
  8245. },
  8246. {
  8247. xtype: 'pvetextfield',
  8248. deleteEmpty: !me.create,
  8249. fieldLabel: gettext('IP address'),
  8250. vtype: 'IPAddress',
  8251. name: 'address'
  8252. },
  8253. {
  8254. xtype: 'pvetextfield',
  8255. deleteEmpty: !me.create,
  8256. fieldLabel: gettext('Subnet mask'),
  8257. vtype: 'IPAddress',
  8258. name: 'netmask',
  8259. validator: function(value) {
  8260. /*jslint confusion: true */
  8261. if (!me.items) {
  8262. return true;
  8263. }
  8264. var address = me.down('field[name=address]').getValue();
  8265. if (value !== '') {
  8266. if (address === '') {
  8267. return "Subnet mask requires option 'IP address'";
  8268. }
  8269. } else {
  8270. if (address !== '') {
  8271. return "Option 'IP address' requires a subnet mask";
  8272. }
  8273. }
  8274.  
  8275. return true;
  8276. }
  8277. },
  8278. {
  8279. xtype: 'pvetextfield',
  8280. deleteEmpty: !me.create,
  8281. fieldLabel: 'Gateway',
  8282. vtype: 'IPAddress',
  8283. name: 'gateway'
  8284. }
  8285. ];
  8286.  
  8287. Ext.applyIf(me, {
  8288. url: url,
  8289. method: method,
  8290. items: {
  8291. xtype: 'inputpanel',
  8292. column1: column1,
  8293. column2: column2
  8294. }
  8295. });
  8296.  
  8297. me.callParent();
  8298.  
  8299. if (me.create) {
  8300. me.down('field[name=iface]').setValue(me.iface_default);
  8301. } else {
  8302. me.load({
  8303. success: function(response, options) {
  8304. var data = response.result.data;
  8305. if (data.type !== me.iftype) {
  8306. var msg = "Got unexpected device type";
  8307. Ext.Msg.alert(gettext('Error'), msg, function() {
  8308. me.close();
  8309. });
  8310. return;
  8311. }
  8312. me.setValues(data);
  8313. me.isValid(); // trigger validation
  8314. }
  8315. });
  8316. }
  8317. }
  8318. });
  8319. Ext.define('PVE.node.NetworkView', {
  8320. extend: 'Ext.panel.Panel',
  8321.  
  8322. alias: ['widget.pveNodeNetworkView'],
  8323.  
  8324. initComponent : function() {
  8325. var me = this;
  8326.  
  8327. var nodename = me.pveSelNode.data.node;
  8328. if (!nodename) {
  8329. throw "no node name specified";
  8330. }
  8331.  
  8332. var store = Ext.create('Ext.data.Store', {
  8333. model: 'pve-networks',
  8334. proxy: {
  8335. type: 'pve',
  8336. url: "/api2/json/nodes/" + nodename + "/network"
  8337. },
  8338. sorters: [
  8339. {
  8340. property : 'iface',
  8341. direction: 'ASC'
  8342. }
  8343. ]
  8344. });
  8345.  
  8346. var reload = function() {
  8347. var changeitem = me.down('#changes');
  8348. PVE.Utils.API2Request({
  8349. url: '/nodes/' + nodename + '/network',
  8350. failure: function(response, opts) {
  8351. changeitem.update('Error: ' + response.htmlStatus);
  8352. store.loadData({});
  8353. },
  8354. success: function(response, opts) {
  8355. var result = Ext.decode(response.responseText);
  8356. store.loadData(result.data);
  8357. var changes = result.changes;
  8358. if (changes === undefined || changes === '') {
  8359. changes = gettext("No changes");
  8360. }
  8361. changeitem.update("<pre>" + Ext.htmlEncode(changes) + "</pre>");
  8362. }
  8363. });
  8364. };
  8365.  
  8366. var run_editor = function() {
  8367. var grid = me.down('gridpanel');
  8368. var sm = grid.getSelectionModel();
  8369. var rec = sm.getSelection()[0];
  8370. if (!rec) {
  8371. return;
  8372. }
  8373.  
  8374. var win = Ext.create('PVE.node.NetworkEdit', {
  8375. pveSelNode: me.pveSelNode,
  8376. iface: rec.data.iface,
  8377. iftype: rec.data.type
  8378. });
  8379. win.show();
  8380. win.on('destroy', reload);
  8381. };
  8382.  
  8383. var edit_btn = new Ext.Button({
  8384. text: gettext('Edit'),
  8385. disabled: true,
  8386. handler: run_editor
  8387. });
  8388.  
  8389. var del_btn = new Ext.Button({
  8390. text: gettext('Remove'),
  8391. disabled: true,
  8392. handler: function(){
  8393. var grid = me.down('gridpanel');
  8394. var sm = grid.getSelectionModel();
  8395. var rec = sm.getSelection()[0];
  8396. if (!rec) {
  8397. return;
  8398. }
  8399.  
  8400. var iface = rec.data.iface;
  8401.  
  8402. PVE.Utils.API2Request({
  8403. url: '/nodes/' + nodename + '/network/' + iface,
  8404. method: 'DELETE',
  8405. waitMsgTarget: me,
  8406. callback: function() {
  8407. reload();
  8408. },
  8409. failure: function(response, opts) {
  8410. Ext.Msg.alert('Error', response.htmlStatus);
  8411. }
  8412. });
  8413. }
  8414. });
  8415.  
  8416. var set_button_status = function() {
  8417. var grid = me.down('gridpanel');
  8418. var sm = grid.getSelectionModel();
  8419. var rec = sm.getSelection()[0];
  8420.  
  8421. edit_btn.setDisabled(!rec);
  8422. del_btn.setDisabled(!rec);
  8423. };
  8424.  
  8425. PVE.Utils.monStoreErrors(me, store);
  8426.  
  8427. var render_ports = function(value, metaData, record) {
  8428. if (value === 'bridge') {
  8429. return record.data.bridge_ports;
  8430. } else if (value === 'bond') {
  8431. return record.data.slaves;
  8432. }
  8433. };
  8434.  
  8435. Ext.apply(me, {
  8436. layout: 'border',
  8437. tbar: [
  8438. {
  8439. text: gettext('Create'),
  8440. menu: new Ext.menu.Menu({
  8441. items: [
  8442. {
  8443. text: 'Bridge',
  8444. handler: function() {
  8445. var next;
  8446. for (next = 0; next <= 9999; next++) {
  8447. if (!store.data.get('vmbr' + next.toString())) {
  8448. break;
  8449. }
  8450. }
  8451.  
  8452. var win = Ext.create('PVE.node.NetworkEdit', {
  8453. pveSelNode: me.pveSelNode,
  8454. iftype: 'bridge',
  8455. iface_default: 'vmbr' + next.toString()
  8456. });
  8457. win.on('destroy', reload);
  8458. win.show();
  8459. }
  8460. },
  8461. {
  8462. text: 'Bond',
  8463. handler: function() {
  8464. var next;
  8465. for (next = 0; next <= 9999; next++) {
  8466. if (!store.data.get('bond' + next.toString())) {
  8467. break;
  8468. }
  8469. }
  8470. var win = Ext.create('PVE.node.NetworkEdit', {
  8471. pveSelNode: me.pveSelNode,
  8472. iftype: 'bond',
  8473. iface_default: 'bond' + next.toString()
  8474. });
  8475. win.on('destroy', reload);
  8476. win.show();
  8477. }
  8478. }
  8479. ]
  8480. })
  8481. }, ' ',
  8482. {
  8483. text: gettext('Revert changes'),
  8484. handler: function() {
  8485. PVE.Utils.API2Request({
  8486. url: '/nodes/' + nodename + '/network',
  8487. method: 'DELETE',
  8488. waitMsgTarget: me,
  8489. callback: function() {
  8490. reload();
  8491. },
  8492. failure: function(response, opts) {
  8493. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  8494. }
  8495. });
  8496. }
  8497. },
  8498. edit_btn,
  8499. del_btn
  8500. ],
  8501. items: [
  8502. {
  8503. xtype: 'gridpanel',
  8504. stateful: false,
  8505. store: store,
  8506. region: 'center',
  8507. border: false,
  8508. columns: [
  8509. {
  8510. header: gettext('Name'),
  8511. width: 100,
  8512. sortable: true,
  8513. dataIndex: 'iface'
  8514. },
  8515. {
  8516. xtype: 'booleancolumn',
  8517. header: gettext('Active'),
  8518. width: 80,
  8519. sortable: true,
  8520. dataIndex: 'active',
  8521. trueText: 'Yes',
  8522. falseText: 'No',
  8523. undefinedText: 'No'
  8524. },
  8525. {
  8526. xtype: 'booleancolumn',
  8527. header: 'Autostart',
  8528. width: 80,
  8529. sortable: true,
  8530. dataIndex: 'autostart',
  8531. trueText: 'Yes',
  8532. falseText: 'No',
  8533. undefinedText: 'No'
  8534. },
  8535. {
  8536. header: 'Ports/Slaves',
  8537. dataIndex: 'type',
  8538. renderer: render_ports
  8539. },
  8540. {
  8541. header: gettext('IP address'),
  8542. sortable: true,
  8543. dataIndex: 'address'
  8544. },
  8545. {
  8546. header: gettext('Subnet mask'),
  8547. sortable: true,
  8548. dataIndex: 'netmask'
  8549. },
  8550. {
  8551. header: 'Gateway',
  8552. sortable: true,
  8553. dataIndex: 'gateway'
  8554. }
  8555. ],
  8556. listeners: {
  8557. selectionchange: set_button_status,
  8558. itemdblclick: run_editor
  8559. }
  8560. },
  8561. {
  8562. border: false,
  8563. region: 'south',
  8564. autoScroll: true,
  8565. itemId: 'changes',
  8566. tbar: [
  8567. gettext('Pending changes') + ' (' +
  8568. gettext('Please reboot to activate changes') + ')'
  8569. ],
  8570. split: true,
  8571. bodyPadding: 5,
  8572. flex: 0.6,
  8573. html: gettext("No changes")
  8574. }
  8575. ],
  8576. listeners: {
  8577. show: reload
  8578. }
  8579. });
  8580.  
  8581. me.callParent();
  8582. }
  8583. }, function() {
  8584.  
  8585. Ext.define('pve-networks', {
  8586. extend: 'Ext.data.Model',
  8587. fields: [
  8588. 'iface', 'type', 'active', 'autostart',
  8589. 'bridge_ports', 'slaves', 'address',
  8590. 'netmask', 'gateway'
  8591. ],
  8592. idProperty: 'iface'
  8593. });
  8594.  
  8595. });
  8596. Ext.define('PVE.node.Tasks', {
  8597. extend: 'Ext.grid.GridPanel',
  8598.  
  8599. alias: ['widget.pveNodeTasks'],
  8600.  
  8601. initComponent : function() {
  8602. var me = this;
  8603.  
  8604. var nodename = me.pveSelNode.data.node;
  8605. if (!nodename) {
  8606. throw "no node name specified";
  8607. }
  8608.  
  8609. var store = Ext.create('Ext.data.Store', {
  8610. pageSize: 500,
  8611. buffered: true,
  8612. remoteFilter: true,
  8613. model: 'pve-tasks',
  8614. proxy: {
  8615. type: 'pve',
  8616. startParam: 'start',
  8617. limitParam: 'limit',
  8618. url: "/api2/json/nodes/" + nodename + "/tasks"
  8619. }
  8620. });
  8621.  
  8622. var userfilter = '';
  8623. var filter_errors = 0;
  8624.  
  8625. // fixme: scroller update fails
  8626. // http://www.sencha.com/forum/showthread.php?133677-scroller-does-not-adjust-to-the-filtered-grid-data&p=602887
  8627. var reload_task = new Ext.util.DelayedTask(function() {
  8628. var params = {
  8629. errors: filter_errors
  8630. };
  8631. if (userfilter) {
  8632. params.userfilter = userfilter;
  8633. }
  8634. store.proxy.extraParams = params;
  8635. store.filter();
  8636. });
  8637.  
  8638. var run_task_viewer = function() {
  8639. var sm = me.getSelectionModel();
  8640. var rec = sm.getSelection()[0];
  8641. if (!rec) {
  8642. return;
  8643. }
  8644.  
  8645. var win = Ext.create('PVE.window.TaskViewer', {
  8646. upid: rec.data.upid
  8647. });
  8648. win.show();
  8649. };
  8650.  
  8651. var view_btn = new Ext.Button({
  8652. text: gettext('View'),
  8653. disabled: true,
  8654. handler: run_task_viewer
  8655. });
  8656.  
  8657.  
  8658. Ext.apply(me, {
  8659. store: store,
  8660. stateful: false,
  8661. verticalScrollerType: 'paginggridscroller',
  8662. loadMask: true,
  8663. invalidateScrollerOnRefresh: false,
  8664. viewConfig: {
  8665. trackOver: false,
  8666. stripeRows: false, // does not work with getRowClass()
  8667.  
  8668. getRowClass: function(record, index) {
  8669. var status = record.get('status');
  8670.  
  8671. if (status && status != 'OK') {
  8672. return "x-form-invalid-field";
  8673. }
  8674. }
  8675. },
  8676. tbar: [
  8677. view_btn, '->', gettext('User name') +':', ' ',
  8678. {
  8679. xtype: 'textfield',
  8680. width: 200,
  8681. value: userfilter,
  8682. enableKeyEvents: true,
  8683. listeners: {
  8684. keyup: function(field, e) {
  8685. userfilter = field.getValue();
  8686. reload_task.delay(500);
  8687. }
  8688. }
  8689. }, ' ', gettext('Only Errors') + ':', ' ',
  8690. {
  8691. xtype: 'checkbox',
  8692. hideLabel: true,
  8693. checked: filter_errors,
  8694. listeners: {
  8695. change: function(field, checked) {
  8696. filter_errors = checked ? 1 : 0;
  8697. reload_task.delay(10);
  8698. }
  8699. }
  8700. }, ' '
  8701. ],
  8702. sortableColumns: false,
  8703. columns: [
  8704. {
  8705. header: gettext("Start Time"),
  8706. dataIndex: 'starttime',
  8707. width: 100,
  8708. renderer: function(value) {
  8709. return Ext.Date.format(value, "M d H:i:s");
  8710. }
  8711. },
  8712. {
  8713. header: gettext("End Time"),
  8714. dataIndex: 'endtime',
  8715. width: 100,
  8716. renderer: function(value, metaData, record) {
  8717. return Ext.Date.format(value,"M d H:i:s");
  8718. }
  8719. },
  8720. {
  8721. header: gettext("Node"),
  8722. dataIndex: 'node',
  8723. width: 100
  8724. },
  8725. {
  8726. header: gettext("User name"),
  8727. dataIndex: 'user',
  8728. width: 150
  8729. },
  8730. {
  8731. header: gettext("Description"),
  8732. dataIndex: 'upid',
  8733. flex: 1,
  8734. renderer: PVE.Utils.render_upid
  8735. },
  8736. {
  8737. header: gettext("Status"),
  8738. dataIndex: 'status',
  8739. width: 200,
  8740. renderer: function(value, metaData, record) {
  8741. if (value == 'OK') {
  8742. return 'OK';
  8743. }
  8744. // metaData.attr = 'style="color:red;"';
  8745. return "ERROR: " + value;
  8746. }
  8747. }
  8748. ],
  8749. listeners: {
  8750. itemdblclick: run_task_viewer,
  8751. selectionchange: function(v, selections) {
  8752. view_btn.setDisabled(!(selections && selections[0]));
  8753. },
  8754. show: function() { reload_task.delay(10); }
  8755. }
  8756. });
  8757.  
  8758. me.callParent();
  8759.  
  8760. store.guaranteeRange(0, store.pageSize - 1);
  8761. }
  8762. });
  8763.  
  8764. Ext.define('PVE.node.SubscriptionKeyEdit', {
  8765. extend: 'PVE.window.Edit',
  8766.  
  8767. initComponent : function() {
  8768. var me = this;
  8769.  
  8770. Ext.apply(me, {
  8771. title: gettext('Upload Subscription Key'),
  8772. width: 300,
  8773. items: {
  8774. xtype: 'textfield',
  8775. name: 'key',
  8776. value: '',
  8777. fieldLabel: gettext('Subscription Key')
  8778. }
  8779. });
  8780.  
  8781. me.callParent();
  8782.  
  8783. me.load();
  8784. }
  8785. });
  8786.  
  8787. Ext.define('PVE.node.Subscription', {
  8788. extend: 'PVE.grid.ObjectGrid',
  8789.  
  8790. alias: ['widget.pveNodeSubscription'],
  8791.  
  8792. features: [ {ftype: 'selectable'}],
  8793.  
  8794. initComponent : function() {
  8795. var me = this;
  8796.  
  8797. if (!me.nodename) {
  8798. throw "no node name specified";
  8799. }
  8800.  
  8801. var reload = function() {
  8802. me.rstore.load();
  8803. };
  8804.  
  8805. var baseurl = '/nodes/' + me.nodename + '/subscription';
  8806.  
  8807. var render_status = function(value) {
  8808.  
  8809. var message = me.getObjectValue('message');
  8810.  
  8811. if (message) {
  8812. return value + ": " + message;
  8813. }
  8814. return value;
  8815. };
  8816.  
  8817. var rows = {
  8818. productname: {
  8819. header: gettext('Type')
  8820. },
  8821. key: {
  8822. header: gettext('Subscription Key')
  8823. },
  8824. status: {
  8825. header: gettext('Status'),
  8826. renderer: render_status
  8827. },
  8828. message: {
  8829. visible: false
  8830. },
  8831. serverid: {
  8832. header: gettext('Server ID')
  8833. },
  8834. sockets: {
  8835. header: 'Sockets'
  8836. },
  8837. checktime: {
  8838. header: 'Last checked',
  8839. renderer: PVE.Utils.render_timestamp
  8840. }
  8841. };
  8842.  
  8843. Ext.applyIf(me, {
  8844. url: '/api2/json' + baseurl,
  8845. cwidth1: 170,
  8846. tbar: [
  8847. {
  8848. text: gettext('Upload Subscription Key'),
  8849. handler: function() {
  8850. var win = Ext.create('PVE.node.SubscriptionKeyEdit', {
  8851. url: '/api2/extjs/' + baseurl
  8852. });
  8853. win.show();
  8854. win.on('destroy', reload);
  8855. }
  8856. },
  8857. {
  8858. text: gettext('Check'),
  8859. handler: function() {
  8860. PVE.Utils.API2Request({
  8861. params: { force: 1 },
  8862. url: baseurl,
  8863. method: 'POST',
  8864. waitMsgTarget: me,
  8865. failure: function(response, opts) {
  8866. Ext.Msg.alert('Error', response.htmlStatus);
  8867. },
  8868. callback: reload
  8869. });
  8870. }
  8871. }
  8872. ],
  8873. rows: rows,
  8874. listeners: {
  8875. show: reload
  8876. }
  8877. });
  8878.  
  8879. me.callParent();
  8880. }
  8881. }, function() {
  8882.  
  8883. Ext.define('pve-services', {
  8884. extend: 'Ext.data.Model',
  8885. fields: [ 'service', 'name', 'desc', 'state' ],
  8886. idProperty: 'service'
  8887. });
  8888.  
  8889. });
  8890. Ext.define('PVE.node.Config', {
  8891. extend: 'PVE.panel.Config',
  8892. alias: 'widget.PVE.node.Config',
  8893.  
  8894. initComponent: function() {
  8895. var me = this;
  8896.  
  8897. var nodename = me.pveSelNode.data.node;
  8898. if (!nodename) {
  8899. throw "no node name specified";
  8900. }
  8901.  
  8902. var caps = Ext.state.Manager.get('GuiCap');
  8903.  
  8904. me.statusStore = Ext.create('PVE.data.ObjectStore', {
  8905. url: "/api2/json/nodes/" + nodename + "/status",
  8906. interval: 1000
  8907. });
  8908.  
  8909. var node_command = function(cmd) {
  8910. PVE.Utils.API2Request({
  8911. params: { command: cmd },
  8912. url: '/nodes/' + nodename + '/status',
  8913. method: 'POST',
  8914. waitMsgTarget: me,
  8915. failure: function(response, opts) {
  8916. Ext.Msg.alert('Error', response.htmlStatus);
  8917. }
  8918. });
  8919. };
  8920.  
  8921. var restartBtn = Ext.create('PVE.button.Button', {
  8922. text: gettext('Restart'),
  8923. disabled: !caps.nodes['Sys.PowerMgmt'],
  8924. confirmMsg: Ext.String.format(gettext("Do you really want to restart node {0}?"), nodename),
  8925. handler: function() {
  8926. node_command('reboot');
  8927. }
  8928. });
  8929.  
  8930. var shutdownBtn = Ext.create('PVE.button.Button', {
  8931. text: gettext('Shutdown'),
  8932. disabled: !caps.nodes['Sys.PowerMgmt'],
  8933. confirmMsg: Ext.String.format(gettext("Do you really want to shutdown node {0}?"), nodename),
  8934. handler: function() {
  8935. node_command('shutdown');
  8936. }
  8937. });
  8938.  
  8939. var shellBtn = Ext.create('Ext.Button', {
  8940. text: gettext('Shell'),
  8941. disabled: !caps.nodes['Sys.Console'],
  8942. handler: function() {
  8943. var url = Ext.urlEncode({
  8944. console: 'shell',
  8945. node: nodename
  8946. });
  8947. var nw = window.open("?" + url, '_blank',
  8948. "innerWidth=745,innerheight=427");
  8949. nw.focus();
  8950. }
  8951. });
  8952.  
  8953. me.items = [];
  8954.  
  8955. Ext.apply(me, {
  8956. title: gettext('Node') + " '" + nodename + "'",
  8957. hstateid: 'nodetab',
  8958. defaults: { statusStore: me.statusStore },
  8959. tbar: [ restartBtn, shutdownBtn, shellBtn ]
  8960. });
  8961.  
  8962. if (caps.nodes['Sys.Audit']) {
  8963. me.items.push([
  8964. {
  8965. title: gettext('Summary'),
  8966. itemId: 'summary',
  8967. xtype: 'pveNodeSummary'
  8968. },
  8969. {
  8970. title: gettext('Services'),
  8971. itemId: 'services',
  8972. xtype: 'pveNodeServiceView'
  8973. },
  8974. {
  8975. title: gettext('Network'),
  8976. itemId: 'network',
  8977. xtype: 'pveNodeNetworkView'
  8978. },
  8979. {
  8980. title: 'DNS',
  8981. itemId: 'dns',
  8982. xtype: 'pveNodeDNSView'
  8983. },
  8984. {
  8985. title: gettext('Time'),
  8986. itemId: 'time',
  8987. xtype: 'pveNodeTimeView'
  8988. }
  8989. ]);
  8990. }
  8991.  
  8992. if (caps.nodes['Sys.Syslog']) {
  8993. me.items.push([
  8994. {
  8995. title: 'Syslog',
  8996. itemId: 'syslog',
  8997. xtype: 'pveLogView',
  8998. url: "/api2/extjs/nodes/" + nodename + "/syslog"
  8999. }
  9000. ]);
  9001. }
  9002.  
  9003. me.items.push([
  9004. {
  9005. title: 'Task History',
  9006. itemId: 'tasks',
  9007. xtype: 'pveNodeTasks'
  9008. }
  9009. ]);
  9010.  
  9011.  
  9012. if (caps.nodes['Sys.Audit']) {
  9013. me.items.push([
  9014. {
  9015. title: 'UBC',
  9016. itemId: 'ubc',
  9017. xtype: 'pveNodeBCFailCnt'
  9018. }
  9019. ]);
  9020. }
  9021.  
  9022. me.items.push([
  9023. {
  9024. title: 'Subscription',
  9025. itemId: 'support',
  9026. xtype: 'pveNodeSubscription',
  9027. nodename: nodename
  9028. }
  9029. ]);
  9030.  
  9031. me.callParent();
  9032.  
  9033. me.statusStore.on('load', function(s, records, success) {
  9034. var uptimerec = s.data.get('uptime');
  9035. var powermgmt = uptimerec ? uptimerec.data.value : false;
  9036. if (!caps.nodes['Sys.PowerMgmt']) {
  9037. powermgmt = false;
  9038. }
  9039. restartBtn.setDisabled(!powermgmt);
  9040. shutdownBtn.setDisabled(!powermgmt);
  9041. shellBtn.setDisabled(!powermgmt);
  9042. });
  9043.  
  9044. me.on('afterrender', function() {
  9045. me.statusStore.startUpdate();
  9046. });
  9047.  
  9048. me.on('destroy', function() {
  9049. me.statusStore.stopUpdate();
  9050. });
  9051. }
  9052. });
  9053. Ext.define('PVE.qemu.StatusView', {
  9054. extend: 'PVE.grid.ObjectGrid',
  9055. alias: ['widget.pveQemuStatusView'],
  9056.  
  9057. initComponent : function() {
  9058. var me = this;
  9059.  
  9060. var nodename = me.pveSelNode.data.node;
  9061. if (!nodename) {
  9062. throw "no node name specified";
  9063. }
  9064.  
  9065. var vmid = me.pveSelNode.data.vmid;
  9066. if (!vmid) {
  9067. throw "no VM ID specified";
  9068. }
  9069.  
  9070. var render_cpu = function(value, metaData, record, rowIndex, colIndex, store) {
  9071. if (!me.getObjectValue('uptime')) {
  9072. return '-';
  9073. }
  9074.  
  9075. var maxcpu = me.getObjectValue('cpus', 1);
  9076.  
  9077. if (!(Ext.isNumeric(value) && Ext.isNumeric(maxcpu) && (maxcpu >= 1))) {
  9078. return '-';
  9079. }
  9080.  
  9081. var per = (value * 100);
  9082.  
  9083. return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
  9084. };
  9085.  
  9086. var render_mem = function(value, metaData, record, rowIndex, colIndex, store) {
  9087. var maxmem = me.getObjectValue('maxmem', 0);
  9088. var per = (value / maxmem)*100;
  9089. var text = "<div>Total: " + PVE.Utils.format_size(maxmem) + "</div>" +
  9090. "<div>Used: " + PVE.Utils.format_size(value) + "</div>";
  9091. return text;
  9092. };
  9093.  
  9094. var rows = {
  9095. name: { header: gettext('Name'), defaultValue: 'no name specified' },
  9096. qmpstatus: { header: gettext('Status'), defaultValue: 'unknown' },
  9097. cpu: { header: 'CPU usage', required: true, renderer: render_cpu },
  9098. cpus: { visible: false },
  9099. mem: { header: 'Memory usage', required: true, renderer: render_mem },
  9100. maxmem: { visible: false },
  9101. uptime: { header: gettext('Uptime'), required: true, renderer: PVE.Utils.render_uptime },
  9102. ha: { header: 'Managed by HA', required: true, renderer: PVE.Utils.format_boolean }
  9103. };
  9104.  
  9105. Ext.applyIf(me, {
  9106. cwidth1: 150,
  9107. height: 166,
  9108. rows: rows
  9109. });
  9110.  
  9111. me.callParent();
  9112. }
  9113. });
  9114. Ext.define('PVE.window.Migrate', {
  9115. extend: 'Ext.window.Window',
  9116.  
  9117. resizable: false,
  9118.  
  9119. migrate: function(target, online) {
  9120. var me = this;
  9121. PVE.Utils.API2Request({
  9122. params: { target: target, online: online },
  9123. url: '/nodes/' + me.nodename + '/' + me.vmtype + '/' + me.vmid + "/migrate",
  9124. waitMsgTarget: me,
  9125. method: 'POST',
  9126. failure: function(response, opts) {
  9127. Ext.Msg.alert('Error', response.htmlStatus);
  9128. },
  9129. success: function(response, options) {
  9130. var upid = response.result.data;
  9131.  
  9132. var win = Ext.create('PVE.window.TaskViewer', {
  9133. upid: upid
  9134. });
  9135. win.show();
  9136. me.close();
  9137. }
  9138. });
  9139. },
  9140.  
  9141. initComponent : function() {
  9142. var me = this;
  9143.  
  9144. if (!me.nodename) {
  9145. throw "no node name specified";
  9146. }
  9147.  
  9148. if (!me.vmid) {
  9149. throw "no VM ID specified";
  9150. }
  9151.  
  9152. if (!me.vmtype) {
  9153. throw "no VM type specified";
  9154. }
  9155.  
  9156. me.formPanel = Ext.create('Ext.form.Panel', {
  9157. bodyPadding: 10,
  9158. border: false,
  9159. fieldDefaults: {
  9160. labelWidth: 100,
  9161. anchor: '100%'
  9162. },
  9163. items: [
  9164. {
  9165. xtype: 'PVE.form.NodeSelector',
  9166. name: 'target',
  9167. fieldLabel: 'Target node',
  9168. allowBlank: false,
  9169. onlineValidator: true
  9170. },
  9171. {
  9172. xtype: 'pvecheckbox',
  9173. name: 'online',
  9174. uncheckedValue: 0,
  9175. defaultValue: 0,
  9176. fieldLabel: 'Online'
  9177. }
  9178. ]
  9179. });
  9180.  
  9181. var form = me.formPanel.getForm();
  9182.  
  9183. var submitBtn = Ext.create('Ext.Button', {
  9184. text: 'Migrate',
  9185. handler: function() {
  9186. var values = form.getValues();
  9187. me.migrate(values.target, values.online);
  9188. }
  9189. });
  9190.  
  9191. Ext.apply(me, {
  9192. title: "Migrate VM " + me.vmid,
  9193. width: 350,
  9194. modal: true,
  9195. layout: 'auto',
  9196. border: false,
  9197. items: [ me.formPanel ],
  9198. buttons: [ submitBtn ]
  9199. });
  9200.  
  9201. me.callParent();
  9202. }
  9203. });
  9204. Ext.define('PVE.qemu.Monitor', {
  9205. extend: 'Ext.panel.Panel',
  9206.  
  9207. alias: 'widget.pveQemuMonitor',
  9208.  
  9209. maxLines: 500,
  9210.  
  9211. initComponent : function() {
  9212. var me = this;
  9213.  
  9214. var nodename = me.pveSelNode.data.node;
  9215. if (!nodename) {
  9216. throw "no node name specified";
  9217. }
  9218.  
  9219. var vmid = me.pveSelNode.data.vmid;
  9220. if (!vmid) {
  9221. throw "no VM ID specified";
  9222. }
  9223.  
  9224. var lines = [];
  9225.  
  9226. var textbox = Ext.createWidget('panel', {
  9227. region: 'center',
  9228. xtype: 'panel',
  9229. autoScroll: true,
  9230. border: true,
  9231. margins: '5 5 5 5',
  9232. bodyStyle: 'font-family: monospace;'
  9233. });
  9234.  
  9235. var scrollToEnd = function() {
  9236. var el = textbox.getTargetEl();
  9237. var dom = Ext.getDom(el);
  9238.  
  9239. var clientHeight = dom.clientHeight;
  9240. // BrowserBug: clientHeight reports 0 in IE9 StrictMode
  9241. // Instead we are using offsetHeight and hardcoding borders
  9242. if (Ext.isIE9 && Ext.isStrict) {
  9243. clientHeight = dom.offsetHeight + 2;
  9244. }
  9245. dom.scrollTop = dom.scrollHeight - clientHeight;
  9246. };
  9247.  
  9248. var refresh = function() {
  9249. textbox.update('<pre>' + lines.join('\n') + '</pre>');
  9250. scrollToEnd();
  9251. };
  9252.  
  9253. var addLine = function(line) {
  9254. lines.push(line);
  9255. if (lines.length > me.maxLines) {
  9256. lines.shift();
  9257. }
  9258. };
  9259.  
  9260. var executeCmd = function(cmd) {
  9261. addLine("# " + Ext.htmlEncode(cmd));
  9262. refresh();
  9263. PVE.Utils.API2Request({
  9264. params: { command: cmd },
  9265. url: '/nodes/' + nodename + '/qemu/' + vmid + "/monitor",
  9266. method: 'POST',
  9267. waitMsgTarget: me,
  9268. success: function(response, opts) {
  9269. var res = response.result.data;
  9270. Ext.Array.each(res.split('\n'), function(line) {
  9271. addLine(Ext.htmlEncode(line));
  9272. });
  9273. refresh();
  9274. },
  9275. failure: function(response, opts) {
  9276. Ext.Msg.alert('Error', response.htmlStatus);
  9277. }
  9278. });
  9279. };
  9280.  
  9281. Ext.apply(me, {
  9282. layout: { type: 'border' },
  9283. border: false,
  9284. items: [
  9285. textbox,
  9286. {
  9287. region: 'south',
  9288. margins:'0 5 5 5',
  9289. border: false,
  9290. xtype: 'textfield',
  9291. name: 'cmd',
  9292. value: '',
  9293. fieldStyle: 'font-family: monospace;',
  9294. allowBlank: true,
  9295. listeners: {
  9296. afterrender: function(f) {
  9297. f.focus(false);
  9298. addLine("Type 'help' for help.");
  9299. refresh();
  9300. },
  9301. specialkey: function(f, e) {
  9302. if (e.getKey() === e.ENTER) {
  9303. var cmd = f.getValue();
  9304. f.setValue('');
  9305. executeCmd(cmd);
  9306. }
  9307. }
  9308. }
  9309. }
  9310. ],
  9311. listeners: {
  9312. show: function() {
  9313. var field = me.query('textfield[name="cmd"]')[0];
  9314. field.focus(false, true);
  9315. }
  9316. }
  9317. });
  9318.  
  9319. me.callParent();
  9320. }
  9321. });
  9322. Ext.define('PVE.qemu.Summary', {
  9323. extend: 'Ext.panel.Panel',
  9324. alias: 'widget.pveQemuSummary',
  9325.  
  9326. initComponent: function() {
  9327. var me = this;
  9328.  
  9329. var nodename = me.pveSelNode.data.node;
  9330. if (!nodename) {
  9331. throw "no node name specified";
  9332. }
  9333.  
  9334. var vmid = me.pveSelNode.data.vmid;
  9335. if (!vmid) {
  9336. throw "no VM ID specified";
  9337. }
  9338.  
  9339. if (!me.workspace) {
  9340. throw "no workspace specified";
  9341. }
  9342.  
  9343. if (!me.statusStore) {
  9344. throw "no status storage specified";
  9345. }
  9346.  
  9347. var rstore = me.statusStore;
  9348.  
  9349. var statusview = Ext.create('PVE.qemu.StatusView', {
  9350. title: 'Status',
  9351. pveSelNode: me.pveSelNode,
  9352. width: 400,
  9353. rstore: rstore
  9354. });
  9355.  
  9356. var rrdurl = "/api2/png/nodes/" + nodename + "/qemu/" + vmid + "/rrd";
  9357.  
  9358. var notesview = Ext.create('PVE.panel.NotesView', {
  9359. pveSelNode: me.pveSelNode,
  9360. flex: 1
  9361. });
  9362.  
  9363. Ext.apply(me, {
  9364. tbar: [ '->', { xtype: 'pveRRDTypeSelector' } ],
  9365. autoScroll: true,
  9366. bodyStyle: 'padding:10px',
  9367. defaults: {
  9368. style: 'padding-top:10px',
  9369. width: 800
  9370. },
  9371. items: [
  9372. {
  9373. style: 'padding-top:0px',
  9374. layout: {
  9375. type: 'hbox',
  9376. align: 'stretchmax'
  9377. },
  9378. border: false,
  9379. items: [ statusview, notesview ]
  9380. },
  9381. {
  9382. xtype: 'pveRRDView',
  9383. title: "CPU usage %",
  9384. pveSelNode: me.pveSelNode,
  9385. datasource: 'cpu',
  9386. rrdurl: rrdurl
  9387. },
  9388. {
  9389. xtype: 'pveRRDView',
  9390. title: "Memory usage",
  9391. pveSelNode: me.pveSelNode,
  9392. datasource: 'mem,maxmem',
  9393. rrdurl: rrdurl
  9394. },
  9395. {
  9396. xtype: 'pveRRDView',
  9397. title: "Network traffic",
  9398. pveSelNode: me.pveSelNode,
  9399. datasource: 'netin,netout',
  9400. rrdurl: rrdurl
  9401. },
  9402. {
  9403. xtype: 'pveRRDView',
  9404. title: "Disk IO",
  9405. pveSelNode: me.pveSelNode,
  9406. datasource: 'diskread,diskwrite',
  9407. rrdurl: rrdurl
  9408. }
  9409. ]
  9410. });
  9411.  
  9412. me.on('show', function() {
  9413. notesview.load();
  9414. });
  9415.  
  9416. me.callParent();
  9417. }
  9418. });
  9419. Ext.define('PVE.qemu.OSTypeInputPanel', {
  9420. extend: 'PVE.panel.InputPanel',
  9421. alias: 'widget.PVE.qemu.OSTypeInputPanel',
  9422.  
  9423. initComponent : function() {
  9424. var me = this;
  9425.  
  9426. me.column1 = [
  9427. {
  9428. xtype: 'component',
  9429. html: 'Microsoft Windows',
  9430. cls:'x-form-check-group-label'
  9431. },
  9432. {
  9433. xtype: 'radiofield',
  9434. name: 'ostype',
  9435. inputValue: 'win7'
  9436. },
  9437. {
  9438. xtype: 'radiofield',
  9439. name: 'ostype',
  9440. inputValue: 'w2k8'
  9441. },
  9442. {
  9443. xtype: 'radiofield',
  9444. name: 'ostype',
  9445. inputValue: 'wxp'
  9446. },
  9447. {
  9448. xtype: 'radiofield',
  9449. name: 'ostype',
  9450. inputValue: 'w2k'
  9451. }
  9452. ];
  9453.  
  9454. me.column2 = [
  9455. {
  9456. xtype: 'component',
  9457. html: 'Linux/Other',
  9458. cls:'x-form-check-group-label'
  9459. },
  9460. {
  9461. xtype: 'radiofield',
  9462. name: 'ostype',
  9463. inputValue: 'l26'
  9464. },
  9465. {
  9466. xtype: 'radiofield',
  9467. name: 'ostype',
  9468. inputValue: 'l24'
  9469. },
  9470. {
  9471. xtype: 'radiofield',
  9472. name: 'ostype',
  9473. inputValue: 'other'
  9474. }
  9475. ];
  9476.  
  9477. Ext.Array.each(me.column1, function(def) {
  9478. if (def.inputValue) {
  9479. def.boxLabel = PVE.Utils.render_kvm_ostype(def.inputValue);
  9480. }
  9481. });
  9482. Ext.Array.each(me.column2, function(def) {
  9483. if (def.inputValue) {
  9484. def.boxLabel = PVE.Utils.render_kvm_ostype(def.inputValue);
  9485. }
  9486. });
  9487.  
  9488. Ext.apply(me, {
  9489. useFieldContainer: {
  9490. xtype: 'radiogroup',
  9491. allowBlank: false
  9492. }
  9493. });
  9494.  
  9495. me.callParent();
  9496. }
  9497. });
  9498.  
  9499. Ext.define('PVE.qemu.OSTypeEdit', {
  9500. extend: 'PVE.window.Edit',
  9501.  
  9502. initComponent : function() {
  9503. var me = this;
  9504.  
  9505. Ext.apply(me, {
  9506. subject: 'OS Type',
  9507. items: Ext.create('PVE.qemu.OSTypeInputPanel')
  9508. });
  9509.  
  9510. me.callParent();
  9511.  
  9512. me.load({
  9513. success: function(response, options) {
  9514. var value = response.result.data.ostype || 'other';
  9515. me.setValues({ ostype: value});
  9516. }
  9517. });
  9518. }
  9519. });
  9520. Ext.define('PVE.qemu.ProcessorInputPanel', {
  9521. extend: 'PVE.panel.InputPanel',
  9522. alias: 'widget.PVE.qemu.ProcessorInputPanel',
  9523.  
  9524. initComponent : function() {
  9525. var me = this;
  9526.  
  9527. me.column1 = [
  9528. {
  9529. xtype: 'numberfield',
  9530. name: 'sockets',
  9531. minValue: 1,
  9532. maxValue: 4,
  9533. value: '1',
  9534. fieldLabel: 'Sockets',
  9535. allowBlank: false,
  9536. listeners: {
  9537. change: function(f, value) {
  9538. var sockets = me.down('field[name=sockets]').getValue();
  9539. var cores = me.down('field[name=cores]').getValue();
  9540. me.down('field[name=totalcores]').setValue(sockets*cores);
  9541. }
  9542. }
  9543. },
  9544. {
  9545. xtype: 'numberfield',
  9546. name: 'cores',
  9547. minValue: 1,
  9548. maxValue: 32,
  9549. value: '1',
  9550. fieldLabel: 'Cores',
  9551. allowBlank: false,
  9552. listeners: {
  9553. change: function(f, value) {
  9554. var sockets = me.down('field[name=sockets]').getValue();
  9555. var cores = me.down('field[name=cores]').getValue();
  9556. me.down('field[name=totalcores]').setValue(sockets*cores);
  9557. }
  9558. }
  9559. }
  9560. ];
  9561.  
  9562.  
  9563. me.column2 = [
  9564. {
  9565. xtype: 'CPUModelSelector',
  9566. name: 'cpu',
  9567. value: '',
  9568. fieldLabel: 'CPU type'
  9569. },
  9570. {
  9571. xtype: 'displayfield',
  9572. fieldLabel: 'Total cores',
  9573. name: 'totalcores',
  9574. value: '1'
  9575. }
  9576.  
  9577. ];
  9578.  
  9579. me.callParent();
  9580. }
  9581. });
  9582.  
  9583. Ext.define('PVE.qemu.ProcessorEdit', {
  9584. extend: 'PVE.window.Edit',
  9585.  
  9586. initComponent : function() {
  9587. var me = this;
  9588.  
  9589. Ext.apply(me, {
  9590. subject: gettext('Processors'),
  9591. items: Ext.create('PVE.qemu.ProcessorInputPanel')
  9592. });
  9593.  
  9594. me.callParent();
  9595.  
  9596. me.load();
  9597. }
  9598. });Ext.define('PVE.qemu.BootOrderPanel', {
  9599. extend: 'PVE.panel.InputPanel',
  9600.  
  9601. vmconfig: {}, // store loaded vm config
  9602.  
  9603. bootdisk: undefined,
  9604. curSel1: '',
  9605. curSel2: '',
  9606. curSel3: '',
  9607.  
  9608. onGetValues: function(values) {
  9609. var me = this;
  9610.  
  9611. var order = '';
  9612.  
  9613. if (me.curSel1) {
  9614. order = order + me.curSel1;
  9615. }
  9616. if (me.curSel2) {
  9617. order = order + me.curSel2;
  9618. }
  9619. if (me.curSel3) {
  9620. order = order + me.curSel3;
  9621. }
  9622.  
  9623. var res = { boot: order };
  9624. if (me.bootdisk && (me.curSel1 === 'c' || me.curSel2 === 'c' || me.curSel3 === 'c') ) {
  9625. res.bootdisk = me.bootdisk;
  9626. } else {
  9627. res['delete'] = 'bootdisk';
  9628. }
  9629.  
  9630. return res;
  9631. },
  9632.  
  9633. setVMConfig: function(vmconfig) {
  9634. var me = this;
  9635.  
  9636. me.vmconfig = vmconfig;
  9637.  
  9638. var order = me.vmconfig.boot || 'cdn';
  9639. me.bootdisk = me.vmconfig.bootdisk;
  9640. if (!me.vmconfig[me.bootdisk]) {
  9641. me.bootdisk = undefined;
  9642. }
  9643. me.curSel1 = order.substring(0, 1) || '';
  9644. me.curSel2 = order.substring(1, 2) || '';
  9645. me.curSel3 = order.substring(2, 3) || '';
  9646.  
  9647. me.compute_sel1();
  9648.  
  9649. me.kv1.resetOriginalValue();
  9650. me.kv2.resetOriginalValue();
  9651. me.kv3.resetOriginalValue();
  9652. },
  9653.  
  9654. genList: function(includeNone, sel1, sel2) {
  9655. var me = this;
  9656. var list = [];
  9657.  
  9658. if (sel1 !== 'c' && (sel2 !== 'c')) {
  9659. Ext.Object.each(me.vmconfig, function(key, value) {
  9660. if ((/^(ide|scsi|virtio)\d+$/).test(key) &&
  9661. !(/media=cdrom/).test(value)) {
  9662. list.push([key, "Disk '" + key + "'"]);
  9663. }
  9664. });
  9665. }
  9666.  
  9667. if (sel1 !== 'd' && (sel2 !== 'd')) {
  9668. list.push(['d', 'CD-ROM']);
  9669. }
  9670. if (sel1 !== 'n' && (sel2 !== 'n')) {
  9671. list.push(['n', gettext('Network')]);
  9672. }
  9673. //if (sel1 !== 'a' && (sel2 !== 'a')) {
  9674. // list.push(['a', 'Floppy']);
  9675. //}
  9676.  
  9677. if (includeNone) {
  9678. list.push(['', 'none']);
  9679. }
  9680.  
  9681. return list;
  9682. },
  9683.  
  9684. compute_sel3: function() {
  9685. var me = this;
  9686. var list = me.genList(true, me.curSel1, me.curSel2);
  9687. me.kv3.store.loadData(list);
  9688. me.kv3.setValue((me.curSel3 === 'c') ? me.bootdisk : me.curSel3);
  9689. },
  9690.  
  9691. compute_sel2: function() {
  9692. var me = this;
  9693. var list = me.genList(true, me.curSel1);
  9694. me.kv2.store.loadData(list);
  9695. me.kv2.setValue((me.curSel2 === 'c') ? me.bootdisk : me.curSel2);
  9696. me.compute_sel3();
  9697. },
  9698.  
  9699. compute_sel1: function() {
  9700. var me = this;
  9701. var list = me.genList(false);
  9702. me.kv1.store.loadData(list);
  9703. me.kv1.setValue((me.curSel1 === 'c') ? me.bootdisk : me.curSel1);
  9704. me.compute_sel2();
  9705. },
  9706.  
  9707. initComponent : function() {
  9708. var me = this;
  9709.  
  9710. me.kv1 = Ext.create('PVE.form.KVComboBox', {
  9711. fieldLabel: gettext('Boot device') + " 1",
  9712. labelWidth: 120,
  9713. name: 'bd1',
  9714. allowBlank: false,
  9715. data: []
  9716. });
  9717.  
  9718. me.kv2 = Ext.create('PVE.form.KVComboBox', {
  9719. fieldLabel: gettext('Boot device') + " 2",
  9720. labelWidth: 120,
  9721. name: 'bd2',
  9722. allowBlank: false,
  9723. data: []
  9724. });
  9725.  
  9726. me.kv3 = Ext.create('PVE.form.KVComboBox', {
  9727. fieldLabel: gettext('Boot device') + " 3",
  9728. labelWidth: 120,
  9729. name: 'bd3',
  9730. allowBlank: false,
  9731. data: []
  9732. });
  9733.  
  9734. me.mon(me.kv1, 'change', function(t, value) {
  9735. if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9736. me.curSel1 = 'c';
  9737. me.bootdisk = value;
  9738. } else {
  9739. me.curSel1 = value;
  9740. }
  9741. me.compute_sel2();
  9742. });
  9743.  
  9744. me.mon(me.kv2, 'change', function(t, value) {
  9745. if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9746. me.curSel2 = 'c';
  9747. me.bootdisk = value;
  9748. } else {
  9749. me.curSel2 = value;
  9750. }
  9751. me.compute_sel3();
  9752. });
  9753.  
  9754. me.mon(me.kv3, 'change', function(t, value) {
  9755. if ((/^(ide|scsi|virtio)\d+$/).test(value)) {
  9756. me.curSel3 = 'c';
  9757. me.bootdisk = value;
  9758. } else {
  9759. me.curSel3 = value;
  9760. }
  9761. });
  9762.  
  9763. Ext.apply(me, {
  9764. items: [ me.kv1, me.kv2, me.kv3 ]
  9765. });
  9766.  
  9767. me.callParent();
  9768. }
  9769. });
  9770.  
  9771. Ext.define('PVE.qemu.BootOrderEdit', {
  9772. extend: 'PVE.window.Edit',
  9773.  
  9774. initComponent : function() {
  9775. var me = this;
  9776.  
  9777. var ipanel = Ext.create('PVE.qemu.BootOrderPanel', {});
  9778.  
  9779. me.items = [ ipanel ];
  9780.  
  9781. me.subject = gettext('Boot order');
  9782.  
  9783. me.callParent();
  9784.  
  9785. me.load({
  9786. success: function(response, options) {
  9787. ipanel.setVMConfig(response.result.data);
  9788. }
  9789. });
  9790. }
  9791. });
  9792. Ext.define('PVE.qemu.MemoryInputPanel', {
  9793. extend: 'PVE.panel.InputPanel',
  9794. alias: 'widget.PVE.qemu.MemoryInputPanel',
  9795.  
  9796. insideWizard: false,
  9797.  
  9798. initComponent : function() {
  9799. var me = this;
  9800.  
  9801. var labelWidth = 120;
  9802.  
  9803. var items = {
  9804. xtype: 'numberfield',
  9805. name: 'memory',
  9806. minValue: 32,
  9807. maxValue: 512*1024,
  9808. value: '512',
  9809. step: 32,
  9810. fieldLabel: gettext('Memory') + ' (MB)',
  9811. labelWidth: labelWidth,
  9812. allowBlank: false
  9813. };
  9814.  
  9815. if (me.insideWizard) {
  9816. me.column1 = items;
  9817. } else {
  9818. me.items = items;
  9819. }
  9820.  
  9821. me.callParent();
  9822. }
  9823. });
  9824.  
  9825. Ext.define('PVE.qemu.MemoryEdit', {
  9826. extend: 'PVE.window.Edit',
  9827.  
  9828. initComponent : function() {
  9829. var me = this;
  9830.  
  9831. Ext.apply(me, {
  9832. subject: gettext('Memory'),
  9833. items: Ext.create('PVE.qemu.MemoryInputPanel')
  9834. });
  9835.  
  9836. me.callParent();
  9837.  
  9838. me.load();
  9839. }
  9840. });Ext.define('PVE.qemu.NetworkInputPanel', {
  9841. extend: 'PVE.panel.InputPanel',
  9842. alias: 'widget.PVE.qemu.NetworkInputPanel',
  9843.  
  9844. insideWizard: false,
  9845.  
  9846. onGetValues: function(values) {
  9847. var me = this;
  9848.  
  9849. me.network.model = values.model;
  9850. if (values.networkmode === 'none') {
  9851. return {};
  9852. } else if (values.networkmode === 'bridge') {
  9853. me.network.bridge = values.bridge;
  9854. me.network.tag = values.tag;
  9855. } else {
  9856. me.network.bridge = undefined;
  9857. }
  9858. me.network.macaddr = values.macaddr;
  9859.  
  9860. if (values.rate) {
  9861. me.network.rate = values.rate;
  9862. } else {
  9863. delete me.network.rate;
  9864. }
  9865.  
  9866. var params = {};
  9867.  
  9868. params[me.confid] = PVE.Parser.printQemuNetwork(me.network);
  9869.  
  9870. return params;
  9871. },
  9872.  
  9873. setNetwork: function(confid, data) {
  9874. var me = this;
  9875.  
  9876. me.confid = confid;
  9877.  
  9878. if (data) {
  9879. data.networkmode = data.bridge ? 'bridge' : 'nat';
  9880. } else {
  9881. data = {};
  9882. data.networkmode = 'bridge';
  9883. }
  9884. me.network = data;
  9885.  
  9886. me.setValues(me.network);
  9887. },
  9888.  
  9889. setNodename: function(nodename) {
  9890. var me = this;
  9891.  
  9892. me.bridgesel.setNodename(nodename);
  9893. },
  9894.  
  9895. initComponent : function() {
  9896. var me = this;
  9897.  
  9898. me.network = {};
  9899. me.confid = 'net0';
  9900.  
  9901. me.bridgesel = Ext.create('PVE.form.BridgeSelector', {
  9902. name: 'bridge',
  9903. fieldLabel: 'Bridge',
  9904. nodename: me.nodename,
  9905. labelAlign: 'right',
  9906. autoSelect: true,
  9907. allowBlank: false
  9908. });
  9909.  
  9910. me.column1 = [
  9911. {
  9912. xtype: 'radiofield',
  9913. name: 'networkmode',
  9914. height: 22, // hack: set same height as text fields
  9915. inputValue: 'bridge',
  9916. boxLabel: 'Bridged mode',
  9917. checked: true,
  9918. listeners: {
  9919. change: function(f, value) {
  9920. if (!me.rendered) {
  9921. return;
  9922. }
  9923. me.down('field[name=bridge]').setDisabled(!value);
  9924. me.down('field[name=bridge]').validate();
  9925. me.down('field[name=tag]').setDisabled(!value);
  9926. }
  9927. }
  9928. },
  9929. me.bridgesel,
  9930. {
  9931. xtype: 'numberfield',
  9932. name: 'tag',
  9933. minValue: 1,
  9934. maxValue: 4094,
  9935. value: '',
  9936. emptyText: 'no VLAN',
  9937. fieldLabel: 'VLAN Tag',
  9938. labelAlign: 'right',
  9939. allowBlank: true
  9940. },
  9941. {
  9942. xtype: 'radiofield',
  9943. name: 'networkmode',
  9944. height: 22, // hack: set same height as text fields
  9945. inputValue: 'nat',
  9946. boxLabel: 'NAT mode'
  9947. }
  9948. ];
  9949.  
  9950. if (me.insideWizard) {
  9951. me.column1.push({
  9952. xtype: 'radiofield',
  9953. name: 'networkmode',
  9954. height: 22, // hack: set same height as text fields
  9955. inputValue: 'none',
  9956. boxLabel: 'No network device'
  9957. });
  9958. }
  9959.  
  9960. me.column2 = [
  9961. {
  9962. xtype: 'PVE.form.NetworkCardSelector',
  9963. name: 'model',
  9964. fieldLabel: 'Model',
  9965. value: 'rtl8139',
  9966. allowBlank: false
  9967. },
  9968. {
  9969. xtype: 'textfield',
  9970. name: 'macaddr',
  9971. fieldLabel: 'MAC address',
  9972. vtype: 'MacAddress',
  9973. allowBlank: true,
  9974. emptyText: 'auto'
  9975. },
  9976. {
  9977. xtype: 'numberfield',
  9978. name: 'rate',
  9979. fieldLabel: 'Rate limit (MB/s)',
  9980. minValue: 0,
  9981. maxValue: 10*1024,
  9982. value: '',
  9983. emptyText: 'unlimited',
  9984. allowBlank: true
  9985. }
  9986. ];
  9987.  
  9988. me.callParent();
  9989. }
  9990. });
  9991.  
  9992. Ext.define('PVE.qemu.NetworkEdit', {
  9993. extend: 'PVE.window.Edit',
  9994.  
  9995. isAdd: true,
  9996.  
  9997. initComponent : function() {
  9998. /*jslint confusion: true */
  9999.  
  10000. var me = this;
  10001.  
  10002. var nodename = me.pveSelNode.data.node;
  10003. if (!nodename) {
  10004. throw "no node name specified";
  10005. }
  10006.  
  10007. me.create = me.confid ? false : true;
  10008.  
  10009. var ipanel = Ext.create('PVE.qemu.NetworkInputPanel', {
  10010. confid: me.confid,
  10011. nodename: nodename
  10012. });
  10013.  
  10014. Ext.applyIf(me, {
  10015. subject: gettext('Network Device'),
  10016. items: ipanel
  10017. });
  10018.  
  10019. me.callParent();
  10020.  
  10021. me.load({
  10022. success: function(response, options) {
  10023. var i, confid;
  10024. me.vmconfig = response.result.data;
  10025. if (!me.create) {
  10026. var value = me.vmconfig[me.confid];
  10027. var network = PVE.Parser.parseQemuNetwork(me.confid, value);
  10028. if (!network) {
  10029. Ext.Msg.alert('Error', 'Unable to parse network options');
  10030. me.close();
  10031. return;
  10032. }
  10033. ipanel.setNetwork(me.confid, network);
  10034. } else {
  10035. for (i = 0; i < 100; i++) {
  10036. confid = 'net' + i.toString();
  10037. if (!Ext.isDefined(me.vmconfig[confid])) {
  10038. me.confid = confid;
  10039. break;
  10040. }
  10041. }
  10042. ipanel.setNetwork(me.confid);
  10043. }
  10044. }
  10045. });
  10046. }
  10047. });
  10048. // fixme: howto avoid jslint type confusion?
  10049. /*jslint confusion: true */
  10050. Ext.define('PVE.qemu.CDInputPanel', {
  10051. extend: 'PVE.panel.InputPanel',
  10052. alias: 'widget.PVE.qemu.CDInputPanel',
  10053.  
  10054. insideWizard: false,
  10055.  
  10056. onGetValues: function(values) {
  10057. var me = this;
  10058.  
  10059. var confid = me.confid || (values.controller + values.deviceid);
  10060.  
  10061. me.drive.media = 'cdrom';
  10062. if (values.mediaType === 'iso') {
  10063. me.drive.file = values.cdimage;
  10064. } else if (values.mediaType === 'cdrom') {
  10065. me.drive.file = 'cdrom';
  10066. } else {
  10067. me.drive.file = 'none';
  10068. }
  10069.  
  10070. var params = {};
  10071.  
  10072. params[confid] = PVE.Parser.printQemuDrive(me.drive);
  10073.  
  10074. return params;
  10075. },
  10076.  
  10077. setVMConfig: function(vmconfig) {
  10078. var me = this;
  10079.  
  10080. if (me.bussel) {
  10081. me.bussel.setVMConfig(vmconfig, 'cdrom');
  10082. }
  10083. },
  10084.  
  10085. setDrive: function(drive) {
  10086. var me = this;
  10087.  
  10088. var values = {};
  10089. if (drive.file === 'cdrom') {
  10090. values.mediaType = 'cdrom';
  10091. } else if (drive.file === 'none') {
  10092. values.mediaType = 'none';
  10093. } else {
  10094. values.mediaType = 'iso';
  10095. var match = drive.file.match(/^([^:]+):/);
  10096. if (match) {
  10097. values.cdstorage = match[1];
  10098. values.cdimage = drive.file;
  10099. }
  10100. }
  10101.  
  10102. me.drive = drive;
  10103.  
  10104. me.setValues(values);
  10105. },
  10106.  
  10107. setNodename: function(nodename) {
  10108. var me = this;
  10109.  
  10110. me.cdstoragesel.setNodename(nodename);
  10111. me.cdfilesel.setStorage(undefined, nodename);
  10112. },
  10113.  
  10114. initComponent : function() {
  10115. var me = this;
  10116.  
  10117. me.drive = {};
  10118.  
  10119. var items = [];
  10120.  
  10121. if (!me.confid) {
  10122. me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
  10123. noVirtIO: true
  10124. });
  10125. items.push(me.bussel);
  10126. }
  10127.  
  10128. items.push({
  10129. xtype: 'radiofield',
  10130. name: 'mediaType',
  10131. inputValue: 'iso',
  10132. boxLabel: 'Use CD/DVD disc image file (iso)',
  10133. checked: true,
  10134. listeners: {
  10135. change: function(f, value) {
  10136. if (!me.rendered) {
  10137. return;
  10138. }
  10139. me.down('field[name=cdstorage]').setDisabled(!value);
  10140. me.down('field[name=cdimage]').setDisabled(!value);
  10141. me.down('field[name=cdimage]').validate();
  10142. }
  10143. }
  10144. });
  10145.  
  10146. me.cdfilesel = Ext.create('PVE.form.FileSelector', {
  10147. name: 'cdimage',
  10148. nodename: me.nodename,
  10149. storageContent: 'iso',
  10150. fieldLabel: 'ISO Image',
  10151. labelAlign: 'right',
  10152. allowBlank: false
  10153. });
  10154.  
  10155. me.cdstoragesel = Ext.create('PVE.form.StorageSelector', {
  10156. name: 'cdstorage',
  10157. nodename: me.nodename,
  10158. fieldLabel: gettext('Storage'),
  10159. labelAlign: 'right',
  10160. storageContent: 'iso',
  10161. allowBlank: false,
  10162. autoSelect: me.insideWizard,
  10163. listeners: {
  10164. change: function(f, value) {
  10165. me.cdfilesel.setStorage(value);
  10166. }
  10167. }
  10168. });
  10169.  
  10170. items.push(me.cdstoragesel);
  10171. items.push(me.cdfilesel);
  10172.  
  10173. items.push({
  10174. xtype: 'radiofield',
  10175. name: 'mediaType',
  10176. inputValue: 'cdrom',
  10177. boxLabel: 'Use physical CD/DVD Drive'
  10178. });
  10179.  
  10180. items.push({
  10181. xtype: 'radiofield',
  10182. name: 'mediaType',
  10183. inputValue: 'none',
  10184. boxLabel: 'Do not use any media'
  10185. });
  10186.  
  10187. if (me.insideWizard) {
  10188. me.column1 = items;
  10189. } else {
  10190. me.items = items;
  10191. }
  10192.  
  10193. me.callParent();
  10194. }
  10195. });
  10196.  
  10197. Ext.define('PVE.qemu.CDEdit', {
  10198. extend: 'PVE.window.Edit',
  10199.  
  10200. initComponent : function() {
  10201. var me = this;
  10202.  
  10203. var nodename = me.pveSelNode.data.node;
  10204. if (!nodename) {
  10205. throw "no node name specified";
  10206. }
  10207.  
  10208. me.create = me.confid ? false : true;
  10209.  
  10210. var ipanel = Ext.create('PVE.qemu.CDInputPanel', {
  10211. confid: me.confid,
  10212. nodename: nodename
  10213. });
  10214.  
  10215. Ext.applyIf(me, {
  10216. subject: 'CD/DVD Drive',
  10217. items: [ ipanel ]
  10218. });
  10219.  
  10220. me.callParent();
  10221.  
  10222. me.load({
  10223. success: function(response, options) {
  10224. ipanel.setVMConfig(response.result.data);
  10225. if (me.confid) {
  10226. var value = response.result.data[me.confid];
  10227. var drive = PVE.Parser.parseQemuDrive(me.confid, value);
  10228. if (!drive) {
  10229. Ext.Msg.alert('Error', 'Unable to parse drive options');
  10230. me.close();
  10231. return;
  10232. }
  10233. ipanel.setDrive(drive);
  10234. }
  10235. }
  10236. });
  10237. }
  10238. });
  10239. // fixme: howto avoid jslint type confusion?
  10240. /*jslint confusion: true */
  10241. Ext.define('PVE.qemu.HDInputPanel', {
  10242. extend: 'PVE.panel.InputPanel',
  10243. alias: 'widget.PVE.qemu.HDInputPanel',
  10244.  
  10245. insideWizard: false,
  10246.  
  10247. unused: false, // ADD usused disk imaged
  10248.  
  10249. vmconfig: {}, // used to select usused disks
  10250.  
  10251. onGetValues: function(values) {
  10252. var me = this;
  10253.  
  10254. var confid = me.confid || (values.controller + values.deviceid);
  10255.  
  10256. if (me.unused) {
  10257. me.drive.file = me.vmconfig[values.unusedId];
  10258. confid = values.controller + values.deviceid;
  10259. } else if (me.create) {
  10260. if (values.hdimage) {
  10261. me.drive.file = values.hdimage;
  10262. } else {
  10263. me.drive.file = values.hdstorage + ":" + values.disksize;
  10264. }
  10265. me.drive.format = values.diskformat;
  10266. }
  10267.  
  10268. if (values.cache) {
  10269. me.drive.cache = values.cache;
  10270. } else {
  10271. delete me.drive.cache;
  10272. }
  10273.  
  10274. if (values.nobackup) {
  10275. me.drive.backup = 'no';
  10276. } else {
  10277. delete me.drive.backup;
  10278. }
  10279.  
  10280. if (values.bps_rd) {
  10281. me.drive.bps_rd = Ext.Number.toFixed(values.bps_rd * 1024 * 1024);
  10282. } else {
  10283. delete me.drive.bps_rd;
  10284. }
  10285. if (values.bps_wr) {
  10286. me.drive.bps_wr = Ext.Number.toFixed(values.bps_wr * 1024 * 1024);
  10287. } else {
  10288. delete me.drive.bps_wr;
  10289. }
  10290. if (values.iops_rd) {
  10291. me.drive.iops_rd = values.iops_rd;
  10292. } else {
  10293. delete me.drive.iops_rd;
  10294. }
  10295. if (values.iops_wr) {
  10296. me.drive.iops_wr = values.iops_wr;
  10297. } else {
  10298. delete me.drive.iops_wr;
  10299. }
  10300.  
  10301. var params = {};
  10302.  
  10303. params[confid] = PVE.Parser.printQemuDrive(me.drive);
  10304.  
  10305. return params;
  10306. },
  10307.  
  10308. setVMConfig: function(vmconfig) {
  10309. var me = this;
  10310.  
  10311. me.vmconfig = vmconfig;
  10312.  
  10313. if (me.bussel) {
  10314. me.bussel.setVMConfig(vmconfig, true);
  10315. }
  10316. if (me.unusedDisks) {
  10317. var disklist = [];
  10318. Ext.Object.each(vmconfig, function(key, value) {
  10319. if (key.match(/^unused\d+$/)) {
  10320. disklist.push([key, value]);
  10321. }
  10322. });
  10323. me.unusedDisks.store.loadData(disklist);
  10324. me.unusedDisks.setValue(me.confid);
  10325. }
  10326. },
  10327.  
  10328. setDrive: function(drive) {
  10329. var me = this;
  10330.  
  10331. me.drive = drive;
  10332.  
  10333. var values = {};
  10334. var match = drive.file.match(/^([^:]+):/);
  10335. if (match) {
  10336. values.hdstorage = match[1];
  10337. }
  10338.  
  10339. values.hdimage = drive.file;
  10340. values.nobackup = (drive.backup === 'no');
  10341. values.diskformat = drive.format || 'raw';
  10342. values.cache = drive.cache || '';
  10343. values.bps_rd = drive.bps_rd / (1024 * 1024);
  10344. values.bps_wr = drive.bps_wr / (1024 * 1024);
  10345. values.iops_rd = drive.iops_rd;
  10346. values.iops_wr = drive.iops_wr;
  10347.  
  10348. me.setValues(values);
  10349. },
  10350.  
  10351. setNodename: function(nodename) {
  10352. var me = this;
  10353. me.hdstoragesel.setNodename(nodename);
  10354. me.hdfilesel.setStorage(undefined, nodename);
  10355. },
  10356.  
  10357. initComponent : function() {
  10358. var me = this;
  10359.  
  10360. me.drive = {};
  10361.  
  10362. me.column1 = [];
  10363. me.column2 = [];
  10364.  
  10365. if (!me.confid || me.unused) {
  10366. me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
  10367. // boot from scsi does not work in kvm 1.0
  10368. noScsi: me.insideWizard ? true : false,
  10369. vmconfig: me.insideWizard ? {ide2: 'cdrom'} : {}
  10370. });
  10371. me.column1.push(me.bussel);
  10372. }
  10373.  
  10374. if (me.unused) {
  10375. me.unusedDisks = Ext.create('PVE.form.KVComboBox', {
  10376. name: 'unusedId',
  10377. fieldLabel: gettext('Disk image'),
  10378. matchFieldWidth: false,
  10379. listConfig: {
  10380. width: 350
  10381. },
  10382. data: [],
  10383. allowBlank: false
  10384. });
  10385. me.column1.push(me.unusedDisks);
  10386. } else if (me.create) {
  10387. me.formatsel = Ext.create('PVE.form.DiskFormatSelector', {
  10388. name: 'diskformat',
  10389. fieldLabel: gettext('Format'),
  10390. value: 'raw',
  10391. allowBlank: false
  10392. });
  10393.  
  10394. me.hdfilesel = Ext.create('PVE.form.FileSelector', {
  10395. name: 'hdimage',
  10396. nodename: me.nodename,
  10397. storageContent: 'images',
  10398. fieldLabel: gettext('Disk image'),
  10399. disabled: true,
  10400. hidden: true,
  10401. allowBlank: false
  10402. });
  10403.  
  10404. me.hdsizesel = Ext.createWidget('numberfield', {
  10405. name: 'disksize',
  10406. minValue: 0.001,
  10407. maxValue: 128*1024,
  10408. decimalPrecision: 3,
  10409. value: '32',
  10410. fieldLabel: gettext('Disk size') + ' (GB)',
  10411. allowBlank: false
  10412. });
  10413.  
  10414. me.hdstoragesel = Ext.create('PVE.form.StorageSelector', {
  10415. name: 'hdstorage',
  10416. nodename: me.nodename,
  10417. fieldLabel: gettext('Storage'),
  10418. storageContent: 'images',
  10419. autoSelect: me.insideWizard,
  10420. allowBlank: false,
  10421. listeners: {
  10422. change: function(f, value) {
  10423. var rec = f.store.getById(value);
  10424. if (rec.data.type === 'iscsi') {
  10425. me.hdfilesel.setStorage(value);
  10426. me.hdfilesel.setDisabled(false);
  10427. me.formatsel.setValue('raw');
  10428. me.formatsel.setDisabled(true);
  10429. me.hdfilesel.setVisible(true);
  10430. me.hdsizesel.setDisabled(true);
  10431. me.hdsizesel.setVisible(false);
  10432. } else if (rec.data.type === 'lvm') {
  10433. me.hdfilesel.setDisabled(true);
  10434. me.hdfilesel.setVisible(false);
  10435. me.formatsel.setValue('raw');
  10436. me.formatsel.setDisabled(true);
  10437. me.hdsizesel.setDisabled(false);
  10438. me.hdsizesel.setVisible(true);
  10439. } else {
  10440. me.hdfilesel.setDisabled(true);
  10441. me.hdfilesel.setVisible(false);
  10442. me.formatsel.setDisabled(false);
  10443. me.hdsizesel.setDisabled(false);
  10444. me.hdsizesel.setVisible(true);
  10445. }
  10446. }
  10447. }
  10448. });
  10449. me.column1.push(me.hdstoragesel);
  10450. me.column1.push(me.hdfilesel);
  10451. me.column1.push(me.hdsizesel);
  10452. me.column1.push(me.formatsel);
  10453.  
  10454. } else {
  10455. me.column1.push({
  10456. xtype: 'textfield',
  10457. disabled: true,
  10458. submitValue: false,
  10459. fieldLabel: gettext('Disk image'),
  10460. name: 'hdimage'
  10461. });
  10462. }
  10463.  
  10464. me.column1.push({
  10465. xtype: 'CacheTypeSelector',
  10466. name: 'cache',
  10467. value: '',
  10468. fieldLabel: 'Cache'
  10469. });
  10470.  
  10471. if (!me.insideWizard) {
  10472. me.column1.push({
  10473. xtype: 'pvecheckbox',
  10474. fieldLabel: gettext('No backup'),
  10475. name: 'nobackup'
  10476. });
  10477. }
  10478.  
  10479. var width2 = 120;
  10480.  
  10481. me.bps_rd = Ext.widget('numberfield', {
  10482. name: 'bps_rd',
  10483. minValue: 1,
  10484. step: 1,
  10485. fieldLabel: gettext('Read limit') + ' (MB/s)',
  10486. labelWidth: width2,
  10487. emptyText: gettext('unlimited')
  10488. });
  10489. me.column2.push(me.bps_rd);
  10490.  
  10491. me.bps_wr = Ext.widget('numberfield', {
  10492. name: 'bps_wr',
  10493. minValue: 1,
  10494. step: 1,
  10495. fieldLabel: gettext('Write limit') + ' (MB/s)',
  10496. labelWidth: width2,
  10497. emptyText: gettext('unlimited')
  10498. });
  10499. me.column2.push(me.bps_wr);
  10500.  
  10501. me.iops_rd = Ext.widget('numberfield', {
  10502. name: 'iops_rd',
  10503. minValue: 10,
  10504. step: 10,
  10505. fieldLabel: gettext('Read limit') + ' (ops/s)',
  10506. labelWidth: width2,
  10507. emptyText: gettext('unlimited')
  10508. });
  10509. me.column2.push(me.iops_rd);
  10510.  
  10511. me.iops_wr = Ext.widget('numberfield', {
  10512. name: 'iops_wr',
  10513. minValue: 10,
  10514. step: 10,
  10515. fieldLabel: gettext('Write limit') + ' (ops/s)',
  10516. labelWidth: width2,
  10517. emptyText: gettext('unlimited')
  10518. });
  10519. me.column2.push(me.iops_wr);
  10520.  
  10521. me.callParent();
  10522. }
  10523. });
  10524.  
  10525. Ext.define('PVE.qemu.HDEdit', {
  10526. extend: 'PVE.window.Edit',
  10527.  
  10528. isAdd: true,
  10529.  
  10530. initComponent : function() {
  10531. var me = this;
  10532.  
  10533. var nodename = me.pveSelNode.data.node;
  10534. if (!nodename) {
  10535. throw "no node name specified";
  10536. }
  10537.  
  10538. var unused = me.confid && me.confid.match(/^unused\d+$/);
  10539.  
  10540. me.create = me.confid ? unused : true;
  10541.  
  10542. var ipanel = Ext.create('PVE.qemu.HDInputPanel', {
  10543. confid: me.confid,
  10544. nodename: nodename,
  10545. unused: unused,
  10546. create: me.create
  10547. });
  10548.  
  10549. var subject;
  10550. if (unused) {
  10551. me.subject = gettext('Unused Disk');
  10552. } else if (me.create) {
  10553. me.subject = gettext('Hard Disk');
  10554. } else {
  10555. me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
  10556. }
  10557.  
  10558. me.items = [ ipanel ];
  10559.  
  10560. me.callParent();
  10561.  
  10562. me.load({
  10563. success: function(response, options) {
  10564. ipanel.setVMConfig(response.result.data);
  10565. if (me.confid) {
  10566. var value = response.result.data[me.confid];
  10567. var drive = PVE.Parser.parseQemuDrive(me.confid, value);
  10568. if (!drive) {
  10569. Ext.Msg.alert('Error', 'Unable to parse drive options');
  10570. me.close();
  10571. return;
  10572. }
  10573. ipanel.setDrive(drive);
  10574. me.isValid(); // trigger validation
  10575. }
  10576. }
  10577. });
  10578. }
  10579. });
  10580. Ext.define('PVE.qemu.DisplayEdit', {
  10581. extend: 'PVE.window.Edit',
  10582.  
  10583. initComponent : function() {
  10584. var me = this;
  10585.  
  10586. Ext.apply(me, {
  10587. subject: gettext('Display'),
  10588. width: 350,
  10589. items: {
  10590. xtype: 'DisplaySelector',
  10591. name: 'vga',
  10592. value: '',
  10593. fieldLabel: gettext('Graphic card')
  10594. }
  10595. });
  10596.  
  10597. me.callParent();
  10598.  
  10599. me.load();
  10600. }
  10601. });
  10602. Ext.define('PVE.qemu.KeyboardEdit', {
  10603. extend: 'PVE.window.Edit',
  10604.  
  10605. initComponent : function() {
  10606. var me = this;
  10607.  
  10608. Ext.applyIf(me, {
  10609. subject: gettext('Keyboard Layout'),
  10610. items: {
  10611. xtype: 'VNCKeyboardSelector',
  10612. name: 'keyboard',
  10613. value: '',
  10614. fieldLabel: gettext('Keyboard Layout')
  10615. }
  10616. });
  10617.  
  10618. me.callParent();
  10619.  
  10620. me.load();
  10621. }
  10622. });
  10623. // fixme: howto avoid jslint type confusion?
  10624. /*jslint confusion: true */
  10625. Ext.define('PVE.qemu.HardwareView', {
  10626. extend: 'PVE.grid.ObjectGrid',
  10627. alias: ['widget.PVE.qemu.HardwareView'],
  10628.  
  10629. renderKey: function(key, metaData, record, rowIndex, colIndex, store) {
  10630. var me = this;
  10631. var rows = me.rows;
  10632. var rowdef = rows[key] || {};
  10633.  
  10634. if (rowdef.tdCls) {
  10635. metaData.tdCls = rowdef.tdCls;
  10636. if (rowdef.tdCls == 'pve-itype-icon-storage') {
  10637. if (record.data.value.match(/media=cdrom/)) {
  10638. metaData.tdCls = 'pve-itype-icon-cdrom';
  10639. return rowdef.cdheader;
  10640. }
  10641. }
  10642. }
  10643. return rowdef.header || key;
  10644. },
  10645.  
  10646. initComponent : function() {
  10647. var me = this;
  10648. var i, confid;
  10649.  
  10650. var nodename = me.pveSelNode.data.node;
  10651. if (!nodename) {
  10652. throw "no node name specified";
  10653. }
  10654.  
  10655. var vmid = me.pveSelNode.data.vmid;
  10656. if (!vmid) {
  10657. throw "no VM ID specified";
  10658. }
  10659.  
  10660. var caps = Ext.state.Manager.get('GuiCap');
  10661.  
  10662. var rows = {
  10663. memory: {
  10664. header: gettext('Memory'),
  10665. editor: caps.vms['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined,
  10666. never_delete: true,
  10667. defaultValue: 512,
  10668. tdCls: 'pve-itype-icon-memory',
  10669. renderer: function(value) {
  10670. return PVE.Utils.format_size(value*1024*1024);
  10671. }
  10672. },
  10673. sockets: {
  10674. header: gettext('Processors'),
  10675. never_delete: true,
  10676. editor: (caps.vms['VM.Config.CPU'] || caps.vms['VM.Config.HWType']) ?
  10677. 'PVE.qemu.ProcessorEdit' : undefined,
  10678. tdCls: 'pve-itype-icon-processor',
  10679. defaultValue: 1,
  10680. renderer: function(value, metaData, record, rowIndex, colIndex, store) {
  10681. var model = me.getObjectValue('cpu');
  10682. var cores = me.getObjectValue('cores');
  10683. var res = '';
  10684. if (!cores || (cores <= 1)) {
  10685. res = value;
  10686. } else {
  10687. res = (value*cores) + ' (' + value + ' sockets, ' + cores + ' cores)';
  10688. }
  10689. if (model) {
  10690. res += ' [' + model + ']';
  10691. }
  10692. return res;
  10693. }
  10694. },
  10695. keyboard: {
  10696. header: gettext('Keyboard Layout'),
  10697. never_delete: true,
  10698. editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.KeyboardEdit' : undefined,
  10699. tdCls: 'pve-itype-icon-keyboard',
  10700. defaultValue: '',
  10701. renderer: PVE.Utils.render_kvm_language
  10702. },
  10703. vga: {
  10704. header: gettext('Display'),
  10705. editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined,
  10706. never_delete: true,
  10707. tdCls: 'pve-itype-icon-display',
  10708. defaultValue: '',
  10709. renderer: PVE.Utils.render_kvm_vga_driver
  10710. },
  10711. cores: {
  10712. visible: false
  10713. },
  10714. cpu: {
  10715. visible: false
  10716. }
  10717. };
  10718.  
  10719. for (i = 0; i < 4; i++) {
  10720. confid = "ide" + i;
  10721. rows[confid] = {
  10722. group: 1,
  10723. tdCls: 'pve-itype-icon-storage',
  10724. editor: 'PVE.qemu.HDEdit',
  10725. never_delete: caps.vms['VM.Config.Disk'] ? false : true,
  10726. header: gettext('Hard Disk') + ' (' + confid +')',
  10727. cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
  10728. };
  10729. }
  10730. for (i = 0; i < 16; i++) {
  10731. confid = "scsi" + i;
  10732. rows[confid] = {
  10733. group: 1,
  10734. tdCls: 'pve-itype-icon-storage',
  10735. editor: 'PVE.qemu.HDEdit',
  10736. never_delete: caps.vms['VM.Config.Disk'] ? false : true,
  10737. header: gettext('Hard Disk') + ' (' + confid +')',
  10738. cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
  10739. };
  10740. }
  10741. for (i = 0; i < 16; i++) {
  10742. confid = "virtio" + i;
  10743. rows[confid] = {
  10744. group: 1,
  10745. tdCls: 'pve-itype-icon-storage',
  10746. editor: 'PVE.qemu.HDEdit',
  10747. never_delete: caps.vms['VM.Config.Disk'] ? false : true,
  10748. header: gettext('Hard Disk') + ' (' + confid +')',
  10749. cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
  10750. };
  10751. }
  10752. for (i = 0; i < 32; i++) {
  10753. confid = "net" + i;
  10754. rows[confid] = {
  10755. group: 2,
  10756. tdCls: 'pve-itype-icon-network',
  10757. editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
  10758. never_delete: caps.vms['VM.Config.Network'] ? false : true,
  10759. header: gettext('Network Device') + ' (' + confid +')'
  10760. };
  10761. }
  10762. for (i = 0; i < 8; i++) {
  10763. rows["unused" + i] = {
  10764. group: 3,
  10765. tdCls: 'pve-itype-icon-storage',
  10766. editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
  10767. header: gettext('Unused Disk') + ' ' + i
  10768. };
  10769. }
  10770.  
  10771. var sorterFn = function(rec1, rec2) {
  10772. var v1 = rec1.data.key;
  10773. var v2 = rec2.data.key;
  10774. var g1 = rows[v1].group || 0;
  10775. var g2 = rows[v2].group || 0;
  10776.  
  10777. return (g1 !== g2) ?
  10778. (g1 > g2 ? 1 : -1) : (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
  10779. };
  10780.  
  10781. var reload = function() {
  10782. me.rstore.load();
  10783. };
  10784.  
  10785. var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
  10786.  
  10787. var sm = Ext.create('Ext.selection.RowModel', {});
  10788.  
  10789. var run_editor = function() {
  10790. var rec = sm.getSelection()[0];
  10791. if (!rec) {
  10792. return;
  10793. }
  10794.  
  10795. var rowdef = rows[rec.data.key];
  10796. if (!rowdef.editor) {
  10797. return;
  10798. }
  10799.  
  10800. var editor = rowdef.editor;
  10801. if (rowdef.tdCls == 'pve-itype-icon-storage') {
  10802. if (rec.data.value.match(/media=cdrom/)) {
  10803. editor = 'PVE.qemu.CDEdit';
  10804. }
  10805. }
  10806.  
  10807. var win = Ext.create(editor, {
  10808. pveSelNode: me.pveSelNode,
  10809. confid: rec.data.key,
  10810. url: '/api2/extjs/' + baseurl
  10811. });
  10812.  
  10813. win.show();
  10814. win.on('destroy', reload);
  10815. };
  10816.  
  10817. var edit_btn = new PVE.button.Button({
  10818. text: gettext('Edit'),
  10819. selModel: sm,
  10820. disabled: true,
  10821. enableFn: function(rec) {
  10822. if (!rec) {
  10823. return false;
  10824. }
  10825. var rowdef = rows[rec.data.key];
  10826. return !!rowdef.editor;
  10827. },
  10828. handler: run_editor
  10829. });
  10830.  
  10831. var remove_btn = new PVE.button.Button({
  10832. text: gettext('Remove'),
  10833. selModel: sm,
  10834. disabled: true,
  10835. dangerous: true,
  10836. confirmMsg: function(rec) {
  10837. var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  10838. "'" + me.renderKey(rec.data.key, {}, rec) + "'");
  10839. if (rec.data.key.match(/^unused\d+$/)) {
  10840. msg += " " + gettext('This will permanently erase all image data.');
  10841. }
  10842.  
  10843. return msg;
  10844. },
  10845. enableFn: function(rec) {
  10846. if (!rec) {
  10847. return false;
  10848. }
  10849. var rowdef = rows[rec.data.key];
  10850.  
  10851. return rowdef.never_delete !== true;
  10852. },
  10853. handler: function(b, e, rec) {
  10854. PVE.Utils.API2Request({
  10855. url: '/api2/extjs/' + baseurl,
  10856. waitMsgTarget: me,
  10857. method: 'PUT',
  10858. params: {
  10859. 'delete': rec.data.key
  10860. },
  10861. callback: function() {
  10862. reload();
  10863. },
  10864. failure: function (response, opts) {
  10865. Ext.Msg.alert('Error',response.htmlStatus);
  10866. }
  10867. });
  10868. }
  10869. });
  10870.  
  10871. Ext.applyIf(me, {
  10872. url: '/api2/json/' + baseurl,
  10873. selModel: sm,
  10874. cwidth1: 170,
  10875. tbar: [
  10876. {
  10877. text: gettext('Add'),
  10878. menu: new Ext.menu.Menu({
  10879. items: [
  10880. {
  10881. text: gettext('Hard Disk'),
  10882. iconCls: 'pve-itype-icon-storage',
  10883. disabled: !caps.vms['VM.Config.Disk'],
  10884. handler: function() {
  10885. var win = Ext.create('PVE.qemu.HDEdit', {
  10886. url: '/api2/extjs/' + baseurl,
  10887. pveSelNode: me.pveSelNode
  10888. });
  10889. win.on('destroy', reload);
  10890. win.show();
  10891. }
  10892. },
  10893. {
  10894. text: gettext('CD/DVD Drive'),
  10895. iconCls: 'pve-itype-icon-cdrom',
  10896. disabled: !caps.vms['VM.Config.Disk'],
  10897. handler: function() {
  10898. var win = Ext.create('PVE.qemu.CDEdit', {
  10899. url: '/api2/extjs/' + baseurl,
  10900. pveSelNode: me.pveSelNode
  10901. });
  10902. win.on('destroy', reload);
  10903. win.show();
  10904. }
  10905. },
  10906. {
  10907. text: gettext('Network Device'),
  10908. iconCls: 'pve-itype-icon-network',
  10909. disabled: !caps.vms['VM.Config.Network'],
  10910. handler: function() {
  10911. var win = Ext.create('PVE.qemu.NetworkEdit', {
  10912. url: '/api2/extjs/' + baseurl,
  10913. pveSelNode: me.pveSelNode
  10914. });
  10915. win.on('destroy', reload);
  10916. win.show();
  10917. }
  10918. }
  10919. ]
  10920. })
  10921. },
  10922. remove_btn,
  10923. edit_btn
  10924. ],
  10925. rows: rows,
  10926. sorterFn: sorterFn,
  10927. listeners: {
  10928. show: reload,
  10929. itemdblclick: run_editor
  10930. }
  10931. });
  10932.  
  10933. me.callParent();
  10934. }
  10935. });
  10936. Ext.define('PVE.qemu.StartupInputPanel', {
  10937. extend: 'PVE.panel.InputPanel',
  10938.  
  10939. onGetValues: function(values) {
  10940. var me = this;
  10941.  
  10942. var res = PVE.Parser.printStartup(values);
  10943.  
  10944. if (res === undefined || res === '') {
  10945. return { 'delete': 'startup' };
  10946. }
  10947.  
  10948. return { startup: res };
  10949. },
  10950.  
  10951. setStartup: function(value) {
  10952. var me = this;
  10953.  
  10954. var startup = PVE.Parser.parseStartup(value);
  10955. if (startup) {
  10956. console.dir(startup);
  10957. me.setValues(startup);
  10958. }
  10959. },
  10960.  
  10961. initComponent : function() {
  10962. var me = this;
  10963.  
  10964. me.items = [
  10965. {
  10966. xtype: 'textfield',
  10967. name: 'order',
  10968. defaultValue: '',
  10969. emptyText: 'any',
  10970. fieldLabel: gettext('Start order')
  10971. },
  10972. {
  10973. xtype: 'textfield',
  10974. name: 'up',
  10975. defaultValue: '',
  10976. emptyText: 'default',
  10977. fieldLabel: gettext('Startup delay')
  10978. },
  10979. {
  10980. xtype: 'textfield',
  10981. name: 'down',
  10982. defaultValue: '',
  10983. emptyText: 'default',
  10984. fieldLabel: gettext('Shutdown timeout')
  10985. }
  10986. ];
  10987.  
  10988. me.callParent();
  10989. }
  10990. });
  10991.  
  10992. Ext.define('PVE.qemu.StartupEdit', {
  10993. extend: 'PVE.window.Edit',
  10994.  
  10995. initComponent : function() {
  10996. /*jslint confusion: true */
  10997.  
  10998. var me = this;
  10999.  
  11000. var ipanel = Ext.create('PVE.qemu.StartupInputPanel', {});
  11001.  
  11002. Ext.applyIf(me, {
  11003. subject: gettext('Start/Shutdown order'),
  11004. fieldDefaults: {
  11005. labelWidth: 120
  11006. },
  11007. items: ipanel
  11008. });
  11009.  
  11010. me.callParent();
  11011.  
  11012. me.load({
  11013. success: function(response, options) {
  11014. var i, confid;
  11015. me.vmconfig = response.result.data;
  11016. ipanel.setStartup(me.vmconfig.startup);
  11017. }
  11018. });
  11019. }
  11020. });
  11021. /*jslint confusion: true */
  11022. Ext.define('PVE.qemu.Options', {
  11023. extend: 'PVE.grid.ObjectGrid',
  11024. alias: ['widget.PVE.qemu.Options'],
  11025.  
  11026. initComponent : function() {
  11027. var me = this;
  11028. var i;
  11029.  
  11030. var nodename = me.pveSelNode.data.node;
  11031. if (!nodename) {
  11032. throw "no node name specified";
  11033. }
  11034.  
  11035. var vmid = me.pveSelNode.data.vmid;
  11036. if (!vmid) {
  11037. throw "no VM ID specified";
  11038. }
  11039.  
  11040. var caps = Ext.state.Manager.get('GuiCap');
  11041.  
  11042. var rows = {
  11043. name: {
  11044. required: true,
  11045. defaultValue: me.pveSelNode.data.name,
  11046. header: gettext('Name'),
  11047. editor: caps.vms['VM.Config.Options'] ? {
  11048. xtype: 'pveWindowEdit',
  11049. subject: gettext('Name'),
  11050. items: {
  11051. xtype: 'textfield',
  11052. name: 'name',
  11053. vtype: 'DnsName',
  11054. value: '',
  11055. fieldLabel: gettext('Name'),
  11056. allowBlank: true
  11057. }
  11058. } : undefined
  11059. },
  11060. onboot: {
  11061. header: gettext('Start at boot'),
  11062. defaultValue: '',
  11063. renderer: PVE.Utils.format_boolean,
  11064. editor: caps.vms['VM.Config.Options'] ? {
  11065. xtype: 'pveWindowEdit',
  11066. subject: gettext('Start at boot'),
  11067. items: {
  11068. xtype: 'pvecheckbox',
  11069. name: 'onboot',
  11070. uncheckedValue: 0,
  11071. defaultValue: 0,
  11072. deleteDefaultValue: true,
  11073. fieldLabel: gettext('Start at boot')
  11074. }
  11075. } : undefined
  11076. },
  11077. startup: {
  11078. header: gettext('Start/Shutdown order'),
  11079. defaultValue: '',
  11080. renderer: PVE.Utils.render_kvm_startup,
  11081. editor: caps.vms['VM.Config.Options'] && caps.nodes['Sys.Modify'] ?
  11082. 'PVE.qemu.StartupEdit' : undefined
  11083. },
  11084. ostype: {
  11085. header: 'OS Type',
  11086. editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.OSTypeEdit' : undefined,
  11087. renderer: PVE.Utils.render_kvm_ostype,
  11088. defaultValue: 'other'
  11089. },
  11090. bootdisk: {
  11091. visible: false
  11092. },
  11093. boot: {
  11094. header: gettext('Boot order'),
  11095. defaultValue: 'cdn',
  11096. editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.BootOrderEdit' : undefined,
  11097. renderer: function(order) {
  11098. var i;
  11099. var text = '';
  11100. var bootdisk = me.getObjectValue('bootdisk');
  11101. order = order || 'cdn';
  11102. for (i = 0; i < order.length; i++) {
  11103. var sel = order.substring(i, i + 1);
  11104. if (text) {
  11105. text += ', ';
  11106. }
  11107. if (sel === 'c') {
  11108. if (bootdisk) {
  11109. text += "Disk '" + bootdisk + "'";
  11110. } else {
  11111. text += "Disk";
  11112. }
  11113. } else if (sel === 'n') {
  11114. text += 'Network';
  11115. } else if (sel === 'a') {
  11116. text += 'Floppy';
  11117. } else if (sel === 'd') {
  11118. text += 'CD-ROM';
  11119. } else {
  11120. text += sel;
  11121. }
  11122. }
  11123. return text;
  11124. }
  11125. },
  11126. acpi: {
  11127. header: 'ACPI support',
  11128. defaultValue: true,
  11129. renderer: PVE.Utils.format_boolean,
  11130. editor: caps.vms['VM.Config.HWType'] ? {
  11131. xtype: 'pveWindowEdit',
  11132. subject: 'ACPI support',
  11133. items: {
  11134. xtype: 'pvecheckbox',
  11135. name: 'acpi',
  11136. checked: true,
  11137. uncheckedValue: 0,
  11138. defaultValue: 1,
  11139. deleteDefaultValue: true,
  11140. fieldLabel: gettext('Enabled')
  11141. }
  11142. } : undefined
  11143. },
  11144. kvm: {
  11145. header: 'KVM hardware virtualization',
  11146. defaultValue: true,
  11147. renderer: PVE.Utils.format_boolean,
  11148. editor: caps.vms['VM.Config.HWType'] ? {
  11149. xtype: 'pveWindowEdit',
  11150. subject: 'KVM hardware virtualization',
  11151. items: {
  11152. xtype: 'pvecheckbox',
  11153. name: 'kvm',
  11154. checked: true,
  11155. uncheckedValue: 0,
  11156. defaultValue: 1,
  11157. deleteDefaultValue: true,
  11158. fieldLabel: gettext('Enabled')
  11159. }
  11160. } : undefined
  11161. },
  11162. cpuunits: {
  11163. header: 'CPU units',
  11164. defaultValue: '1000',
  11165. editor: caps.vms['VM.Config.CPU'] ? {
  11166. xtype: 'pveWindowEdit',
  11167. subject: 'CPU units',
  11168. items: {
  11169. xtype: 'numberfield',
  11170. name: 'cpuunits',
  11171. fieldLabel: 'CPU units',
  11172. minValue: 8,
  11173. maxValue: 500000,
  11174. defaultValue: 1000,
  11175. allowBlank: false
  11176. }
  11177. } : undefined
  11178. },
  11179. freeze: {
  11180. header: 'Freeze CPU at startup',
  11181. defaultValue: false,
  11182. renderer: PVE.Utils.format_boolean,
  11183. editor: caps.vms['VM.PowerMgmt'] ? {
  11184. xtype: 'pveWindowEdit',
  11185. subject: 'Freeze CPU at startup',
  11186. items: {
  11187. xtype: 'pvecheckbox',
  11188. name: 'freeze',
  11189. uncheckedValue: 0,
  11190. defaultValue: 0,
  11191. deleteDefaultValue: true,
  11192. labelWidth: 140,
  11193. fieldLabel: 'Freeze CPU at startup'
  11194. }
  11195. } : undefined
  11196. },
  11197. localtime: {
  11198. header: 'Use local time for RTC',
  11199. defaultValue: false,
  11200. renderer: PVE.Utils.format_boolean,
  11201. editor: caps.vms['VM.Config.Options'] ? {
  11202. xtype: 'pveWindowEdit',
  11203. subject: 'Use local time for RTC',
  11204. items: {
  11205. xtype: 'pvecheckbox',
  11206. name: 'localtime',
  11207. uncheckedValue: 0,
  11208. defaultValue: 0,
  11209. deleteDefaultValue: true,
  11210. labelWidth: 140,
  11211. fieldLabel: 'Use local time for RTC'
  11212. }
  11213. } : undefined
  11214. },
  11215. startdate: {
  11216. header: 'RTC start date',
  11217. defaultValue: 'now',
  11218. editor: caps.vms['VM.Config.Options'] ? {
  11219. xtype: 'pveWindowEdit',
  11220. subject: 'RTC start date',
  11221. items: {
  11222. xtype: 'pvetextfield',
  11223. name: 'startdate',
  11224. deleteEmpty: true,
  11225. value: 'now',
  11226. fieldLabel: 'RTC start date',
  11227. vtype: 'QemuStartDate',
  11228. allowBlank: true
  11229. }
  11230. } : undefined
  11231. }
  11232. };
  11233.  
  11234. var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
  11235.  
  11236. var reload = function() {
  11237. me.rstore.load();
  11238. };
  11239.  
  11240. var run_editor = function() {
  11241. var sm = me.getSelectionModel();
  11242. var rec = sm.getSelection()[0];
  11243. if (!rec) {
  11244. return;
  11245. }
  11246.  
  11247. var rowdef = rows[rec.data.key];
  11248. if (!rowdef.editor) {
  11249. return;
  11250. }
  11251.  
  11252. var win;
  11253. if (Ext.isString(rowdef.editor)) {
  11254. win = Ext.create(rowdef.editor, {
  11255. pveSelNode: me.pveSelNode,
  11256. confid: rec.data.key,
  11257. url: '/api2/extjs/' + baseurl
  11258. });
  11259. } else {
  11260. var config = Ext.apply({
  11261. pveSelNode: me.pveSelNode,
  11262. confid: rec.data.key,
  11263. url: '/api2/extjs/' + baseurl
  11264. }, rowdef.editor);
  11265. win = Ext.createWidget(rowdef.editor.xtype, config);
  11266. win.load();
  11267. }
  11268.  
  11269. win.show();
  11270. win.on('destroy', reload);
  11271. };
  11272.  
  11273. var edit_btn = new Ext.Button({
  11274. text: gettext('Edit'),
  11275. disabled: true,
  11276. handler: run_editor
  11277. });
  11278.  
  11279. var set_button_status = function() {
  11280. var sm = me.getSelectionModel();
  11281. var rec = sm.getSelection()[0];
  11282.  
  11283. if (!rec) {
  11284. edit_btn.disable();
  11285. return;
  11286. }
  11287. var rowdef = rows[rec.data.key];
  11288. edit_btn.setDisabled(!rowdef.editor);
  11289. };
  11290.  
  11291. Ext.applyIf(me, {
  11292. url: "/api2/json/nodes/" + nodename + "/qemu/" + vmid + "/config",
  11293. cwidth1: 150,
  11294. tbar: [ edit_btn ],
  11295. rows: rows,
  11296. listeners: {
  11297. itemdblclick: run_editor,
  11298. selectionchange: set_button_status
  11299. }
  11300. });
  11301.  
  11302. me.callParent();
  11303.  
  11304. me.on('show', reload);
  11305. }
  11306. });
  11307.  
  11308. Ext.define('PVE.qemu.Config', {
  11309. extend: 'PVE.panel.Config',
  11310. alias: 'widget.PVE.qemu.Config',
  11311.  
  11312. initComponent: function() {
  11313. var me = this;
  11314.  
  11315. var nodename = me.pveSelNode.data.node;
  11316. if (!nodename) {
  11317. throw "no node name specified";
  11318. }
  11319.  
  11320. var vmid = me.pveSelNode.data.vmid;
  11321. if (!vmid) {
  11322. throw "no VM ID specified";
  11323. }
  11324.  
  11325. var caps = Ext.state.Manager.get('GuiCap');
  11326.  
  11327. me.statusStore = Ext.create('PVE.data.ObjectStore', {
  11328. url: "/api2/json/nodes/" + nodename + "/qemu/" + vmid + "/status/current",
  11329. interval: 1000
  11330. });
  11331.  
  11332. var vm_command = function(cmd, params) {
  11333. PVE.Utils.API2Request({
  11334. params: params,
  11335. url: '/nodes/' + nodename + '/qemu/' + vmid + "/status/" + cmd,
  11336. waitMsgTarget: me,
  11337. method: 'POST',
  11338. failure: function(response, opts) {
  11339. Ext.Msg.alert('Error', response.htmlStatus);
  11340. }
  11341. });
  11342. };
  11343.  
  11344. var startBtn = Ext.create('Ext.Button', {
  11345. text: gettext('Start'),
  11346. disabled: !caps.vms['VM.PowerMgmt'],
  11347. handler: function() {
  11348. vm_command('start');
  11349. }
  11350. });
  11351.  
  11352. var stopBtn = Ext.create('PVE.button.Button', {
  11353. text: gettext('Stop'),
  11354. disabled: !caps.vms['VM.PowerMgmt'],
  11355. confirmMsg: Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid),
  11356. handler: function() {
  11357. vm_command("stop", { timeout: 30 });
  11358. }
  11359. });
  11360.  
  11361. var migrateBtn = Ext.create('Ext.Button', {
  11362. text: gettext('Migrate'),
  11363. disabled: !caps.vms['VM.Migrate'],
  11364. handler: function() {
  11365. var win = Ext.create('PVE.window.Migrate', {
  11366. vmtype: 'qemu',
  11367. nodename: nodename,
  11368. vmid: vmid
  11369. });
  11370. win.show();
  11371. }
  11372. });
  11373.  
  11374. var resetBtn = Ext.create('PVE.button.Button', {
  11375. text: gettext('Reset'),
  11376. disabled: !caps.vms['VM.PowerMgmt'],
  11377. confirmMsg: Ext.String.format(gettext("Do you really want to reset VM {0}?"), vmid),
  11378. handler: function() {
  11379. vm_command("reset");
  11380. }
  11381. });
  11382.  
  11383. var shutdownBtn = Ext.create('PVE.button.Button', {
  11384. text: gettext('Shutdown'),
  11385. disabled: !caps.vms['VM.PowerMgmt'],
  11386. confirmMsg: Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid),
  11387. handler: function() {
  11388. vm_command('shutdown', { timeout: 30 });
  11389. }
  11390. });
  11391.  
  11392. var removeBtn = Ext.create('PVE.button.Button', {
  11393. text: gettext('Remove'),
  11394. disabled: !caps.vms['VM.Allocate'],
  11395. dangerous: true,
  11396. confirmMsg: Ext.String.format(gettext('Are you sure you want to remove VM {0}? This will permanently erase all VM data.'), vmid),
  11397. handler: function() {
  11398. PVE.Utils.API2Request({
  11399. url: '/nodes/' + nodename + '/qemu/' + vmid,
  11400. method: 'DELETE',
  11401. waitMsgTarget: me,
  11402. failure: function(response, opts) {
  11403. Ext.Msg.alert('Error', response.htmlStatus);
  11404. }
  11405. });
  11406. }
  11407. });
  11408.  
  11409. var vmname = me.pveSelNode.data.name;
  11410.  
  11411. var consoleBtn = Ext.create('Ext.Button', {
  11412. text: gettext('Console'),
  11413. disabled: !caps.vms['VM.Console'],
  11414. handler: function() {
  11415. PVE.Utils.openConoleWindow('kvm', vmid, nodename, vmname);
  11416. }
  11417. });
  11418.  
  11419. var descr = vmid + " (" + (vmname ? "'" + vmname + "' " : "'VM " + vmid + "'") + ")";
  11420.  
  11421. Ext.apply(me, {
  11422. title: Ext.String.format(gettext("Virtual Machine {0} on node {1}"), descr, "'" + nodename + "'"),
  11423. hstateid: 'kvmtab',
  11424. tbar: [ startBtn, shutdownBtn, stopBtn, resetBtn,
  11425. removeBtn, migrateBtn, consoleBtn ],
  11426. defaults: { statusStore: me.statusStore },
  11427. items: [
  11428. {
  11429. title: gettext('Summary'),
  11430. xtype: 'pveQemuSummary',
  11431. itemId: 'summary'
  11432. },
  11433. {
  11434. title: gettext('Hardware'),
  11435. itemId: 'hardware',
  11436. xtype: 'PVE.qemu.HardwareView'
  11437. },
  11438. {
  11439. title: gettext('Options'),
  11440. itemId: 'options',
  11441. xtype: 'PVE.qemu.Options'
  11442. }
  11443. ]
  11444. });
  11445.  
  11446. if (caps.vms['VM.Monitor']) {
  11447. me.items.push({
  11448. title: gettext('Monitor'),
  11449. itemId: 'monitor',
  11450. xtype: 'pveQemuMonitor'
  11451. });
  11452. }
  11453.  
  11454. if (caps.vms['VM.Backup']) {
  11455. me.items.push({
  11456. title: gettext('Backup'),
  11457. xtype: 'pveBackupView',
  11458. itemId: 'backup'
  11459. });
  11460. }
  11461.  
  11462. if (caps.vms['Permissions.Modify']) {
  11463. me.items.push({
  11464. xtype: 'pveACLView',
  11465. title: gettext('Permissions'),
  11466. itemId: 'permissions',
  11467. path: '/vms/' + vmid
  11468. });
  11469. }
  11470.  
  11471. me.callParent();
  11472.  
  11473. me.statusStore.on('load', function(s, records, success) {
  11474. var status;
  11475. if (!success) {
  11476. me.workspace.checkVmMigration(me.pveSelNode);
  11477. status = 'unknown';
  11478. } else {
  11479. var rec = s.data.get('status');
  11480. status = rec ? rec.data.value : 'unknown';
  11481. }
  11482.  
  11483. startBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'running');
  11484. resetBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running');
  11485. shutdownBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running');
  11486. stopBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'stopped');
  11487. removeBtn.setDisabled(!caps.vms['VM.Allocate'] || status !== 'stopped');
  11488. });
  11489.  
  11490. me.on('afterrender', function() {
  11491. me.statusStore.startUpdate();
  11492. });
  11493.  
  11494. me.on('destroy', function() {
  11495. me.statusStore.stopUpdate();
  11496. });
  11497. }
  11498. });
  11499. // fixme: howto avoid jslint type confusion?
  11500. /*jslint confusion: true */
  11501. Ext.define('PVE.qemu.CreateWizard', {
  11502. extend: 'PVE.window.Wizard',
  11503.  
  11504. initComponent: function() {
  11505. var me = this;
  11506.  
  11507. var nextvmid = PVE.data.ResourceStore.findNextVMID();
  11508.  
  11509. var summarystore = Ext.create('Ext.data.Store', {
  11510. model: 'KeyValue',
  11511. sorters: [
  11512. {
  11513. property : 'key',
  11514. direction: 'ASC'
  11515. }
  11516. ]
  11517. });
  11518.  
  11519. var cdpanel = Ext.create('PVE.qemu.CDInputPanel', {
  11520. title: 'CD/DVD',
  11521. confid: 'ide2',
  11522. insideWizard: true
  11523. });
  11524.  
  11525. var hdpanel = Ext.create('PVE.qemu.HDInputPanel', {
  11526. title: gettext('Hard Disk'),
  11527. create: true,
  11528. insideWizard: true
  11529. });
  11530.  
  11531. var networkpanel = Ext.create('PVE.qemu.NetworkInputPanel', {
  11532. title: gettext('Network'),
  11533. insideWizard: true
  11534. });
  11535.  
  11536. Ext.applyIf(me, {
  11537. subject: gettext('Virtual Machine'),
  11538. items: [
  11539. {
  11540. xtype: 'inputpanel',
  11541. title: gettext('General'),
  11542. column1: [
  11543. {
  11544. xtype: 'PVE.form.NodeSelector',
  11545. name: 'nodename',
  11546. fieldLabel: gettext('Node'),
  11547. allowBlank: false,
  11548. onlineValidator: true,
  11549. listeners: {
  11550. change: function(f, value) {
  11551. networkpanel.setNodename(value);
  11552. hdpanel.setNodename(value);
  11553. cdpanel.setNodename(value);
  11554. }
  11555. }
  11556. },
  11557. {
  11558. xtype: 'pveVMIDSelector',
  11559. name: 'vmid',
  11560. value: nextvmid,
  11561. validateExists: false
  11562. },
  11563. {
  11564. xtype: 'textfield',
  11565. name: 'name',
  11566. value: '',
  11567. fieldLabel: gettext('Name'),
  11568. allowBlank: true
  11569. }
  11570. ],
  11571. column2: [
  11572. {
  11573. xtype: 'pvePoolSelector',
  11574. fieldLabel: gettext('Resource Pool'),
  11575. name: 'pool',
  11576. value: '',
  11577. allowBlank: true
  11578. }
  11579. ],
  11580. onGetValues: function(values) {
  11581. if (!values.name) {
  11582. delete values.name;
  11583. }
  11584. if (!values.pool) {
  11585. delete values.pool;
  11586. }
  11587. return values;
  11588. }
  11589. },
  11590. {
  11591. title: 'OS',
  11592. xtype: 'PVE.qemu.OSTypeInputPanel'
  11593. },
  11594. cdpanel,
  11595. hdpanel,
  11596. {
  11597. xtype: 'PVE.qemu.ProcessorInputPanel',
  11598. title: 'CPU'
  11599. },
  11600. {
  11601. xtype: 'PVE.qemu.MemoryInputPanel',
  11602. insideWizard: true,
  11603. title: gettext('Memory')
  11604. },
  11605. networkpanel,
  11606. {
  11607. title: gettext('Confirm'),
  11608. layout: 'fit',
  11609. items: [
  11610. {
  11611. title: gettext('Settings'),
  11612. xtype: 'grid',
  11613. store: summarystore,
  11614. columns: [
  11615. {header: 'Key', width: 150, dataIndex: 'key'},
  11616. {header: 'Value', flex: 1, dataIndex: 'value'}
  11617. ]
  11618. }
  11619. ],
  11620. listeners: {
  11621. show: function(panel) {
  11622. var form = me.down('form').getForm();
  11623. var kv = me.getValues();
  11624. var data = [];
  11625. Ext.Object.each(kv, function(key, value) {
  11626. if (key === 'delete') { // ignore
  11627. return;
  11628. }
  11629. var html = Ext.htmlEncode(Ext.JSON.encode(value));
  11630. data.push({ key: key, value: value });
  11631. });
  11632. summarystore.suspendEvents();
  11633. summarystore.removeAll();
  11634. summarystore.add(data);
  11635. summarystore.sort();
  11636. summarystore.resumeEvents();
  11637. summarystore.fireEvent('datachanged', summarystore);
  11638.  
  11639. }
  11640. },
  11641. onSubmit: function() {
  11642. var kv = me.getValues();
  11643. delete kv['delete'];
  11644.  
  11645. var nodename = kv.nodename;
  11646. delete kv.nodename;
  11647.  
  11648. PVE.Utils.API2Request({
  11649. url: '/nodes/' + nodename + '/qemu',
  11650. waitMsgTarget: me,
  11651. method: 'POST',
  11652. params: kv,
  11653. success: function(response){
  11654. me.close();
  11655. },
  11656. failure: function(response, opts) {
  11657. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  11658. }
  11659. });
  11660. }
  11661. }
  11662. ]
  11663. });
  11664.  
  11665. me.callParent();
  11666. }
  11667. });
  11668.  
  11669.  
  11670.  
  11671.  
  11672. Ext.define('PVE.openvz.StatusView', {
  11673. extend: 'PVE.grid.ObjectGrid',
  11674.  
  11675. initComponent : function() {
  11676. var me = this;
  11677.  
  11678. var nodename = me.pveSelNode.data.node;
  11679. if (!nodename) {
  11680. throw "no node name specified";
  11681. }
  11682.  
  11683. var vmid = me.pveSelNode.data.vmid;
  11684. if (!vmid) {
  11685. throw "no VM ID specified";
  11686. }
  11687.  
  11688. var render_cpu = function(value, metaData, record, rowIndex, colIndex, store) {
  11689. if (!me.getObjectValue('uptime')) {
  11690. return '-';
  11691. }
  11692.  
  11693. var maxcpu = me.getObjectValue('cpus', 1);
  11694.  
  11695. if (!(Ext.isNumeric(value) && Ext.isNumeric(maxcpu) && (maxcpu >= 1))) {
  11696. return '-';
  11697. }
  11698.  
  11699. var cpu = value * 100;
  11700. return cpu.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
  11701.  
  11702. };
  11703.  
  11704. var render_mem = function(value, metaData, record, rowIndex, colIndex, store) {
  11705. var maxmem = me.getObjectValue('maxmem', 0);
  11706. var per = (value / maxmem)*100;
  11707. var text = "<div>Total: " + PVE.Utils.format_size(maxmem) + "</div>" +
  11708. "<div>Used: " + PVE.Utils.format_size(value) + "</div>";
  11709. return text;
  11710. };
  11711.  
  11712. var render_swap = function(value, metaData, record, rowIndex, colIndex, store) {
  11713. var maxswap = me.getObjectValue('maxswap', 0);
  11714. var per = (value / maxswap)*100;
  11715. var text = "<div>Total: " + PVE.Utils.format_size(maxswap) + "</div>" +
  11716. "<div>Used: " + PVE.Utils.format_size(value) + "</div>";
  11717. return text;
  11718. };
  11719.  
  11720. var render_status = function(value, metaData, record, rowIndex, colIndex, store) {
  11721. var failcnt = me.getObjectValue('failcnt', 0);
  11722. if (failcnt > 0) {
  11723. return value + " (failure count " + failcnt.toString() + ")";
  11724. }
  11725. return value;
  11726. };
  11727.  
  11728. var rows = {
  11729. name: { header: gettext('Name'), defaultValue: 'no name specified' },
  11730. status: { header: gettext('Status'), defaultValue: 'unknown', renderer: render_status },
  11731. failcnt: { visible: false },
  11732. cpu: { header: 'CPU usage', required: true, renderer: render_cpu },
  11733. cpus: { visible: false },
  11734. mem: { header: 'Memory usage', required: true, renderer: render_mem },
  11735. maxmem: { visible: false },
  11736. swap: { header: 'VSwap usage', required: true, renderer: render_swap },
  11737. maxswap: { visible: false },
  11738. uptime: { header: gettext('Uptime'), required: true, renderer: PVE.Utils.render_uptime },
  11739. ha: { header: 'Managed by HA', required: true, renderer: PVE.Utils.format_boolean }
  11740. };
  11741.  
  11742. Ext.applyIf(me, {
  11743. cwidth1: 150,
  11744. height: 200,
  11745. rows: rows
  11746. });
  11747.  
  11748. me.callParent();
  11749. }
  11750. });
  11751. Ext.define('PVE.openvz.Summary', {
  11752. extend: 'Ext.panel.Panel',
  11753. alias: 'widget.pveOpenVZSummary',
  11754.  
  11755. initComponent: function() {
  11756. var me = this;
  11757.  
  11758. var nodename = me.pveSelNode.data.node;
  11759. if (!nodename) {
  11760. throw "no node name specified";
  11761. }
  11762.  
  11763. var vmid = me.pveSelNode.data.vmid;
  11764. if (!vmid) {
  11765. throw "no VM ID specified";
  11766. }
  11767.  
  11768. if (!me.workspace) {
  11769. throw "no workspace specified";
  11770. }
  11771.  
  11772. if (!me.statusStore) {
  11773. throw "no status storage specified";
  11774. }
  11775.  
  11776. var rstore = me.statusStore;
  11777.  
  11778. var statusview = Ext.create('PVE.openvz.StatusView', {
  11779. title: 'Status',
  11780. pveSelNode: me.pveSelNode,
  11781. width: 400,
  11782. rstore: rstore
  11783. });
  11784.  
  11785. var rrdurl = "/api2/png/nodes/" + nodename + "/openvz/" + vmid + "/rrd";
  11786.  
  11787. var notesview = Ext.create('PVE.panel.NotesView', {
  11788. pveSelNode: me.pveSelNode,
  11789. flex: 1
  11790. });
  11791.  
  11792. Ext.apply(me, {
  11793. tbar: [
  11794. '->',
  11795. {
  11796. xtype: 'pveRRDTypeSelector'
  11797. }
  11798. ],
  11799. autoScroll: true,
  11800. bodyStyle: 'padding:10px',
  11801. defaults: {
  11802. style: 'padding-top:10px',
  11803. width: 800
  11804. },
  11805. items: [
  11806. {
  11807. style: 'padding-top:0px',
  11808. layout: {
  11809. type: 'hbox',
  11810. align: 'stretchmax'
  11811. },
  11812. border: false,
  11813. items: [ statusview, notesview ]
  11814. },
  11815. {
  11816. xtype: 'pveRRDView',
  11817. title: "CPU usage %",
  11818. pveSelNode: me.pveSelNode,
  11819. datasource: 'cpu',
  11820. rrdurl: rrdurl
  11821. },
  11822. {
  11823. xtype: 'pveRRDView',
  11824. title: "Memory usage",
  11825. pveSelNode: me.pveSelNode,
  11826. datasource: 'mem,maxmem',
  11827. rrdurl: rrdurl
  11828. },
  11829. {
  11830. xtype: 'pveRRDView',
  11831. title: "Network traffic",
  11832. pveSelNode: me.pveSelNode,
  11833. datasource: 'netin,netout',
  11834. rrdurl: rrdurl
  11835. },
  11836. {
  11837. xtype: 'pveRRDView',
  11838. title: "Disk IO",
  11839. pveSelNode: me.pveSelNode,
  11840. datasource: 'diskread,diskwrite',
  11841. rrdurl: rrdurl
  11842. }
  11843. ]
  11844. });
  11845.  
  11846. me.on('show', function() {
  11847. notesview.load();
  11848. });
  11849.  
  11850. me.callParent();
  11851. }
  11852. });
  11853. Ext.define('PVE.openvz.RessourceInputPanel', {
  11854. extend: 'PVE.panel.InputPanel',
  11855. alias: 'widget.pveOpenVZResourceInputPanel',
  11856.  
  11857. insideWizard: false,
  11858.  
  11859. initComponent : function() {
  11860. var me = this;
  11861.  
  11862. var labelWidth = 120;
  11863.  
  11864. me.column1 = [
  11865. {
  11866. xtype: 'numberfield',
  11867. name: 'memory',
  11868. minValue: 32,
  11869. maxValue: 512*1024,
  11870. value: '512',
  11871. step: 32,
  11872. fieldLabel: gettext('Memory') + ' (MB)',
  11873. labelWidth: labelWidth,
  11874. allowBlank: false
  11875. },
  11876. {
  11877. xtype: 'numberfield',
  11878. name: 'swap',
  11879. minValue: 0,
  11880. maxValue: 128*1024,
  11881. value: '512',
  11882. step: 32,
  11883. fieldLabel: gettext('Swap') + ' (MB)',
  11884. labelWidth: labelWidth,
  11885. allowBlank: false
  11886. }
  11887. ];
  11888.  
  11889. me.column2 = [
  11890. {
  11891. xtype: 'numberfield',
  11892. name: 'disk',
  11893. minValue: 0.001,
  11894. maxValue: 128*1024,
  11895. decimalPrecision: 3,
  11896. value: '4',
  11897. step: 1,
  11898. fieldLabel: gettext('Disk size') + ' (GB)',
  11899. labelWidth: labelWidth,
  11900. allowBlank: false
  11901. },
  11902. {
  11903. xtype: 'numberfield',
  11904. name: 'cpus',
  11905. minValue: 1,
  11906. value: '1',
  11907. step: 1,
  11908. fieldLabel: 'CPUs',
  11909. labelWidth: labelWidth,
  11910. allowBlank: false
  11911. }
  11912. ];
  11913.  
  11914. me.callParent();
  11915. }
  11916. });
  11917.  
  11918. Ext.define('PVE.openvz.RessourceEdit', {
  11919. extend: 'PVE.window.Edit',
  11920.  
  11921. initComponent : function() {
  11922. var me = this;
  11923.  
  11924. Ext.apply(me, {
  11925. subject: gettext('Resources'),
  11926. items: Ext.create('PVE.openvz.RessourceInputPanel')
  11927. });
  11928.  
  11929. me.callParent();
  11930.  
  11931. me.load();
  11932. }
  11933. });// fixme: howto avoid jslint type confusion?
  11934. /*jslint confusion: true */
  11935. Ext.define('PVE.openvz.RessourceView', {
  11936. extend: 'PVE.grid.ObjectGrid',
  11937. alias: ['widget.pveOpenVZRessourceView'],
  11938.  
  11939. initComponent : function() {
  11940. var me = this;
  11941. var i, confid;
  11942.  
  11943. var nodename = me.pveSelNode.data.node;
  11944. if (!nodename) {
  11945. throw "no node name specified";
  11946. }
  11947.  
  11948. var vmid = me.pveSelNode.data.vmid;
  11949. if (!vmid) {
  11950. throw "no VM ID specified";
  11951. }
  11952.  
  11953. var caps = Ext.state.Manager.get('GuiCap');
  11954.  
  11955. var resEditor = (caps.vms['VM.Config.Memory'] || caps.vms['VM.Config.Disk'] ||
  11956. caps.vms['VM.Config.CPU']) ? 'PVE.openvz.RessourceEdit' : undefined;
  11957.  
  11958.  
  11959. var rows = {
  11960. memory: {
  11961. header: gettext('Memory'),
  11962. editor: resEditor,
  11963. never_delete: true,
  11964. renderer: function(value) {
  11965. return PVE.Utils.format_size(value*1024*1024);
  11966. }
  11967. },
  11968. swap: {
  11969. header: gettext('Swap'),
  11970. editor: resEditor,
  11971. never_delete: true,
  11972. renderer: function(value) {
  11973. return PVE.Utils.format_size(value*1024*1024);
  11974. }
  11975. },
  11976. cpus: {
  11977. header: gettext('Processors'),
  11978. never_delete: true,
  11979. editor: resEditor,
  11980. defaultValue: 1
  11981. },
  11982. disk: {
  11983. header: gettext('Disk size'),
  11984. editor: resEditor,
  11985. never_delete: true,
  11986. renderer: function(value) {
  11987. return PVE.Utils.format_size(value*1024*1024*1024);
  11988. }
  11989. }
  11990. };
  11991.  
  11992. var reload = function() {
  11993. me.rstore.load();
  11994. };
  11995.  
  11996. var baseurl = 'nodes/' + nodename + '/openvz/' + vmid + '/config';
  11997.  
  11998. var sm = Ext.create('Ext.selection.RowModel', {});
  11999.  
  12000. var run_editor = function() {
  12001. var rec = sm.getSelection()[0];
  12002. if (!rec) {
  12003. return;
  12004. }
  12005.  
  12006. var rowdef = rows[rec.data.key];
  12007. if (!rowdef.editor) {
  12008. return;
  12009. }
  12010.  
  12011. var editor = rowdef.editor;
  12012.  
  12013. var win = Ext.create(editor, {
  12014. pveSelNode: me.pveSelNode,
  12015. confid: rec.data.key,
  12016. url: '/api2/extjs/' + baseurl
  12017. });
  12018.  
  12019. win.show();
  12020. win.on('destroy', reload);
  12021. };
  12022.  
  12023. var edit_btn = new PVE.button.Button({
  12024. text: gettext('Edit'),
  12025. selModel: sm,
  12026. disabled: true,
  12027. enableFn: function(rec) {
  12028. if (!rec) {
  12029. return false;
  12030. }
  12031. var rowdef = rows[rec.data.key];
  12032. return !!rowdef.editor;
  12033. },
  12034. handler: run_editor
  12035. });
  12036.  
  12037. Ext.applyIf(me, {
  12038. url: '/api2/json/' + baseurl,
  12039. selModel: sm,
  12040. cwidth1: 170,
  12041. tbar: [ edit_btn ],
  12042. rows: rows,
  12043. listeners: {
  12044. show: reload,
  12045. itemdblclick: run_editor
  12046. }
  12047. });
  12048.  
  12049. me.callParent();
  12050. }
  12051. });
  12052. /*jslint confusion: true */
  12053. Ext.define('PVE.openvz.Options', {
  12054. extend: 'PVE.grid.ObjectGrid',
  12055. alias: ['widget.pveOpenVZOptions'],
  12056.  
  12057. initComponent : function() {
  12058. var me = this;
  12059. var i;
  12060.  
  12061. var nodename = me.pveSelNode.data.node;
  12062. if (!nodename) {
  12063. throw "no node name specified";
  12064. }
  12065.  
  12066. var vmid = me.pveSelNode.data.vmid;
  12067. if (!vmid) {
  12068. throw "no VM ID specified";
  12069. }
  12070.  
  12071. var caps = Ext.state.Manager.get('GuiCap');
  12072.  
  12073. var rows = {
  12074. onboot: {
  12075. header: gettext('Start at boot'),
  12076. defaultValue: '',
  12077. renderer: PVE.Utils.format_boolean,
  12078. editor: caps.vms['VM.Config.Options'] ? {
  12079. xtype: 'pveWindowEdit',
  12080. subject: gettext('Start at boot'),
  12081. items: {
  12082. xtype: 'pvecheckbox',
  12083. name: 'onboot',
  12084. uncheckedValue: 0,
  12085. defaultValue: 0,
  12086. fieldLabel: gettext('Start at boot')
  12087. }
  12088. } : undefined
  12089. },
  12090. ostemplate: {
  12091. header: gettext('Template'),
  12092. defaultValue: 'no set'
  12093. },
  12094. storage: {
  12095. header: gettext('Storage'),
  12096. defaultValue: 'no set'
  12097. },
  12098. cpuunits: {
  12099. header: 'CPU units',
  12100. defaultValue: '1000',
  12101. editor: caps.vms['VM.Config.CPU'] ? {
  12102. xtype: 'pveWindowEdit',
  12103. subject: 'CPU units',
  12104. items: {
  12105. xtype: 'numberfield',
  12106. name: 'cpuunits',
  12107. fieldLabel: 'CPU units',
  12108. minValue: 8,
  12109. maxValue: 500000,
  12110. allowBlank: false
  12111. }
  12112. } : undefined
  12113. },
  12114. quotaugidlimit: {
  12115. header: 'Quota UGID limit',
  12116. defaultValue: '0',
  12117. renderer: function(value) {
  12118. if (value == 0) {
  12119. return 'User quotas disabled.';
  12120. }
  12121. return value;
  12122. },
  12123. editor: caps.vms['VM.Config.Disk'] ? {
  12124. xtype: 'pveWindowEdit',
  12125. subject: 'Quota UGID limit (0 to disable user quotas)',
  12126. items: {
  12127. xtype: 'numberfield',
  12128. name: 'quotaugidlimit',
  12129. fieldLabel: 'UGID limit',
  12130. minValue: 0,
  12131. allowBlank: false
  12132. }
  12133. } : undefined
  12134. },
  12135. quotatime: {
  12136. header: 'Quota Grace period',
  12137. defaultValue: '0',
  12138. editor: caps.vms['VM.Config.Disk'] ? {
  12139. xtype: 'pveWindowEdit',
  12140. subject: 'Quota Grace period (seconds)',
  12141. items: {
  12142. xtype: 'numberfield',
  12143. name: 'quotatime',
  12144. minValue: 0,
  12145. allowBlank: false,
  12146. fieldLabel: 'Grace period'
  12147. }
  12148. } : undefined
  12149. }
  12150. };
  12151.  
  12152. var baseurl = 'nodes/' + nodename + '/openvz/' + vmid + '/config';
  12153.  
  12154. var reload = function() {
  12155. me.rstore.load();
  12156. };
  12157.  
  12158. var sm = Ext.create('Ext.selection.RowModel', {});
  12159.  
  12160. var run_editor = function() {
  12161. var rec = sm.getSelection()[0];
  12162. if (!rec) {
  12163. return;
  12164. }
  12165.  
  12166. var rowdef = rows[rec.data.key];
  12167. if (!rowdef.editor) {
  12168. return;
  12169. }
  12170.  
  12171. var config = Ext.apply({
  12172. pveSelNode: me.pveSelNode,
  12173. confid: rec.data.key,
  12174. url: '/api2/extjs/' + baseurl
  12175. }, rowdef.editor);
  12176. var win = Ext.createWidget(rowdef.editor.xtype, config);
  12177. win.load();
  12178.  
  12179. win.show();
  12180. win.on('destroy', reload);
  12181. };
  12182.  
  12183. var edit_btn = new PVE.button.Button({
  12184. text: gettext('Edit'),
  12185. disabled: true,
  12186. selModel: sm,
  12187. enableFn: function(rec) {
  12188. var rowdef = rows[rec.data.key];
  12189. return !!rowdef.editor;
  12190. },
  12191. handler: run_editor
  12192. });
  12193.  
  12194. Ext.applyIf(me, {
  12195. url: "/api2/json/nodes/" + nodename + "/openvz/" + vmid + "/config",
  12196. selModel: sm,
  12197. cwidth1: 150,
  12198. tbar: [ edit_btn ],
  12199. rows: rows,
  12200. listeners: {
  12201. itemdblclick: run_editor
  12202. }
  12203. });
  12204.  
  12205. me.callParent();
  12206.  
  12207. me.on('show', reload);
  12208. }
  12209. });
  12210.  
  12211. /*jslint confusion: true */
  12212. Ext.define('PVE.OpenVZ.NetIfEdit', {
  12213. extend: 'PVE.window.Edit',
  12214.  
  12215. isAdd: true,
  12216.  
  12217. getValues: function() {
  12218. var me = this;
  12219.  
  12220. var values = me.formPanel.getValues();
  12221.  
  12222. if (!me.create) {
  12223. values.ifname = me.ifname;
  12224. }
  12225.  
  12226. var newdata = Ext.clone(me.netif);
  12227. newdata[values.ifname] = values;
  12228. return { netif: PVE.Parser.printOpenVZNetIf(newdata) };
  12229. },
  12230.  
  12231. initComponent : function() {
  12232. var me = this;
  12233.  
  12234. if (!me.dataCache) {
  12235. throw "no dataCache specified";
  12236. }
  12237.  
  12238. if (!me.nodename) {
  12239. throw "no node name specified";
  12240. }
  12241.  
  12242. me.netif = PVE.Parser.parseOpenVZNetIf(me.dataCache.netif) || {};
  12243.  
  12244. var cdata = {};
  12245.  
  12246. if (!me.create) {
  12247. if (!me.ifname) {
  12248. throw "no interface name specified";
  12249. }
  12250. cdata = me.netif[me.ifname];
  12251. if (!cdata) {
  12252. throw "no such interface '" + me.ifname + "'";
  12253. }
  12254. }
  12255.  
  12256. Ext.apply(me, {
  12257. subject: gettext('Network Device') + ' (veth)',
  12258. digest: me.dataCache.digest,
  12259. width: 350,
  12260. fieldDefaults: {
  12261. labelWidth: 130
  12262. },
  12263. items: [
  12264. {
  12265. xtype: me.create ? 'textfield' : 'displayfield',
  12266. name: 'ifname',
  12267. height: 22, // hack: set same height as text fields
  12268. fieldLabel: gettext('Name') + ' (i.e. eth0)',
  12269. allowBlank: false,
  12270. value: cdata.ifname,
  12271. validator: function(value) {
  12272. if (me.create && me.netif[value]) {
  12273. return "interface name already in use";
  12274. }
  12275. return true;
  12276. }
  12277. },
  12278. {
  12279. xtype: 'textfield',
  12280. name: 'mac',
  12281. fieldLabel: 'MAC',
  12282. vtype: 'MacAddress',
  12283. value: cdata.mac,
  12284. allowBlank: me.create,
  12285. emptyText: 'auto'
  12286. },
  12287. {
  12288. xtype: 'PVE.form.BridgeSelector',
  12289. name: 'bridge',
  12290. nodename: me.nodename,
  12291. fieldLabel: 'Bridge',
  12292. value: cdata.bridge,
  12293. allowBlank: false
  12294. },
  12295. {
  12296. xtype: 'textfield',
  12297. name: 'host_ifname',
  12298. fieldLabel: 'Host device name',
  12299. value: cdata.host_ifname,
  12300. allowBlank: true,
  12301. emptyText: 'auto'
  12302. },
  12303. {
  12304. xtype: 'textfield',
  12305. name: 'host_mac',
  12306. fieldLabel: 'Host MAC address',
  12307. vtype: 'MacAddress',
  12308. value: cdata.host_mac,
  12309. allowBlank: true,
  12310. emptyText: 'auto'
  12311. }
  12312. ]
  12313. });
  12314.  
  12315. me.callParent();
  12316. }
  12317. });
  12318.  
  12319. Ext.define('PVE.OpenVZ.IPAdd', {
  12320. extend: 'PVE.window.Edit',
  12321.  
  12322. isAdd: true,
  12323.  
  12324. create: true,
  12325.  
  12326. getValues: function() {
  12327. var me = this;
  12328.  
  12329. var values = me.formPanel.getValues();
  12330. console.dir(values);
  12331.  
  12332. if (me.dataCache.ip_address) {
  12333. return { ip_address: me.dataCache.ip_address + ' ' + values.ipaddress };
  12334. } else {
  12335. return { ip_address: values.ipaddress };
  12336. }
  12337. },
  12338.  
  12339. initComponent : function() {
  12340. var me = this;
  12341.  
  12342. if (!me.dataCache) {
  12343. throw "no dataCache specified";
  12344. }
  12345.  
  12346. Ext.apply(me, {
  12347. subject: gettext('IP address') + ' (venet)',
  12348. digest: me.dataCache.digest,
  12349. width: 350,
  12350. items: {
  12351. xtype: 'textfield',
  12352. name: 'ipaddress',
  12353. fieldLabel: gettext('IP address'),
  12354. vtype: 'IPAddress',
  12355. allowBlank: false
  12356. }
  12357. });
  12358.  
  12359. me.callParent();
  12360. }
  12361. });
  12362.  
  12363.  
  12364. Ext.define('PVE.openvz.NetworkView', {
  12365. extend: 'Ext.grid.GridPanel',
  12366. alias: ['widget.pveOpenVZNetworkView'],
  12367.  
  12368. dataCache: {}, // used to store result of last load
  12369.  
  12370. ipAddressText: gettext('IP address'),
  12371. networkText: gettext('Network'),
  12372. networkDeviceText: gettext('Network Device'),
  12373.  
  12374. renderType: function(value, metaData, record, rowIndex, colIndex, store) {
  12375. var me = this;
  12376. if (value === 'ip') {
  12377. return me.ipAddressText;
  12378. } else if (value === 'net') {
  12379. return me.networkText;
  12380. } else if (value === 'veth') {
  12381. return me.networkDeviceText;
  12382. } else {
  12383. return value;
  12384. }
  12385. },
  12386.  
  12387. renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
  12388. var type = record.data.type;
  12389. if (type === 'veth') {
  12390. return record.data.ifname;
  12391. } else {
  12392. return value;
  12393. }
  12394. },
  12395.  
  12396. load: function() {
  12397. var me = this;
  12398.  
  12399. PVE.Utils.setErrorMask(me, true);
  12400.  
  12401. PVE.Utils.API2Request({
  12402. url: me.url,
  12403. failure: function(response, opts) {
  12404. PVE.Utils.setErrorMask(me, gettext('Error') + ': ' + response.htmlStatus);
  12405. },
  12406. success: function(response, opts) {
  12407. PVE.Utils.setErrorMask(me, false);
  12408. var result = Ext.decode(response.responseText);
  12409. var data = result.data || {};
  12410. me.dataCache = data;
  12411. var ipAddress = data.ip_address;
  12412. var records = [];
  12413. if (ipAddress) {
  12414. var ind = 0;
  12415. Ext.Array.each(ipAddress.split(' '), function(value) {
  12416. if (value) {
  12417. records.push({
  12418. type: 'ip',
  12419. id: 'ip' + ind,
  12420. value: value
  12421. });
  12422. ind++;
  12423. }
  12424. });
  12425. }
  12426. var netif = PVE.Parser.parseOpenVZNetIf(me.dataCache.netif);
  12427. if (netif) {
  12428. Ext.Object.each(netif, function(iface, data) {
  12429.  
  12430. records.push(Ext.apply({
  12431. type: 'veth',
  12432. id: iface,
  12433. value: data.raw
  12434. }, data));
  12435. });
  12436. }
  12437. me.store.loadData(records);
  12438. }
  12439. });
  12440. },
  12441.  
  12442. initComponent : function() {
  12443. var me = this;
  12444.  
  12445. var nodename = me.pveSelNode.data.node;
  12446. if (!nodename) {
  12447. throw "no node name specified";
  12448. }
  12449.  
  12450. var vmid = me.pveSelNode.data.vmid;
  12451. if (!vmid) {
  12452. throw "no VM ID specified";
  12453. }
  12454.  
  12455. var caps = Ext.state.Manager.get('GuiCap');
  12456.  
  12457. me.url = '/nodes/' + nodename + '/openvz/' + vmid + '/config';
  12458.  
  12459. var store = new Ext.data.Store({
  12460. model: 'pve-openvz-network'
  12461. });
  12462.  
  12463. var sm = Ext.create('Ext.selection.RowModel', {});
  12464.  
  12465. var remove_btn = new PVE.button.Button({
  12466. text: gettext('Remove'),
  12467. disabled: true,
  12468. selModel: sm,
  12469. enableFn: function(rec) {
  12470. return !!caps.vms['VM.Config.Network'];
  12471. },
  12472. confirmMsg: function (rec) {
  12473. var idtext = rec.id;
  12474. if (rec.data.type === 'ip') {
  12475. idtext = rec.data.value;
  12476. } else if (rec.data.type === 'veth') {
  12477. idtext = rec.data.id;
  12478. }
  12479. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  12480. "'" + idtext + "'");
  12481. },
  12482. handler: function(btn, event, rec) {
  12483. var values = { digest: me.dataCache.digest };
  12484.  
  12485. if (rec.data.type === 'ip') {
  12486. var ipa = [];
  12487. Ext.Array.each(me.dataCache.ip_address.split(' '), function(value) {
  12488. if (value && value !== rec.data.value) {
  12489. ipa.push(value);
  12490. }
  12491. });
  12492. values.ip_address = ipa.join(' ');
  12493. } else if (rec.data.type === 'veth') {
  12494. var netif = PVE.Parser.parseOpenVZNetIf(me.dataCache.netif);
  12495. delete netif[rec.data.id];
  12496. values.netif = PVE.Parser.printOpenVZNetIf(netif);
  12497. } else {
  12498. return; // not implemented
  12499. }
  12500.  
  12501. PVE.Utils.API2Request({
  12502. url: me.url,
  12503. waitMsgTarget: me,
  12504. method: 'PUT',
  12505. params: values,
  12506. callback: function() {
  12507. me.load();
  12508. },
  12509. failure: function (response, opts) {
  12510. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  12511. }
  12512. });
  12513. }
  12514. });
  12515.  
  12516. var run_editor = function() {
  12517. var rec = sm.getSelection()[0];
  12518. if (!rec || rec.data.type !== 'veth') {
  12519. return;
  12520. }
  12521.  
  12522. if (!caps.vms['VM.Config.Network']) {
  12523. return false;
  12524. }
  12525.  
  12526. var win = Ext.create('PVE.OpenVZ.NetIfEdit', {
  12527. url: me.url,
  12528. nodename: nodename,
  12529. dataCache: me.dataCache,
  12530. ifname: rec.data.id
  12531. });
  12532. win.on('destroy', me.load, me);
  12533. win.show();
  12534. };
  12535.  
  12536. var edit_btn = new PVE.button.Button({
  12537. text: gettext('Edit'),
  12538. selModel: sm,
  12539. disabled: true,
  12540. enableFn: function(rec) {
  12541. if (!caps.vms['VM.Config.Network']) {
  12542. return false;
  12543. }
  12544. return rec.data.type === 'veth';
  12545. },
  12546. handler: run_editor
  12547. });
  12548.  
  12549.  
  12550. Ext.applyIf(me, {
  12551. store: store,
  12552. selModel: sm,
  12553. stateful: false,
  12554. tbar: [
  12555. {
  12556. text: gettext('Add'),
  12557. menu: new Ext.menu.Menu({
  12558. items: [
  12559. {
  12560. text: gettext('IP address') + ' (venet)',
  12561. disabled: !caps.vms['VM.Config.Network'],
  12562. //plain: true,
  12563. //iconCls: 'pve-itype-icon-storage',
  12564. handler: function() {
  12565. var win = Ext.create('PVE.OpenVZ.IPAdd', {
  12566. url: me.url,
  12567. dataCache: me.dataCache
  12568. });
  12569. win.on('destroy', me.load, me);
  12570. win.show();
  12571. }
  12572. },
  12573. {
  12574. text: gettext('Network Device') + ' (veth)',
  12575. disabled: !caps.vms['VM.Config.Network'],
  12576. //plain: true,
  12577. //iconCls: 'pve-itype-icon-storage',
  12578. handler: function() {
  12579. var win = Ext.create('PVE.OpenVZ.NetIfEdit', {
  12580. url: me.url,
  12581. nodename: nodename,
  12582. create: true,
  12583. dataCache: me.dataCache
  12584. });
  12585. win.on('destroy', me.load, me);
  12586. win.show();
  12587. }
  12588. }
  12589. ]
  12590. })
  12591. },
  12592. remove_btn,
  12593. edit_btn
  12594. ],
  12595. columns: [
  12596. {
  12597. header: gettext('Type'),
  12598. width: 110,
  12599. dataIndex: 'type',
  12600. renderer: me.renderType
  12601. },
  12602. {
  12603. header: gettext('IP address') +'/' + gettext('Name'),
  12604. width: 110,
  12605. dataIndex: 'value',
  12606. renderer: me.renderValue
  12607. },
  12608. {
  12609. header: 'Bridge',
  12610. width: 110,
  12611. dataIndex: 'bridge'
  12612. },
  12613. {
  12614. header: 'MAC',
  12615. width: 110,
  12616. dataIndex: 'mac'
  12617. },
  12618. {
  12619. header: 'Host ifname',
  12620. width: 110,
  12621. dataIndex: 'host_ifname'
  12622. },
  12623. {
  12624. header: 'Host MAC',
  12625. width: 110,
  12626. dataIndex: 'host_mac'
  12627. }
  12628. ],
  12629. listeners: {
  12630. show: me.load,
  12631. itemdblclick: run_editor
  12632. }
  12633. });
  12634.  
  12635. me.callParent();
  12636.  
  12637. me.load();
  12638. }
  12639. }, function() {
  12640.  
  12641. Ext.define('pve-openvz-network', {
  12642. extend: "Ext.data.Model",
  12643. proxy: { type: 'memory' },
  12644. fields: [ 'id', 'type', 'value', 'ifname', 'mac', 'bridge', 'host_ifname', 'host_mac' ]
  12645. });
  12646.  
  12647. });
  12648.  
  12649. /*jslint confusion: true */
  12650. Ext.define('PVE.openvz.DNS', {
  12651. extend: 'PVE.grid.ObjectGrid',
  12652. alias: ['widget.pveOpenVZDNS'],
  12653.  
  12654. initComponent : function() {
  12655. var me = this;
  12656. var i;
  12657.  
  12658. var nodename = me.pveSelNode.data.node;
  12659. if (!nodename) {
  12660. throw "no node name specified";
  12661. }
  12662.  
  12663. var vmid = me.pveSelNode.data.vmid;
  12664. if (!vmid) {
  12665. throw "no VM ID specified";
  12666. }
  12667.  
  12668. var caps = Ext.state.Manager.get('GuiCap');
  12669.  
  12670. var rows = {
  12671. hostname: {
  12672. required: true,
  12673. defaultValue: me.pveSelNode.data.name,
  12674. header: 'Hostname',
  12675. editor: caps.vms['VM.Config.Network'] ? {
  12676. xtype: 'pveWindowEdit',
  12677. subject: 'Hostname',
  12678. items: {
  12679. xtype: 'textfield',
  12680. name: 'hostname',
  12681. value: '',
  12682. fieldLabel: 'Hostname',
  12683. allowBlank: true,
  12684. emptyText: me.pveSelNode.data.name
  12685. }
  12686. } : undefined
  12687. },
  12688. searchdomain: {
  12689. header: 'DNS domain',
  12690. defaultValue: '',
  12691. editor: caps.vms['VM.Config.Network'] ? {
  12692. xtype: 'pveWindowEdit',
  12693. subject: 'DNS domain',
  12694. items: {
  12695. xtype: 'pvetextfield',
  12696. name: 'searchdomain',
  12697. fieldLabel: 'DNS domain',
  12698. allowBlank: false
  12699. }
  12700. } : undefined
  12701. },
  12702. nameserver: {
  12703. header: gettext('DNS server'),
  12704. defaultValue: '',
  12705. editor: caps.vms['VM.Config.Network'] ? {
  12706. xtype: 'pveWindowEdit',
  12707. subject: gettext('DNS server'),
  12708. items: {
  12709. xtype: 'pvetextfield',
  12710. name: 'nameserver',
  12711. fieldLabel: gettext('DNS server'),
  12712. allowBlank: false
  12713. }
  12714. } : undefined
  12715. }
  12716. };
  12717.  
  12718. var baseurl = 'nodes/' + nodename + '/openvz/' + vmid + '/config';
  12719.  
  12720. var reload = function() {
  12721. me.rstore.load();
  12722. };
  12723.  
  12724. var sm = Ext.create('Ext.selection.RowModel', {});
  12725.  
  12726. var run_editor = function() {
  12727. var rec = sm.getSelection()[0];
  12728. if (!rec) {
  12729. return;
  12730. }
  12731.  
  12732. var rowdef = rows[rec.data.key];
  12733. if (!rowdef.editor) {
  12734. return;
  12735. }
  12736.  
  12737. var config = Ext.apply({
  12738. pveSelNode: me.pveSelNode,
  12739. confid: rec.data.key,
  12740. url: '/api2/extjs/' + baseurl
  12741. }, rowdef.editor);
  12742. var win = Ext.createWidget(rowdef.editor.xtype, config);
  12743. win.load();
  12744.  
  12745. win.show();
  12746. win.on('destroy', reload);
  12747. };
  12748.  
  12749. var edit_btn = new PVE.button.Button({
  12750. text: gettext('Edit'),
  12751. disabled: true,
  12752. selModel: sm,
  12753. enableFn: function(rec) {
  12754. var rowdef = rows[rec.data.key];
  12755. return !!rowdef.editor;
  12756. },
  12757. handler: run_editor
  12758. });
  12759.  
  12760. var set_button_status = function() {
  12761. var sm = me.getSelectionModel();
  12762. var rec = sm.getSelection()[0];
  12763.  
  12764. if (!rec) {
  12765. edit_btn.disable();
  12766. return;
  12767. }
  12768. var rowdef = rows[rec.data.key];
  12769. edit_btn.setDisabled(!rowdef.editor);
  12770. };
  12771.  
  12772. Ext.applyIf(me, {
  12773. url: "/api2/json/nodes/" + nodename + "/openvz/" + vmid + "/config",
  12774. selModel: sm,
  12775. cwidth1: 150,
  12776. tbar: [ edit_btn ],
  12777. rows: rows,
  12778. listeners: {
  12779. itemdblclick: run_editor,
  12780. selectionchange: set_button_status
  12781. }
  12782. });
  12783.  
  12784. me.callParent();
  12785.  
  12786. me.on('show', reload);
  12787. }
  12788. });
  12789.  
  12790. /*jslint confusion: true */
  12791. Ext.define('PVE.openvz.BeanCounterGrid', {
  12792. extend: 'Ext.grid.GridPanel',
  12793. alias: ['widget.pveBeanCounterGrid'],
  12794.  
  12795. renderUbc: function(value, metaData, record, rowIndex, colIndex, store) {
  12796.  
  12797. if (value === 9223372036854775807) {
  12798. return '-';
  12799. }
  12800.  
  12801. if (record.id.match(/pages$/)) {
  12802. return PVE.Utils.format_size(value*4096);
  12803. }
  12804. if (record.id.match(/(size|buf)$/)) {
  12805. return PVE.Utils.format_size(value);
  12806. }
  12807.  
  12808. return value;
  12809. },
  12810.  
  12811. initComponent : function() {
  12812. var me = this;
  12813.  
  12814. if (!me.url) {
  12815. throw "no url specified";
  12816. }
  12817.  
  12818. var store = new Ext.data.Store({
  12819. model: 'pve-openvz-ubc',
  12820. proxy: {
  12821. type: 'pve',
  12822. url: me.url
  12823. },
  12824. sorters: [
  12825. {
  12826. property : 'id',
  12827. direction: 'ASC'
  12828. }
  12829. ]
  12830. });
  12831.  
  12832. var reload = function() {
  12833. store.load();
  12834. };
  12835.  
  12836. Ext.applyIf(me, {
  12837. store: store,
  12838. stateful: false,
  12839. columns: [
  12840. {
  12841. header: 'Ressource',
  12842. width: 100,
  12843. dataIndex: 'id'
  12844. },
  12845. {
  12846. header: 'held',
  12847. width: 100,
  12848. renderer: me.renderUbc,
  12849. dataIndex: 'held'
  12850. },
  12851. {
  12852. header: 'maxheld',
  12853. width: 100,
  12854. renderer: me.renderUbc,
  12855. dataIndex: 'maxheld'
  12856. },
  12857. {
  12858. header: 'barrier',
  12859. width: 100,
  12860. renderer: me.renderUbc,
  12861. dataIndex: 'bar'
  12862. },
  12863. {
  12864. header: 'limit',
  12865. width: 100,
  12866. renderer: me.renderUbc,
  12867. dataIndex: 'lim'
  12868. },
  12869. {
  12870. header: 'failcnt',
  12871. width: 100,
  12872. dataIndex: 'failcnt'
  12873. }
  12874. ],
  12875. listeners: {
  12876. show: reload
  12877. }
  12878. });
  12879.  
  12880. me.callParent();
  12881.  
  12882. }
  12883. }, function() {
  12884.  
  12885. Ext.define('pve-openvz-ubc', {
  12886. extend: "Ext.data.Model",
  12887. fields: [ 'id',
  12888. { name: 'held', type: 'number' },
  12889. { name: 'maxheld', type: 'number' },
  12890. { name: 'bar', type: 'number' },
  12891. { name: 'lim', type: 'number' },
  12892. { name: 'failcnt', type: 'number' }
  12893. ]
  12894. });
  12895.  
  12896. });
  12897. Ext.define('PVE.openvz.Config', {
  12898. extend: 'PVE.panel.Config',
  12899. alias: 'widget.PVE.openvz.Config',
  12900.  
  12901. initComponent: function() {
  12902. var me = this;
  12903.  
  12904. var nodename = me.pveSelNode.data.node;
  12905. if (!nodename) {
  12906. throw "no node name specified";
  12907. }
  12908.  
  12909. var vmid = me.pveSelNode.data.vmid;
  12910. if (!vmid) {
  12911. throw "no VM ID specified";
  12912. }
  12913.  
  12914. var caps = Ext.state.Manager.get('GuiCap');
  12915.  
  12916. me.statusStore = Ext.create('PVE.data.ObjectStore', {
  12917. url: "/api2/json/nodes/" + nodename + "/openvz/" + vmid + "/status/current",
  12918. interval: 1000
  12919. });
  12920.  
  12921. var vm_command = function(cmd, params) {
  12922. PVE.Utils.API2Request({
  12923. params: params,
  12924. url: '/nodes/' + nodename + '/openvz/' + vmid + "/status/" + cmd,
  12925. waitMsgTarget: me,
  12926. method: 'POST',
  12927. failure: function(response, opts) {
  12928. Ext.Msg.alert('Error', response.htmlStatus);
  12929. }
  12930. });
  12931. };
  12932.  
  12933. var startBtn = Ext.create('Ext.Button', {
  12934. text: gettext('Start'),
  12935. disabled: !caps.vms['VM.PowerMgmt'],
  12936. handler: function() {
  12937. vm_command('start');
  12938. }
  12939. });
  12940.  
  12941. var umountBtn = Ext.create('Ext.Button', {
  12942. text: gettext('Unmount'),
  12943. disabled: true,
  12944. hidden: true,
  12945. handler: function() {
  12946. vm_command('umount');
  12947. }
  12948. });
  12949.  
  12950. var stopBtn = Ext.create('PVE.button.Button', {
  12951. text: gettext('Stop'),
  12952. disabled: !caps.vms['VM.PowerMgmt'],
  12953. confirmMsg: Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid),
  12954. handler: function() {
  12955. vm_command("stop");
  12956. }
  12957. });
  12958.  
  12959. var shutdownBtn = Ext.create('PVE.button.Button', {
  12960. text: gettext('Shutdown'),
  12961. disabled: !caps.vms['VM.PowerMgmt'],
  12962. confirmMsg: Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid),
  12963. handler: function() {
  12964. vm_command('shutdown');
  12965. }
  12966. });
  12967.  
  12968. var migrateBtn = Ext.create('Ext.Button', {
  12969. text: gettext('Migrate'),
  12970. disabled: !caps.vms['VM.Migrate'],
  12971. handler: function() {
  12972. var win = Ext.create('PVE.window.Migrate', {
  12973. vmtype: 'openvz',
  12974. nodename: nodename,
  12975. vmid: vmid
  12976. });
  12977. win.show();
  12978. }
  12979. });
  12980.  
  12981. var removeBtn = Ext.create('PVE.button.Button', {
  12982. text: gettext('Remove'),
  12983. disabled: !caps.vms['VM.Allocate'],
  12984. dangerous: true,
  12985. confirmMsg: Ext.String.format(gettext('Are you sure you want to remove VM {0}? This will permanently erase all VM data.'), vmid),
  12986. handler: function() {
  12987. PVE.Utils.API2Request({
  12988. url: '/nodes/' + nodename + '/openvz/' + vmid,
  12989. method: 'DELETE',
  12990. waitMsgTarget: me,
  12991. failure: function(response, opts) {
  12992. Ext.Msg.alert('Error', response.htmlStatus);
  12993. }
  12994. });
  12995. }
  12996. });
  12997.  
  12998. var vmname = me.pveSelNode.data.name;
  12999.  
  13000. var consoleBtn = Ext.create('Ext.Button', {
  13001. text: gettext('Console'),
  13002. disabled: !caps.vms['VM.Console'],
  13003. handler: function() {
  13004. PVE.Utils.openConoleWindow('openvz', vmid, nodename, vmname);
  13005. }
  13006. });
  13007.  
  13008. var descr = vmid + " (" + (vmname ? "'" + vmname + "' " : "'CT " + vmid + "'") + ")";
  13009.  
  13010. Ext.apply(me, {
  13011. title: Ext.String.format(gettext("Container {0} on node {1}"), descr, "'" + nodename + "'"),
  13012. hstateid: 'ovztab',
  13013. tbar: [ startBtn, shutdownBtn, umountBtn, stopBtn, removeBtn,
  13014. migrateBtn, consoleBtn ],
  13015. defaults: { statusStore: me.statusStore },
  13016. items: [
  13017. {
  13018. title: gettext('Summary'),
  13019. xtype: 'pveOpenVZSummary',
  13020. itemId: 'summary'
  13021. },
  13022. {
  13023. title: gettext('Resources'),
  13024. itemId: 'resources',
  13025. xtype: 'pveOpenVZRessourceView'
  13026. },
  13027. {
  13028. title: gettext('Network'),
  13029. itemId: 'network',
  13030. xtype: 'pveOpenVZNetworkView'
  13031. },
  13032. {
  13033. title: 'DNS',
  13034. itemId: 'dns',
  13035. xtype: 'pveOpenVZDNS'
  13036. },
  13037. {
  13038. title: gettext('Options'),
  13039. itemId: 'options',
  13040. xtype: 'pveOpenVZOptions'
  13041. },
  13042. {
  13043. title: 'UBC',
  13044. itemId: 'ubc',
  13045. xtype: 'pveBeanCounterGrid',
  13046. url: '/api2/json/nodes/' + nodename + '/openvz/' + vmid + '/status/ubc'
  13047. }
  13048. ]
  13049. });
  13050.  
  13051.  
  13052. if (caps.vms['VM.Console']) {
  13053. me.items.push({
  13054. title: "InitLog",
  13055. itemId: 'initlog',
  13056. xtype: 'pveLogView',
  13057. url: '/api2/extjs/nodes/' + nodename + '/openvz/' + vmid + '/initlog'
  13058. });
  13059. }
  13060.  
  13061. if (caps.vms['VM.Backup']) {
  13062. me.items.push({
  13063. title: gettext('Backup'),
  13064. xtype: 'pveBackupView',
  13065. itemId: 'backup'
  13066. });
  13067. }
  13068.  
  13069. if (caps.vms['Permissions.Modify']) {
  13070. me.items.push({
  13071. xtype: 'pveACLView',
  13072. title: gettext('Permissions'),
  13073. itemId: 'permissions',
  13074. path: '/vms/' + vmid
  13075. });
  13076. }
  13077.  
  13078. me.callParent();
  13079.  
  13080. me.statusStore.on('load', function(s, records, success) {
  13081. var status;
  13082. if (!success) {
  13083. me.workspace.checkVmMigration(me.pveSelNode);
  13084. status = 'unknown';
  13085. } else {
  13086. var rec = s.data.get('status');
  13087. status = rec ? rec.data.value : 'unknown';
  13088. }
  13089. startBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'running');
  13090. shutdownBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running');
  13091. stopBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'stopped');
  13092. removeBtn.setDisabled(!caps.vms['VM.Allocate'] || status !== 'stopped');
  13093.  
  13094. if (status === 'mounted') {
  13095. umountBtn.setDisabled(false);
  13096. umountBtn.setVisible(true);
  13097. stopBtn.setVisible(false);
  13098. } else {
  13099. umountBtn.setDisabled(true);
  13100. umountBtn.setVisible(false);
  13101. stopBtn.setVisible(true);
  13102. }
  13103. });
  13104.  
  13105. me.on('afterrender', function() {
  13106. me.statusStore.startUpdate();
  13107. });
  13108.  
  13109. me.on('destroy', function() {
  13110. me.statusStore.stopUpdate();
  13111. });
  13112. }
  13113. });
  13114. /*jslint confusion: true */
  13115. Ext.define('PVE.openvz.CreateWizard', {
  13116. extend: 'PVE.window.Wizard',
  13117.  
  13118. initComponent: function() {
  13119. var me = this;
  13120.  
  13121. var nextvmid = PVE.data.ResourceStore.findNextVMID();
  13122.  
  13123. var summarystore = Ext.create('Ext.data.Store', {
  13124. model: 'KeyValue',
  13125. sorters: [
  13126. {
  13127. property : 'key',
  13128. direction: 'ASC'
  13129. }
  13130. ]
  13131. });
  13132.  
  13133. var storagesel = Ext.create('PVE.form.StorageSelector', {
  13134. name: 'storage',
  13135. fieldLabel: gettext('Storage'),
  13136. storageContent: 'rootdir',
  13137. autoSelect: true,
  13138. allowBlank: false
  13139. });
  13140.  
  13141. var tmplsel = Ext.create('PVE.form.FileSelector', {
  13142. name: 'ostemplate',
  13143. storageContent: 'vztmpl',
  13144. fieldLabel: gettext('Template'),
  13145. allowBlank: false
  13146. });
  13147.  
  13148. var tmplstoragesel = Ext.create('PVE.form.StorageSelector', {
  13149. name: 'tmplstorage',
  13150. fieldLabel: gettext('Storage'),
  13151. storageContent: 'vztmpl',
  13152. autoSelect: true,
  13153. allowBlank: false,
  13154. listeners: {
  13155. change: function(f, value) {
  13156. tmplsel.setStorage(value);
  13157. }
  13158. }
  13159. });
  13160.  
  13161. var bridgesel = Ext.create('PVE.form.BridgeSelector', {
  13162. name: 'bridge',
  13163. fieldLabel: 'Bridge',
  13164. labelAlign: 'right',
  13165. autoSelect: true,
  13166. disabled: true,
  13167. allowBlank: false
  13168. });
  13169.  
  13170. Ext.applyIf(me, {
  13171. subject: gettext('OpenVZ Container'),
  13172. items: [
  13173. {
  13174. xtype: 'inputpanel',
  13175. title: gettext('General'),
  13176. column1: [
  13177. {
  13178. xtype: 'PVE.form.NodeSelector',
  13179. name: 'nodename',
  13180. fieldLabel: gettext('Node'),
  13181. allowBlank: false,
  13182. onlineValidator: true,
  13183. listeners: {
  13184. change: function(f, value) {
  13185. tmplstoragesel.setNodename(value);
  13186. tmplsel.setStorage(undefined, value);
  13187. bridgesel.setNodename(value);
  13188. storagesel.setNodename(value);
  13189. }
  13190. }
  13191. },
  13192. {
  13193. xtype: 'pveVMIDSelector',
  13194. name: 'vmid',
  13195. value: nextvmid,
  13196. validateExists: false
  13197. },
  13198. {
  13199. xtype: 'pvetextfield',
  13200. name: 'hostname',
  13201. value: '',
  13202. fieldLabel: 'Hostname',
  13203. skipEmptyText: true,
  13204. allowBlank: true
  13205. }
  13206. ],
  13207. column2: [
  13208. {
  13209. xtype: 'pvePoolSelector',
  13210. fieldLabel: gettext('Resource Pool'),
  13211. name: 'pool',
  13212. value: '',
  13213. allowBlank: true
  13214. },
  13215. storagesel,
  13216. {
  13217. xtype: 'textfield',
  13218. inputType: 'password',
  13219. name: 'password',
  13220. value: '',
  13221. fieldLabel: gettext('Password'),
  13222. allowBlank: false,
  13223. minLength: 5,
  13224. change: function(f, value) {
  13225. if (!me.rendered) {
  13226. return;
  13227. }
  13228. me.down('field[name=confirmpw]').validate();
  13229. }
  13230. },
  13231. {
  13232. xtype: 'textfield',
  13233. inputType: 'password',
  13234. name: 'confirmpw',
  13235. value: '',
  13236. fieldLabel: gettext('Confirm password'),
  13237. allowBlank: false,
  13238. validator: function(value) {
  13239. var pw = me.down('field[name=password]').getValue();
  13240. if (pw !== value) {
  13241. return "Passowords does not match!";
  13242. }
  13243. return true;
  13244. }
  13245. }
  13246. ],
  13247. onGetValues: function(values) {
  13248. delete values.confirmpw;
  13249. if (!values.pool) {
  13250. delete values.pool;
  13251. }
  13252. return values;
  13253. }
  13254. },
  13255. {
  13256. xtype: 'inputpanel',
  13257. title: gettext('Template'),
  13258. column1: [ tmplstoragesel, tmplsel]
  13259. },
  13260. {
  13261. xtype: 'pveOpenVZResourceInputPanel',
  13262. title: gettext('Resources')
  13263. },
  13264. {
  13265. xtype: 'inputpanel',
  13266. title: gettext('Network'),
  13267. column1: [
  13268. {
  13269. xtype: 'radiofield',
  13270. name: 'networkmode',
  13271. inputValue: 'routed',
  13272. boxLabel: 'Routed mode (venet)',
  13273. checked: true,
  13274. listeners: {
  13275. change: function(f, value) {
  13276. if (!me.rendered) {
  13277. return;
  13278. }
  13279. me.down('field[name=ip_address]').setDisabled(!value);
  13280. me.down('field[name=ip_address]').validate();
  13281. }
  13282. }
  13283. },
  13284. {
  13285. xtype: 'textfield',
  13286. name: 'ip_address',
  13287. vtype: 'IPAddress',
  13288. value: '',
  13289. fieldLabel: gettext('IP address'),
  13290. labelAlign: 'right',
  13291. allowBlank: false
  13292. }
  13293. ],
  13294. column2: [
  13295. {
  13296. xtype: 'radiofield',
  13297. name: 'networkmode',
  13298. inputValue: 'bridge',
  13299. boxLabel: 'Bridged mode',
  13300. checked: false,
  13301. listeners: {
  13302. change: function(f, value) {
  13303. if (!me.rendered) {
  13304. return;
  13305. }
  13306. me.down('field[name=bridge]').setDisabled(!value);
  13307. me.down('field[name=bridge]').validate();
  13308. }
  13309. }
  13310. },
  13311. bridgesel
  13312. ],
  13313. onGetValues: function(values) {
  13314. if (values.networkmode === 'bridge') {
  13315. return { netif: 'ifname=eth0,bridge=' + values.bridge };
  13316. } else {
  13317. return { ip_address: values.ip_address };
  13318. }
  13319. }
  13320. },
  13321. {
  13322. xtype: 'inputpanel',
  13323. title: 'DNS',
  13324. column1: [
  13325. {
  13326. xtype: 'pvetextfield',
  13327. name: 'searchdomain',
  13328. skipEmptyText: true,
  13329. fieldLabel: 'DNS domain',
  13330. emptyText: 'use host settings',
  13331. allowBlank: true,
  13332. listeners: {
  13333. change: function(f, value) {
  13334. if (!me.rendered) {
  13335. return;
  13336. }
  13337. var field = me.down('#dns1');
  13338. field.setDisabled(!value);
  13339. field.clearInvalid();
  13340. field = me.down('#dns2');
  13341. field.setDisabled(!value);
  13342. field.clearInvalid();
  13343. }
  13344. }
  13345. },
  13346. {
  13347. xtype: 'pvetextfield',
  13348. fieldLabel: gettext('DNS server') + " 1",
  13349. vtype: 'IPAddress',
  13350. allowBlank: true,
  13351. disabled: true,
  13352. name: 'nameserver',
  13353. itemId: 'dns1'
  13354. },
  13355. {
  13356. xtype: 'pvetextfield',
  13357. fieldLabel: gettext('DNS server') + " 2",
  13358. vtype: 'IPAddress',
  13359. skipEmptyText: true,
  13360. disabled: true,
  13361. name: 'nameserver',
  13362. itemId: 'dns2'
  13363. }
  13364. ]
  13365. },
  13366. {
  13367. title: gettext('Confirm'),
  13368. layout: 'fit',
  13369. items: [
  13370. {
  13371. title: gettext('Settings'),
  13372. xtype: 'grid',
  13373. store: summarystore,
  13374. columns: [
  13375. {header: 'Key', width: 150, dataIndex: 'key'},
  13376. {header: 'Value', flex: 1, dataIndex: 'value'}
  13377. ]
  13378. }
  13379. ],
  13380. listeners: {
  13381. show: function(panel) {
  13382. var form = me.down('form').getForm();
  13383. var kv = me.getValues();
  13384. var data = [];
  13385. Ext.Object.each(kv, function(key, value) {
  13386. if (key === 'delete' || key === 'tmplstorage') { // ignore
  13387. return;
  13388. }
  13389. if (key === 'password') { // don't show pw
  13390. return;
  13391. }
  13392. var html = Ext.htmlEncode(Ext.JSON.encode(value));
  13393. data.push({ key: key, value: value });
  13394. });
  13395. summarystore.suspendEvents();
  13396. summarystore.removeAll();
  13397. summarystore.add(data);
  13398. summarystore.sort();
  13399. summarystore.resumeEvents();
  13400. summarystore.fireEvent('datachanged', summarystore);
  13401. }
  13402. },
  13403. onSubmit: function() {
  13404. var kv = me.getValues();
  13405. delete kv['delete'];
  13406.  
  13407. var nodename = kv.nodename;
  13408. delete kv.nodename;
  13409. delete kv.tmplstorage;
  13410.  
  13411. PVE.Utils.API2Request({
  13412. url: '/nodes/' + nodename + '/openvz',
  13413. waitMsgTarget: me,
  13414. method: 'POST',
  13415. params: kv,
  13416. success: function(response, opts){
  13417. var upid = response.result.data;
  13418.  
  13419. var win = Ext.create('PVE.window.TaskViewer', {
  13420. upid: upid
  13421. });
  13422. win.show();
  13423. me.close();
  13424. },
  13425. failure: function(response, opts) {
  13426. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  13427. }
  13428. });
  13429. }
  13430. }
  13431. ]
  13432. });
  13433.  
  13434. me.callParent();
  13435. }
  13436. });
  13437.  
  13438.  
  13439.  
  13440. Ext.define('PVE.pool.StatusView', {
  13441. extend: 'PVE.grid.ObjectGrid',
  13442. alias: ['widget.pvePoolStatusView'],
  13443.  
  13444. initComponent : function() {
  13445. var me = this;
  13446.  
  13447. var pool = me.pveSelNode.data.pool;
  13448. if (!pool) {
  13449. throw "no pool specified";
  13450. }
  13451.  
  13452. var rows = {
  13453. comment: {
  13454. header: gettext('Comment'),
  13455. required: true
  13456. }
  13457. };
  13458.  
  13459. Ext.applyIf(me, {
  13460. title: gettext('Status'),
  13461. url: "/api2/json/pools/" + pool,
  13462. cwidth1: 150,
  13463. interval: 30000,
  13464. //height: 195,
  13465. rows: rows
  13466. });
  13467.  
  13468. me.callParent();
  13469. }
  13470. });
  13471. Ext.define('PVE.pool.Summary', {
  13472. extend: 'Ext.panel.Panel',
  13473. alias: 'widget.pvePoolSummary',
  13474.  
  13475. initComponent: function() {
  13476. var me = this;
  13477.  
  13478. var pool = me.pveSelNode.data.pool;
  13479. if (!pool) {
  13480. throw "no pool specified";
  13481. }
  13482.  
  13483. var statusview = Ext.create('PVE.pool.StatusView', {
  13484. pveSelNode: me.pveSelNode,
  13485. style: 'padding-top:0px'
  13486. });
  13487.  
  13488. var rstore = statusview.rstore;
  13489.  
  13490. Ext.apply(me, {
  13491. autoScroll: true,
  13492. bodyStyle: 'padding:10px',
  13493. defaults: {
  13494. style: 'padding-top:10px',
  13495. width: 800
  13496. },
  13497. items: [ statusview ]
  13498. });
  13499.  
  13500. me.on('show', rstore.startUpdate);
  13501. me.on('hide', rstore.stopUpdate);
  13502. me.on('destroy', rstore.stopUpdate);
  13503.  
  13504. me.callParent();
  13505. }
  13506. });
  13507. Ext.define('PVE.pool.Config', {
  13508. extend: 'PVE.panel.Config',
  13509. alias: 'widget.pvePoolConfig',
  13510.  
  13511. initComponent: function() {
  13512. var me = this;
  13513.  
  13514. var pool = me.pveSelNode.data.pool;
  13515. if (!pool) {
  13516. throw "no pool specified";
  13517. }
  13518.  
  13519. Ext.apply(me, {
  13520. title: Ext.String.format(gettext("Resource Pool") + ': ' + pool),
  13521. hstateid: 'pooltab',
  13522. items: [
  13523. {
  13524. title: gettext('Summary'),
  13525. xtype: 'pvePoolSummary',
  13526. itemId: 'summary'
  13527. },
  13528. {
  13529. title: gettext('Members'),
  13530. xtype: 'pvePoolMembers',
  13531. pool: pool,
  13532. itemId: 'members'
  13533. },
  13534. {
  13535. xtype: 'pveACLView',
  13536. title: gettext('Permissions'),
  13537. itemId: 'permissions',
  13538. path: '/pool/' + pool
  13539. }
  13540. ]
  13541. });
  13542.  
  13543. me.callParent();
  13544. }
  13545. });
  13546. Ext.define('PVE.grid.TemplateSelector', {
  13547. extend: 'Ext.grid.GridPanel',
  13548.  
  13549. alias: ['widget.pveTemplateSelector'],
  13550.  
  13551. initComponent : function() {
  13552. var me = this;
  13553.  
  13554. if (!me.nodename) {
  13555. throw "no node name specified";
  13556. }
  13557.  
  13558. var baseurl = "/nodes/" + me.nodename + "/aplinfo";
  13559. var store = new Ext.data.Store({
  13560. model: 'pve-aplinfo',
  13561. groupField: 'section',
  13562. proxy: {
  13563. type: 'pve',
  13564. url: '/api2/json' + baseurl
  13565. }
  13566. });
  13567.  
  13568. var sm = Ext.create('Ext.selection.RowModel', {});
  13569.  
  13570. var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
  13571. groupHeaderTpl: '{[ "Section: " + values.name ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
  13572. });
  13573.  
  13574. var reload = function() {
  13575. store.load();
  13576. };
  13577.  
  13578. PVE.Utils.monStoreErrors(me, store);
  13579.  
  13580. Ext.apply(me, {
  13581. store: store,
  13582. selModel: sm,
  13583. stateful: false,
  13584. viewConfig: {
  13585. trackOver: false
  13586. },
  13587. features: [ groupingFeature ],
  13588. columns: [
  13589. {
  13590. header: gettext('Type'),
  13591. width: 80,
  13592. dataIndex: 'type'
  13593. },
  13594. {
  13595. header: gettext('Package'),
  13596. flex: 1,
  13597. dataIndex: 'package'
  13598. },
  13599. {
  13600. header: gettext('Version'),
  13601. width: 80,
  13602. dataIndex: 'version'
  13603. },
  13604. {
  13605. header: gettext('Description'),
  13606. flex: 1.5,
  13607. dataIndex: 'headline'
  13608. }
  13609. ],
  13610. listeners: {
  13611. afterRender: reload
  13612. }
  13613. });
  13614.  
  13615. me.callParent();
  13616. }
  13617.  
  13618. }, function() {
  13619.  
  13620. Ext.define('pve-aplinfo', {
  13621. extend: 'Ext.data.Model',
  13622. fields: [
  13623. 'template', 'type', 'package', 'version', 'headline', 'infopage',
  13624. 'description', 'os', 'section'
  13625. ],
  13626. idProperty: 'template'
  13627. });
  13628.  
  13629. });
  13630.  
  13631. Ext.define('PVE.storage.TemplateDownload', {
  13632. extend: 'Ext.window.Window',
  13633. alias: ['widget.pveTemplateDownload'],
  13634.  
  13635. modal: true,
  13636.  
  13637. initComponent : function() {
  13638. /*jslint confusion: true */
  13639. var me = this;
  13640.  
  13641. var grid = Ext.create('PVE.grid.TemplateSelector', {
  13642. border: false,
  13643. autoScroll: true,
  13644. nodename: me.nodename
  13645. });
  13646.  
  13647. var sm = grid.getSelectionModel();
  13648.  
  13649. var submitBtn = Ext.create('PVE.button.Button', {
  13650. text: gettext('Download'),
  13651. disabled: true,
  13652. selModel: sm,
  13653. handler: function(button, event, rec) {
  13654. PVE.Utils.API2Request({
  13655. url: '/nodes/' + me.nodename + '/aplinfo',
  13656. params: {
  13657. storage: me.storage,
  13658. template: rec.data.template
  13659. },
  13660. method: 'POST',
  13661. failure: function (response, opts) {
  13662. Ext.Msg.alert('Error', response.htmlStatus);
  13663. },
  13664. success: function(response, options) {
  13665. var upid = response.result.data;
  13666.  
  13667. var win = Ext.create('PVE.window.TaskViewer', {
  13668. upid: upid
  13669. });
  13670. win.show();
  13671. me.close();
  13672. }
  13673. });
  13674. }
  13675. });
  13676.  
  13677. Ext.applyIf(me, {
  13678. title: gettext('Templates'),
  13679. layout: 'fit',
  13680. width: 600,
  13681. height: 400,
  13682. items: grid,
  13683. buttons: [ submitBtn ]
  13684. });
  13685.  
  13686. me.callParent();
  13687. }
  13688. });
  13689.  
  13690. Ext.define('PVE.storage.Upload', {
  13691. extend: 'Ext.window.Window',
  13692. alias: ['widget.pveStorageUpload'],
  13693.  
  13694. resizable: false,
  13695.  
  13696. modal: true,
  13697.  
  13698. initComponent : function() {
  13699. /*jslint confusion: true */
  13700. var me = this;
  13701.  
  13702. var xhr;
  13703.  
  13704. if (!me.nodename) {
  13705. throw "no node name specified";
  13706. }
  13707.  
  13708. if (!me.storage) {
  13709. throw "no storage ID specified";
  13710. }
  13711.  
  13712. var baseurl = "/nodes/" + me.nodename + "/storage/" + me.storage + "/upload";
  13713.  
  13714. var pbar = Ext.create('Ext.ProgressBar', {
  13715. text: 'Ready',
  13716. hidden: true
  13717. });
  13718.  
  13719. me.formPanel = Ext.create('Ext.form.Panel', {
  13720. method: 'POST',
  13721. waitMsgTarget: true,
  13722. bodyPadding: 10,
  13723. border: false,
  13724. width: 300,
  13725. fieldDefaults: {
  13726. labelWidth: 100,
  13727. anchor: '100%'
  13728. },
  13729. items: [
  13730. {
  13731. xtype: 'pveKVComboBox',
  13732. data: [
  13733. ['iso', 'ISO image'],
  13734. ['backup', 'VZDump backup file'],
  13735. ['vztmpl', 'OpenVZ template']
  13736. ],
  13737. fieldLabel: gettext('Content'),
  13738. name: 'content',
  13739. value: 'iso'
  13740. },
  13741. {
  13742. xtype: 'filefield',
  13743. name: 'filename',
  13744. buttonText: gettext('Select File...'),
  13745. allowBlank: false
  13746. },
  13747. pbar
  13748. ]
  13749. });
  13750.  
  13751. var form = me.formPanel.getForm();
  13752.  
  13753. var doStandardSubmit = function() {
  13754. form.submit({
  13755. url: "/api2/htmljs" + baseurl,
  13756. waitMsg: gettext('Uploading file...'),
  13757. success: function(f, action) {
  13758. me.close();
  13759. },
  13760. failure: function(f, action) {
  13761. var msg = PVE.Utils.extractFormActionError(action);
  13762. Ext.Msg.alert(gettext('Error'), msg);
  13763. }
  13764. });
  13765. };
  13766.  
  13767. var updateProgress = function(per, bytes) {
  13768. var text = (per * 100).toFixed(2) + '%';
  13769. if (bytes) {
  13770. text += " (" + PVE.Utils.format_size(bytes) + ')';
  13771. }
  13772. pbar.updateProgress(per, text);
  13773. };
  13774.  
  13775. var abortBtn = Ext.create('Ext.Button', {
  13776. text: gettext('Abort'),
  13777. disabled: true,
  13778. handler: function() {
  13779. me.close();
  13780. }
  13781. });
  13782.  
  13783. var submitBtn = Ext.create('Ext.Button', {
  13784. text: gettext('Upload'),
  13785. disabled: true,
  13786. handler: function(button) {
  13787. var fd;
  13788. try {
  13789. fd = new FormData();
  13790. } catch (err) {
  13791. doStandardSubmit();
  13792. return;
  13793. }
  13794.  
  13795. button.setDisabled(true);
  13796. abortBtn.setDisabled(false);
  13797.  
  13798. var field = form.findField('content');
  13799. fd.append("content", field.getValue());
  13800. field.setDisabled(true);
  13801.  
  13802. field = form.findField('filename');
  13803. var file = field.fileInputEl.dom;
  13804. fd.append("filename", file.files[0]);
  13805. field.setDisabled(true);
  13806.  
  13807. pbar.setVisible(true);
  13808. updateProgress(0);
  13809.  
  13810. xhr = new XMLHttpRequest();
  13811.  
  13812. xhr.addEventListener("load", function(e) {
  13813. if (xhr.status == 200) {
  13814. me.close();
  13815. } else {
  13816. var msg = "Error " + xhr.status.toString() + ": " + Ext.htmlEncode(xhr.statusText);
  13817. Ext.Msg.alert(gettext('Error'), msg, function(btn) {
  13818. me.close();
  13819. });
  13820.  
  13821. }
  13822. }, false);
  13823.  
  13824. xhr.addEventListener("error", function(e) {
  13825. var msg = "Error " + e.target.status.toString() + " occurred while receiving the document.";
  13826. Ext.Msg.alert(gettext('Error'), msg, function(btn) {
  13827. me.close();
  13828. });
  13829. });
  13830.  
  13831. xhr.upload.addEventListener("progress", function(evt) {
  13832. if (evt.lengthComputable) {
  13833. var percentComplete = evt.loaded / evt.total;
  13834. updateProgress(percentComplete, evt.loaded);
  13835. }
  13836. }, false);
  13837.  
  13838. xhr.open("POST", "/api2/json" + baseurl, true);
  13839. xhr.send(fd);
  13840. }
  13841. });
  13842.  
  13843. form.on('validitychange', function(f, valid) {
  13844. submitBtn.setDisabled(!valid);
  13845. });
  13846.  
  13847. Ext.applyIf(me, {
  13848. title: gettext('Upload'),
  13849. items: me.formPanel,
  13850. buttons: [ abortBtn, submitBtn ],
  13851. listeners: {
  13852. close: function() {
  13853. if (xhr) {
  13854. xhr.abort();
  13855. }
  13856. }
  13857. }
  13858. });
  13859.  
  13860. me.callParent();
  13861. }
  13862. });
  13863.  
  13864. Ext.define('PVE.storage.ContentView', {
  13865. extend: 'Ext.grid.GridPanel',
  13866.  
  13867. alias: ['widget.pveStorageContentView'],
  13868.  
  13869. initComponent : function() {
  13870. var me = this;
  13871.  
  13872. var nodename = me.pveSelNode.data.node;
  13873. if (!nodename) {
  13874. throw "no node name specified";
  13875. }
  13876.  
  13877. var storage = me.pveSelNode.data.storage;
  13878. if (!storage) {
  13879. throw "no storage ID specified";
  13880. }
  13881.  
  13882. var baseurl = "/nodes/" + nodename + "/storage/" + storage + "/content";
  13883. var store = new Ext.data.Store({
  13884. model: 'pve-storage-content',
  13885. groupField: 'content',
  13886. proxy: {
  13887. type: 'pve',
  13888. url: '/api2/json' + baseurl
  13889. },
  13890. sorters: {
  13891. property: 'volid',
  13892. order: 'DESC'
  13893. }
  13894. });
  13895.  
  13896. var sm = Ext.create('Ext.selection.RowModel', {});
  13897.  
  13898. var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
  13899. groupHeaderTpl: '{[ PVE.Utils.format_content_types(values.name) ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
  13900. });
  13901.  
  13902. var reload = function() {
  13903. store.load();
  13904. };
  13905.  
  13906. PVE.Utils.monStoreErrors(me, store);
  13907.  
  13908. Ext.apply(me, {
  13909. store: store,
  13910. selModel: sm,
  13911. stateful: false,
  13912. viewConfig: {
  13913. trackOver: false
  13914. },
  13915. features: [ groupingFeature ],
  13916. tbar: [
  13917. {
  13918. xtype: 'pveButton',
  13919. text: gettext('Restore'),
  13920. selModel: sm,
  13921. disabled: true,
  13922. enableFn: function(rec) {
  13923. return rec && rec.data.content === 'backup';
  13924. },
  13925. handler: function(b, e, rec) {
  13926. var vmtype;
  13927. if (rec.data.volid.match(/vzdump-qemu-/)) {
  13928. vmtype = 'qemu';
  13929. } else if (rec.data.volid.match(/vzdump-openvz-/)) {
  13930. vmtype = 'openvz';
  13931. } else {
  13932. return;
  13933. }
  13934.  
  13935. var win = Ext.create('PVE.window.Restore', {
  13936. nodename: nodename,
  13937. volid: rec.data.volid,
  13938. volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
  13939. vmtype: vmtype
  13940. });
  13941. win.show();
  13942. win.on('destroy', reload);
  13943. }
  13944. },
  13945. {
  13946. xtype: 'pveButton',
  13947. text: gettext('Remove'),
  13948. selModel: sm,
  13949. disabled: true,
  13950. confirmMsg: function(rec) {
  13951. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  13952. "'" + rec.data.volid + "'");
  13953. },
  13954. enableFn: function(rec) {
  13955. return rec && rec.data.content !== 'images';
  13956. },
  13957. handler: function(b, e, rec) {
  13958. PVE.Utils.API2Request({
  13959. url: baseurl + '/' + rec.data.volid,
  13960. method: 'DELETE',
  13961. waitMsgTarget: me,
  13962. callback: function() {
  13963. reload();
  13964. },
  13965. failure: function (response, opts) {
  13966. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  13967. }
  13968. });
  13969. }
  13970. },
  13971. {
  13972. text: gettext('Templates'),
  13973. handler: function() {
  13974. var win = Ext.create('PVE.storage.TemplateDownload', {
  13975. nodename: nodename,
  13976. storage: storage
  13977. });
  13978. win.show();
  13979. win.on('destroy', reload);
  13980. }
  13981. },
  13982. {
  13983. text: gettext('Upload'),
  13984. handler: function() {
  13985. var win = Ext.create('PVE.storage.Upload', {
  13986. nodename: nodename,
  13987. storage: storage
  13988. });
  13989. win.show();
  13990. win.on('destroy', reload);
  13991. }
  13992. }
  13993. ],
  13994. columns: [
  13995. {
  13996. header: gettext('Name'),
  13997. flex: 1,
  13998. sortable: true,
  13999. renderer: PVE.Utils.render_storage_content,
  14000. dataIndex: 'text'
  14001. },
  14002. {
  14003. header: gettext('Format'),
  14004. width: 100,
  14005. dataIndex: 'format'
  14006. },
  14007. {
  14008. header: gettext('Size'),
  14009. width: 100,
  14010. renderer: PVE.Utils.format_size,
  14011. dataIndex: 'size'
  14012. }
  14013. ],
  14014. listeners: {
  14015. show: reload
  14016. }
  14017. });
  14018.  
  14019. me.callParent();
  14020. }
  14021. }, function() {
  14022.  
  14023. Ext.define('pve-storage-content', {
  14024. extend: 'Ext.data.Model',
  14025. fields: [
  14026. 'volid', 'content', 'format', 'size', 'used', 'vmid',
  14027. 'channel', 'id', 'lun',
  14028. {
  14029. name: 'text',
  14030. convert: function(value, record) {
  14031. if (value) {
  14032. return value;
  14033. }
  14034. return PVE.Utils.render_storage_content(value, {}, record);
  14035. }
  14036. }
  14037. ],
  14038. idProperty: 'volid'
  14039. });
  14040.  
  14041. });Ext.define('PVE.storage.StatusView', {
  14042. extend: 'PVE.grid.ObjectGrid',
  14043. alias: ['widget.pveStorageStatusView'],
  14044.  
  14045. initComponent : function() {
  14046. var me = this;
  14047.  
  14048. var nodename = me.pveSelNode.data.node;
  14049. if (!nodename) {
  14050. throw "no node name specified";
  14051. }
  14052.  
  14053. var storage = me.pveSelNode.data.storage;
  14054. if (!storage) {
  14055. throw "no storage ID specified";
  14056. }
  14057.  
  14058. var rows = {
  14059. disable: {
  14060. header: gettext('Enabled'),
  14061. required: true,
  14062. renderer: PVE.Utils.format_neg_boolean
  14063. },
  14064. active: {
  14065. header: gettext('Active'),
  14066. required: true,
  14067. renderer: PVE.Utils.format_boolean
  14068. },
  14069. content: {
  14070. header: gettext('Content'),
  14071. required: true,
  14072. renderer: PVE.Utils.format_content_types
  14073. },
  14074. type: {
  14075. header: gettext('Type'),
  14076. required: true,
  14077. renderer: PVE.Utils.format_storage_type
  14078. },
  14079. shared: {
  14080. header: gettext('Shared'),
  14081. required: true,
  14082. renderer: PVE.Utils.format_boolean
  14083. },
  14084. total: {
  14085. header: gettext('Size'),
  14086. required: true,
  14087. renderer: PVE.Utils.render_size
  14088. },
  14089. used: {
  14090. header: gettext('Used'),
  14091. required: true,
  14092. renderer: function(value) {
  14093. // do not confuse users with filesystem details
  14094. var total = me.getObjectValue('total', 0);
  14095. var avail = me.getObjectValue('avail', 0);
  14096. return PVE.Utils.render_size(total - avail);
  14097. }
  14098. },
  14099. avail: {
  14100. header: gettext('Avail'),
  14101. required: true,
  14102. renderer: PVE.Utils.render_size
  14103. }
  14104. };
  14105.  
  14106. Ext.applyIf(me, {
  14107. title: gettext('Status'),
  14108. url: "/api2/json/nodes/" + nodename + "/storage/" + storage + "/status",
  14109. cwidth1: 150,
  14110. interval: 30000,
  14111. //height: 195,
  14112. rows: rows
  14113. });
  14114.  
  14115. me.callParent();
  14116. }
  14117. });
  14118. Ext.define('PVE.storage.Summary', {
  14119. extend: 'Ext.panel.Panel',
  14120. alias: 'widget.pveStorageSummary',
  14121.  
  14122. initComponent: function() {
  14123. var me = this;
  14124.  
  14125. var nodename = me.pveSelNode.data.node;
  14126. if (!nodename) {
  14127. throw "no node name specified";
  14128. }
  14129.  
  14130. var storage = me.pveSelNode.data.storage;
  14131. if (!storage) {
  14132. throw "no storage ID specified";
  14133. }
  14134.  
  14135. var statusview = Ext.create('PVE.storage.StatusView', {
  14136. pveSelNode: me.pveSelNode,
  14137. style: 'padding-top:0px'
  14138. });
  14139.  
  14140. var rstore = statusview.rstore;
  14141.  
  14142. var rrdurl = "/api2/png/nodes/" + nodename + "/storage/" + storage + "/rrd";
  14143.  
  14144. Ext.apply(me, {
  14145. autoScroll: true,
  14146. bodyStyle: 'padding:10px',
  14147. defaults: {
  14148. style: 'padding-top:10px',
  14149. width: 800
  14150. },
  14151. tbar: [
  14152. '->',
  14153. {
  14154. xtype: 'pveRRDTypeSelector'
  14155. }
  14156. ],
  14157. items: [
  14158. statusview,
  14159. {
  14160. xtype: 'pveRRDView',
  14161. title: "Usage",
  14162. pveSelNode: me.pveSelNode,
  14163. datasource: 'total,used',
  14164. rrdurl: rrdurl
  14165. }
  14166. ]
  14167. });
  14168.  
  14169. me.on('show', rstore.startUpdate);
  14170. me.on('hide', rstore.stopUpdate);
  14171. me.on('destroy', rstore.stopUpdate);
  14172.  
  14173. me.callParent();
  14174. }
  14175. });
  14176. Ext.define('PVE.storage.Browser', {
  14177. extend: 'PVE.panel.Config',
  14178. alias: 'widget.PVE.storage.Browser',
  14179.  
  14180. initComponent: function() {
  14181. var me = this;
  14182.  
  14183. var nodename = me.pveSelNode.data.node;
  14184. if (!nodename) {
  14185. throw "no node name specified";
  14186. }
  14187.  
  14188. var storeid = me.pveSelNode.data.storage;
  14189. if (!storeid) {
  14190. throw "no storage ID specified";
  14191. }
  14192.  
  14193. var caps = Ext.state.Manager.get('GuiCap');
  14194.  
  14195. me.items = [
  14196. {
  14197. title: gettext('Summary'),
  14198. xtype: 'pveStorageSummary',
  14199. itemId: 'summary'
  14200. }
  14201. ];
  14202.  
  14203. Ext.apply(me, {
  14204. title: Ext.String.format(gettext("Storage {0} on node {1}"),
  14205. "'" + storeid + "'", "'" + nodename + "'"),
  14206. hstateid: 'storagetab'
  14207. });
  14208.  
  14209. if (caps.storage['Datastore.Allocate']) {
  14210. me.items.push({
  14211. xtype: 'pveStorageContentView',
  14212. title: gettext('Content'),
  14213. itemId: 'content'
  14214. });
  14215. }
  14216.  
  14217. if (caps.storage['Permissions.Modify']) {
  14218. me.items.push({
  14219. xtype: 'pveACLView',
  14220. title: gettext('Permissions'),
  14221. itemId: 'permissions',
  14222. path: '/storage/' + storeid
  14223. });
  14224. }
  14225.  
  14226. me.callParent();
  14227. }
  14228. });
  14229. Ext.define('PVE.storage.DirInputPanel', {
  14230. extend: 'PVE.panel.InputPanel',
  14231.  
  14232. onGetValues: function(values) {
  14233. var me = this;
  14234.  
  14235. if (me.create) {
  14236. values.type = 'dir';
  14237. } else {
  14238. delete values.storage;
  14239. }
  14240.  
  14241. values.disable = values.enable ? 0 : 1;
  14242. delete values.enable;
  14243.  
  14244. return values;
  14245. },
  14246.  
  14247. initComponent : function() {
  14248. var me = this;
  14249.  
  14250.  
  14251. me.column1 = [
  14252. {
  14253. xtype: me.create ? 'textfield' : 'displayfield',
  14254. name: 'storage',
  14255. height: 22, // hack: set same height as text fields
  14256. value: me.storageId || '',
  14257. fieldLabel: 'ID',
  14258. vtype: 'StorageId',
  14259. allowBlank: false
  14260. },
  14261. {
  14262. xtype: me.create ? 'textfield' : 'displayfield',
  14263. height: 22, // hack: set same height as text fields
  14264. name: 'path',
  14265. value: '',
  14266. fieldLabel: gettext('Directory'),
  14267. allowBlank: false
  14268. },
  14269. {
  14270. xtype: 'pveContentTypeSelector',
  14271. name: 'content',
  14272. value: 'images',
  14273. multiSelect: true,
  14274. fieldLabel: gettext('Content'),
  14275. allowBlank: false
  14276. }
  14277. ];
  14278.  
  14279. me.column2 = [
  14280. {
  14281. xtype: 'pvecheckbox',
  14282. name: 'enable',
  14283. checked: true,
  14284. uncheckedValue: 0,
  14285. fieldLabel: gettext('Enable')
  14286. },
  14287. {
  14288. xtype: 'pvecheckbox',
  14289. name: 'shared',
  14290. uncheckedValue: 0,
  14291. fieldLabel: gettext('Shared')
  14292. },
  14293. {
  14294. xtype: 'numberfield',
  14295. fieldLabel: gettext('Max Backups'),
  14296. name: 'maxfiles',
  14297. minValue: 0,
  14298. maxValue: 365,
  14299. value: me.create ? '1' : undefined,
  14300. allowBlank: false
  14301. }
  14302. ];
  14303.  
  14304. if (me.create || me.storageId !== 'local') {
  14305. me.column2.unshift({
  14306. xtype: 'PVE.form.NodeSelector',
  14307. name: 'nodes',
  14308. fieldLabel: gettext('Nodes'),
  14309. emptyText: gettext('All') + ' (' +
  14310. gettext('No restrictions') +')',
  14311. multiSelect: true,
  14312. autoSelect: false
  14313. });
  14314. }
  14315.  
  14316. me.callParent();
  14317. }
  14318. });
  14319.  
  14320. Ext.define('PVE.storage.DirEdit', {
  14321. extend: 'PVE.window.Edit',
  14322.  
  14323. initComponent : function() {
  14324. var me = this;
  14325.  
  14326. me.create = !me.storageId;
  14327.  
  14328. if (me.create) {
  14329. me.url = '/api2/extjs/storage';
  14330. me.method = 'POST';
  14331. } else {
  14332. me.url = '/api2/extjs/storage/' + me.storageId;
  14333. me.method = 'PUT';
  14334. }
  14335.  
  14336. var ipanel = Ext.create('PVE.storage.DirInputPanel', {
  14337. create: me.create,
  14338. storageId: me.storageId
  14339. });
  14340.  
  14341. Ext.apply(me, {
  14342. subject: 'Directory',
  14343. isAdd: true,
  14344. items: [ ipanel ]
  14345. });
  14346.  
  14347. me.callParent();
  14348.  
  14349. if (!me.create) {
  14350. me.load({
  14351. success: function(response, options) {
  14352. var values = response.result.data;
  14353. var ctypes = values.content || '';
  14354.  
  14355. values.content = ctypes.split(',');
  14356.  
  14357. if (values.nodes) {
  14358. values.nodes = values.nodes.split(',');
  14359. }
  14360. values.enable = values.disable ? 0 : 1;
  14361.  
  14362. ipanel.setValues(values);
  14363. }
  14364. });
  14365. }
  14366. }
  14367. });
  14368. Ext.define('PVE.storage.NFSScan', {
  14369. extend: 'Ext.form.field.ComboBox',
  14370. alias: 'widget.pveNFSScan',
  14371.  
  14372. queryParam: 'server',
  14373.  
  14374. doRawQuery: function() {
  14375. },
  14376.  
  14377. onTriggerClick: function() {
  14378. var me = this;
  14379.  
  14380. if (!me.queryCaching || me.lastQuery !== me.nfsServer) {
  14381. me.store.removeAll();
  14382. }
  14383.  
  14384. me.allQuery = me.nfsServer;
  14385.  
  14386. me.callParent();
  14387. },
  14388.  
  14389. setServer: function(server) {
  14390. var me = this;
  14391.  
  14392. me.nfsServer = server;
  14393. },
  14394.  
  14395. initComponent : function() {
  14396. var me = this;
  14397.  
  14398. if (!me.nodename) {
  14399. me.nodename = 'localhost';
  14400. }
  14401.  
  14402. var store = Ext.create('Ext.data.Store', {
  14403. fields: [ 'path', 'options' ],
  14404. proxy: {
  14405. type: 'pve',
  14406. url: '/api2/json/nodes/' + me.nodename + '/scan/nfs'
  14407. }
  14408. });
  14409.  
  14410. Ext.apply(me, {
  14411. store: store,
  14412. valueField: 'path',
  14413. displayField: 'path',
  14414. matchFieldWidth: false,
  14415. listConfig: {
  14416. loadingText: 'Scanning...',
  14417. width: 350
  14418. }
  14419. });
  14420.  
  14421. me.callParent();
  14422. }
  14423. });
  14424.  
  14425. Ext.define('PVE.storage.NFSInputPanel', {
  14426. extend: 'PVE.panel.InputPanel',
  14427.  
  14428. onGetValues: function(values) {
  14429. var me = this;
  14430.  
  14431. if (me.create) {
  14432. values.type = 'nfs';
  14433. // hack: for now we always create nvf v3
  14434. // fixme: make this configurable
  14435. values.options = 'vers=3';
  14436. } else {
  14437. delete values.storage;
  14438. }
  14439.  
  14440. values.disable = values.enable ? 0 : 1;
  14441. delete values.enable;
  14442.  
  14443. return values;
  14444. },
  14445.  
  14446. initComponent : function() {
  14447. var me = this;
  14448.  
  14449.  
  14450. me.column1 = [
  14451. {
  14452. xtype: me.create ? 'textfield' : 'displayfield',
  14453. name: 'storage',
  14454. height: 22, // hack: set same height as text fields
  14455. value: me.storageId || '',
  14456. fieldLabel: 'ID',
  14457. vtype: 'StorageId',
  14458. allowBlank: false
  14459. },
  14460. {
  14461. xtype: me.create ? 'textfield' : 'displayfield',
  14462. height: 22, // hack: set same height as text fields
  14463. name: 'server',
  14464. value: '',
  14465. fieldLabel: gettext('Server'),
  14466. allowBlank: false,
  14467. listeners: {
  14468. change: function(f, value) {
  14469. if (me.create) {
  14470. var exportField = me.down('field[name=export]');
  14471. exportField.setServer(value);
  14472. exportField.setValue('');
  14473. }
  14474. }
  14475. }
  14476. },
  14477. {
  14478. xtype: me.create ? 'pveNFSScan' : 'displayfield',
  14479. height: 22, // hack: set same height as text fields
  14480. name: 'export',
  14481. value: '',
  14482. fieldLabel: 'Export',
  14483. allowBlank: false
  14484. },
  14485. {
  14486. xtype: 'pveContentTypeSelector',
  14487. name: 'content',
  14488. value: 'images',
  14489. multiSelect: true,
  14490. fieldLabel: gettext('Content'),
  14491. allowBlank: false
  14492. }
  14493. ];
  14494.  
  14495. me.column2 = [
  14496. {
  14497. xtype: 'PVE.form.NodeSelector',
  14498. name: 'nodes',
  14499. fieldLabel: gettext('Nodes'),
  14500. emptyText: gettext('All') + ' (' +
  14501. gettext('No restrictions') +')',
  14502. multiSelect: true,
  14503. autoSelect: false
  14504. },
  14505. {
  14506. xtype: 'pvecheckbox',
  14507. name: 'enable',
  14508. checked: true,
  14509. uncheckedValue: 0,
  14510. fieldLabel: gettext('Enable')
  14511. },
  14512. {
  14513. xtype: 'numberfield',
  14514. fieldLabel: gettext('Max Backups'),
  14515. name: 'maxfiles',
  14516. minValue: 0,
  14517. maxValue: 365,
  14518. value: me.create ? '1' : undefined,
  14519. allowBlank: false
  14520. }
  14521. ];
  14522.  
  14523. me.callParent();
  14524. }
  14525. });
  14526.  
  14527. Ext.define('PVE.storage.NFSEdit', {
  14528. extend: 'PVE.window.Edit',
  14529.  
  14530. initComponent : function() {
  14531. var me = this;
  14532.  
  14533. me.create = !me.storageId;
  14534.  
  14535. if (me.create) {
  14536. me.url = '/api2/extjs/storage';
  14537. me.method = 'POST';
  14538. } else {
  14539. me.url = '/api2/extjs/storage/' + me.storageId;
  14540. me.method = 'PUT';
  14541. }
  14542.  
  14543. var ipanel = Ext.create('PVE.storage.NFSInputPanel', {
  14544. create: me.create,
  14545. storageId: me.storageId
  14546. });
  14547.  
  14548. Ext.apply(me, {
  14549. subject: 'NFS share',
  14550. isAdd: true,
  14551. items: [ ipanel ]
  14552. });
  14553.  
  14554. me.callParent();
  14555.  
  14556. if (!me.create) {
  14557. me.load({
  14558. success: function(response, options) {
  14559. var values = response.result.data;
  14560. var ctypes = values.content || '';
  14561.  
  14562. values.content = ctypes.split(',');
  14563.  
  14564. if (values.nodes) {
  14565. values.nodes = values.nodes.split(',');
  14566. }
  14567. values.enable = values.disable ? 0 : 1;
  14568. ipanel.setValues(values);
  14569. }
  14570. });
  14571. }
  14572. }
  14573. });
  14574. Ext.define('PVE.storage.IScsiScan', {
  14575. extend: 'Ext.form.field.ComboBox',
  14576. alias: 'widget.pveIScsiScan',
  14577.  
  14578. queryParam: 'portal',
  14579.  
  14580. doRawQuery: function() {
  14581. },
  14582.  
  14583. onTriggerClick: function() {
  14584. var me = this;
  14585.  
  14586. if (!me.queryCaching || me.lastQuery !== me.portal) {
  14587. me.store.removeAll();
  14588. }
  14589.  
  14590. me.allQuery = me.portal;
  14591.  
  14592. me.callParent();
  14593. },
  14594.  
  14595. setPortal: function(portal) {
  14596. var me = this;
  14597.  
  14598. me.portal = portal;
  14599. },
  14600.  
  14601. initComponent : function() {
  14602. var me = this;
  14603.  
  14604. if (!me.nodename) {
  14605. me.nodename = 'localhost';
  14606. }
  14607.  
  14608. var store = Ext.create('Ext.data.Store', {
  14609. fields: [ 'target', 'portal' ],
  14610. proxy: {
  14611. type: 'pve',
  14612. url: '/api2/json/nodes/' + me.nodename + '/scan/iscsi'
  14613. }
  14614. });
  14615.  
  14616. Ext.apply(me, {
  14617. store: store,
  14618. valueField: 'target',
  14619. displayField: 'target',
  14620. matchFieldWidth: false,
  14621. listConfig: {
  14622. loadingText: 'Scanning...',
  14623. width: 350
  14624. }
  14625. });
  14626.  
  14627. me.callParent();
  14628. }
  14629. });
  14630.  
  14631. Ext.define('PVE.storage.IScsiInputPanel', {
  14632. extend: 'PVE.panel.InputPanel',
  14633.  
  14634. onGetValues: function(values) {
  14635. var me = this;
  14636.  
  14637. if (me.create) {
  14638. values.type = 'iscsi';
  14639. } else {
  14640. delete values.storage;
  14641. }
  14642.  
  14643. values.content = values.luns ? 'images' : 'none';
  14644. delete values.luns;
  14645.  
  14646. values.disable = values.enable ? 0 : 1;
  14647. delete values.enable;
  14648.  
  14649. return values;
  14650. },
  14651.  
  14652. initComponent : function() {
  14653. var me = this;
  14654.  
  14655.  
  14656. me.column1 = [
  14657. {
  14658. xtype: me.create ? 'textfield' : 'displayfield',
  14659. name: 'storage',
  14660. height: 22, // hack: set same height as text fields
  14661. value: me.storageId || '',
  14662. fieldLabel: 'ID',
  14663. vtype: 'StorageId',
  14664. allowBlank: false
  14665. },
  14666. {
  14667. xtype: me.create ? 'textfield' : 'displayfield',
  14668. height: 22, // hack: set same height as text fields
  14669. name: 'portal',
  14670. value: '',
  14671. fieldLabel: 'Portal',
  14672. allowBlank: false,
  14673. listeners: {
  14674. change: function(f, value) {
  14675. if (me.create) {
  14676. var exportField = me.down('field[name=target]');
  14677. exportField.setPortal(value);
  14678. exportField.setValue('');
  14679. }
  14680. }
  14681. }
  14682. },
  14683. {
  14684. readOnly: !me.create,
  14685. xtype: me.create ? 'pveIScsiScan' : 'displayfield',
  14686. name: 'target',
  14687. value: '',
  14688. fieldLabel: 'Target',
  14689. allowBlank: false
  14690. }
  14691. ];
  14692.  
  14693. me.column2 = [
  14694. {
  14695. xtype: 'PVE.form.NodeSelector',
  14696. name: 'nodes',
  14697. fieldLabel: gettext('Nodes'),
  14698. emptyText: gettext('All') + ' (' +
  14699. gettext('No restrictions') +')',
  14700. multiSelect: true,
  14701. autoSelect: false
  14702. },
  14703. {
  14704. xtype: 'pvecheckbox',
  14705. name: 'enable',
  14706. checked: true,
  14707. uncheckedValue: 0,
  14708. fieldLabel: gettext('Enable')
  14709. },
  14710. {
  14711. xtype: 'checkbox',
  14712. name: 'luns',
  14713. checked: true,
  14714. fieldLabel: gettext('Use LUNs directly')
  14715. }
  14716. ];
  14717.  
  14718. me.callParent();
  14719. }
  14720. });
  14721.  
  14722. Ext.define('PVE.storage.IScsiEdit', {
  14723. extend: 'PVE.window.Edit',
  14724.  
  14725. initComponent : function() {
  14726. var me = this;
  14727.  
  14728. me.create = !me.storageId;
  14729.  
  14730. if (me.create) {
  14731. me.url = '/api2/extjs/storage';
  14732. me.method = 'POST';
  14733. } else {
  14734. me.url = '/api2/extjs/storage/' + me.storageId;
  14735. me.method = 'PUT';
  14736. }
  14737.  
  14738. var ipanel = Ext.create('PVE.storage.IScsiInputPanel', {
  14739. create: me.create,
  14740. storageId: me.storageId
  14741. });
  14742.  
  14743. Ext.apply(me, {
  14744. subject: 'iSCSI target',
  14745. isAdd: true,
  14746. items: [ ipanel ]
  14747. });
  14748.  
  14749. me.callParent();
  14750.  
  14751. if (!me.create) {
  14752. me.load({
  14753. success: function(response, options) {
  14754. var values = response.result.data;
  14755. var ctypes = values.content || '';
  14756.  
  14757. if (values.storage === 'local') {
  14758. values.content = ctypes.split(',');
  14759. }
  14760. if (values.nodes) {
  14761. values.nodes = values.nodes.split(',');
  14762. }
  14763. values.enable = values.disable ? 0 : 1;
  14764. values.luns = (values.content === 'images') ? true : false;
  14765.  
  14766. ipanel.setValues(values);
  14767. }
  14768. });
  14769. }
  14770. }
  14771. });
  14772. Ext.define('PVE.storage.VgSelector', {
  14773. extend: 'Ext.form.field.ComboBox',
  14774. alias: 'widget.pveVgSelector',
  14775.  
  14776. initComponent : function() {
  14777. var me = this;
  14778.  
  14779. if (!me.nodename) {
  14780. me.nodename = 'localhost';
  14781. }
  14782.  
  14783. var store = Ext.create('Ext.data.Store', {
  14784. autoLoad: {}, // true,
  14785. fields: [ 'vg', 'size', 'free' ],
  14786. proxy: {
  14787. type: 'pve',
  14788. url: '/api2/json/nodes/' + me.nodename + '/scan/lvm'
  14789. }
  14790. });
  14791.  
  14792. Ext.apply(me, {
  14793. store: store,
  14794. valueField: 'vg',
  14795. displayField: 'vg',
  14796. queryMode: 'local',
  14797. editable: false,
  14798. listConfig: {
  14799. loadingText: 'Scanning...'
  14800. }
  14801. });
  14802.  
  14803. me.callParent();
  14804. }
  14805. });
  14806.  
  14807. Ext.define('PVE.storage.BaseStorageSelector', {
  14808. extend: 'Ext.form.field.ComboBox',
  14809. alias: 'widget.pveBaseStorageSelector',
  14810.  
  14811. existingGroupsText: gettext("Existing volume groups"),
  14812.  
  14813. initComponent : function() {
  14814. var me = this;
  14815.  
  14816. var store = Ext.create('Ext.data.Store', {
  14817. autoLoad: {
  14818. addRecords: true,
  14819. params: {
  14820. type: 'iscsi'
  14821. }
  14822. },
  14823. fields: [ 'storage', 'type', 'content',
  14824. {
  14825. name: 'text',
  14826. convert: function(value, record) {
  14827. if (record.data.storage) {
  14828. return record.data.storage + " (iSCSI)";
  14829. } else {
  14830. return me.existingGroupsText;
  14831. }
  14832. }
  14833. }],
  14834. proxy: {
  14835. type: 'pve',
  14836. url: '/api2/json/storage/'
  14837. }
  14838. });
  14839.  
  14840. store.loadData([{ storage: '' }], true);
  14841.  
  14842. Ext.apply(me, {
  14843. store: store,
  14844. queryMode: 'local',
  14845. editable: false,
  14846. value: '',
  14847. valueField: 'storage',
  14848. displayField: 'text'
  14849. });
  14850.  
  14851. me.callParent();
  14852. }
  14853. });
  14854.  
  14855. Ext.define('PVE.storage.LVMInputPanel', {
  14856. extend: 'PVE.panel.InputPanel',
  14857.  
  14858. onGetValues: function(values) {
  14859. var me = this;
  14860.  
  14861. if (me.create) {
  14862. values.type = 'lvm';
  14863. values.content = 'images';
  14864. } else {
  14865. delete values.storage;
  14866. }
  14867.  
  14868. values.disable = values.enable ? 0 : 1;
  14869. delete values.enable;
  14870.  
  14871. return values;
  14872. },
  14873.  
  14874. initComponent : function() {
  14875. var me = this;
  14876.  
  14877. me.column1 = [
  14878. {
  14879. xtype: me.create ? 'textfield' : 'displayfield',
  14880. name: 'storage',
  14881. height: 22, // hack: set same height as text fields
  14882. value: me.storageId || '',
  14883. fieldLabel: 'ID',
  14884. vtype: 'StorageId',
  14885. submitValue: !!me.create,
  14886. allowBlank: false
  14887. }
  14888. ];
  14889.  
  14890. var vgnameField = Ext.createWidget(me.create ? 'textfield' : 'displayfield', {
  14891. height: 22, // hack: set same height as text fields
  14892. name: 'vgname',
  14893. hidden: !!me.create,
  14894. disabled: !!me.create,
  14895. value: '',
  14896. fieldLabel: gettext('Volume group'),
  14897. allowBlank: false
  14898. });
  14899.  
  14900. if (me.create) {
  14901. var vgField = Ext.create('PVE.storage.VgSelector', {
  14902. name: 'vgname',
  14903. fieldLabel: gettext('Volume group'),
  14904. allowBlank: false
  14905. });
  14906.  
  14907. var baseField = Ext.createWidget('pveFileSelector', {
  14908. name: 'base',
  14909. hidden: true,
  14910. disabled: true,
  14911. nodename: 'localhost',
  14912. storageContent: 'images',
  14913. fieldLabel: gettext('Base volume'),
  14914. allowBlank: false
  14915. });
  14916.  
  14917. me.column1.push({
  14918. xtype: 'pveBaseStorageSelector',
  14919. name: 'basesel',
  14920. fieldLabel: gettext('Base storage'),
  14921. submitValue: false,
  14922. listeners: {
  14923. change: function(f, value) {
  14924. if (value) {
  14925. vgnameField.setVisible(true);
  14926. vgnameField.setDisabled(false);
  14927. vgField.setVisible(false);
  14928. vgField.setDisabled(true);
  14929. baseField.setVisible(true);
  14930. baseField.setDisabled(false);
  14931. } else {
  14932. vgnameField.setVisible(false);
  14933. vgnameField.setDisabled(true);
  14934. vgField.setVisible(true);
  14935. vgField.setDisabled(false);
  14936. baseField.setVisible(false);
  14937. baseField.setDisabled(true);
  14938. }
  14939. baseField.setStorage(value);
  14940. }
  14941. }
  14942. });
  14943.  
  14944. me.column1.push(baseField);
  14945.  
  14946. me.column1.push(vgField);
  14947. }
  14948.  
  14949. me.column1.push(vgnameField);
  14950.  
  14951. me.column2 = [
  14952. {
  14953. xtype: 'PVE.form.NodeSelector',
  14954. name: 'nodes',
  14955. fieldLabel: gettext('Nodes'),
  14956. emptyText: gettext('All') + ' (' +
  14957. gettext('No restrictions') +')',
  14958. multiSelect: true,
  14959. autoSelect: false
  14960. },
  14961. {
  14962. xtype: 'pvecheckbox',
  14963. name: 'enable',
  14964. checked: true,
  14965. uncheckedValue: 0,
  14966. fieldLabel: gettext('Enable')
  14967. },
  14968. {
  14969. xtype: 'pvecheckbox',
  14970. name: 'shared',
  14971. uncheckedValue: 0,
  14972. fieldLabel: gettext('Shared')
  14973. }
  14974. ];
  14975.  
  14976. me.callParent();
  14977. }
  14978. });
  14979.  
  14980. Ext.define('PVE.storage.LVMEdit', {
  14981. extend: 'PVE.window.Edit',
  14982.  
  14983. initComponent : function() {
  14984. var me = this;
  14985.  
  14986. me.create = !me.storageId;
  14987.  
  14988. if (me.create) {
  14989. me.url = '/api2/extjs/storage';
  14990. me.method = 'POST';
  14991. } else {
  14992. me.url = '/api2/extjs/storage/' + me.storageId;
  14993. me.method = 'PUT';
  14994. }
  14995.  
  14996. var ipanel = Ext.create('PVE.storage.LVMInputPanel', {
  14997. create: me.create,
  14998. storageId: me.storageId
  14999. });
  15000.  
  15001. Ext.apply(me, {
  15002. subject: 'LVM group',
  15003. isAdd: true,
  15004. items: [ ipanel ]
  15005. });
  15006.  
  15007. me.callParent();
  15008.  
  15009. if (!me.create) {
  15010. me.load({
  15011. success: function(response, options) {
  15012. var values = response.result.data;
  15013. if (values.nodes) {
  15014. values.nodes = values.nodes.split(',');
  15015. }
  15016. values.enable = values.disable ? 0 : 1;
  15017. ipanel.setValues(values);
  15018. }
  15019. });
  15020. }
  15021. }
  15022. });
  15023. Ext.define('PVE.dc.NodeView', {
  15024. extend: 'Ext.grid.GridPanel',
  15025.  
  15026. alias: ['widget.pveDcNodeView'],
  15027.  
  15028. initComponent : function() {
  15029. var me = this;
  15030.  
  15031. var rstore = Ext.create('PVE.data.UpdateStore', {
  15032. interval: 3000,
  15033. storeid: 'pve-dc-nodes',
  15034. model: 'pve-dc-nodes',
  15035. proxy: {
  15036. type: 'pve',
  15037. url: "/api2/json/cluster/status"
  15038. },
  15039. filters: {
  15040. property: 'type',
  15041. value : 'node'
  15042. }
  15043. });
  15044.  
  15045. var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  15046.  
  15047. Ext.apply(me, {
  15048. store: store,
  15049. stateful: false,
  15050. columns: [
  15051. {
  15052. header: gettext('Name'),
  15053. width: 200,
  15054. sortable: true,
  15055. dataIndex: 'name'
  15056. },
  15057. {
  15058. header: 'ID',
  15059. width: 50,
  15060. sortable: true,
  15061. dataIndex: 'nodeid'
  15062. },
  15063. {
  15064. header: gettext('Online'),
  15065. width: 100,
  15066. sortable: true,
  15067. dataIndex: 'state',
  15068. renderer: PVE.Utils.format_boolean
  15069. },
  15070. {
  15071. header: gettext('Support'),
  15072. width: 100,
  15073. sortable: true,
  15074. dataIndex: 'level',
  15075. renderer: PVE.Utils.render_support_level
  15076. },
  15077. {
  15078. header: gettext('Estranged'),
  15079. width: 100,
  15080. sortable: true,
  15081. dataIndex: 'estranged',
  15082. renderer: PVE.Utils.format_boolean
  15083. },
  15084. {
  15085. header: gettext('Server Address'),
  15086. width: 100,
  15087. sortable: true,
  15088. dataIndex: 'ip'
  15089. },
  15090. {
  15091. header: gettext('Services'),
  15092. flex: 1,
  15093. width: 80,
  15094. sortable: true,
  15095. dataIndex: 'pmxcfs',
  15096. renderer: function(value, metaData, record) {
  15097. var list = [];
  15098. var data = record.data;
  15099. if (data) {
  15100. if (data.pmxcfs) {
  15101. list.push('PVECluster');
  15102. }
  15103. if (data.rgmanager) {
  15104. list.push('RGManager');
  15105. }
  15106.  
  15107. }
  15108. return list.join(', ');
  15109. }
  15110. }
  15111. ],
  15112. listeners: {
  15113. show: rstore.startUpdate,
  15114. hide: rstore.stopUpdate,
  15115. destroy: rstore.stopUpdate
  15116. }
  15117. });
  15118.  
  15119. me.callParent();
  15120. }
  15121. }, function() {
  15122.  
  15123. Ext.define('pve-dc-nodes', {
  15124. extend: 'Ext.data.Model',
  15125. fields: [ 'id', 'type', 'name', 'state', 'nodeid', 'ip',
  15126. 'pmxcfs', 'rgmanager', 'estranged', 'level' ],
  15127. idProperty: 'id'
  15128. });
  15129.  
  15130. });
  15131.  
  15132. Ext.define('PVE.dc.HAServiceView', {
  15133. extend: 'Ext.grid.GridPanel',
  15134.  
  15135. alias: ['widget.pveHaServiceView'],
  15136.  
  15137. initComponent : function() {
  15138. var me = this;
  15139.  
  15140. var rstore = Ext.create('PVE.data.UpdateStore', {
  15141. interval: 3000,
  15142. storeid: 'pve-ha-services',
  15143. model: 'pve-ha-services',
  15144. proxy: {
  15145. type: 'pve',
  15146. url: "/api2/json/cluster/status"
  15147. },
  15148. filters: {
  15149. property: 'type',
  15150. value : 'group'
  15151. }
  15152. });
  15153.  
  15154. var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
  15155.  
  15156. var noClusterText = gettext("Standalone node - no cluster defined");
  15157. var status = Ext.create('Ext.Component', {
  15158. padding: 2,
  15159. html: '&nbsp;',
  15160. dock: 'bottom'
  15161. });
  15162.  
  15163. Ext.apply(me, {
  15164. store: store,
  15165. stateful: false,
  15166. //tbar: [ 'start', 'stop' ],
  15167. bbar: [ status ],
  15168. columns: [
  15169. {
  15170. header: gettext('Name'),
  15171. flex: 1,
  15172. sortable: true,
  15173. dataIndex: 'name'
  15174. },
  15175. {
  15176. header: gettext('Owner'),
  15177. flex: 1,
  15178. sortable: true,
  15179. dataIndex: 'owner'
  15180. },
  15181. {
  15182. header: gettext('Status'),
  15183. width: 80,
  15184. sortable: true,
  15185. dataIndex: 'state_str'
  15186. },
  15187. {
  15188. header: gettext('Restarts'),
  15189. width: 80,
  15190. sortable: true,
  15191. dataIndex: 'restarts'
  15192. },
  15193. {
  15194. header: gettext('Last transition'),
  15195. width: 200,
  15196. sortable: true,
  15197. dataIndex: 'last_transition'
  15198. },
  15199. {
  15200. header: gettext('Last owner'),
  15201. flex: 1,
  15202. sortable: true,
  15203. dataIndex: 'last_owner'
  15204. }
  15205. ],
  15206. listeners: {
  15207. show: rstore.startUpdate,
  15208. hide: rstore.stopUpdate,
  15209. destroy: rstore.stopUpdate
  15210. }
  15211. });
  15212.  
  15213. me.callParent();
  15214.  
  15215. rstore.on('load', function(s, records, success) {
  15216. if (!success) {
  15217. return;
  15218. }
  15219.  
  15220. var cluster_rec = rstore.getById('cluster');
  15221. var quorum_rec = rstore.getById('quorum');
  15222.  
  15223. if (!(cluster_rec && quorum_rec)) {
  15224. status.update(noClusterText);
  15225. return;
  15226. }
  15227.  
  15228. var cluster_raw = cluster_rec.raw;
  15229. var quorum_raw = quorum_rec.raw;
  15230. if (!(cluster_raw && quorum_raw)) {
  15231. status.update(noClusterText);
  15232. return;
  15233. }
  15234.  
  15235. status.update("Quorate: " + PVE.Utils.format_boolean(quorum_raw.quorate));
  15236. });
  15237.  
  15238. }
  15239. }, function() {
  15240.  
  15241. Ext.define('pve-ha-services', {
  15242. extend: 'Ext.data.Model',
  15243. fields: [ 'id', 'type', 'name', 'owner', 'last_owner', 'state_str', 'restarts',
  15244. { name: 'last_transition', type: 'date', dateFormat: 'timestamp'}
  15245. ],
  15246. idProperty: 'id'
  15247. });
  15248.  
  15249. });
  15250.  
  15251.  
  15252. Ext.define('PVE.dc.Summary', {
  15253. extend: 'Ext.panel.Panel',
  15254.  
  15255. alias: ['widget.pveDcSummary'],
  15256.  
  15257. initComponent: function() {
  15258. var me = this;
  15259.  
  15260. var hagrid = Ext.create('PVE.dc.HAServiceView', {
  15261. title: gettext('HA Service Status'),
  15262. region: 'south',
  15263. border: false,
  15264. split: true,
  15265. flex: 1
  15266. });
  15267.  
  15268. var nodegrid = Ext.create('PVE.dc.NodeView', {
  15269. title: gettext('Nodes'),
  15270. border: false,
  15271. region: 'center',
  15272. flex: 3
  15273. });
  15274.  
  15275. Ext.apply(me, {
  15276. layout: 'border',
  15277. items: [ nodegrid, hagrid ],
  15278. listeners: {
  15279. show: function() {
  15280. hagrid.fireEvent('show', hagrid);
  15281. nodegrid.fireEvent('show', hagrid);
  15282. },
  15283. hide: function() {
  15284. hagrid.fireEvent('hide', hagrid);
  15285. nodegrid.fireEvent('hide', hagrid);
  15286. }
  15287. }
  15288. });
  15289.  
  15290. me.callParent();
  15291. }
  15292. });
  15293. Ext.define('PVE.dc.HttpProxyEdit', {
  15294. extend: 'PVE.window.Edit',
  15295.  
  15296. initComponent : function() {
  15297. var me = this;
  15298.  
  15299. Ext.applyIf(me, {
  15300. subject: 'HTTP proxy',
  15301. items: {
  15302. xtype: 'pvetextfield',
  15303. name: 'http_proxy',
  15304. vtype: 'HttpProxy',
  15305. emptyText: gettext('Do not use any proxy'),
  15306. deleteEmpty: true,
  15307. value: '',
  15308. fieldLabel: 'HTTP proxy'
  15309. }
  15310. });
  15311.  
  15312. me.callParent();
  15313.  
  15314. me.load();
  15315. }
  15316. });
  15317.  
  15318. Ext.define('PVE.dc.KeyboardEdit', {
  15319. extend: 'PVE.window.Edit',
  15320.  
  15321. initComponent : function() {
  15322. var me = this;
  15323.  
  15324. Ext.applyIf(me, {
  15325. subject: gettext('Keyboard Layout'),
  15326. items: {
  15327. xtype: 'VNCKeyboardSelector',
  15328. name: 'keyboard',
  15329. value: '',
  15330. fieldLabel: gettext('Keyboard Layout')
  15331. }
  15332. });
  15333.  
  15334. me.callParent();
  15335.  
  15336. me.load();
  15337. }
  15338. });
  15339.  
  15340. Ext.define('PVE.dc.OptionView', {
  15341. extend: 'PVE.grid.ObjectGrid',
  15342. alias: ['widget.pveDcOptionView'],
  15343.  
  15344. noProxyText: gettext('Do not use any proxy'),
  15345.  
  15346. initComponent : function() {
  15347. var me = this;
  15348.  
  15349. var reload = function() {
  15350. me.rstore.load();
  15351. };
  15352.  
  15353. var rows = {
  15354. keyboard: {
  15355. header: gettext('Keyboard Layout'),
  15356. editor: 'PVE.dc.KeyboardEdit',
  15357. renderer: PVE.Utils.render_kvm_language,
  15358. required: true
  15359. },
  15360. http_proxy: {
  15361. header: 'HTTP proxy',
  15362. editor: 'PVE.dc.HttpProxyEdit',
  15363. required: true,
  15364. renderer: function(value) {
  15365. if (!value) {
  15366. return me.noProxyText;
  15367. }
  15368. return value;
  15369. }
  15370. }
  15371. };
  15372.  
  15373. var sm = Ext.create('Ext.selection.RowModel', {});
  15374.  
  15375. var run_editor = function() {
  15376. var rec = sm.getSelection()[0];
  15377. if (!rec) {
  15378. return;
  15379. }
  15380.  
  15381. var rowdef = rows[rec.data.key];
  15382. if (!rowdef.editor) {
  15383. return;
  15384. }
  15385.  
  15386. var win = Ext.create(rowdef.editor, {
  15387. url: "/api2/extjs/cluster/options",
  15388. confid: rec.data.key
  15389. });
  15390. win.show();
  15391. win.on('destroy', reload);
  15392. };
  15393.  
  15394. var edit_btn = new PVE.button.Button({
  15395. text: gettext('Edit'),
  15396. disabled: true,
  15397. selModel: sm,
  15398. handler: run_editor
  15399. });
  15400.  
  15401. Ext.applyIf(me, {
  15402. url: "/api2/json/cluster/options",
  15403. cwidth1: 130,
  15404. interval: 1000,
  15405. selModel: sm,
  15406. tbar: [ edit_btn ],
  15407. rows: rows,
  15408. listeners: {
  15409. itemdblclick: run_editor
  15410. }
  15411. });
  15412.  
  15413. me.callParent();
  15414.  
  15415. me.on('show', reload);
  15416. }
  15417. });
  15418. Ext.define('PVE.dc.StorageView', {
  15419. extend: 'Ext.grid.GridPanel',
  15420.  
  15421. alias: ['widget.pveStorageView'],
  15422.  
  15423. initComponent : function() {
  15424. var me = this;
  15425.  
  15426. var store = new Ext.data.Store({
  15427. model: 'pve-storage',
  15428. proxy: {
  15429. type: 'pve',
  15430. url: "/api2/json/storage"
  15431. },
  15432. sorters: {
  15433. property: 'storage',
  15434. order: 'DESC'
  15435. }
  15436. });
  15437.  
  15438. var reload = function() {
  15439. store.load();
  15440. };
  15441.  
  15442. var sm = Ext.create('Ext.selection.RowModel', {});
  15443.  
  15444. var run_editor = function() {
  15445. var rec = sm.getSelection()[0];
  15446. if (!rec) {
  15447. return;
  15448. }
  15449. var type = rec.data.type;
  15450.  
  15451. var editor;
  15452. if (type === 'dir') {
  15453. editor = 'PVE.storage.DirEdit';
  15454. } else if (type === 'nfs') {
  15455. editor = 'PVE.storage.NFSEdit';
  15456. } else if (type === 'lvm') {
  15457. editor = 'PVE.storage.LVMEdit';
  15458. } else if (type === 'iscsi') {
  15459. editor = 'PVE.storage.IScsiEdit';
  15460. } else {
  15461. return;
  15462. }
  15463. var win = Ext.create(editor, {
  15464. storageId: rec.data.storage
  15465. });
  15466.  
  15467. win.show();
  15468. win.on('destroy', reload);
  15469. };
  15470.  
  15471. var edit_btn = new PVE.button.Button({
  15472. text: gettext('Edit'),
  15473. disabled: true,
  15474. selModel: sm,
  15475. handler: run_editor
  15476. });
  15477.  
  15478. var remove_btn = new PVE.button.Button({
  15479. text: gettext('Remove'),
  15480. disabled: true,
  15481. selModel: sm,
  15482. confirmMsg: function (rec) {
  15483. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  15484. "'" + rec.data.storage + "'");
  15485. },
  15486. handler: function(btn, event, rec) {
  15487. PVE.Utils.API2Request({
  15488. url: '/storage/' + rec.data.storage,
  15489. method: 'DELETE',
  15490. waitMsgTarget: me,
  15491. callback: function() {
  15492. reload();
  15493. },
  15494. failure: function (response, opts) {
  15495. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  15496. }
  15497. });
  15498. }
  15499. });
  15500.  
  15501. Ext.apply(me, {
  15502. store: store,
  15503. selModel: sm,
  15504. stateful: false,
  15505. viewConfig: {
  15506. trackOver: false
  15507. },
  15508. tbar: [
  15509. {
  15510. text: gettext('Add'),
  15511. menu: new Ext.menu.Menu({
  15512. items: [
  15513. {
  15514. text: 'Directory',
  15515. iconCls: 'pve-itype-icon-itype',
  15516. handler: function() {
  15517. var win = Ext.create('PVE.storage.DirEdit', {});
  15518. win.on('destroy', reload);
  15519. win.show();
  15520. }
  15521.  
  15522. },
  15523. {
  15524. text: 'LVM group',
  15525. handler: function() {
  15526. var win = Ext.create('PVE.storage.LVMEdit', {});
  15527. win.on('destroy', reload);
  15528. win.show();
  15529. }
  15530. },
  15531. {
  15532. text: 'NFS share',
  15533. iconCls: 'pve-itype-icon-node',
  15534. handler: function() {
  15535. var win = Ext.create('PVE.storage.NFSEdit', {});
  15536. win.on('destroy', reload);
  15537. win.show();
  15538. }
  15539. },
  15540. {
  15541. text: 'iSCSI target',
  15542. iconCls: 'pve-itype-icon-node',
  15543. handler: function() {
  15544. var win = Ext.create('PVE.storage.IScsiEdit', {});
  15545. win.on('destroy', reload);
  15546. win.show();
  15547. }
  15548. }
  15549. ]
  15550. })
  15551. },
  15552. remove_btn,
  15553. edit_btn
  15554. ],
  15555. columns: [
  15556. {
  15557. header: 'ID',
  15558. width: 100,
  15559. sortable: true,
  15560. dataIndex: 'storage'
  15561. },
  15562. {
  15563. header: gettext('Type'),
  15564. width: 60,
  15565. sortable: true,
  15566. dataIndex: 'type',
  15567. renderer: PVE.Utils.format_storage_type
  15568. },
  15569. {
  15570. header: gettext('Content'),
  15571. width: 150,
  15572. sortable: true,
  15573. dataIndex: 'content',
  15574. renderer: PVE.Utils.format_content_types
  15575. },
  15576. {
  15577. header: 'Path/Target',
  15578. flex: 1,
  15579. sortable: true,
  15580. dataIndex: 'path',
  15581. renderer: function(value, metaData, record) {
  15582. if (record.data.target) {
  15583. return record.data.target;
  15584. }
  15585. return value;
  15586. }
  15587. },
  15588. {
  15589. header: gettext('Shared'),
  15590. width: 80,
  15591. sortable: true,
  15592. dataIndex: 'shared',
  15593. renderer: PVE.Utils.format_boolean
  15594. },
  15595. {
  15596. header: gettext('Enable'),
  15597. width: 80,
  15598. sortable: true,
  15599. dataIndex: 'disable',
  15600. renderer: PVE.Utils.format_neg_boolean
  15601. }
  15602. ],
  15603. listeners: {
  15604. show: reload,
  15605. itemdblclick: run_editor
  15606. }
  15607. });
  15608.  
  15609. me.callParent();
  15610. }
  15611. }, function() {
  15612.  
  15613. Ext.define('pve-storage', {
  15614. extend: 'Ext.data.Model',
  15615. fields: [
  15616. 'path', 'type', 'content', 'server', 'portal', 'target', 'export', 'storage',
  15617. { name: 'shared', type: 'boolean'},
  15618. { name: 'disable', type: 'boolean'}
  15619. ],
  15620. idProperty: 'storage'
  15621. });
  15622.  
  15623. });Ext.define('PVE.dc.UserEdit', {
  15624. extend: 'PVE.window.Edit',
  15625. alias: ['widget.pveDcUserEdit'],
  15626.  
  15627. isAdd: true,
  15628.  
  15629. initComponent : function() {
  15630. var me = this;
  15631.  
  15632. me.create = !me.userid;
  15633.  
  15634. var url;
  15635. var method;
  15636. var realm;
  15637.  
  15638. if (me.create) {
  15639. url = '/api2/extjs/access/users';
  15640. method = 'POST';
  15641. } else {
  15642. url = '/api2/extjs/access/users/' + me.userid;
  15643. method = 'PUT';
  15644. }
  15645.  
  15646. var verifypw;
  15647. var pwfield;
  15648.  
  15649. var validate_pw = function() {
  15650. if (verifypw.getValue() !== pwfield.getValue()) {
  15651. return gettext("Passwords does not match");
  15652. }
  15653. return true;
  15654. };
  15655.  
  15656. verifypw = Ext.createWidget('textfield', {
  15657. inputType: 'password',
  15658. fieldLabel: gettext('Confirm password'),
  15659. name: 'verifypassword',
  15660. submitValue: false,
  15661. disabled: true,
  15662. hidden: true,
  15663. validator: validate_pw
  15664. });
  15665.  
  15666. pwfield = Ext.createWidget('textfield', {
  15667. inputType: 'password',
  15668. fieldLabel: gettext('Password'),
  15669. minLength: 5,
  15670. name: 'password',
  15671. disabled: true,
  15672. hidden: true,
  15673. validator: validate_pw
  15674. });
  15675.  
  15676. var update_passwd_field = function(realm) {
  15677. if (realm === 'pve') {
  15678. pwfield.setVisible(true);
  15679. pwfield.setDisabled(false);
  15680. verifypw.setVisible(true);
  15681. verifypw.setDisabled(false);
  15682. } else {
  15683. pwfield.setVisible(false);
  15684. pwfield.setDisabled(true);
  15685. verifypw.setVisible(false);
  15686. verifypw.setDisabled(true);
  15687. }
  15688.  
  15689. };
  15690.  
  15691. var column1 = [
  15692. {
  15693. xtype: me.create ? 'textfield' : 'displayfield',
  15694. height: 22, // hack: set same height as text fields
  15695. name: 'userid',
  15696. fieldLabel: gettext('User name'),
  15697. value: me.userid,
  15698. allowBlank: false,
  15699. submitValue: me.create ? true : false
  15700. },
  15701. pwfield, verifypw,
  15702. {
  15703. xtype: 'pveGroupSelector',
  15704. name: 'groups',
  15705. multiSelect: true,
  15706. allowBlank: true,
  15707. fieldLabel: gettext('Group')
  15708. },
  15709. {
  15710. xtype: 'datefield',
  15711. name: 'expire',
  15712. emptyText: 'never',
  15713. format: 'Y-m-d',
  15714. submitFormat: 'U',
  15715. fieldLabel: gettext('Expire')
  15716. },
  15717. {
  15718. xtype: 'pvecheckbox',
  15719. fieldLabel: gettext('Enabled'),
  15720. name: 'enable',
  15721. uncheckedValue: 0,
  15722. defaultValue: 1,
  15723. checked: true
  15724. }
  15725. ];
  15726.  
  15727. var column2 = [
  15728. {
  15729. xtype: 'textfield',
  15730. name: 'firstname',
  15731. fieldLabel: gettext('First Name')
  15732. },
  15733. {
  15734. xtype: 'textfield',
  15735. name: 'lastname',
  15736. fieldLabel: gettext('Last Name')
  15737. },
  15738. {
  15739. xtype: 'textfield',
  15740. name: 'email',
  15741. fieldLabel: 'E-Mail',
  15742. vtype: 'email'
  15743. },
  15744. {
  15745. xtype: 'textfield',
  15746. name: 'comment',
  15747. fieldLabel: gettext('Comment')
  15748. }
  15749. ];
  15750.  
  15751. if (me.create) {
  15752. column1.splice(1,0,{
  15753. xtype: 'pveRealmComboBox',
  15754. name: 'realm',
  15755. fieldLabel: gettext('Realm'),
  15756. allowBlank: false,
  15757. matchFieldWidth: false,
  15758. listConfig: { width: 300 },
  15759. listeners: {
  15760. change: function(combo, newValue){
  15761. realm = newValue;
  15762. update_passwd_field(realm);
  15763. }
  15764. },
  15765. submitValue: false
  15766. });
  15767. }
  15768.  
  15769. var ipanel = Ext.create('PVE.panel.InputPanel', {
  15770. column1: column1,
  15771. column2: column2,
  15772. onGetValues: function(values) {
  15773. // hack: ExtJS datefield does not submit 0, so we need to set that
  15774. if (!values.expire) {
  15775. values.expire = 0;
  15776. }
  15777.  
  15778. if (realm) {
  15779. values.userid = values.userid + '@' + realm;
  15780. }
  15781.  
  15782. if (!values.password) {
  15783. delete values.password;
  15784. }
  15785.  
  15786. return values;
  15787. }
  15788. });
  15789.  
  15790. Ext.applyIf(me, {
  15791. subject: gettext('User'),
  15792. url: url,
  15793. method: method,
  15794. items: [ ipanel ]
  15795. });
  15796.  
  15797. me.callParent();
  15798.  
  15799. if (!me.create) {
  15800. me.load({
  15801. success: function(response, options) {
  15802. var data = response.result.data;
  15803. if (Ext.isDefined(data.expire)) {
  15804. if (data.expire) {
  15805. data.expire = new Date(data.expire * 1000);
  15806. } else {
  15807. // display 'never' instead of '1970-01-01'
  15808. data.expire = null;
  15809. }
  15810. }
  15811. me.setValues(data);
  15812. }
  15813. });
  15814. }
  15815. }
  15816. });
  15817. Ext.define('PVE.window.PasswordEdit', {
  15818. extend: 'PVE.window.Edit',
  15819.  
  15820. initComponent : function() {
  15821. var me = this;
  15822.  
  15823. if (!me.userid) {
  15824. throw "no userid specified";
  15825. }
  15826.  
  15827. var verifypw;
  15828. var pwfield;
  15829.  
  15830. var validate_pw = function() {
  15831. if (verifypw.getValue() !== pwfield.getValue()) {
  15832. return gettext("Passwords does not match");
  15833. }
  15834. return true;
  15835. };
  15836.  
  15837. verifypw = Ext.createWidget('textfield', {
  15838. inputType: 'password',
  15839. fieldLabel: gettext('Confirm password'),
  15840. name: 'verifypassword',
  15841. submitValue: false,
  15842. validator: validate_pw
  15843. });
  15844.  
  15845. pwfield = Ext.createWidget('textfield', {
  15846. inputType: 'password',
  15847. fieldLabel: gettext('Password'),
  15848. minLength: 5,
  15849. name: 'password',
  15850. validator: validate_pw
  15851. });
  15852.  
  15853. Ext.apply(me, {
  15854. subject: gettext('Password'),
  15855. url: '/api2/extjs/access/password',
  15856. items: [
  15857. pwfield, verifypw,
  15858. {
  15859. xtype: 'hiddenfield',
  15860. name: 'userid',
  15861. value: me.userid
  15862. }
  15863. ]
  15864. });
  15865.  
  15866. me.callParent();
  15867. }
  15868. });
  15869.  
  15870. Ext.define('PVE.dc.UserView', {
  15871. extend: 'Ext.grid.GridPanel',
  15872.  
  15873. alias: ['widget.pveUserView'],
  15874.  
  15875. initComponent : function() {
  15876. var me = this;
  15877.  
  15878. var caps = Ext.state.Manager.get('GuiCap');
  15879.  
  15880. var store = new Ext.data.Store({
  15881. id: "users",
  15882. model: 'pve-users',
  15883. sorters: {
  15884. property: 'userid',
  15885. order: 'DESC'
  15886. }
  15887. });
  15888.  
  15889. var reload = function() {
  15890. store.load();
  15891. };
  15892.  
  15893. var sm = Ext.create('Ext.selection.RowModel', {});
  15894.  
  15895. var remove_btn = new PVE.button.Button({
  15896. text: gettext('Remove'),
  15897. disabled: true,
  15898. selModel: sm,
  15899. enableFn: function(rec) {
  15900. if (!caps.access['User.Modify']) {
  15901. return false;
  15902. }
  15903. return rec.data.userid !== 'root@pam';
  15904. },
  15905. confirmMsg: function (rec) {
  15906. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  15907. "'" + rec.data.userid + "'");
  15908. },
  15909. handler: function(btn, event, rec) {
  15910. var userid = rec.data.userid;
  15911.  
  15912. PVE.Utils.API2Request({
  15913. url: '/access/users/' + userid,
  15914. method: 'DELETE',
  15915. waitMsgTarget: me,
  15916. callback: function() {
  15917. reload();
  15918. },
  15919. failure: function (response, opts) {
  15920. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  15921. }
  15922. });
  15923. }
  15924. });
  15925.  
  15926. var run_editor = function() {
  15927. var rec = sm.getSelection()[0];
  15928. if (!rec || !caps.access['User.Modify']) {
  15929. return;
  15930. }
  15931.  
  15932. var win = Ext.create('PVE.dc.UserEdit',{
  15933. userid: rec.data.userid
  15934. });
  15935. win.on('destroy', reload);
  15936. win.show();
  15937. };
  15938.  
  15939. var edit_btn = new PVE.button.Button({
  15940. text: gettext('Edit'),
  15941. disabled: true,
  15942. enableFn: function(rec) {
  15943. return !!caps.access['User.Modify'];
  15944. },
  15945. selModel: sm,
  15946. handler: run_editor
  15947. });
  15948.  
  15949. var pwchange_btn = new PVE.button.Button({
  15950. text: gettext('Password'),
  15951. disabled: true,
  15952. selModel: sm,
  15953. handler: function(btn, event, rec) {
  15954. var win = Ext.create('PVE.window.PasswordEdit',{
  15955. userid: rec.data.userid
  15956. });
  15957. win.on('destroy', reload);
  15958. win.show();
  15959. }
  15960. });
  15961.  
  15962. var tbar = [
  15963. {
  15964. text: gettext('Add'),
  15965. disabled: !caps.access['User.Modify'],
  15966. handler: function() {
  15967. var win = Ext.create('PVE.dc.UserEdit',{
  15968. });
  15969. win.on('destroy', reload);
  15970. win.show();
  15971. }
  15972. },
  15973. edit_btn, remove_btn, pwchange_btn
  15974. ];
  15975.  
  15976. var render_full_name = function(firstname, metaData, record) {
  15977.  
  15978. var first = firstname || '';
  15979. var last = record.data.lastname || '';
  15980. return first + " " + last;
  15981. };
  15982.  
  15983. var render_username = function(userid) {
  15984. return userid.match(/^(.+)(@[^@]+)$/)[1];
  15985. };
  15986.  
  15987. var render_realm = function(userid) {
  15988. return userid.match(/@([^@]+)$/)[1];
  15989. };
  15990.  
  15991. Ext.apply(me, {
  15992. store: store,
  15993. selModel: sm,
  15994. stateful: false,
  15995. tbar: tbar,
  15996. viewConfig: {
  15997. trackOver: false
  15998. },
  15999. columns: [
  16000. {
  16001. header: gettext('User name'),
  16002. width: 200,
  16003. sortable: true,
  16004. renderer: render_username,
  16005. dataIndex: 'userid'
  16006. },
  16007. {
  16008. header: gettext('Realm'),
  16009. width: 100,
  16010. sortable: true,
  16011. renderer: render_realm,
  16012. dataIndex: 'userid'
  16013. },
  16014. {
  16015. header: gettext('Enabled'),
  16016. width: 80,
  16017. sortable: true,
  16018. renderer: PVE.Utils.format_boolean,
  16019. dataIndex: 'enable'
  16020. },
  16021. {
  16022. header: gettext('Expire'),
  16023. width: 80,
  16024. sortable: true,
  16025. renderer: PVE.Utils.format_expire,
  16026. dataIndex: 'expire'
  16027. },
  16028. {
  16029. header: gettext('Name'),
  16030. width: 150,
  16031. sortable: true,
  16032. renderer: render_full_name,
  16033. dataIndex: 'firstname'
  16034. },
  16035. {
  16036. id: 'comment',
  16037. header: gettext('Comment'),
  16038. sortable: false,
  16039. dataIndex: 'comment',
  16040. flex: 1
  16041. }
  16042. ],
  16043. listeners: {
  16044. show: reload,
  16045. itemdblclick: run_editor
  16046. }
  16047. });
  16048.  
  16049. me.callParent();
  16050. }
  16051. });
  16052. Ext.define('PVE.dc.PoolView', {
  16053. extend: 'Ext.grid.GridPanel',
  16054.  
  16055. alias: ['widget.pvePoolView'],
  16056.  
  16057. initComponent : function() {
  16058. var me = this;
  16059.  
  16060. var store = new Ext.data.Store({
  16061. model: 'pve-pools',
  16062. sorters: {
  16063. property: 'poolid',
  16064. order: 'DESC'
  16065. }
  16066. });
  16067.  
  16068. var reload = function() {
  16069. store.load();
  16070. };
  16071.  
  16072. var sm = Ext.create('Ext.selection.RowModel', {});
  16073.  
  16074. var remove_btn = new PVE.button.Button({
  16075. text: gettext('Remove'),
  16076. disabled: true,
  16077. selModel: sm,
  16078. confirmMsg: function (rec) {
  16079. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  16080. "'" + rec.data.poolid + "'");
  16081. },
  16082. handler: function(btn, event, rec) {
  16083. PVE.Utils.API2Request({
  16084. url: '/pools/' + rec.data.poolid,
  16085. method: 'DELETE',
  16086. waitMsgTarget: me,
  16087. callback: function() {
  16088. reload();
  16089. },
  16090. failure: function (response, opts) {
  16091. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  16092. }
  16093. });
  16094. }
  16095. });
  16096.  
  16097. var run_editor = function() {
  16098. var rec = sm.getSelection()[0];
  16099. if (!rec) {
  16100. return;
  16101. }
  16102.  
  16103. var win = Ext.create('PVE.dc.PoolEdit',{
  16104. poolid: rec.data.poolid
  16105. });
  16106. win.on('destroy', reload);
  16107. win.show();
  16108. };
  16109.  
  16110. var edit_btn = new PVE.button.Button({
  16111. text: gettext('Edit'),
  16112. disabled: true,
  16113. selModel: sm,
  16114. handler: run_editor
  16115. });
  16116.  
  16117. var tbar = [
  16118. {
  16119. text: gettext('Create'),
  16120. handler: function() {
  16121. var win = Ext.create('PVE.dc.PoolEdit', {});
  16122. win.on('destroy', reload);
  16123. win.show();
  16124. }
  16125. },
  16126. edit_btn, remove_btn
  16127. ];
  16128.  
  16129. PVE.Utils.monStoreErrors(me, store);
  16130.  
  16131. Ext.apply(me, {
  16132. store: store,
  16133. selModel: sm,
  16134. stateful: false,
  16135. tbar: tbar,
  16136. viewConfig: {
  16137. trackOver: false
  16138. },
  16139. columns: [
  16140. {
  16141. header: gettext('Name'),
  16142. width: 200,
  16143. sortable: true,
  16144. dataIndex: 'poolid'
  16145. },
  16146. {
  16147. header: gettext('Comment'),
  16148. sortable: false,
  16149. dataIndex: 'comment',
  16150. flex: 1
  16151. }
  16152. ],
  16153. listeners: {
  16154. show: reload,
  16155. itemdblclick: run_editor
  16156. }
  16157. });
  16158.  
  16159. me.callParent();
  16160. }
  16161. });
  16162. Ext.define('PVE.dc.PoolEdit', {
  16163. extend: 'PVE.window.Edit',
  16164. alias: ['widget.pveDcPoolEdit'],
  16165.  
  16166. initComponent : function() {
  16167. var me = this;
  16168.  
  16169. me.create = !me.poolid;
  16170.  
  16171. var url;
  16172. var method;
  16173.  
  16174. if (me.create) {
  16175. url = '/api2/extjs/pools';
  16176. method = 'POST';
  16177. } else {
  16178. url = '/api2/extjs/pools/' + me.poolid;
  16179. method = 'PUT';
  16180. }
  16181.  
  16182. Ext.applyIf(me, {
  16183. subject: gettext('Pool'),
  16184. url: url,
  16185. method: method,
  16186. items: [
  16187. {
  16188. xtype: me.create ? 'pvetextfield' : 'displayfield',
  16189. fieldLabel: gettext('Name'),
  16190. name: 'poolid',
  16191. value: me.poolid,
  16192. allowBlank: false
  16193. },
  16194. {
  16195. xtype: 'textfield',
  16196. fieldLabel: gettext('Comment'),
  16197. name: 'comment',
  16198. allowBlank: true
  16199. }
  16200. ]
  16201. });
  16202.  
  16203. me.callParent();
  16204.  
  16205. if (!me.create) {
  16206. me.load();
  16207. }
  16208. }
  16209. });
  16210. Ext.define('PVE.dc.GroupView', {
  16211. extend: 'Ext.grid.GridPanel',
  16212.  
  16213. alias: ['widget.pveGroupView'],
  16214.  
  16215. initComponent : function() {
  16216. var me = this;
  16217.  
  16218. var store = new Ext.data.Store({
  16219. model: 'pve-groups',
  16220. sorters: {
  16221. property: 'groupid',
  16222. order: 'DESC'
  16223. }
  16224. });
  16225.  
  16226. var reload = function() {
  16227. store.load();
  16228. };
  16229.  
  16230. var sm = Ext.create('Ext.selection.RowModel', {});
  16231.  
  16232. var remove_btn = new PVE.button.Button({
  16233. text: gettext('Remove'),
  16234. disabled: true,
  16235. selModel: sm,
  16236. confirmMsg: function (rec) {
  16237. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  16238. "'" + rec.data.groupid + "'");
  16239. },
  16240. handler: function(btn, event, rec) {
  16241. PVE.Utils.API2Request({
  16242. url: '/access/groups/' + rec.data.groupid,
  16243. method: 'DELETE',
  16244. waitMsgTarget: me,
  16245. callback: function() {
  16246. reload();
  16247. },
  16248. failure: function (response, opts) {
  16249. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  16250. }
  16251. });
  16252. }
  16253. });
  16254.  
  16255. var run_editor = function() {
  16256. var rec = sm.getSelection()[0];
  16257. if (!rec) {
  16258. return;
  16259. }
  16260.  
  16261. var win = Ext.create('PVE.dc.GroupEdit',{
  16262. groupid: rec.data.groupid
  16263. });
  16264. win.on('destroy', reload);
  16265. win.show();
  16266. };
  16267.  
  16268. var edit_btn = new PVE.button.Button({
  16269. text: gettext('Edit'),
  16270. disabled: true,
  16271. selModel: sm,
  16272. handler: run_editor
  16273. });
  16274.  
  16275. var tbar = [
  16276. {
  16277. text: gettext('Create'),
  16278. handler: function() {
  16279. var win = Ext.create('PVE.dc.GroupEdit', {});
  16280. win.on('destroy', reload);
  16281. win.show();
  16282. }
  16283. },
  16284. edit_btn, remove_btn
  16285. ];
  16286.  
  16287. PVE.Utils.monStoreErrors(me, store);
  16288.  
  16289. Ext.apply(me, {
  16290. store: store,
  16291. selModel: sm,
  16292. stateful: false,
  16293. tbar: tbar,
  16294. viewConfig: {
  16295. trackOver: false
  16296. },
  16297. columns: [
  16298. {
  16299. header: gettext('Name'),
  16300. width: 200,
  16301. sortable: true,
  16302. dataIndex: 'groupid'
  16303. },
  16304. {
  16305. header: gettext('Comment'),
  16306. sortable: false,
  16307. dataIndex: 'comment',
  16308. flex: 1
  16309. }
  16310. ],
  16311. listeners: {
  16312. show: reload,
  16313. itemdblclick: run_editor
  16314. }
  16315. });
  16316.  
  16317. me.callParent();
  16318. }
  16319. });
  16320. Ext.define('PVE.dc.GroupEdit', {
  16321. extend: 'PVE.window.Edit',
  16322. alias: ['widget.pveDcGroupEdit'],
  16323.  
  16324. initComponent : function() {
  16325. var me = this;
  16326.  
  16327. me.create = !me.groupid;
  16328.  
  16329. var url;
  16330. var method;
  16331.  
  16332. if (me.create) {
  16333. url = '/api2/extjs/access/groups';
  16334. method = 'POST';
  16335. } else {
  16336. url = '/api2/extjs/access/groups/' + me.groupid;
  16337. method = 'PUT';
  16338. }
  16339.  
  16340. Ext.applyIf(me, {
  16341. subject: gettext('Group'),
  16342. url: url,
  16343. method: method,
  16344. items: [
  16345. {
  16346. xtype: me.create ? 'pvetextfield' : 'displayfield',
  16347. fieldLabel: gettext('Name'),
  16348. name: 'groupid',
  16349. value: me.groupid,
  16350. allowBlank: false
  16351. },
  16352. {
  16353. xtype: 'textfield',
  16354. fieldLabel: gettext('Comment'),
  16355. name: 'comment',
  16356. allowBlank: true
  16357. }
  16358. ]
  16359. });
  16360.  
  16361. me.callParent();
  16362.  
  16363. if (!me.create) {
  16364. me.load();
  16365. }
  16366. }
  16367. });
  16368. Ext.define('PVE.dc.RoleView', {
  16369. extend: 'Ext.grid.GridPanel',
  16370.  
  16371. alias: ['widget.pveRoleView'],
  16372.  
  16373. initComponent : function() {
  16374. var me = this;
  16375.  
  16376. var store = new Ext.data.Store({
  16377. model: 'pve-roles',
  16378. sorters: {
  16379. property: 'roleid',
  16380. order: 'DESC'
  16381. }
  16382. });
  16383.  
  16384. var render_privs = function(value, metaData) {
  16385.  
  16386. if (!value) {
  16387. return '-';
  16388. }
  16389.  
  16390. // allow word wrap
  16391. metaData.style = 'white-space:normal;';
  16392.  
  16393. return value.replace(/\,/g, ' ');
  16394. };
  16395.  
  16396. PVE.Utils.monStoreErrors(me, store);
  16397.  
  16398. Ext.apply(me, {
  16399. store: store,
  16400. stateful: false,
  16401.  
  16402. viewConfig: {
  16403. trackOver: false
  16404. },
  16405. columns: [
  16406. {
  16407. header: gettext('Name'),
  16408. width: 150,
  16409. sortable: true,
  16410. dataIndex: 'roleid'
  16411. },
  16412. {
  16413. id: 'privs',
  16414. header: gettext('Privileges'),
  16415. sortable: false,
  16416. renderer: render_privs,
  16417. dataIndex: 'privs',
  16418. flex: 1
  16419. }
  16420. ],
  16421. listeners: {
  16422. show: function() {
  16423. store.load();
  16424. }
  16425. }
  16426. });
  16427.  
  16428. me.callParent();
  16429. }
  16430. });Ext.define('PVE.dc.ACLAdd', {
  16431. extend: 'PVE.window.Edit',
  16432. alias: ['widget.pveACLAdd'],
  16433.  
  16434. initComponent : function() {
  16435. /*jslint confusion: true */
  16436. var me = this;
  16437.  
  16438. me.create = true;
  16439.  
  16440. var items = [
  16441. {
  16442. xtype: me.path ? 'hiddenfield' : 'textfield',
  16443. name: 'path',
  16444. value: me.path,
  16445. allowBlank: false,
  16446. fieldLabel: gettext('Path')
  16447. }
  16448. ];
  16449.  
  16450. if (me.aclType === 'group') {
  16451. me.subject = gettext("Group Permission");
  16452. items.push({
  16453. xtype: 'pveGroupSelector',
  16454. name: 'groups',
  16455. fieldLabel: gettext('Group')
  16456. });
  16457. } else if (me.aclType === 'user') {
  16458. me.subject = gettext("User Permission");
  16459. items.push({
  16460. xtype: 'pveUserSelector',
  16461. name: 'users',
  16462. fieldLabel: gettext('User')
  16463. });
  16464. } else {
  16465. throw "unknown ACL type";
  16466. }
  16467.  
  16468. items.push({
  16469. xtype: 'pveRoleSelector',
  16470. name: 'roles',
  16471. value: 'NoAccess',
  16472. fieldLabel: gettext('Role')
  16473. });
  16474.  
  16475. if (!me.path) {
  16476. items.push({
  16477. xtype: 'pvecheckbox',
  16478. name: 'propagate',
  16479. checked: true,
  16480. fieldLabel: gettext('Propagate')
  16481. });
  16482. }
  16483.  
  16484. var ipanel = Ext.create('PVE.panel.InputPanel', {
  16485. items: items
  16486. });
  16487.  
  16488. Ext.apply(me, {
  16489. url: '/access/acl',
  16490. method: 'PUT',
  16491. isAdd: true,
  16492. items: [ ipanel ]
  16493. });
  16494.  
  16495. me.callParent();
  16496. }
  16497. });
  16498.  
  16499. Ext.define('PVE.dc.ACLView', {
  16500. extend: 'Ext.grid.GridPanel',
  16501.  
  16502. alias: ['widget.pveACLView'],
  16503.  
  16504. // use fixed path
  16505. path: undefined,
  16506.  
  16507. initComponent : function() {
  16508. var me = this;
  16509.  
  16510. var store = new Ext.data.Store({
  16511. model: 'pve-acl',
  16512. proxy: {
  16513. type: 'pve',
  16514. url: "/api2/json/access/acl"
  16515. },
  16516. sorters: {
  16517. property: 'path',
  16518. order: 'DESC'
  16519. }
  16520. });
  16521.  
  16522. if (me.path) {
  16523. store.filters.add(new Ext.util.Filter({
  16524. filterFn: function(item) {
  16525. if (item.data.path === me.path) {
  16526. return true;
  16527. }
  16528. }
  16529. }));
  16530. }
  16531.  
  16532. var render_ugid = function(ugid, metaData, record) {
  16533. if (record.data.type == 'group') {
  16534. return '@' + ugid;
  16535. }
  16536.  
  16537. return ugid;
  16538. };
  16539.  
  16540. var columns = [
  16541. {
  16542. header: gettext('User') + '/' + gettext('Group'),
  16543. flex: 1,
  16544. sortable: true,
  16545. renderer: render_ugid,
  16546. dataIndex: 'ugid'
  16547. },
  16548. {
  16549. header: gettext('Role'),
  16550. flex: 1,
  16551. sortable: true,
  16552. dataIndex: 'roleid'
  16553. }
  16554. ];
  16555.  
  16556. if (!me.path) {
  16557. columns.unshift({
  16558. header: gettext('Path'),
  16559. flex: 1,
  16560. sortable: true,
  16561. dataIndex: 'path'
  16562. });
  16563. columns.push({
  16564. header: gettext('Propagate'),
  16565. width: 80,
  16566. sortable: true,
  16567. dataIndex: 'propagate'
  16568. });
  16569. }
  16570.  
  16571. var sm = Ext.create('Ext.selection.RowModel', {});
  16572.  
  16573. var reload = function() {
  16574. store.load();
  16575. };
  16576.  
  16577. var remove_btn = new PVE.button.Button({
  16578. text: gettext('Remove'),
  16579. disabled: true,
  16580. selModel: sm,
  16581. confirmMsg: gettext('Are you sure you want to remove this entry'),
  16582. handler: function(btn, event, rec) {
  16583. var params = {
  16584. 'delete': 1,
  16585. path: rec.data.path,
  16586. roles: rec.data.roleid
  16587. };
  16588. if (rec.data.type === 'group') {
  16589. params.groups = rec.data.ugid;
  16590. } else if (rec.data.type === 'user') {
  16591. params.users = rec.data.ugid;
  16592. } else {
  16593. throw 'unknown data type';
  16594. }
  16595.  
  16596. PVE.Utils.API2Request({
  16597. url: '/access/acl',
  16598. params: params,
  16599. method: 'PUT',
  16600. waitMsgTarget: me,
  16601. callback: function() {
  16602. reload();
  16603. },
  16604. failure: function (response, opts) {
  16605. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  16606. }
  16607. });
  16608. }
  16609. });
  16610.  
  16611. PVE.Utils.monStoreErrors(me, store);
  16612.  
  16613. Ext.apply(me, {
  16614. store: store,
  16615. selModel: sm,
  16616. stateful: false,
  16617. tbar: [
  16618. {
  16619. text: gettext('Add'),
  16620. menu: new Ext.menu.Menu({
  16621. items: [
  16622. {
  16623. text: gettext('Group Permission'),
  16624. handler: function() {
  16625. var win = Ext.create('PVE.dc.ACLAdd',{
  16626. aclType: 'group',
  16627. path: me.path
  16628. });
  16629. win.on('destroy', reload);
  16630. win.show();
  16631. }
  16632. },
  16633. {
  16634. text: gettext('User Permission'),
  16635. handler: function() {
  16636. var win = Ext.create('PVE.dc.ACLAdd',{
  16637. aclType: 'user',
  16638. path: me.path
  16639. });
  16640. win.on('destroy', reload);
  16641. win.show();
  16642. }
  16643. }
  16644. ]
  16645. })
  16646. },
  16647. remove_btn
  16648. ],
  16649. viewConfig: {
  16650. trackOver: false
  16651. },
  16652. columns: columns,
  16653. listeners: {
  16654. show: reload
  16655. }
  16656. });
  16657.  
  16658. me.callParent();
  16659. }
  16660. }, function() {
  16661.  
  16662. Ext.define('pve-acl', {
  16663. extend: 'Ext.data.Model',
  16664. fields: [
  16665. 'path', 'type', 'ugid', 'roleid',
  16666. {
  16667. name: 'propagate',
  16668. type: 'boolean'
  16669. }
  16670. ]
  16671. });
  16672.  
  16673. });Ext.define('PVE.dc.AuthView', {
  16674. extend: 'Ext.grid.GridPanel',
  16675.  
  16676. alias: ['widget.pveAuthView'],
  16677.  
  16678. initComponent : function() {
  16679. var me = this;
  16680.  
  16681. var store = new Ext.data.Store({
  16682. model: 'pve-domains',
  16683. sorters: {
  16684. property: 'realm',
  16685. order: 'DESC'
  16686. }
  16687. });
  16688.  
  16689. var reload = function() {
  16690. store.load();
  16691. };
  16692.  
  16693. var sm = Ext.create('Ext.selection.RowModel', {});
  16694.  
  16695. var run_editor = function() {
  16696. var rec = sm.getSelection()[0];
  16697. if (!rec) {
  16698. return;
  16699. }
  16700.  
  16701. if (rec.data.type === 'pve' || rec.data.type === 'pam') {
  16702. return;
  16703. }
  16704.  
  16705. var win = Ext.create('PVE.dc.AuthEdit',{
  16706. realm: rec.data.realm,
  16707. authType: rec.data.type
  16708. });
  16709. win.on('destroy', reload);
  16710. win.show();
  16711. };
  16712.  
  16713. var edit_btn = new PVE.button.Button({
  16714. text: gettext('Edit'),
  16715. disabled: true,
  16716. selModel: sm,
  16717. enableFn: function(rec) {
  16718. return !(rec.data.type === 'pve' || rec.data.type === 'pam');
  16719. },
  16720. handler: run_editor
  16721. });
  16722.  
  16723. var remove_btn = new PVE.button.Button({
  16724. text: gettext('Remove'),
  16725. disabled: true,
  16726. selModel: sm,
  16727. confirmMsg: function (rec) {
  16728. return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
  16729. "'" + rec.data.realm + "'");
  16730. },
  16731. enableFn: function(rec) {
  16732. return !(rec.data.type === 'pve' || rec.data.type === 'pam');
  16733. },
  16734. handler: function(btn, event, rec) {
  16735. var realm = rec.data.realm;
  16736.  
  16737. PVE.Utils.API2Request({
  16738. url: '/access/domains/' + realm,
  16739. method: 'DELETE',
  16740. waitMsgTarget: me,
  16741. callback: function() {
  16742. reload();
  16743. },
  16744. failure: function (response, opts) {
  16745. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  16746. }
  16747. });
  16748. }
  16749. });
  16750.  
  16751. var tbar = [
  16752. {
  16753. text: gettext('Add'),
  16754. menu: new Ext.menu.Menu({
  16755. items: [
  16756. {
  16757. text: 'Active Directory Server',
  16758. handler: function() {
  16759. var win = Ext.create('PVE.dc.AuthEdit', {
  16760. authType: 'ad'
  16761. });
  16762. win.on('destroy', reload);
  16763. win.show();
  16764. }
  16765. },
  16766. {
  16767. text: 'LDAP Server',
  16768. handler: function() {
  16769. var win = Ext.create('PVE.dc.AuthEdit',{
  16770. authType: 'ldap'
  16771. });
  16772. win.on('destroy', reload);
  16773. win.show();
  16774. }
  16775. }
  16776. ]
  16777. })
  16778. },
  16779. edit_btn, remove_btn
  16780. ];
  16781.  
  16782. Ext.apply(me, {
  16783. store: store,
  16784. selModel: sm,
  16785. stateful: false,
  16786. tbar: tbar,
  16787. viewConfig: {
  16788. trackOver: false
  16789. },
  16790. columns: [
  16791. {
  16792. header: gettext('Realm'),
  16793. width: 100,
  16794. sortable: true,
  16795. dataIndex: 'realm'
  16796. },
  16797. {
  16798. header: gettext('Type'),
  16799. width: 100,
  16800. sortable: true,
  16801. dataIndex: 'type'
  16802. },
  16803. {
  16804. id: 'comment',
  16805. header: gettext('Comment'),
  16806. sortable: false,
  16807. dataIndex: 'comment',
  16808. flex: 1
  16809. }
  16810. ],
  16811. listeners: {
  16812. show: reload,
  16813. itemdblclick: run_editor
  16814. }
  16815. });
  16816.  
  16817. me.callParent();
  16818. }
  16819. });
  16820. Ext.define('PVE.dc.AuthEdit', {
  16821. extend: 'PVE.window.Edit',
  16822. alias: ['widget.pveDcAuthEdit'],
  16823.  
  16824. isAdd: true,
  16825.  
  16826. initComponent : function() {
  16827. var me = this;
  16828.  
  16829. me.create = !me.realm;
  16830.  
  16831. var url;
  16832. var method;
  16833. var serverlist;
  16834.  
  16835. if (me.create) {
  16836. url = '/api2/extjs/access/domains';
  16837. method = 'POST';
  16838. } else {
  16839. url = '/api2/extjs/access/domains/' + me.realm;
  16840. method = 'PUT';
  16841. }
  16842.  
  16843. var column1 = [
  16844. {
  16845. xtype: me.create ? 'textfield' : 'displayfield',
  16846. height: 22, // hack: set same height as text fields
  16847. name: 'realm',
  16848. fieldLabel: gettext('Realm'),
  16849. value: me.realm,
  16850. allowBlank: false
  16851. }
  16852. ];
  16853.  
  16854. if (me.authType === 'ad') {
  16855.  
  16856. me.subject = 'Active Directory Server';
  16857.  
  16858. column1.push({
  16859. xtype: 'textfield',
  16860. name: 'domain',
  16861. fieldLabel: 'Domain',
  16862. emptyText: 'company.net',
  16863. allowBlank: false
  16864. });
  16865.  
  16866. } else if (me.authType === 'ldap') {
  16867.  
  16868. me.subject = 'LDAP Server';
  16869.  
  16870. column1.push({
  16871. xtype: 'textfield',
  16872. name: 'base_dn',
  16873. fieldLabel: 'Base Domain Name',
  16874. emptyText: 'CN=Users,DC=Company,DC=net',
  16875. allowBlank: false
  16876. });
  16877.  
  16878. column1.push({
  16879. xtype: 'textfield',
  16880. name: 'user_attr',
  16881. emptyText: 'uid / sAMAccountName',
  16882. fieldLabel: 'User Attribute Name',
  16883. allowBlank: false
  16884. });
  16885.  
  16886. } else {
  16887. throw 'unknown auth type ';
  16888. }
  16889.  
  16890. column1.push({
  16891. xtype: 'textfield',
  16892. name: 'comment',
  16893. fieldLabel: gettext('Comment')
  16894. });
  16895.  
  16896. column1.push({
  16897. xtype: 'pvecheckbox',
  16898. fieldLabel: gettext('Default'),
  16899. name: 'default',
  16900. uncheckedValue: 0
  16901. });
  16902.  
  16903. var column2 = [
  16904. {
  16905. xtype: 'textfield',
  16906. fieldLabel: gettext('Server'),
  16907. name: 'server1',
  16908. allowBlank: false
  16909. },
  16910. {
  16911. xtype: 'pvetextfield',
  16912. fieldLabel: gettext('Fallback Server'),
  16913. deleteEmpty: !me.create,
  16914. name: 'server2'
  16915. },
  16916. {
  16917. xtype: 'numberfield',
  16918. name: 'port',
  16919. fieldLabel: gettext('Port'),
  16920. minValue: 1,
  16921. maxValue: 65535,
  16922. emptyText: gettext('Default'),
  16923. submitEmptyText: false
  16924. },
  16925. {
  16926. xtype: 'pvecheckbox',
  16927. fieldLabel: 'SSL',
  16928. name: 'secure',
  16929. uncheckedValue: 0
  16930. }
  16931. ];
  16932.  
  16933. var ipanel = Ext.create('PVE.panel.InputPanel', {
  16934. column1: column1,
  16935. column2: column2,
  16936. onGetValues: function(values) {
  16937. if (!values.port) {
  16938. if (!me.create) {
  16939. PVE.Utils.assemble_field_data(values, { 'delete': 'port' });
  16940. }
  16941. delete values.port;
  16942. }
  16943.  
  16944. if (me.create) {
  16945. values.type = me.authType;
  16946. }
  16947.  
  16948. return values;
  16949. }
  16950. });
  16951.  
  16952. Ext.applyIf(me, {
  16953. url: url,
  16954. method: method,
  16955. fieldDefaults: {
  16956. labelWidth: 120
  16957. },
  16958. items: [ ipanel ]
  16959. });
  16960.  
  16961. me.callParent();
  16962.  
  16963. if (!me.create) {
  16964. me.load({
  16965. success: function(response, options) {
  16966. var data = response.result.data || {};
  16967. // just to be sure (should not happen)
  16968. if (data.type !== me.authType) {
  16969. me.close();
  16970. throw "got wrong auth type";
  16971. }
  16972. me.setValues(data);
  16973. }
  16974. });
  16975. }
  16976. }
  16977. });
  16978. Ext.define('PVE.dc.BackupEdit', {
  16979. extend: 'PVE.window.Edit',
  16980. alias: ['widget.pveDcBackupEdit'],
  16981.  
  16982. initComponent : function() {
  16983. /*jslint confusion: true */
  16984. var me = this;
  16985.  
  16986. me.create = !me.jobid;
  16987.  
  16988. var url;
  16989. var method;
  16990.  
  16991. if (me.create) {
  16992. url = '/api2/extjs/cluster/backup';
  16993. method = 'POST';
  16994. } else {
  16995. url = '/api2/extjs/cluster/backup/' + me.jobid;
  16996. method = 'PUT';
  16997. }
  16998.  
  16999. var vmidField = Ext.create('Ext.form.field.Hidden', {
  17000. name: 'vmid'
  17001. });
  17002.  
  17003. var selModeField = Ext.create('PVE.form.KVComboBox', {
  17004. xtype: 'pveKVComboBox',
  17005. data: [
  17006. ['include', gettext('Include selected VMs')],
  17007. ['all', gettext('All')],
  17008. ['exclude', gettext('Exclude selected VMs')]
  17009. ],
  17010. fieldLabel: gettext('Selection mode'),
  17011. name: 'selMode',
  17012. value: ''
  17013. });
  17014.  
  17015. var insideUpdate = false;
  17016.  
  17017. var sm = Ext.create('Ext.selection.CheckboxModel', {
  17018. mode: 'SIMPLE',
  17019. listeners: {
  17020. selectionchange: function(model, selected) {
  17021. if (!insideUpdate) { // avoid endless loop
  17022. var sel = [];
  17023. Ext.Array.each(selected, function(record) {
  17024. sel.push(record.data.vmid);
  17025. });
  17026.  
  17027. vmidField.setValue(sel);
  17028. }
  17029. }
  17030. }
  17031. });
  17032.  
  17033. var storagesel = Ext.create('PVE.form.StorageSelector', {
  17034. fieldLabel: gettext('Storage'),
  17035. nodename: 'localhost',
  17036. storageContent: 'backup',
  17037. allowBlank: false,
  17038. name: 'storage'
  17039. });
  17040.  
  17041. var store = new Ext.data.Store({
  17042. model: 'PVEResources',
  17043. sorters: {
  17044. property: 'vmid',
  17045. order: 'ASC'
  17046. }
  17047. });
  17048.  
  17049. var vmgrid = Ext.createWidget('grid', {
  17050. store: store,
  17051. border: true,
  17052. height: 300,
  17053. selModel: sm,
  17054. disabled: true,
  17055. columns: [
  17056. {
  17057. header: 'ID',
  17058. dataIndex: 'vmid',
  17059. width: 60
  17060. },
  17061. {
  17062. header: gettext('Node'),
  17063. dataIndex: 'node'
  17064. },
  17065. {
  17066. header: gettext('Status'),
  17067. dataIndex: 'uptime',
  17068. renderer: function(value) {
  17069. if (value) {
  17070. return PVE.Utils.runningText;
  17071. } else {
  17072. return PVE.Utils.stoppedText;
  17073. }
  17074. }
  17075. },
  17076. {
  17077. header: gettext('Name'),
  17078. dataIndex: 'name',
  17079. flex: 1
  17080. },
  17081. {
  17082. header: gettext('Type'),
  17083. dataIndex: 'type'
  17084. }
  17085. ]
  17086. });
  17087.  
  17088. var nodesel = Ext.create('PVE.form.NodeSelector', {
  17089. name: 'node',
  17090. fieldLabel: gettext('Node'),
  17091. allowBlank: true,
  17092. editable: true,
  17093. autoSelect: false,
  17094. emptyText: '-- ' + gettext('All') + ' --',
  17095. listeners: {
  17096. change: function(f, value) {
  17097. storagesel.setNodename(value || 'localhost');
  17098. var mode = selModeField.getValue();
  17099. store.clearFilter();
  17100. store.filterBy(function(rec) {
  17101. return (!value || rec.get('node') === value);
  17102. });
  17103. if (mode === 'all') {
  17104. sm.selectAll(true);
  17105. }
  17106. }
  17107. }
  17108. });
  17109.  
  17110. var column1 = [
  17111. nodesel,
  17112. storagesel,
  17113. {
  17114. xtype: 'pveDayOfWeekSelector',
  17115. name: 'dow',
  17116. fieldLabel: gettext('Day of week'),
  17117. multiSelect: true,
  17118. value: ['sat'],
  17119. allowBlank: false
  17120. },
  17121. {
  17122. xtype: 'timefield',
  17123. fieldLabel: gettext('Start Time'),
  17124. name: 'starttime',
  17125. format: 'H:i',
  17126. value: '00:00',
  17127. allowBlank: false
  17128. },
  17129. selModeField
  17130. ];
  17131.  
  17132. var column2 = [
  17133. {
  17134. xtype: 'textfield',
  17135. fieldLabel: gettext('Send email to'),
  17136. name: 'mailto'
  17137. },
  17138. {
  17139. xtype: 'pveCompressionSelector',
  17140. fieldLabel: gettext('Compression'),
  17141. name: 'compress',
  17142. deleteEmpty: me.create ? false : true,
  17143. value: me.create ? 'lzo' : ''
  17144. },
  17145. {
  17146. xtype: 'pveBackupModeSelector',
  17147. fieldLabel: gettext('Mode'),
  17148. value: 'snapshot',
  17149. name: 'mode'
  17150. },
  17151. vmidField
  17152. ];
  17153.  
  17154. var ipanel = Ext.create('PVE.panel.InputPanel', {
  17155. column1: column1,
  17156. column2: column2,
  17157. onGetValues: function(values) {
  17158. if (!values.node) {
  17159. if (!me.create) {
  17160. PVE.Utils.assemble_field_data(values, { 'delete': 'node' });
  17161. }
  17162. delete values.node;
  17163. }
  17164.  
  17165. var selMode = values.selMode;
  17166. delete values.selMode;
  17167.  
  17168. if (selMode === 'all') {
  17169. values.all = 1;
  17170. values.exclude = '';
  17171. delete values.vmid;
  17172. } else if (selMode === 'exclude') {
  17173. values.all = 1;
  17174. values.exclude = values.vmid;
  17175. delete values.vmid;
  17176. }
  17177. return values;
  17178. }
  17179. });
  17180.  
  17181. var update_vmid_selection = function(list, mode) {
  17182. if (insideUpdate) {
  17183. return; // should not happen - just to be sure
  17184. }
  17185. insideUpdate = true;
  17186. if (mode !== 'all') {
  17187. sm.deselectAll(true);
  17188. if (list) {
  17189. Ext.Array.each(list.split(','), function(vmid) {
  17190. var rec = store.findRecord('vmid', vmid);
  17191. if (rec) {
  17192. sm.select(rec, true);
  17193. }
  17194. });
  17195. }
  17196. }
  17197. insideUpdate = false;
  17198. };
  17199.  
  17200. vmidField.on('change', function(f, value) {
  17201. var mode = selModeField.getValue();
  17202. update_vmid_selection(value, mode);
  17203. });
  17204.  
  17205. selModeField.on('change', function(f, value, oldValue) {
  17206. if (value === 'all') {
  17207. sm.selectAll(true);
  17208. vmgrid.setDisabled(true);
  17209. } else {
  17210. vmgrid.setDisabled(false);
  17211. }
  17212. if (oldValue === 'all') {
  17213. sm.deselectAll(true);
  17214. vmidField.setValue('');
  17215. }
  17216. var list = vmidField.getValue();
  17217. update_vmid_selection(list, value);
  17218. });
  17219.  
  17220. var reload = function() {
  17221. store.load({
  17222. params: { type: 'vm' },
  17223. callback: function() {
  17224. var node = nodesel.getValue();
  17225. store.clearFilter();
  17226. store.filterBy(function(rec) {
  17227. return (!node || rec.get('node') === node);
  17228. });
  17229. var list = vmidField.getValue();
  17230. var mode = selModeField.getValue();
  17231. if (mode === 'all') {
  17232. sm.selectAll(true);
  17233. } else {
  17234. update_vmid_selection(list, mode);
  17235. }
  17236. }
  17237. });
  17238. };
  17239.  
  17240. Ext.applyIf(me, {
  17241. subject: gettext("Backup Job"),
  17242. url: url,
  17243. method: method,
  17244. items: [ ipanel, vmgrid ]
  17245. });
  17246.  
  17247. me.callParent();
  17248.  
  17249. if (me.create) {
  17250. selModeField.setValue('include');
  17251. } else {
  17252. me.load({
  17253. success: function(response, options) {
  17254. var data = response.result.data;
  17255.  
  17256. data.dow = data.dow.split(',');
  17257.  
  17258. if (data.all || data.exclude) {
  17259. if (data.exclude) {
  17260. data.vmid = data.exclude;
  17261. data.selMode = 'exclude';
  17262. } else {
  17263. data.vmid = '';
  17264. data.selMode = 'all';
  17265. }
  17266. } else {
  17267. data.selMode = 'include';
  17268. }
  17269.  
  17270. me.setValues(data);
  17271. }
  17272. });
  17273. }
  17274.  
  17275. reload();
  17276. }
  17277. });
  17278.  
  17279.  
  17280. Ext.define('PVE.dc.BackupView', {
  17281. extend: 'Ext.grid.GridPanel',
  17282.  
  17283. alias: ['widget.pveDcBackupView'],
  17284.  
  17285. allText: '-- ' + gettext('All') + ' --',
  17286. allExceptText: gettext('All except {0}'),
  17287.  
  17288. initComponent : function() {
  17289. var me = this;
  17290.  
  17291. var store = new Ext.data.Store({
  17292. model: 'pve-cluster-backup',
  17293. proxy: {
  17294. type: 'pve',
  17295. url: "/api2/json/cluster/backup"
  17296. }
  17297. });
  17298.  
  17299. var reload = function() {
  17300. store.load();
  17301. };
  17302.  
  17303. var sm = Ext.create('Ext.selection.RowModel', {});
  17304.  
  17305. var run_editor = function() {
  17306. var rec = sm.getSelection()[0];
  17307. if (!rec) {
  17308. return;
  17309. }
  17310.  
  17311. var win = Ext.create('PVE.dc.BackupEdit',{
  17312. jobid: rec.data.id
  17313. });
  17314. win.on('destroy', reload);
  17315. win.show();
  17316. };
  17317.  
  17318. var edit_btn = new PVE.button.Button({
  17319. text: gettext('Edit'),
  17320. disabled: true,
  17321. selModel: sm,
  17322. handler: run_editor
  17323. });
  17324.  
  17325. var remove_btn = new PVE.button.Button({
  17326. text: gettext('Remove'),
  17327. disabled: true,
  17328. selModel: sm,
  17329. confirmMsg: gettext('Are you sure you want to remove this entry'),
  17330. handler: function(btn, event, rec) {
  17331. PVE.Utils.API2Request({
  17332. url: '/cluster/backup/' + rec.data.id,
  17333. method: 'DELETE',
  17334. waitMsgTarget: me,
  17335. callback: function() {
  17336. reload();
  17337. },
  17338. failure: function (response, opts) {
  17339. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  17340. }
  17341. });
  17342. }
  17343. });
  17344.  
  17345. PVE.Utils.monStoreErrors(me, store);
  17346.  
  17347. Ext.apply(me, {
  17348. store: store,
  17349. selModel: sm,
  17350. stateful: false,
  17351. viewConfig: {
  17352. trackOver: false
  17353. },
  17354. tbar: [
  17355. {
  17356. text: gettext('Add'),
  17357. handler: function() {
  17358. var win = Ext.create('PVE.dc.BackupEdit',{});
  17359. win.on('destroy', reload);
  17360. win.show();
  17361. }
  17362. },
  17363. remove_btn,
  17364. edit_btn
  17365. ],
  17366. columns: [
  17367. {
  17368. header: gettext('Node'),
  17369. width: 100,
  17370. sortable: true,
  17371. dataIndex: 'node',
  17372. renderer: function(value) {
  17373. if (value) {
  17374. return value;
  17375. }
  17376. return me.allText;
  17377. }
  17378. },
  17379. {
  17380. header: gettext('Day of week'),
  17381. width: 200,
  17382. sortable: false,
  17383. dataIndex: 'dow'
  17384. },
  17385. {
  17386. header: gettext('Start Time'),
  17387. width: 60,
  17388. sortable: true,
  17389. dataIndex: 'starttime'
  17390. },
  17391. {
  17392. header: gettext('Storage'),
  17393. width: 100,
  17394. sortable: true,
  17395. dataIndex: 'storage'
  17396. },
  17397. {
  17398. header: gettext('Selection'),
  17399. flex: 1,
  17400. sortable: false,
  17401. dataIndex: 'vmid',
  17402. renderer: function(value, metaData, record) {
  17403. /*jslint confusion: true */
  17404. if (record.data.all) {
  17405. if (record.data.exclude) {
  17406. return Ext.String.format(me.allExceptText, record.data.exclude);
  17407. }
  17408. return me.allText;
  17409. }
  17410. if (record.data.vmid) {
  17411. return record.data.vmid;
  17412. }
  17413.  
  17414. return "-";
  17415. }
  17416. }
  17417. ],
  17418. listeners: {
  17419. show: reload,
  17420. itemdblclick: run_editor
  17421. }
  17422. });
  17423.  
  17424. me.callParent();
  17425. }
  17426. }, function() {
  17427.  
  17428. Ext.define('pve-cluster-backup', {
  17429. extend: 'Ext.data.Model',
  17430. fields: [
  17431. 'id', 'starttime', 'dow',
  17432. 'storage', 'node', 'vmid', 'exclude',
  17433. 'mailto',
  17434. { name: 'all', type: 'boolean' },
  17435. { name: 'snapshot', type: 'boolean' },
  17436. { name: 'stop', type: 'boolean' },
  17437. { name: 'suspend', type: 'boolean' },
  17438. { name: 'compress', type: 'boolean' }
  17439. ]
  17440. });
  17441. });/*jslint confusion: true */
  17442. Ext.define('PVE.dc.vmHAServiceEdit', {
  17443. extend: 'PVE.window.Edit',
  17444.  
  17445. initComponent : function() {
  17446. var me = this;
  17447.  
  17448. me.create = me.vmid ? false : true;
  17449.  
  17450. if (me.vmid) {
  17451. me.create = false;
  17452. me.url = "/cluster/ha/groups/pvevm:" + me.vmid;
  17453. me.method = 'PUT';
  17454. } else {
  17455. me.create = true;
  17456. me.url = "/cluster/ha/groups";
  17457. me.method = 'POST';
  17458. }
  17459.  
  17460. Ext.apply(me, {
  17461. subject: gettext('HA managed VM/CT'),
  17462. width: 350,
  17463. items: [
  17464. {
  17465. xtype: me.create ? 'pveVMIDSelector' : 'displayfield',
  17466. name: 'vmid',
  17467. validateExists: true,
  17468. value: me.vmid || '',
  17469. fieldLabel: "VM ID"
  17470. },
  17471. {
  17472. xtype: 'pvecheckbox',
  17473. name: 'autostart',
  17474. checked: true,
  17475. fieldLabel: 'autostart'
  17476. }
  17477. ]
  17478. });
  17479.  
  17480. me.callParent();
  17481.  
  17482. if (!me.create) {
  17483. me.load();
  17484. }
  17485. }
  17486. });
  17487.  
  17488. Ext.define('PVE.dc.HAConfig', {
  17489. extend: 'Ext.panel.Panel',
  17490. alias: 'widget.pveDcHAConfig',
  17491.  
  17492. clusterInfo: {}, // reload store data here
  17493.  
  17494. reload: function() {
  17495. var me = this;
  17496.  
  17497. var getClusterInfo = function(conf) {
  17498.  
  17499. var info = {};
  17500.  
  17501. if (!(conf && conf.children && conf.children[0])) {
  17502. return info;
  17503. }
  17504.  
  17505. var cluster = conf.children[0];
  17506.  
  17507. if (cluster.text !== 'cluster' || !cluster.config_version) {
  17508. return info;
  17509. }
  17510.  
  17511. info.version = cluster.config_version;
  17512.  
  17513. Ext.Array.each(cluster.children, function(item) {
  17514. if (item.text === 'fencedevices') {
  17515. // fixme: make sure each node uses at least one fence device
  17516. info.fenceDevices = true;
  17517. } else if (item.text === 'rm') {
  17518. info.ha = true;
  17519. }
  17520. });
  17521.  
  17522. return info;
  17523. };
  17524.  
  17525. PVE.Utils.API2Request({
  17526. url: '/cluster/ha/config',
  17527. waitMsgTarget: me,
  17528. method: 'GET',
  17529. failure: function(response, opts) {
  17530. me.clusterInfo = {};
  17531. PVE.Utils.setErrorMask(me, response.htmlStatus);
  17532. },
  17533. success: function(response, opts) {
  17534. me.clusterInfo = getClusterInfo(response.result.data);
  17535.  
  17536. me.setDisabled(!me.clusterInfo.version);
  17537.  
  17538. me.addMenu.setDisabled(!me.clusterInfo.version);
  17539.  
  17540. // note: this modifies response.result.data
  17541. me.treePanel.setRootNode(response.result.data);
  17542. me.treePanel.expandAll();
  17543.  
  17544.  
  17545. if (response.result.changes) {
  17546. me.commitBtn.setDisabled(false);
  17547. me.revertBtn.setDisabled(false);
  17548. me.diffPanel.setVisible(true);
  17549. me.diffPanel.update("<pre>" + Ext.htmlEncode(response.result.changes) + "</pre>");
  17550. } else {
  17551. me.commitBtn.setDisabled(true);
  17552. me.revertBtn.setDisabled(true);
  17553. me.diffPanel.setVisible(false);
  17554. me.diffPanel.update('');
  17555. }
  17556. }
  17557. });
  17558. },
  17559.  
  17560. initComponent: function() {
  17561. var me = this;
  17562.  
  17563. me.commitBtn = new PVE.button.Button({
  17564. text: gettext('Activate'),
  17565. disabled: true,
  17566. confirmMsg: function () {
  17567. return gettext('Are you sure you want to activate your changes');
  17568. },
  17569. handler: function(btn, event) {
  17570. PVE.Utils.API2Request({
  17571. url: '/cluster/ha/changes',
  17572. method: 'POST',
  17573. waitMsgTarget: me,
  17574. callback: function() {
  17575. me.reload();
  17576. },
  17577. failure: function (response, opts) {
  17578. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  17579. }
  17580. });
  17581. }
  17582. });
  17583.  
  17584. me.revertBtn = new PVE.button.Button({
  17585. text: gettext('Revert changes'),
  17586. disabled: true,
  17587. confirmMsg: function () {
  17588. return gettext('Are you sure you want to revert your changes');
  17589. },
  17590. handler: function(btn, event) {
  17591. PVE.Utils.API2Request({
  17592. url: '/cluster/ha/changes',
  17593. method: 'DELETE',
  17594. waitMsgTarget: me,
  17595. callback: function() {
  17596. me.reload();
  17597. },
  17598. failure: function (response, opts) {
  17599. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  17600. }
  17601. });
  17602. }
  17603. });
  17604.  
  17605. me.addMenu = new Ext.button.Button({
  17606. text: gettext('Add'),
  17607. disabled: true,
  17608. menu: new Ext.menu.Menu({
  17609. items: [
  17610. {
  17611. text: gettext('HA managed VM/CT'),
  17612. handler: function() {
  17613. var win = Ext.create('PVE.dc.vmHAServiceEdit', {});
  17614. win.show();
  17615. win.on('destroy', me.reload, me);
  17616. }
  17617. },
  17618. {
  17619. text: gettext('Failover Domain'),
  17620. handler: function() {
  17621. Ext.Msg.alert(gettext('Error'), "not implemented - sorry");
  17622. }
  17623. }
  17624. ]
  17625. })
  17626. });
  17627.  
  17628. me.treePanel = Ext.create('Ext.tree.Panel', {
  17629. rootVisible: false,
  17630. animate: false,
  17631. region: 'center',
  17632. border: false,
  17633. fields: ['text', 'id', 'vmid', 'name' ],
  17634. columns: [
  17635. {
  17636. xtype: 'treecolumn',
  17637. text: 'Tag',
  17638. dataIndex: 'text',
  17639. width: 200
  17640. },
  17641. {
  17642. text: 'Attributes',
  17643. dataIndex: 'id',
  17644. renderer: function(value, metaData, record) {
  17645. var text = '';
  17646. Ext.Object.each(record.raw, function(key, value) {
  17647. if (key === 'id' || key === 'text') {
  17648. return;
  17649. }
  17650. text += Ext.htmlEncode(key) + '="' +
  17651. Ext.htmlEncode(value) + '" ';
  17652. });
  17653. return text;
  17654. },
  17655. flex: 1
  17656. }
  17657. ]
  17658. });
  17659.  
  17660. var run_editor = function() {
  17661. var rec = me.treePanel.selModel.getSelection()[0];
  17662. if (rec && rec.data.text === 'pvevm') {
  17663. var win = Ext.create('PVE.dc.vmHAServiceEdit', {
  17664. vmid: rec.data.vmid
  17665. });
  17666. win.show();
  17667. win.on('destroy', me.reload, me);
  17668. }
  17669. };
  17670.  
  17671. me.editBtn = new Ext.button.Button({
  17672. text: gettext('Edit'),
  17673. disabled: true,
  17674. handler: run_editor
  17675. });
  17676.  
  17677. me.removeBtn = new Ext.button.Button({
  17678. text: gettext('Remove'),
  17679. disabled: true,
  17680. handler: function() {
  17681. var rec = me.treePanel.selModel.getSelection()[0];
  17682. if (rec && rec.data.text === 'pvevm') {
  17683. var groupid = 'pvevm:' + rec.data.vmid;
  17684. PVE.Utils.API2Request({
  17685. url: '/cluster/ha/groups/' + groupid,
  17686. method: 'DELETE',
  17687. waitMsgTarget: me,
  17688. callback: function() {
  17689. me.reload();
  17690. },
  17691. failure: function (response, opts) {
  17692. Ext.Msg.alert(gettext('Error'), response.htmlStatus);
  17693. }
  17694. });
  17695. }
  17696. }
  17697. });
  17698.  
  17699.  
  17700. me.diffPanel = Ext.create('Ext.panel.Panel', {
  17701. border: false,
  17702. hidden: true,
  17703. region: 'south',
  17704. autoScroll: true,
  17705. itemId: 'changes',
  17706. tbar: [ gettext('Pending changes') ],
  17707. split: true,
  17708. bodyPadding: 5,
  17709. flex: 0.6
  17710. });
  17711.  
  17712. Ext.apply(me, {
  17713. layout: 'border',
  17714. tbar: [ me.addMenu, me.removeBtn, me.editBtn, me.revertBtn, me.commitBtn ],
  17715. items: [ me.treePanel, me.diffPanel ]
  17716. });
  17717.  
  17718. me.callParent();
  17719.  
  17720. me.on('show', me.reload);
  17721.  
  17722. me.treePanel.on("selectionchange", function(sm, selected) {
  17723. var rec = selected[0];
  17724. if (rec && rec.data.text === 'pvevm') {
  17725. me.editBtn.setDisabled(false);
  17726. me.removeBtn.setDisabled(false);
  17727. } else {
  17728. me.editBtn.setDisabled(true);
  17729. me.removeBtn.setDisabled(true);
  17730.  
  17731. }
  17732. });
  17733.  
  17734. me.treePanel.on("itemdblclick", function(v, record) {
  17735. run_editor();
  17736. });
  17737. }
  17738. });
  17739. Ext.define('PVE.dc.Support', {
  17740. extend: 'Ext.panel.Panel',
  17741. alias: 'widget.pveDcSupport',
  17742.  
  17743. invalidHtml: '<h1>No valid subscription</h1>You do not have a valid subscription for this server. Please visit <a target="_blank" href="http://www.proxmox.com/products/proxmox-ve/subscription-service-plans">www.proxmox.com</a> to get a list of available options.',
  17744.  
  17745. communityHtml: 'Please use the public community <a target="_blank" href="http://forum.proxmox.com">forum</a> for any questions.',
  17746.  
  17747. activeHtml: 'Please use our <a target="_blank" href="https://my.proxmox.com">support portal</a> for any questions. You can also use the public community <a target="_blank" href="http://forum.proxmox.com">forum</a> to get additional information.',
  17748.  
  17749. bugzillaHtml: '<h1>Bug Tracking</h1>Our bug tracking system is available <a target="_blank" href="https://bugzilla.proxmox.com">here</a>.',
  17750.  
  17751. docuHtml: '<h1>Documentation</h1>Complete documentation, tutorials, videos and more is available at our <a target="_blank" href="http://pve.proxmox.com/wiki/Documentation">wiki</a>.',
  17752.  
  17753. updateActive: function(data) {
  17754. var me = this;
  17755.  
  17756. var html = '<h1>' + data.productname + '</h1>' + me.activeHtml;
  17757. html += '<br><br>' + me.docuHtml;
  17758. html += '<br><br>' + me.bugzillaHtml;
  17759.  
  17760. me.update(html);
  17761. },
  17762.  
  17763. updateCommunity: function(data) {
  17764. var me = this;
  17765.  
  17766. var html = '<h1>' + data.productname + '</h1>' + me.communityHtml;
  17767. html += '<br><br>' + me.docuHtml;
  17768. html += '<br><br>' + me.bugzillaHtml;
  17769.  
  17770. me.update(html);
  17771. },
  17772.  
  17773. updateInactive: function(data) {
  17774. var me = this;
  17775. me.update(me.invalidHtml);
  17776. },
  17777.  
  17778. initComponent: function() {
  17779. var me = this;
  17780.  
  17781. var reload = function() {
  17782. PVE.Utils.API2Request({
  17783. url: '/nodes/localhost/subscription',
  17784. method: 'GET',
  17785. waitMsgTarget: me,
  17786. failure: function(response, opts) {
  17787. Ext.Msg.alert('Error', response.htmlStatus);
  17788. me.update("Unable to load subscription status: " + response.htmlStatus);
  17789. },
  17790. success: function(response, opts) {
  17791. var data = response.result.data;
  17792.  
  17793. if (data.status === 'Active') {
  17794. if (data.level === 'c') {
  17795. me.updateCommunity(data);
  17796. } else {
  17797. me.updateActive(data);
  17798. }
  17799. } else {
  17800. me.updateInactive(data);
  17801. }
  17802. }
  17803. });
  17804. };
  17805.  
  17806. Ext.apply(me, {
  17807. autoScroll: true,
  17808. bodyStyle: 'padding:10px',
  17809. listeners: {
  17810. show: reload
  17811. }
  17812. });
  17813.  
  17814. me.callParent();
  17815. }
  17816. });Ext.define('PVE.dc.Config', {
  17817. extend: 'PVE.panel.Config',
  17818. alias: 'widget.PVE.dc.Config',
  17819.  
  17820. initComponent: function() {
  17821. var me = this;
  17822.  
  17823. var caps = Ext.state.Manager.get('GuiCap');
  17824.  
  17825. me.items = [];
  17826.  
  17827. Ext.apply(me, {
  17828. title: gettext("Datacenter"),
  17829. hstateid: 'dctab'
  17830. });
  17831.  
  17832. if (caps.dc['Sys.Audit']) {
  17833. me.items.push([
  17834. {
  17835. title: gettext('Summary'),
  17836. xtype: 'pveDcSummary',
  17837. itemId: 'summary'
  17838. },
  17839. {
  17840. xtype: 'pveDcOptionView',
  17841. title: gettext('Options'),
  17842. itemId: 'options'
  17843. }
  17844. ]);
  17845. }
  17846.  
  17847. if (caps.storage['Datastore.Allocate'] || caps.dc['Sys.Audit']) {
  17848. me.items.push({
  17849. xtype: 'pveStorageView',
  17850. title: gettext('Storage'),
  17851. itemId: 'storage'
  17852. });
  17853. }
  17854.  
  17855. if (caps.dc['Sys.Audit']) {
  17856. me.items.push({
  17857. xtype: 'pveDcBackupView',
  17858. title: gettext('Backup'),
  17859. itemId: 'backup'
  17860. });
  17861. }
  17862.  
  17863. me.items.push({
  17864. xtype: 'pveUserView',
  17865. title: gettext('Users'),
  17866. itemId: 'users'
  17867. });
  17868.  
  17869. if (caps.dc['Sys.Audit']) {
  17870. me.items.push([
  17871. {
  17872. xtype: 'pveGroupView',
  17873. title: gettext('Groups'),
  17874. itemId: 'groups'
  17875. },
  17876. {
  17877. xtype: 'pvePoolView',
  17878. title: gettext('Pools'),
  17879. itemId: 'pools'
  17880. },
  17881. {
  17882. xtype: 'pveACLView',
  17883. title: gettext('Permissions'),
  17884. itemId: 'permissions'
  17885. },
  17886. {
  17887. xtype: 'pveRoleView',
  17888. title: gettext('Roles'),
  17889. itemId: 'roles'
  17890. },
  17891. {
  17892. xtype: 'pveAuthView',
  17893. title: gettext('Authentication'),
  17894. itemId: 'domains'
  17895. },
  17896. {
  17897. xtype: 'pveDcHAConfig',
  17898. title: 'HA',
  17899. itemId: 'ha'
  17900. }
  17901. ]);
  17902.  
  17903. me.items.push({
  17904. xtype: 'pveDcSupport',
  17905. title: gettext('Support'),
  17906. itemId: 'support'
  17907. });
  17908. }
  17909.  
  17910. me.callParent();
  17911. }
  17912. });
  17913. /*
  17914. * Workspace base class
  17915. *
  17916. * popup login window when auth fails (call onLogin handler)
  17917. * update (re-login) ticket every 15 minutes
  17918. *
  17919. */
  17920.  
  17921. Ext.define('PVE.Workspace', {
  17922. extend: 'Ext.container.Viewport',
  17923.  
  17924. title: 'Proxmox Virtual Environment',
  17925.  
  17926. loginData: null, // Data from last login call
  17927.  
  17928. onLogin: function(loginData) {},
  17929.  
  17930. // private
  17931. updateLoginData: function(loginData) {
  17932. var me = this;
  17933. me.loginData = loginData;
  17934. PVE.CSRFPreventionToken = loginData.CSRFPreventionToken;
  17935. PVE.UserName = loginData.username;
  17936.  
  17937. if (loginData.cap) {
  17938. Ext.state.Manager.set('GuiCap', loginData.cap);
  17939. }
  17940.  
  17941. // creates a session cookie (expire = null)
  17942. // that way the cookie gets deleted after browser window close
  17943. Ext.util.Cookies.set('PVEAuthCookie', loginData.ticket, null, '/', null, true);
  17944. me.onLogin(loginData);
  17945. },
  17946.  
  17947. // private
  17948. showLogin: function() {
  17949. var me = this;
  17950.  
  17951. PVE.Utils.authClear();
  17952. PVE.UserName = null;
  17953. me.loginData = null;
  17954.  
  17955. if (!me.login) {
  17956. me.login = Ext.create('PVE.window.LoginWindow', {
  17957. handler: function(data) {
  17958. me.login = null;
  17959. me.updateLoginData(data);
  17960. }
  17961. });
  17962. }
  17963. me.onLogin(null);
  17964. me.login.show();
  17965. },
  17966.  
  17967. initComponent : function() {
  17968. var me = this;
  17969.  
  17970. Ext.tip.QuickTipManager.init();
  17971.  
  17972. // fixme: what about other errors
  17973. Ext.Ajax.on('requestexception', function(conn, response, options) {
  17974. if (response.status == 401) { // auth failure
  17975. me.showLogin();
  17976. }
  17977. });
  17978.  
  17979. document.title = me.title;
  17980.  
  17981. me.callParent();
  17982.  
  17983. if (!PVE.Utils.authOK()) {
  17984. me.showLogin();
  17985. } else {
  17986. if (me.loginData) {
  17987. me.onLogin(me.loginData);
  17988. }
  17989. }
  17990.  
  17991. Ext.TaskManager.start({
  17992. run: function() {
  17993. var ticket = PVE.Utils.authOK();
  17994. if (!ticket || !PVE.UserName) {
  17995. return;
  17996. }
  17997.  
  17998. Ext.Ajax.request({
  17999. params: {
  18000. username: PVE.UserName,
  18001. password: ticket
  18002. },
  18003. url: '/api2/json/access/ticket',
  18004. method: 'POST',
  18005. success: function(response, opts) {
  18006. var obj = Ext.decode(response.responseText);
  18007. me.updateLoginData(obj.data);
  18008. }
  18009. });
  18010. },
  18011. interval: 15*60*1000
  18012. });
  18013.  
  18014. }
  18015. });
  18016.  
  18017. Ext.define('PVE.ConsoleWorkspace', {
  18018. extend: 'PVE.Workspace',
  18019.  
  18020. alias: ['widget.pveConsoleWorkspace'],
  18021.  
  18022. title: gettext('Console'),
  18023.  
  18024. initComponent : function() {
  18025. var me = this;
  18026.  
  18027. var param = Ext.Object.fromQueryString(window.location.search);
  18028. var consoleType = me.consoleType || param.console;
  18029.  
  18030. var content;
  18031. if (consoleType === 'kvm') {
  18032. me.title = "VM " + param.vmid;
  18033. if (param.vmname) {
  18034. me.title += " ('" + param.vmname + "')";
  18035. }
  18036. content = {
  18037. xtype: 'pveKVMConsole',
  18038. vmid: param.vmid,
  18039. nodename: param.node,
  18040. vmname: param.vmname,
  18041. toplevel: true
  18042. };
  18043. } else if (consoleType === 'openvz') {
  18044. me.title = "CT " + param.vmid;
  18045. if (param.vmname) {
  18046. me.title += " ('" + param.vmname + "')";
  18047. }
  18048. content = {
  18049. xtype: 'pveOpenVZConsole',
  18050. vmid: param.vmid,
  18051. nodename: param.node,
  18052. vmname: param.vmname,
  18053. toplevel: true
  18054. };
  18055. } else if (consoleType === 'shell') {
  18056. me.title = "node '" + param.node;
  18057. content = {
  18058. xtype: 'pveShell',
  18059. nodename: param.node,
  18060. toplevel: true
  18061. };
  18062. } else {
  18063. content = {
  18064. border: false,
  18065. bodyPadding: 10,
  18066. html: 'Error: No such console type'
  18067. };
  18068. }
  18069.  
  18070. Ext.apply(me, {
  18071. layout: { type: 'fit' },
  18072. border: false,
  18073. items: [ content ]
  18074. });
  18075.  
  18076. me.callParent();
  18077. }
  18078. });
  18079.  
  18080. Ext.define('PVE.StdWorkspace', {
  18081. extend: 'PVE.Workspace',
  18082.  
  18083. alias: ['widget.pveStdWorkspace'],
  18084.  
  18085. // private
  18086. setContent: function(comp) {
  18087. var me = this;
  18088.  
  18089. var cont = me.child('#content');
  18090. cont.removeAll(true);
  18091.  
  18092. if (comp) {
  18093. PVE.Utils.setErrorMask(cont, false);
  18094. comp.border = false;
  18095. cont.add(comp);
  18096. cont.doLayout();
  18097. }
  18098. // else {
  18099. // TODO: display something useful
  18100.  
  18101. // Note:: error mask has wrong zindex, so we do not
  18102. // use that - see bug 114
  18103. // PVE.Utils.setErrorMask(cont, 'nothing selected');
  18104. //}
  18105. },
  18106.  
  18107. selectById: function(nodeid) {
  18108. var me = this;
  18109. var tree = me.down('pveResourceTree');
  18110. tree.selectById(nodeid);
  18111. },
  18112.  
  18113. checkVmMigration: function(record) {
  18114. var me = this;
  18115. var tree = me.down('pveResourceTree');
  18116. tree.checkVmMigration(record);
  18117. },
  18118.  
  18119. onLogin: function(loginData) {
  18120. var me = this;
  18121.  
  18122. me.updateUserInfo();
  18123.  
  18124. if (loginData) {
  18125. PVE.data.ResourceStore.startUpdate();
  18126.  
  18127. PVE.Utils.API2Request({
  18128. url: '/version',
  18129. method: 'GET',
  18130. success: function(response) {
  18131. PVE.VersionInfo = response.result.data;
  18132. me.updateVersionInfo();
  18133. }
  18134. });
  18135. }
  18136. },
  18137.  
  18138. updateUserInfo: function() {
  18139. var me = this;
  18140.  
  18141. var ui = me.query('#userinfo')[0];
  18142.  
  18143. if (PVE.UserName) {
  18144. var msg = Ext.String.format(gettext("You are logged in as {0}"), "'" + PVE.UserName + "'");
  18145. ui.update('<div class="x-unselectable" style="white-space:nowrap;">' + msg + '</div>');
  18146. } else {
  18147. ui.update('');
  18148. }
  18149. ui.doLayout();
  18150. },
  18151.  
  18152. updateVersionInfo: function() {
  18153. var me = this;
  18154.  
  18155. var ui = me.query('#versioninfo')[0];
  18156.  
  18157. if (PVE.VersionInfo) {
  18158. var version = PVE.VersionInfo.version + '-' + PVE.VersionInfo.release + '/' +
  18159. PVE.VersionInfo.repoid;
  18160. ui.update('<span class="x-panel-header-text">Proxmox Virtual Environment<br>' + gettext('Version') + ': ' + version + "</span>");
  18161. } else {
  18162. ui.update('<span class="x-panel-header-text">Proxmox Virtual Environment</span>');
  18163. }
  18164. ui.doLayout();
  18165. },
  18166.  
  18167. initComponent : function() {
  18168. var me = this;
  18169.  
  18170. Ext.History.init();
  18171.  
  18172. var sprovider = Ext.create('PVE.StateProvider');
  18173. Ext.state.Manager.setProvider(sprovider);
  18174.  
  18175. var selview = new PVE.form.ViewSelector({});
  18176.  
  18177. var rtree = Ext.createWidget('pveResourceTree', {
  18178. viewFilter: selview.getViewFilter(),
  18179. flex: 1,
  18180. selModel: new Ext.selection.TreeModel({
  18181. listeners: {
  18182. selectionchange: function(sm, selected) {
  18183. var comp;
  18184. var tlckup = {
  18185. root: 'PVE.dc.Config',
  18186. node: 'PVE.node.Config',
  18187. qemu: 'PVE.qemu.Config',
  18188. openvz: 'PVE.openvz.Config',
  18189. storage: 'PVE.storage.Browser',
  18190. pool: 'pvePoolConfig'
  18191. };
  18192.  
  18193. if (selected.length > 0) {
  18194. var n = selected[0];
  18195. comp = {
  18196. xtype: tlckup[n.data.type || 'root'] ||
  18197. 'pvePanelConfig',
  18198. layout: { type: 'fit' },
  18199. showSearch: (n.data.id === 'root') ||
  18200. Ext.isDefined(n.data.groupbyid),
  18201. pveSelNode: n,
  18202. workspace: me,
  18203. viewFilter: selview.getViewFilter()
  18204. };
  18205. }
  18206.  
  18207. me.setContent(comp);
  18208. }
  18209. }
  18210. })
  18211. });
  18212.  
  18213. selview.on('select', function(combo, records) {
  18214. if (records && records.length) {
  18215. var view = combo.getViewFilter();
  18216. rtree.setViewFilter(view);
  18217. }
  18218. });
  18219.  
  18220. var caps = sprovider.get('GuiCap');
  18221.  
  18222. var createVM = Ext.createWidget('button', {
  18223. pack: 'end',
  18224. margins: '3 5 0 0',
  18225. baseCls: 'x-btn',
  18226. text: gettext("Create VM"),
  18227. disabled: !caps.vms['VM.Allocate'],
  18228. handler: function() {
  18229. var wiz = Ext.create('PVE.qemu.CreateWizard', {});
  18230. wiz.show();
  18231. }
  18232. });
  18233.  
  18234. var createCT = Ext.createWidget('button', {
  18235. pack: 'end',
  18236. margins: '3 5 0 0',
  18237. baseCls: 'x-btn',
  18238. text: gettext("Create CT"),
  18239. disabled: !caps.vms['VM.Allocate'],
  18240. handler: function() {
  18241. var wiz = Ext.create('PVE.openvz.CreateWizard', {});
  18242. wiz.show();
  18243. }
  18244. });
  18245.  
  18246. sprovider.on('statechange', function(sp, key, value) {
  18247. if (key === 'GuiCap' && value) {
  18248. caps = value;
  18249. createVM.setDisabled(!caps.vms['VM.Allocate']);
  18250. createCT.setDisabled(!caps.vms['VM.Allocate']);
  18251. }
  18252. });
  18253.  
  18254. Ext.apply(me, {
  18255. layout: { type: 'border' },
  18256. border: false,
  18257. items: [
  18258. {
  18259. region: 'north',
  18260. height: 30,
  18261. layout: {
  18262. type: 'hbox',
  18263. align : 'middle'
  18264. },
  18265. baseCls: 'x-plain',
  18266. defaults: {
  18267. baseCls: 'x-plain'
  18268. },
  18269. border: false,
  18270. margins: '2 0 5 0',
  18271. items: [
  18272. {
  18273. margins: '0 0 0 4',
  18274. html: '<a class="x-unselectable" target=_blank href="http://www.proxmox.com">' +
  18275. '<img height=30 width=209 src="/pve2/images/proxmox_logo.png"/></a>'
  18276. },
  18277. {
  18278. minWidth: 200,
  18279. flex: 1,
  18280. id: 'versioninfo',
  18281. html: '<span class="x-panel-header-text">Proxmox Virtual Environment</span>'
  18282. },
  18283. {
  18284. pack: 'end',
  18285. margins: '8 10 0 10',
  18286. id: 'userinfo',
  18287. stateful: false
  18288. },
  18289. {
  18290. pack: 'end',
  18291. margins: '3 5 0 0',
  18292. xtype: 'button',
  18293. baseCls: 'x-btn',
  18294. text: gettext("Logout"),
  18295. handler: function() {
  18296. PVE.data.ResourceStore.stopUpdate();
  18297. me.showLogin();
  18298. me.setContent();
  18299. var rt = me.down('pveResourceTree');
  18300. rt.clearTree();
  18301. }
  18302. },
  18303. createVM,
  18304. createCT
  18305. ]
  18306. },
  18307. {
  18308. region: 'center',
  18309. id: 'content',
  18310. xtype: 'container',
  18311. layout: { type: 'fit' },
  18312. border: false,
  18313. stateful: false,
  18314. margins: '0 5 0 0',
  18315. items: []
  18316. },
  18317. {
  18318. region: 'west',
  18319. xtype: 'container',
  18320. border: false,
  18321. layout: { type: 'vbox', align: 'stretch' },
  18322. margins: '0 0 0 5',
  18323. split: true,
  18324. width: 200,
  18325. items: [ selview, rtree ]
  18326. },
  18327. {
  18328. xtype: 'pveStatusPanel',
  18329. region: 'south',
  18330. margins:'0 5 5 5',
  18331. height: 200,
  18332. split:true
  18333. }
  18334. ]
  18335. });
  18336.  
  18337. me.callParent();
  18338.  
  18339. me.updateUserInfo();
  18340. }
  18341. });
Add Comment
Please, Sign In to add comment