]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/HardwareView.js
ui: vm/HW: use more font awesome icons and fix color
[pve-manager.git] / www / manager6 / qemu / HardwareView.js
1 Ext.define('PVE.qemu.HardwareView', {
2 extend: 'Proxmox.grid.PendingObjectGrid',
3 alias: ['widget.PVE.qemu.HardwareView'],
4
5 onlineHelp: 'qm_virtual_machines_settings',
6
7 renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
8 var me = this;
9 var rows = me.rows;
10 var rowdef = rows[key] || {};
11 var iconCls = rowdef.iconCls;
12 var icon = '';
13 var txt = (rowdef.header || key);
14
15 metaData.tdAttr = "valign=middle";
16
17 if (rowdef.tdCls) {
18 metaData.tdCls = rowdef.tdCls;
19 if (rowdef.tdCls == 'pve-itype-icon-storage') {
20 var value = me.getObjectValue(key, '', false);
21 if (value === '') {
22 value = me.getObjectValue(key, '', true);
23 }
24 if (value.match(/vm-.*-cloudinit/)) {
25 icon = "<i class='pve-grid-fa fa fa-fw fa-cloud'></i>";
26 metaData.tdCls = " pve-itype-fa";
27 txt = rowdef.cloudheader;
28 } else if (value.match(/media=cdrom/)) {
29 metaData.tdCls = 'pve-itype-icon-cdrom';
30 return rowdef.cdheader;
31 }
32 }
33 } else if (iconCls) {
34 icon = "<i class='pve-grid-fa fa fa-fw fa-" + iconCls + "'></i>";
35 metaData.tdCls += " pve-itype-fa";
36 }
37
38 // only return icons in grid but not remove dialog
39 if (rowIndex !== undefined) {
40 return icon + txt;
41 } else {
42 return txt;
43 }
44 },
45
46 initComponent : function() {
47 var me = this;
48 var i, confid;
49
50 var nodename = me.pveSelNode.data.node;
51 if (!nodename) {
52 throw "no node name specified";
53 }
54
55 var vmid = me.pveSelNode.data.vmid;
56 if (!vmid) {
57 throw "no VM ID specified";
58 }
59
60 var caps = Ext.state.Manager.get('GuiCap');
61 var diskCap = caps.vms['VM.Config.Disk'];
62
63 /*jslint confusion: true */
64 var rows = {
65 memory: {
66 header: gettext('Memory'),
67 editor: caps.vms['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined,
68 never_delete: true,
69 defaultValue: '512',
70 tdCls: 'pve-itype-icon-memory',
71 group: 2,
72 multiKey: ['memory', 'balloon', 'shares'],
73 renderer: function(value, metaData, record, ri, ci, store, pending) {
74 var res = '';
75
76 var max = me.getObjectValue('memory', 512, pending);
77 var balloon = me.getObjectValue('balloon', undefined, pending);
78 var shares = me.getObjectValue('shares', undefined, pending);
79
80 res = Proxmox.Utils.format_size(max*1024*1024);
81
82 if (balloon !== undefined && balloon > 0) {
83 res = Proxmox.Utils.format_size(balloon*1024*1024) + "/" + res;
84
85 if (shares) {
86 res += ' [shares=' + shares +']';
87 }
88 } else if (balloon === 0) {
89 res += ' [balloon=0]';
90 }
91 return res;
92 }
93 },
94 sockets: {
95 header: gettext('Processors'),
96 never_delete: true,
97 editor: (caps.vms['VM.Config.CPU'] || caps.vms['VM.Config.HWType']) ?
98 'PVE.qemu.ProcessorEdit' : undefined,
99 tdCls: 'pve-itype-icon-processor',
100 group: 3,
101 defaultValue: '1',
102 multiKey: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'],
103 renderer: function(value, metaData, record, rowIndex, colIndex, store, pending) {
104
105 var sockets = me.getObjectValue('sockets', 1, pending);
106 var model = me.getObjectValue('cpu', undefined, pending);
107 var cores = me.getObjectValue('cores', 1, pending);
108 var numa = me.getObjectValue('numa', undefined, pending);
109 var vcpus = me.getObjectValue('vcpus', undefined, pending);
110 var cpulimit = me.getObjectValue('cpulimit', undefined, pending);
111 var cpuunits = me.getObjectValue('cpuunits', undefined, pending);
112
113 var res = Ext.String.format('{0} ({1} sockets, {2} cores)',
114 sockets*cores, sockets, cores);
115
116 if (model) {
117 res += ' [' + model + ']';
118 }
119
120 if (numa) {
121 res += ' [numa=' + numa +']';
122 }
123
124 if (vcpus) {
125 res += ' [vcpus=' + vcpus +']';
126 }
127
128 if (cpulimit) {
129 res += ' [cpulimit=' + cpulimit +']';
130 }
131
132 if (cpuunits) {
133 res += ' [cpuunits=' + cpuunits +']';
134 }
135
136 return res;
137 }
138 },
139 bios: {
140 header: 'BIOS',
141 group: 4,
142 never_delete: true,
143 editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.BiosEdit' : undefined,
144 defaultValue: '',
145 iconCls: 'microchip',
146 renderer: PVE.Utils.render_qemu_bios
147 },
148 vga: {
149 header: gettext('Display'),
150 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined,
151 never_delete: true,
152 tdCls: 'pve-itype-icon-display',
153 group:5,
154 defaultValue: '',
155 renderer: PVE.Utils.render_kvm_vga_driver
156 },
157 machine: {
158 header: gettext('Machine'),
159 editor: caps.vms['VM.Config.HWType'] ? {
160 xtype: 'proxmoxWindowEdit',
161 subject: gettext('Machine'),
162 width: 350,
163 items: [{
164 xtype: 'proxmoxKVComboBox',
165 name: 'machine',
166 value: '__default__',
167 fieldLabel: gettext('Machine'),
168 comboItems: [
169 ['__default__', PVE.Utils.render_qemu_machine('')],
170 ['q35', 'q35']
171 ]
172 }]} : undefined,
173 iconCls: 'cogs',
174 never_delete: true,
175 group: 6,
176 defaultValue: '',
177 renderer: PVE.Utils.render_qemu_machine
178 },
179 scsihw: {
180 header: gettext('SCSI Controller'),
181 iconCls: 'database',
182 editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.ScsiHwEdit' : undefined,
183 renderer: PVE.Utils.render_scsihw,
184 group: 7,
185 never_delete: true,
186 defaultValue: ''
187 },
188 cores: {
189 visible: false
190 },
191 cpu: {
192 visible: false
193 },
194 numa: {
195 visible: false
196 },
197 balloon: {
198 visible: false
199 },
200 hotplug: {
201 visible: false
202 },
203 vcpus: {
204 visible: false
205 },
206 cpuunits: {
207 visible: false
208 },
209 cpulimit: {
210 visible: false
211 },
212 shares: {
213 visible: false
214 }
215 };
216 /*jslint confusion: false */
217
218 PVE.Utils.forEachBus(undefined, function(type, id) {
219 var confid = type + id;
220 rows[confid] = {
221 group: 10,
222 tdCls: 'pve-itype-icon-storage',
223 editor: 'PVE.qemu.HDEdit',
224 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
225 header: gettext('Hard Disk') + ' (' + confid +')',
226 cdheader: gettext('CD/DVD Drive') + ' (' + confid +')',
227 cloudheader: gettext('CloudInit Drive') + ' (' + confid + ')'
228 };
229 });
230 for (i = 0; i < 32; i++) {
231 confid = "net" + i.toString();
232 rows[confid] = {
233 group: 15,
234 order: i,
235 tdCls: 'pve-itype-icon-network',
236 editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
237 never_delete: caps.vms['VM.Config.Network'] ? false : true,
238 header: gettext('Network Device') + ' (' + confid +')'
239 };
240 }
241 rows.efidisk0 = {
242 group: 20,
243 tdCls: 'pve-itype-icon-storage',
244 editor: null,
245 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
246 header: gettext('EFI Disk')
247 };
248 for (i = 0; i < 5; i++) {
249 confid = "usb" + i.toString();
250 rows[confid] = {
251 group: 25,
252 order: i,
253 iconCls: 'usb',
254 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined,
255 never_delete: caps.nodes['Sys.Console'] ? false : true,
256 header: gettext('USB Device') + ' (' + confid + ')'
257 };
258 }
259 for (i = 0; i < 4; i++) {
260 confid = "hostpci" + i.toString();
261 rows[confid] = {
262 group: 30,
263 order: i,
264 tdCls: 'pve-itype-icon-pci',
265 never_delete: caps.nodes['Sys.Console'] ? false : true,
266 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined,
267 header: gettext('PCI Device') + ' (' + confid + ')'
268 };
269 }
270 for (i = 0; i < 4; i++) {
271 confid = "serial" + i.toString();
272 rows[confid] = {
273 group: 35,
274 order: i,
275 tdCls: 'pve-itype-icon-serial',
276 never_delete: caps.nodes['Sys.Console'] ? false : true,
277 header: gettext('Serial Port') + ' (' + confid + ')'
278 };
279 }
280 rows.audio0 = {
281 group: 40,
282 iconCls: 'volume-up',
283 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.AudioEdit' : undefined,
284 never_delete: caps.vms['VM.Config.HWType'] ? false : true,
285 header: gettext('Audio Device')
286 };
287 for (i = 0; i < 256; i++) {
288 rows["unused" + i.toString()] = {
289 group: 99,
290 order: i,
291 tdCls: 'pve-itype-icon-storage',
292 editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
293 header: gettext('Unused Disk') + ' ' + i.toString()
294 };
295 }
296
297 var sorterFn = function(rec1, rec2) {
298 var v1 = rec1.data.key;
299 var v2 = rec2.data.key;
300 var g1 = rows[v1].group || 0;
301 var g2 = rows[v2].group || 0;
302 var order1 = rows[v1].order || 0;
303 var order2 = rows[v2].order || 0;
304
305 if ((g1 - g2) !== 0) {
306 return g1 - g2;
307 }
308
309 if ((order1 - order2) !== 0) {
310 return order1 - order2;
311 }
312
313 if (v1 > v2) {
314 return 1;
315 } else if (v1 < v2) {
316 return -1;
317 } else {
318 return 0;
319 }
320 };
321
322 var reload = function() {
323 me.rstore.load();
324 };
325
326 var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
327
328 var sm = Ext.create('Ext.selection.RowModel', {});
329
330 var run_editor = function() {
331 var rec = sm.getSelection()[0];
332 if (!rec) {
333 return;
334 }
335
336 var rowdef = rows[rec.data.key];
337 if (!rowdef.editor) {
338 return;
339 }
340
341 var editor = rowdef.editor;
342 if (rowdef.tdCls == 'pve-itype-icon-storage') {
343 var value = me.getObjectValue(rec.data.key, '', true);
344 if (value.match(/vm-.*-cloudinit/)) {
345 return;
346 } else if (value.match(/media=cdrom/)) {
347 editor = 'PVE.qemu.CDEdit';
348 } else if (!diskCap) {
349 return;
350 }
351 }
352
353 var win;
354
355 if (Ext.isString(editor)) {
356 win = Ext.create(editor, {
357 pveSelNode: me.pveSelNode,
358 confid: rec.data.key,
359 url: '/api2/extjs/' + baseurl
360 });
361 } else {
362 var config = Ext.apply({
363 pveSelNode: me.pveSelNode,
364 confid: rec.data.key,
365 url: '/api2/extjs/' + baseurl
366 }, rowdef.editor);
367 win = Ext.createWidget(rowdef.editor.xtype, config);
368 win.load();
369 }
370
371 win.show();
372 win.on('destroy', reload);
373 };
374
375 var run_resize = function() {
376 var rec = sm.getSelection()[0];
377 if (!rec) {
378 return;
379 }
380
381 var win = Ext.create('PVE.window.HDResize', {
382 disk: rec.data.key,
383 nodename: nodename,
384 vmid: vmid
385 });
386
387 win.show();
388
389 win.on('destroy', reload);
390 };
391
392 var run_move = function() {
393 var rec = sm.getSelection()[0];
394 if (!rec) {
395 return;
396 }
397
398 var win = Ext.create('PVE.window.HDMove', {
399 disk: rec.data.key,
400 nodename: nodename,
401 vmid: vmid
402 });
403
404 win.show();
405
406 win.on('destroy', reload);
407 };
408
409 var edit_btn = new Proxmox.button.Button({
410 text: gettext('Edit'),
411 selModel: sm,
412 disabled: true,
413 handler: run_editor
414 });
415
416 var resize_btn = new Proxmox.button.Button({
417 text: gettext('Resize disk'),
418 selModel: sm,
419 disabled: true,
420 handler: run_resize
421 });
422
423 var move_btn = new Proxmox.button.Button({
424 text: gettext('Move disk'),
425 selModel: sm,
426 disabled: true,
427 handler: run_move
428 });
429
430 var remove_btn = new Proxmox.button.Button({
431 text: gettext('Remove'),
432 defaultText: gettext('Remove'),
433 altText: gettext('Detach'),
434 selModel: sm,
435 disabled: true,
436 dangerous: true,
437 RESTMethod: 'PUT',
438 confirmMsg: function(rec) {
439 var warn = gettext('Are you sure you want to remove entry {0}');
440 if (this.text === this.altText) {
441 warn = gettext('Are you sure you want to detach entry {0}');
442 }
443
444 var entry = rec.data.key;
445 var rendered = me.renderKey(entry, {}, rec);
446 var msg = Ext.String.format(warn, "'" + rendered + "'");
447
448 if (entry.match(/^unused\d+$/)) {
449 msg += " " + gettext('This will permanently erase all data.');
450 }
451
452 return msg;
453 },
454 handler: function(b, e, rec) {
455 Proxmox.Utils.API2Request({
456 url: '/api2/extjs/' + baseurl,
457 waitMsgTarget: me,
458 method: b.RESTMethod,
459 params: {
460 'delete': rec.data.key
461 },
462 callback: function() {
463 reload();
464 },
465 failure: function (response, opts) {
466 Ext.Msg.alert('Error', response.htmlStatus);
467 },
468 success: function(response, options) {
469 if (b.RESTMethod === 'POST') {
470 var upid = response.result.data;
471 var win = Ext.create('Proxmox.window.TaskProgress', {
472 upid: upid,
473 listeners: {
474 destroy: function () {
475 me.reload();
476 }
477 }
478 });
479 win.show();
480 }
481 }
482 });
483 },
484 listeners: {
485 render: function(btn) {
486 // hack: calculate an optimal button width on first display
487 // to prevent the whole toolbar to move when we switch
488 // between the "Remove" and "Detach" labels
489 var def = btn.getSize().width;
490
491 btn.setText(btn.altText);
492 var alt = btn.getSize().width;
493
494 btn.setText(btn.defaultText);
495
496 var optimal = alt > def ? alt : def;
497 btn.setSize({ width: optimal });
498 }
499 }
500 });
501
502 var revert_btn = new Proxmox.button.Button({
503 text: gettext('Revert'),
504 selModel: sm,
505 disabled: true,
506 handler: function(b, e, rec) {
507 var rowdef = me.rows[rec.data.key] || {};
508 var keys = rowdef.multiKey || [ rec.data.key ];
509 var revert = keys.join(',');
510 Proxmox.Utils.API2Request({
511 url: '/api2/extjs/' + baseurl,
512 waitMsgTarget: me,
513 method: 'PUT',
514 params: {
515 'revert': revert
516 },
517 callback: function() {
518 reload();
519 },
520 failure: function (response, opts) {
521 Ext.Msg.alert('Error',response.htmlStatus);
522 }
523 });
524 }
525 });
526
527 var efidisk_menuitem = Ext.create('Ext.menu.Item',{
528 text: gettext('EFI Disk'),
529 iconCls: 'pve-itype-icon-storage',
530 disabled: !caps.vms['VM.Config.Disk'],
531 handler: function() {
532
533 var rstoredata = me.rstore.getData().map;
534 // check if ovmf is configured
535 if (rstoredata.bios && rstoredata.bios.data.value === 'ovmf') {
536 var win = Ext.create('PVE.qemu.EFIDiskEdit', {
537 url: '/api2/extjs/' + baseurl,
538 pveSelNode: me.pveSelNode
539 });
540 win.on('destroy', reload);
541 win.show();
542 } else {
543 Ext.Msg.alert('Error',gettext('Please select OVMF(UEFI) as BIOS first.'));
544 }
545
546 }
547 });
548
549 var set_button_status = function() {
550 var sm = me.getSelectionModel();
551 var rec = sm.getSelection()[0];
552
553 // disable button when we have an efidisk already
554 // disable is ok in this case, because you can instantly
555 // see that there is already one
556 efidisk_menuitem.setDisabled(me.rstore.getData().map.efidisk0 !== undefined);
557 // en/disable usb add button
558 var usbcount = 0;
559 var pcicount = 0;
560 var audiocount = 0;
561 var hasCloudInit = false;
562 me.rstore.getData().items.forEach(function(item){
563 if (/^usb\d+/.test(item.id)) {
564 usbcount++;
565 } else if (/^hostpci\d+/.test(item.id)) {
566 pcicount++;
567 } else if (/^audio\d+/.test(item.id)) {
568 audiocount++;
569 }
570 if (!hasCloudInit && /vm-.*-cloudinit/.test(item.data.value)) {
571 hasCloudInit = true;
572 }
573 });
574
575 // heuristic only for disabling some stuff, the backend has the final word.
576 var noSysConsolePerm = !caps.nodes['Sys.Console'];
577 var noVMConfigHWTypePerm = !caps.vms['VM.Config.HWType'];
578
579 me.down('#addusb').setDisabled(noSysConsolePerm || (usbcount >= 5));
580 me.down('#addpci').setDisabled(noSysConsolePerm || (pcicount >= 4));
581 me.down('#addaudio').setDisabled(noVMConfigHWTypePerm || (audiocount >= 1));
582 me.down('#addci').setDisabled(noSysConsolePerm || hasCloudInit);
583
584 if (!rec) {
585 remove_btn.disable();
586 edit_btn.disable();
587 resize_btn.disable();
588 move_btn.disable();
589 revert_btn.disable();
590 return;
591 }
592 var key = rec.data.key;
593 var value = rec.data.value;
594 var rowdef = rows[key];
595
596 var pending = rec.data['delete'] || me.hasPendingChanges(key);
597 var isCDRom = (value && !!value.toString().match(/media=cdrom/));
598 var isUnusedDisk = key.match(/^unused\d+/);
599 var isUsedDisk = !isUnusedDisk &&
600 rowdef.tdCls == 'pve-itype-icon-storage' &&
601 !isCDRom;
602
603 var isCloudInit = (value && value.toString().match(/vm-.*-cloudinit/));
604
605 var isEfi = (key === 'efidisk0');
606
607 remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true) || (isUnusedDisk && !diskCap));
608 remove_btn.setText((isUsedDisk && !isCloudInit) ? remove_btn.altText : remove_btn.defaultText);
609 remove_btn.RESTMethod = isUnusedDisk ? 'POST':'PUT';
610
611 edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor || isCloudInit || (!isCDRom && !diskCap));
612
613 resize_btn.setDisabled(pending || !isUsedDisk || !diskCap);
614
615 move_btn.setDisabled(pending || !isUsedDisk || !diskCap);
616
617 revert_btn.setDisabled(!pending);
618
619 };
620
621 Ext.apply(me, {
622 url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending',
623 interval: 5000,
624 selModel: sm,
625 run_editor: run_editor,
626 tbar: [
627 {
628 text: gettext('Add'),
629 menu: new Ext.menu.Menu({
630 items: [
631 {
632 text: gettext('Hard Disk'),
633 iconCls: 'pve-itype-icon-storage',
634 disabled: !caps.vms['VM.Config.Disk'],
635 handler: function() {
636 var win = Ext.create('PVE.qemu.HDEdit', {
637 url: '/api2/extjs/' + baseurl,
638 pveSelNode: me.pveSelNode
639 });
640 win.on('destroy', reload);
641 win.show();
642 }
643 },
644 {
645 text: gettext('CD/DVD Drive'),
646 iconCls: 'pve-itype-icon-cdrom',
647 disabled: !caps.vms['VM.Config.Disk'],
648 handler: function() {
649 var win = Ext.create('PVE.qemu.CDEdit', {
650 url: '/api2/extjs/' + baseurl,
651 pveSelNode: me.pveSelNode
652 });
653 win.on('destroy', reload);
654 win.show();
655 }
656 },
657 {
658 text: gettext('Network Device'),
659 iconCls: 'pve-itype-icon-network',
660 disabled: !caps.vms['VM.Config.Network'],
661 handler: function() {
662 var win = Ext.create('PVE.qemu.NetworkEdit', {
663 url: '/api2/extjs/' + baseurl,
664 pveSelNode: me.pveSelNode,
665 isCreate: true
666 });
667 win.on('destroy', reload);
668 win.show();
669 }
670 },
671 efidisk_menuitem,
672 {
673 text: gettext('USB Device'),
674 itemId: 'addusb',
675 iconCls: 'fa fa-usb black',
676 disabled: !caps.nodes['Sys.Console'],
677 handler: function() {
678 var win = Ext.create('PVE.qemu.USBEdit', {
679 url: '/api2/extjs/' + baseurl,
680 pveSelNode: me.pveSelNode
681 });
682 win.on('destroy', reload);
683 win.show();
684 }
685 },
686 {
687 text: gettext('PCI Device'),
688 itemId: 'addpci',
689 iconCls: 'pve-itype-icon-pci',
690 disabled: !caps.nodes['Sys.Console'],
691 handler: function() {
692 var win = Ext.create('PVE.qemu.PCIEdit', {
693 url: '/api2/extjs/' + baseurl,
694 pveSelNode: me.pveSelNode
695 });
696 win.on('destroy', reload);
697 win.show();
698 }
699 },
700 {
701 text: gettext('Serial Port'),
702 itemId: 'addserial',
703 iconCls: 'pve-itype-icon-serial',
704 disabled: !caps.vms['VM.Config.Options'],
705 handler: function() {
706 var win = Ext.create('PVE.qemu.SerialEdit', {
707 url: '/api2/extjs/' + baseurl
708 });
709 win.on('destroy', reload);
710 win.show();
711 }
712 },
713 {
714 text: gettext('CloudInit Drive'),
715 itemId: 'addci',
716 iconCls: 'fa fa-cloud black',
717 disabled: !caps.nodes['Sys.Console'],
718 handler: function() {
719 var win = Ext.create('PVE.qemu.CIDriveEdit', {
720 url: '/api2/extjs/' + baseurl,
721 pveSelNode: me.pveSelNode
722 });
723 win.on('destroy', reload);
724 win.show();
725 }
726 },
727 {
728 text: gettext('Audio Device'),
729 itemId: 'addaudio',
730 iconCls: 'fa fa-volume-up black',
731 disabled: !caps.vms['VM.Config.HWType'],
732 handler: function() {
733 var win = Ext.create('PVE.qemu.AudioEdit', {
734 url: '/api2/extjs/' + baseurl,
735 isCreate: true,
736 isAdd: true
737 });
738 win.on('destroy', reload);
739 win.show();
740 }
741 }
742 ]
743 })
744 },
745 remove_btn,
746 edit_btn,
747 resize_btn,
748 move_btn,
749 revert_btn
750 ],
751 rows: rows,
752 sorterFn: sorterFn,
753 listeners: {
754 itemdblclick: run_editor,
755 selectionchange: set_button_status
756 }
757 });
758
759 me.callParent();
760
761 me.on('activate', me.rstore.startUpdate);
762 me.on('destroy', me.rstore.stopUpdate);
763
764 me.mon(me.rstore, 'refresh', function() {
765 set_button_status();
766 });
767 }
768 });