]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/Utils.js
Rename the SCSI VirtIO controller to better differenciate from VirtIO blk
[pve-manager.git] / www / manager6 / Utils.js
1 Ext.ns('PVE');
2
3 // avoid errors related to Accessible Rich Internet Applications
4 // (access for people with disabilities)
5 // TODO reenable after all components are upgraded
6 Ext.enableAria = false;
7 Ext.enableAriaButtons = false;
8 Ext.enableAriaPanels = false;
9
10 // avoid errors when running without development tools
11 if (!Ext.isDefined(Ext.global.console)) {
12 var console = {
13 dir: function() {},
14 log: function() {}
15 };
16 }
17 console.log("Starting PVE Manager");
18
19 Ext.Ajax.defaultHeaders = {
20 'Accept': 'application/json'
21 };
22
23 Ext.Ajax.on('beforerequest', function(conn, options) {
24 if (PVE.CSRFPreventionToken) {
25 if (!options.headers) {
26 options.headers = {};
27 }
28 options.headers.CSRFPreventionToken = PVE.CSRFPreventionToken;
29 }
30 });
31
32 var IPV4_OCTET = "(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])";
33 var IPV4_REGEXP = "(?:(?:" + IPV4_OCTET + "\\.){3}" + IPV4_OCTET + ")";
34 var IPV6_H16 = "(?:[0-9a-fA-F]{1,4})";
35 var IPV6_LS32 = "(?:(?:" + IPV6_H16 + ":" + IPV6_H16 + ")|" + IPV4_REGEXP + ")";
36
37
38 var IP4_match = new RegExp("^(?:" + IPV4_REGEXP + ")$");
39 var IP4_cidr_match = new RegExp("^(?:" + IPV4_REGEXP + ")\/([0-9]{1,2})$");
40
41 var IPV6_REGEXP = "(?:" +
42 "(?:(?:" + "(?:" + IPV6_H16 + ":){6})" + IPV6_LS32 + ")|" +
43 "(?:(?:" + "::" + "(?:" + IPV6_H16 + ":){5})" + IPV6_LS32 + ")|" +
44 "(?:(?:(?:" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){4})" + IPV6_LS32 + ")|" +
45 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,1}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){3})" + IPV6_LS32 + ")|" +
46 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,2}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){2})" + IPV6_LS32 + ")|" +
47 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,3}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){1})" + IPV6_LS32 + ")|" +
48 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,4}" + IPV6_H16 + ")?::" + ")" + IPV6_LS32 + ")|" +
49 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,5}" + IPV6_H16 + ")?::" + ")" + IPV6_H16 + ")|" +
50 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,7}" + IPV6_H16 + ")?::" + ")" + ")" +
51 ")";
52
53 var IP6_match = new RegExp("^(?:" + IPV6_REGEXP + ")$");
54 var IP6_cidr_match = new RegExp("^(?:" + IPV6_REGEXP + ")\/([0-9]{1,3})$");
55 var IP6_bracket_match = new RegExp("^\\[(" + IPV6_REGEXP + ")\\]");
56
57 var IP64_match = new RegExp("^(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + ")$");
58
59 Ext.define('PVE.Utils', { statics: {
60
61 // this class only contains static functions
62
63 toolkit: undefined, // (extjs|touch), set inside Toolkit.js
64
65 log_severity_hash: {
66 0: "panic",
67 1: "alert",
68 2: "critical",
69 3: "error",
70 4: "warning",
71 5: "notice",
72 6: "info",
73 7: "debug"
74 },
75
76 support_level_hash: {
77 'c': gettext('Community'),
78 'b': gettext('Basic'),
79 's': gettext('Standard'),
80 'p': gettext('Premium')
81 },
82
83 noSubKeyHtml: '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.',
84
85 kvm_ostypes: {
86 other: gettext('Other OS types'),
87 wxp: 'Microsoft Windows XP/2003',
88 w2k: 'Microsoft Windows 2000',
89 w2k8: 'Microsoft Windows Vista/2008',
90 win7: 'Microsoft Windows 7/2008r2',
91 win8: 'Microsoft Windows 8.x/10/2012/r2',
92 l24: 'Linux 2.4 Kernel',
93 l26: 'Linux 4.X/3.X/2.6 Kernel',
94 solaris: 'Solaris Kernel'
95 },
96
97 render_kvm_ostype: function (value) {
98 if (!value) {
99 return gettext('Other OS types');
100 }
101 var text = PVE.Utils.kvm_ostypes[value];
102 if (text) {
103 return text;
104 }
105 return value;
106 },
107
108 render_hotplug_features: function (value) {
109 var fa = [];
110
111 if (!value || (value === '0')) {
112 return gettext('disabled');
113 }
114
115 if (value === '1') {
116 value = 'disk,network,usb';
117 }
118
119 Ext.each(value.split(','), function(el) {
120 if (el === 'disk') {
121 fa.push(gettext('Disk'));
122 } else if (el === 'network') {
123 fa.push(gettext('Network'));
124 } else if (el === 'usb') {
125 fa.push(gettext('USB'));
126 } else if (el === 'memory') {
127 fa.push(gettext('Memory'));
128 } else if (el === 'cpu') {
129 fa.push(gettext('CPU'));
130 } else {
131 fa.push(el);
132 }
133 });
134
135 return fa.join(', ');
136 },
137
138 network_iface_types: {
139 eth: gettext("Network Device"),
140 bridge: 'Linux Bridge',
141 bond: 'Linux Bond',
142 OVSBridge: 'OVS Bridge',
143 OVSBond: 'OVS Bond',
144 OVSPort: 'OVS Port',
145 OVSIntPort: 'OVS IntPort'
146 },
147
148 render_network_iface_type: function(value) {
149 return PVE.Utils.network_iface_types[value] ||
150 PVE.Utils.unknownText;
151 },
152
153 render_qemu_bios: function(value) {
154 if (!value) {
155 return PVE.Utils.defaultText + ' (SeaBIOS)';
156 } else if (value === 'seabios') {
157 return "SeaBIOS";
158 } else if (value === 'ovmf') {
159 return "OVMF (UEFI)";
160 } else {
161 return value;
162 }
163 },
164
165 render_scsihw: function(value) {
166 if (!value) {
167 return PVE.Utils.defaultText + ' (LSI 53C895A)';
168 } else if (value === 'lsi') {
169 return 'LSI 53C895A';
170 } else if (value === 'lsi53c810') {
171 return 'LSI 53C810';
172 } else if (value === 'megasas') {
173 return 'MegaRAID SAS 8708EM2';
174 } else if (value === 'virtio-scsi-pci') {
175 return 'VirtIO SCSI';
176 } else if (value === 'virtio-scsi-single') {
177 return 'VirtIO SCSI single';
178 } else if (value === 'pvscsi') {
179 return 'VMware PVSCSI';
180 } else {
181 return value;
182 }
183 },
184
185 // fixme: auto-generate this
186 // for now, please keep in sync with PVE::Tools::kvmkeymaps
187 kvm_keymaps: {
188 //ar: 'Arabic',
189 da: 'Danish',
190 de: 'German',
191 'de-ch': 'German (Swiss)',
192 'en-gb': 'English (UK)',
193 'en-us': 'English (USA)',
194 es: 'Spanish',
195 //et: 'Estonia',
196 fi: 'Finnish',
197 //fo: 'Faroe Islands',
198 fr: 'French',
199 'fr-be': 'French (Belgium)',
200 'fr-ca': 'French (Canada)',
201 'fr-ch': 'French (Swiss)',
202 //hr: 'Croatia',
203 hu: 'Hungarian',
204 is: 'Icelandic',
205 it: 'Italian',
206 ja: 'Japanese',
207 lt: 'Lithuanian',
208 //lv: 'Latvian',
209 mk: 'Macedonian',
210 nl: 'Dutch',
211 //'nl-be': 'Dutch (Belgium)',
212 no: 'Norwegian',
213 pl: 'Polish',
214 pt: 'Portuguese',
215 'pt-br': 'Portuguese (Brazil)',
216 //ru: 'Russian',
217 sl: 'Slovenian',
218 sv: 'Swedish',
219 //th: 'Thai',
220 tr: 'Turkish'
221 },
222
223 kvm_vga_drivers: {
224 std: gettext('Standard VGA'),
225 vmware: gettext('VMware compatible'),
226 cirrus: 'Cirrus Logic GD5446',
227 qxl: 'SPICE',
228 qxl2: 'SPICE dual monitor',
229 qxl3: 'SPICE three monitors',
230 qxl4: 'SPICE four monitors',
231 serial0: gettext('Serial terminal') + ' 0',
232 serial1: gettext('Serial terminal') + ' 1',
233 serial2: gettext('Serial terminal') + ' 2',
234 serial3: gettext('Serial terminal') + ' 3'
235 },
236
237 render_kvm_language: function (value) {
238 if (!value) {
239 return PVE.Utils.defaultText;
240 }
241 var text = PVE.Utils.kvm_keymaps[value];
242 if (text) {
243 return text + ' (' + value + ')';
244 }
245 return value;
246 },
247
248 kvm_keymap_array: function() {
249 var data = [['__default__', PVE.Utils.render_kvm_language('')]];
250 Ext.Object.each(PVE.Utils.kvm_keymaps, function(key, value) {
251 data.push([key, PVE.Utils.render_kvm_language(value)]);
252 });
253
254 return data;
255 },
256
257 render_console_viewer: function(value) {
258 if (!value || value === '__default__') {
259 return PVE.Utils.defaultText + ' (HTML5)';
260 } else if (value === 'vv') {
261 return 'SPICE (remote-viewer)';
262 } else if (value === 'html5') {
263 return 'HTML5 (noVNC)';
264 } else {
265 return value;
266 }
267 },
268
269 language_map: {
270 zh_CN: 'Chinese',
271 ca: 'Catalan',
272 da: 'Danish',
273 en: 'English',
274 eu: 'Euskera (Basque)',
275 fr: 'French',
276 de: 'German',
277 it: 'Italian',
278 ja: 'Japanese',
279 nb: 'Norwegian (Bokmal)',
280 nn: 'Norwegian (Nynorsk)',
281 fa: 'Persian (Farsi)',
282 pl: 'Polish',
283 pt_BR: 'Portuguese (Brazil)',
284 ru: 'Russian',
285 sl: 'Slovenian',
286 es: 'Spanish',
287 sv: 'Swedish',
288 tr: 'Turkish'
289 },
290
291 render_language: function (value) {
292 if (!value) {
293 return PVE.Utils.defaultText + ' (English)';
294 }
295 var text = PVE.Utils.language_map[value];
296 if (text) {
297 return text + ' (' + value + ')';
298 }
299 return value;
300 },
301
302 language_array: function() {
303 var data = [['__default__', PVE.Utils.render_language('')]];
304 Ext.Object.each(PVE.Utils.language_map, function(key, value) {
305 data.push([key, PVE.Utils.render_language(value)]);
306 });
307
308 return data;
309 },
310
311 render_kvm_vga_driver: function (value) {
312 if (!value) {
313 return PVE.Utils.defaultText;
314 }
315 var text = PVE.Utils.kvm_vga_drivers[value];
316 if (text) {
317 return text + ' (' + value + ')';
318 }
319 return value;
320 },
321
322 kvm_vga_driver_array: function() {
323 var data = [['__default__', PVE.Utils.render_kvm_vga_driver('')]];
324 Ext.Object.each(PVE.Utils.kvm_vga_drivers, function(key, value) {
325 data.push([key, PVE.Utils.render_kvm_vga_driver(value)]);
326 });
327
328 return data;
329 },
330
331 render_kvm_startup: function(value) {
332 var startup = PVE.Parser.parseStartup(value);
333
334 var res = 'order=';
335 if (startup.order === undefined) {
336 res += 'any';
337 } else {
338 res += startup.order;
339 }
340 if (startup.up !== undefined) {
341 res += ',up=' + startup.up;
342 }
343 if (startup.down !== undefined) {
344 res += ',down=' + startup.down;
345 }
346
347 return res;
348 },
349
350 authOK: function() {
351 return Ext.util.Cookies.get('PVEAuthCookie');
352 },
353
354 authClear: function() {
355 Ext.util.Cookies.clear("PVEAuthCookie");
356 },
357
358 // fixme: remove - not needed?
359 gridLineHeigh: function() {
360 return 21;
361
362 //if (Ext.isGecko)
363 //return 23;
364 //return 21;
365 },
366
367 extractRequestError: function(result, verbose) {
368 var msg = gettext('Successful');
369
370 if (!result.success) {
371 msg = gettext("Unknown error");
372 if (result.message) {
373 msg = result.message;
374 if (result.status) {
375 msg += ' (' + result.status + ')';
376 }
377 }
378 if (verbose && Ext.isObject(result.errors)) {
379 msg += "<br>";
380 Ext.Object.each(result.errors, function(prop, desc) {
381 msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
382 Ext.htmlEncode(desc);
383 });
384 }
385 }
386
387 return msg;
388 },
389
390 extractFormActionError: function(action) {
391 var msg;
392 switch (action.failureType) {
393 case Ext.form.action.Action.CLIENT_INVALID:
394 msg = gettext('Form fields may not be submitted with invalid values');
395 break;
396 case Ext.form.action.Action.CONNECT_FAILURE:
397 msg = gettext('Connection error');
398 var resp = action.response;
399 if (resp.status && resp.statusText) {
400 msg += " " + resp.status + ": " + resp.statusText;
401 }
402 break;
403 case Ext.form.action.Action.LOAD_FAILURE:
404 case Ext.form.action.Action.SERVER_INVALID:
405 msg = PVE.Utils.extractRequestError(action.result, true);
406 break;
407 }
408 return msg;
409 },
410
411 // Ext.Ajax.request
412 API2Request: function(reqOpts) {
413
414 var newopts = Ext.apply({
415 waitMsg: gettext('Please wait...')
416 }, reqOpts);
417
418 if (!newopts.url.match(/^\/api2/)) {
419 newopts.url = '/api2/extjs' + newopts.url;
420 }
421 delete newopts.callback;
422
423 var createWrapper = function(successFn, callbackFn, failureFn) {
424 Ext.apply(newopts, {
425 success: function(response, options) {
426 if (options.waitMsgTarget) {
427 if (PVE.Utils.toolkit === 'touch') {
428 options.waitMsgTarget.setMasked(false);
429 } else {
430 options.waitMsgTarget.setLoading(false);
431 }
432 }
433 var result = Ext.decode(response.responseText);
434 response.result = result;
435 if (!result.success) {
436 response.htmlStatus = PVE.Utils.extractRequestError(result, true);
437 Ext.callback(callbackFn, options.scope, [options, false, response]);
438 Ext.callback(failureFn, options.scope, [response, options]);
439 return;
440 }
441 Ext.callback(callbackFn, options.scope, [options, true, response]);
442 Ext.callback(successFn, options.scope, [response, options]);
443 },
444 failure: function(response, options) {
445 if (options.waitMsgTarget) {
446 if (PVE.Utils.toolkit === 'touch') {
447 options.waitMsgTarget.setMasked(false);
448 } else {
449 options.waitMsgTarget.setLoading(false);
450 }
451 }
452 response.result = {};
453 try {
454 response.result = Ext.decode(response.responseText);
455 } catch(e) {}
456 var msg = gettext('Connection error') + ' - server offline?';
457 if (response.aborted) {
458 msg = gettext('Connection error') + ' - aborted.';
459 } else if (response.timedout) {
460 msg = gettext('Connection error') + ' - Timeout.';
461 } else if (response.status && response.statusText) {
462 msg = gettext('Connection error') + ' ' + response.status + ': ' + response.statusText;
463 }
464 response.htmlStatus = msg;
465 Ext.callback(callbackFn, options.scope, [options, false, response]);
466 Ext.callback(failureFn, options.scope, [response, options]);
467 }
468 });
469 };
470
471 createWrapper(reqOpts.success, reqOpts.callback, reqOpts.failure);
472
473 var target = newopts.waitMsgTarget;
474 if (target) {
475 if (PVE.Utils.toolkit === 'touch') {
476 target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg} );
477 } else {
478 // Note: ExtJS bug - this does not work when component is not rendered
479 target.setLoading(newopts.waitMsg);
480 }
481 }
482 Ext.Ajax.request(newopts);
483 },
484
485 assemble_field_data: function(values, data) {
486 if (Ext.isObject(data)) {
487 Ext.Object.each(data, function(name, val) {
488 if (values.hasOwnProperty(name)) {
489 var bucket = values[name];
490 if (!Ext.isArray(bucket)) {
491 bucket = values[name] = [bucket];
492 }
493 if (Ext.isArray(val)) {
494 values[name] = bucket.concat(val);
495 } else {
496 bucket.push(val);
497 }
498 } else {
499 values[name] = val;
500 }
501 });
502 }
503 },
504
505 checked_command: function(orig_cmd) {
506 PVE.Utils.API2Request({
507 url: '/nodes/localhost/subscription',
508 method: 'GET',
509 //waitMsgTarget: me,
510 failure: function(response, opts) {
511 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
512 },
513 success: function(response, opts) {
514 var data = response.result.data;
515
516 if (data.status !== 'Active') {
517 Ext.Msg.show({
518 title: gettext('No valid subscription'),
519 icon: Ext.Msg.WARNING,
520 msg: PVE.Utils.noSubKeyHtml,
521 buttons: Ext.Msg.OK,
522 callback: function(btn) {
523 if (btn !== 'ok') {
524 return;
525 }
526 orig_cmd();
527 }
528 });
529 } else {
530 orig_cmd();
531 }
532 }
533 });
534 },
535
536 task_desc_table: {
537 vncproxy: [ 'VM/CT', gettext('Console') ],
538 spiceproxy: [ 'VM/CT', gettext('Console') + ' (Spice)' ],
539 vncshell: [ '', gettext('Shell') ],
540 spiceshell: [ '', gettext('Shell') + ' (Spice)' ],
541 qmsnapshot: [ 'VM', gettext('Snapshot') ],
542 qmrollback: [ 'VM', gettext('Rollback') ],
543 qmdelsnapshot: [ 'VM', gettext('Delete Snapshot') ],
544 qmcreate: [ 'VM', gettext('Create') ],
545 qmrestore: [ 'VM', gettext('Restore') ],
546 qmdestroy: [ 'VM', gettext('Destroy') ],
547 qmigrate: [ 'VM', gettext('Migrate') ],
548 qmclone: [ 'VM', gettext('Clone') ],
549 qmmove: [ 'VM', gettext('Move disk') ],
550 qmtemplate: [ 'VM', gettext('Convert to template') ],
551 qmstart: [ 'VM', gettext('Start') ],
552 qmstop: [ 'VM', gettext('Stop') ],
553 qmreset: [ 'VM', gettext('Reset') ],
554 qmshutdown: [ 'VM', gettext('Shutdown') ],
555 qmsuspend: [ 'VM', gettext('Suspend') ],
556 qmresume: [ 'VM', gettext('Resume') ],
557 qmconfig: [ 'VM', gettext('Configure') ],
558 vzsnapshot: [ 'CT', gettext('Snapshot') ],
559 vzrollback: [ 'CT', gettext('Rollback') ],
560 vzdelsnapshot: [ 'CT', gettext('Delete Snapshot') ],
561 vzcreate: ['CT', gettext('Create') ],
562 vzrestore: ['CT', gettext('Restore') ],
563 vzdestroy: ['CT', gettext('Destroy') ],
564 vzmigrate: [ 'CT', gettext('Migrate') ],
565 vzclone: [ 'CT', gettext('Clone') ],
566 vztemplate: [ 'CT', gettext('Convert to template') ],
567 vzstart: ['CT', gettext('Start') ],
568 vzstop: ['CT', gettext('Stop') ],
569 vzmount: ['CT', gettext('Mount') ],
570 vzumount: ['CT', gettext('Unmount') ],
571 vzshutdown: ['CT', gettext('Shutdown') ],
572 vzsuspend: [ 'CT', gettext('Suspend') ],
573 vzresume: [ 'CT', gettext('Resume') ],
574 hamigrate: [ 'HA', gettext('Migrate') ],
575 hastart: [ 'HA', gettext('Start') ],
576 hastop: [ 'HA', gettext('Stop') ],
577 srvstart: ['SRV', gettext('Start') ],
578 srvstop: ['SRV', gettext('Stop') ],
579 srvrestart: ['SRV', gettext('Restart') ],
580 srvreload: ['SRV', gettext('Reload') ],
581 cephcreatemon: ['Ceph Monitor', gettext('Create') ],
582 cephdestroymon: ['Ceph Monitor', gettext('Destroy') ],
583 cephcreateosd: ['Ceph OSD', gettext('Create') ],
584 cephdestroyosd: ['Ceph OSD', gettext('Destroy') ],
585 imgcopy: ['', gettext('Copy data') ],
586 imgdel: ['', gettext('Erase data') ],
587 download: ['', gettext('Download') ],
588 vzdump: ['', gettext('Backup') ],
589 aptupdate: ['', gettext('Update package database') ],
590 startall: [ '', gettext('Start all VMs and Containers') ],
591 stopall: [ '', gettext('Stop all VMs and Containers') ],
592 migrateall: [ '', gettext('Migrate all VMs and Containers') ]
593 },
594
595 format_task_description: function(type, id) {
596 var farray = PVE.Utils.task_desc_table[type];
597 if (!farray) {
598 return type;
599 }
600 var prefix = farray[0];
601 var text = farray[1];
602 if (prefix) {
603 return prefix + ' ' + id + ' - ' + text;
604 }
605 return text;
606 },
607
608 parse_task_upid: function(upid) {
609 var task = {};
610
611 var res = upid.match(/^UPID:(\S+):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8,9}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/);
612 if (!res) {
613 throw "unable to parse upid '" + upid + "'";
614 }
615 task.node = res[1];
616 task.pid = parseInt(res[2], 16);
617 task.pstart = parseInt(res[3], 16);
618 task.starttime = parseInt(res[4], 16);
619 task.type = res[5];
620 task.id = res[6];
621 task.user = res[7];
622
623 task.desc = PVE.Utils.format_task_description(task.type, task.id);
624
625 return task;
626 },
627
628 format_size: function(size) {
629 /*jslint confusion: true */
630
631 var units = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
632 var num = 0;
633
634 while (size >= 1024 && ((num++)+1) < units.length) {
635 size = size / 1024;
636 }
637
638 return size.toFixed((num > 0)?2:0) + " " + units[num] + "B";
639 },
640
641 format_html_bar: function(per, text) {
642
643 return "<div class='pve-bar-wrap'>" + text + "<div class='pve-bar-border'>" +
644 "<div class='pve-bar-inner' style='width:" + per + "%;'></div>" +
645 "</div></div>";
646
647 },
648
649 format_cpu_bar: function(per1, per2, text) {
650
651 return "<div class='pve-bar-border'>" +
652 "<div class='pve-bar-inner' style='width:" + per1 + "%;'></div>" +
653 "<div class='pve-bar-inner2' style='width:" + per2 + "%;'></div>" +
654 "<div class='pve-bar-text'>" + text + "</div>" +
655 "</div>";
656 },
657
658 format_large_bar: function(per, text) {
659
660 if (!text) {
661 text = per.toFixed(1) + "%";
662 }
663
664 return "<div class='pve-largebar-border'>" +
665 "<div class='pve-largebar-inner' style='width:" + per + "%;'></div>" +
666 "<div class='pve-largebar-text'>" + text + "</div>" +
667 "</div>";
668 },
669
670 format_duration_long: function(ut) {
671
672 var days = Math.floor(ut / 86400);
673 ut -= days*86400;
674 var hours = Math.floor(ut / 3600);
675 ut -= hours*3600;
676 var mins = Math.floor(ut / 60);
677 ut -= mins*60;
678
679 var hours_str = '00' + hours.toString();
680 hours_str = hours_str.substr(hours_str.length - 2);
681 var mins_str = "00" + mins.toString();
682 mins_str = mins_str.substr(mins_str.length - 2);
683 var ut_str = "00" + ut.toString();
684 ut_str = ut_str.substr(ut_str.length - 2);
685
686 if (days) {
687 var ds = days > 1 ? PVE.Utils.daysText : PVE.Utils.dayText;
688 return days.toString() + ' ' + ds + ' ' +
689 hours_str + ':' + mins_str + ':' + ut_str;
690 } else {
691 return hours_str + ':' + mins_str + ':' + ut_str;
692 }
693 },
694
695 format_duration_short: function(ut) {
696
697 if (ut < 60) {
698 return ut.toString() + 's';
699 }
700
701 if (ut < 3600) {
702 var mins = ut / 60;
703 return mins.toFixed(0) + 'm';
704 }
705
706 if (ut < 86400) {
707 var hours = ut / 3600;
708 return hours.toFixed(0) + 'h';
709 }
710
711 var days = ut / 86400;
712 return days.toFixed(0) + 'd';
713 },
714
715 yesText: gettext('Yes'),
716 noText: gettext('No'),
717 noneText: gettext('none'),
718 errorText: gettext('Error'),
719 unknownText: gettext('Unknown'),
720 defaultText: gettext('Default'),
721 daysText: gettext('days'),
722 dayText: gettext('day'),
723 runningText: gettext('running'),
724 stoppedText: gettext('stopped'),
725 neverText: gettext('never'),
726 totalText: gettext('Total'),
727 usedText: gettext('Used'),
728 directoryText: gettext('Directory'),
729 imagesText: gettext('Disk image'),
730 backupFileText: gettext('VZDump backup file'),
731 vztmplText: gettext('Container template'),
732 isoImageText: gettext('ISO image'),
733 containersText: gettext('Container'),
734 stateText: gettext('State'),
735 groupText: gettext('Group'),
736
737 format_expire: function(date) {
738 if (!date) {
739 return PVE.Utils.neverText;
740 }
741 return Ext.Date.format(date, "Y-m-d");
742 },
743
744 format_storage_type: function(value) {
745 if (value === 'dir') {
746 return PVE.Utils.directoryText;
747 } else if (value === 'nfs') {
748 return 'NFS';
749 } else if (value === 'glusterfs') {
750 return 'GlusterFS';
751 } else if (value === 'lvm') {
752 return 'LVM';
753 } else if (value === 'lvmthin') {
754 return 'LVM-Thin';
755 } else if (value === 'iscsi') {
756 return 'iSCSI';
757 } else if (value === 'rbd') {
758 return 'RBD';
759 } else if (value === 'sheepdog') {
760 return 'Sheepdog';
761 } else if (value === 'zfs') {
762 return 'ZFS over iSCSI';
763 } else if (value === 'zfspool') {
764 return 'ZFS';
765 } else if (value === 'iscsidirect') {
766 return 'iSCSIDirect';
767 } else if (value === 'drbd') {
768 return 'DRBD';
769 } else {
770 return PVE.Utils.unknownText;
771 }
772 },
773
774 format_boolean_with_default: function(value) {
775 if (Ext.isDefined(value) && value !== '__default__') {
776 return value ? PVE.Utils.yesText : PVE.Utils.noText;
777 }
778 return PVE.Utils.defaultText;
779 },
780
781 format_boolean: function(value) {
782 return value ? PVE.Utils.yesText : PVE.Utils.noText;
783 },
784
785 format_neg_boolean: function(value) {
786 return !value ? PVE.Utils.yesText : PVE.Utils.noText;
787 },
788
789 format_ha: function(value) {
790 var text = PVE.Utils.format_boolean(value.managed);
791
792 if (value.managed) {
793 text += ', ' + PVE.Utils.stateText + ': ';
794 text += value.state || PVE.Utils.noneText;
795
796 text += ', ' + PVE.Utils.groupText + ': ';
797 text += value.group || PVE.Utils.noneText;
798 }
799
800 return text;
801 },
802
803 format_content_types: function(value) {
804 var cta = [];
805
806 Ext.each(value.split(',').sort(), function(ct) {
807 if (ct === 'images') {
808 cta.push(PVE.Utils.imagesText);
809 } else if (ct === 'backup') {
810 cta.push(PVE.Utils.backupFileText);
811 } else if (ct === 'vztmpl') {
812 cta.push(PVE.Utils.vztmplText);
813 } else if (ct === 'iso') {
814 cta.push(PVE.Utils.isoImageText);
815 } else if (ct === 'rootdir') {
816 cta.push(PVE.Utils.containersText);
817 }
818 });
819
820 return cta.join(', ');
821 },
822
823 render_storage_content: function(value, metaData, record) {
824 var data = record.data;
825 if (Ext.isNumber(data.channel) &&
826 Ext.isNumber(data.id) &&
827 Ext.isNumber(data.lun)) {
828 return "CH " +
829 Ext.String.leftPad(data.channel,2, '0') +
830 " ID " + data.id + " LUN " + data.lun;
831 }
832 return data.volid.replace(/^.*:(.*\/)?/,'');
833 },
834
835 render_serverity: function (value) {
836 return PVE.Utils.log_severity_hash[value] || value;
837 },
838
839 render_cpu: function(value, metaData, record, rowIndex, colIndex, store) {
840
841 if (!(record.data.uptime && Ext.isNumeric(value))) {
842 return '';
843 }
844
845 var maxcpu = record.data.maxcpu || 1;
846
847 if (!Ext.isNumeric(maxcpu) && (maxcpu >= 1)) {
848 return '';
849 }
850
851 var per = value * 100;
852
853 return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
854 },
855
856 render_size: function(value, metaData, record, rowIndex, colIndex, store) {
857 /*jslint confusion: true */
858
859 if (!Ext.isNumeric(value)) {
860 return '';
861 }
862
863 return PVE.Utils.format_size(value);
864 },
865
866 render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
867 var servertime = new Date(value * 1000);
868 return Ext.Date.format(servertime, 'Y-m-d H:i:s');
869 },
870
871 calculate_mem_usage: function(data) {
872 if (!Ext.isNumeric(data.mem) ||
873 data.maxmem === 0 ||
874 data.uptime < 1) {
875 return -1;
876 }
877
878 return (data.mem / data.maxmem);
879 },
880
881 render_mem_usage_percent: function(value, metaData, record, rowIndex, colIndex, store) {
882 if (!Ext.isNumeric(value) || value === -1) {
883 return '';
884 }
885 if (value > 1 ) {
886 // we got no percentage but bytes
887 var mem = value;
888 var maxmem = record.data.maxmem;
889 if (!record.data.uptime ||
890 maxmem === 0 ||
891 !Ext.isNumeric(mem)) {
892 return '';
893 }
894
895 return ((mem*100)/maxmem).toFixed(1) + " %";
896 }
897 return (value*100).toFixed(1) + " %";
898 },
899
900 render_mem_usage: function(value, metaData, record, rowIndex, colIndex, store) {
901
902 var mem = value;
903 var maxmem = record.data.maxmem;
904
905 if (!record.data.uptime) {
906 return '';
907 }
908
909 if (!(Ext.isNumeric(mem) && maxmem)) {
910 return '';
911 }
912
913 return PVE.Utils.render_size(value);
914 },
915
916 calculate_disk_usage: function(data) {
917
918 if (!Ext.isNumeric(data.disk) ||
919 data.type === 'qemu' ||
920 (data.type === 'lxc' && data.uptime === 0) ||
921 data.maxdisk === 0) {
922 return -1;
923 }
924
925 return (data.disk / data.maxdisk);
926 },
927
928 render_disk_usage_percent: function(value, metaData, record, rowIndex, colIndex, store) {
929 if (!Ext.isNumeric(value) || value === -1) {
930 return '';
931 }
932
933 return (value * 100).toFixed(1) + " %";
934 },
935
936 render_disk_usage: function(value, metaData, record, rowIndex, colIndex, store) {
937
938 var disk = value;
939 var maxdisk = record.data.maxdisk;
940 var type = record.data.type;
941
942 if (!Ext.isNumeric(disk) ||
943 type === 'qemu' ||
944 maxdisk === 0 ||
945 (type === 'lxc' && record.data.uptime === 0)) {
946 return '';
947 }
948
949 return PVE.Utils.render_size(value);
950 },
951
952 render_resource_type: function(value, metaData, record, rowIndex, colIndex, store) {
953
954 var icon = '';
955 var gridcls = '';
956
957 switch (value) {
958 case 'lxc': icon = 'cube';
959 gridcls = '-stopped';
960 break;
961 case 'qemu': icon = 'desktop';
962 gridcls = '-stopped';
963 break;
964 case 'node': icon = 'building';
965 gridcls = '-offline';
966 break;
967 case 'storage': icon = 'database'; break;
968 case 'pool': icon = 'tags'; break;
969 default: icon = 'file';
970 }
971
972 if (value === 'lxc' || value === 'qemu') {
973 if (record.data.running && record.data.status !== 'paused') {
974 gridcls = '-running';
975 } else if (record.data.running) {
976 gridcls = '-paused';
977 }
978 if (record.data.template) {
979 icon = 'file-o';
980 gridcls = '-template-' + value;
981 }
982 } else if (value === 'node') {
983 if (record.data.running) {
984 gridcls = '-online';
985 }
986 }
987
988 var fa = '<i class="fa fa-fw x-fa-grid' + gridcls + ' fa-' + icon + '"></i> ';
989 return fa + value;
990 },
991
992 render_uptime: function(value, metaData, record, rowIndex, colIndex, store) {
993
994 var uptime = value;
995
996 if (uptime === undefined) {
997 return '';
998 }
999
1000 if (uptime <= 0) {
1001 return '-';
1002 }
1003
1004 return PVE.Utils.format_duration_long(uptime);
1005 },
1006
1007 render_support_level: function(value, metaData, record) {
1008 return PVE.Utils.support_level_hash[value] || '-';
1009 },
1010
1011 render_upid: function(value, metaData, record) {
1012 var type = record.data.type;
1013 var id = record.data.id;
1014
1015 return PVE.Utils.format_task_description(type, id);
1016 },
1017
1018 dialog_title: function(subject, create, isAdd) {
1019 if (create) {
1020 if (isAdd) {
1021 return gettext('Add') + ': ' + subject;
1022 } else {
1023 return gettext('Create') + ': ' + subject;
1024 }
1025 } else {
1026 return gettext('Edit') + ': ' + subject;
1027 }
1028 },
1029
1030 windowHostname: function() {
1031 return window.location.hostname.replace(IP6_bracket_match,
1032 function(m, addr, offset, original) { return addr; });
1033 },
1034
1035 openDefaultConsoleWindow: function(allowSpice, vmtype, vmid, nodename, vmname) {
1036 var dv = PVE.Utils.defaultViewer(allowSpice);
1037 PVE.Utils.openConsoleWindow(dv, vmtype, vmid, nodename, vmname);
1038 },
1039
1040 openConsoleWindow: function(viewer, vmtype, vmid, nodename, vmname) {
1041 // kvm, lxc, shell, upgrade
1042
1043 if (vmid == undefined && (vmtype === 'kvm' || vmtype === 'lxc')) {
1044 throw "missing vmid";
1045 }
1046
1047 if (!nodename) {
1048 throw "no nodename specified";
1049 }
1050
1051 if (viewer === 'html5') {
1052 PVE.Utils.openVNCViewer(vmtype, vmid, nodename, vmname);
1053 } else if (viewer === 'vv') {
1054 var url;
1055 var params = { proxy: PVE.Utils.windowHostname() };
1056 if (vmtype === 'kvm') {
1057 url = '/nodes/' + nodename + '/qemu/' + vmid.toString() + '/spiceproxy';
1058 PVE.Utils.openSpiceViewer(url, params);
1059 } else if (vmtype === 'lxc') {
1060 url = '/nodes/' + nodename + '/lxc/' + vmid.toString() + '/spiceproxy';
1061 PVE.Utils.openSpiceViewer(url, params);
1062 } else if (vmtype === 'shell') {
1063 url = '/nodes/' + nodename + '/spiceshell';
1064 PVE.Utils.openSpiceViewer(url, params);
1065 } else if (vmtype === 'upgrade') {
1066 url = '/nodes/' + nodename + '/spiceshell';
1067 params.upgrade = 1;
1068 PVE.Utils.openSpiceViewer(url, params);
1069 }
1070 } else {
1071 throw "unknown viewer type";
1072 }
1073 },
1074
1075 defaultViewer: function(allowSpice) {
1076 var vncdefault = 'html5';
1077 var dv = PVE.VersionInfo.console || vncdefault;
1078 if (dv === 'vv' && !allowSpice) {
1079 dv = vncdefault;
1080 }
1081
1082 return dv;
1083 },
1084
1085 openVNCViewer: function(vmtype, vmid, nodename, vmname) {
1086 var url = Ext.urlEncode({
1087 console: vmtype, // kvm, lxc, upgrade or shell
1088 novnc: 1,
1089 vmid: vmid,
1090 vmname: vmname,
1091 node: nodename
1092 });
1093 var nw = window.open("?" + url, '_blank', "innerWidth=745,innerheight=427");
1094 nw.focus();
1095 },
1096
1097 openSpiceViewer: function(url, params){
1098
1099 var downloadWithName = function(uri, name) {
1100 var link = Ext.DomHelper.append(document.body, {
1101 tag: 'a',
1102 href: uri,
1103 css : 'display:none;visibility:hidden;height:0px;'
1104 });
1105
1106 // Note: we need to tell android the correct file name extension
1107 // but we do not set 'download' tag for other environments, because
1108 // It can have strange side effects (additional user prompt on firefox)
1109 var andriod = navigator.userAgent.match(/Android/i) ? true : false;
1110 if (andriod) {
1111 link.download = name;
1112 }
1113
1114 if (link.fireEvent) {
1115 link.fireEvent('onclick');
1116 } else {
1117 var evt = document.createEvent("MouseEvents");
1118 evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
1119 link.dispatchEvent(evt);
1120 }
1121 };
1122
1123 PVE.Utils.API2Request({
1124 url: url,
1125 params: params,
1126 method: 'POST',
1127 failure: function(response, opts){
1128 Ext.Msg.alert('Error', response.htmlStatus);
1129 },
1130 success: function(response, opts){
1131 var raw = "[virt-viewer]\n";
1132 Ext.Object.each(response.result.data, function(k, v) {
1133 raw += k + "=" + v + "\n";
1134 });
1135 var url = 'data:application/x-virt-viewer;charset=UTF-8,' +
1136 encodeURIComponent(raw);
1137
1138 downloadWithName(url, "pve-spice.vv");
1139 }
1140 });
1141 },
1142
1143 // comp.setLoading() is buggy in ExtJS 4.0.7, so we
1144 // use el.mask() instead
1145 setErrorMask: function(comp, msg) {
1146 var el = comp.el;
1147 if (!el) {
1148 return;
1149 }
1150 if (!msg) {
1151 el.unmask();
1152 } else {
1153 if (msg === true) {
1154 el.mask(gettext("Loading..."));
1155 } else {
1156 el.mask(msg);
1157 }
1158 }
1159 },
1160
1161 monStoreErrors: function(me, store) {
1162 me.mon(store, 'beforeload', function(s, operation, eOpts) {
1163 if (!me.loadCount) {
1164 me.loadCount = 0; // make sure it is numeric
1165 PVE.Utils.setErrorMask(me, true);
1166 }
1167 });
1168
1169 // only works with 'pve' proxy
1170 me.mon(store.proxy, 'afterload', function(proxy, request, success) {
1171 me.loadCount++;
1172
1173 if (success) {
1174 PVE.Utils.setErrorMask(me, false);
1175 return;
1176 }
1177
1178 var msg;
1179 /*jslint nomen: true */
1180 var operation = request._operation;
1181 var error = operation.getError();
1182 if (error.statusText) {
1183 msg = error.statusText + ' (' + error.status + ')';
1184 } else {
1185 msg = gettext('Connection error');
1186 }
1187 PVE.Utils.setErrorMask(me, msg);
1188 });
1189 },
1190
1191 createCmdMenu: function(v, record, item, index, event) {
1192 event.stopEvent();
1193 if (!(v instanceof Ext.tree.View)) {
1194 v.select(record);
1195 }
1196 var menu;
1197
1198 if (record.data.type === 'qemu' && !record.data.template) {
1199 menu = Ext.create('PVE.qemu.CmdMenu', {
1200 pveSelNode: record
1201 });
1202 } else if (record.data.type === 'qemu' && record.data.template) {
1203 menu = Ext.create('PVE.qemu.TemplateMenu', {
1204 pveSelNode: record
1205 });
1206 } else if (record.data.type === 'lxc' && !record.data.template) {
1207 menu = Ext.create('PVE.lxc.CmdMenu', {
1208 pveSelNode: record
1209 });
1210 } else if (record.data.type === 'lxc' && record.data.template) {
1211 /* since clone does not work reliably, disable for now
1212 menu = Ext.create('PVE.lxc.TemplateMenu', {
1213 pveSelNode: record
1214 });
1215 */
1216 return;
1217 } else {
1218 return;
1219 }
1220
1221 menu.showAt(event.getXY());
1222 }
1223 }});
1224