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