]> git.proxmox.com Git - proxmox-backup.git/blame - www/tape/ChangerStatus.js
tape: volume-statistics - use format_size to display byte counts
[proxmox-backup.git] / www / tape / ChangerStatus.js
CommitLineData
a83cedc2
DC
1Ext.define('pbs-slot-model', {
2 extend: 'Ext.data.Model',
24e84128 3 fields: ['entry-id', 'label-text', 'is-labeled', ' model', 'name', 'vendor', 'serial', 'state', 'status', 'pool',
a83cedc2
DC
4 {
5 name: 'is-blocked',
6 calculate: function(data) {
7 return data.state !== undefined;
8 },
9 },
10 ],
11 idProperty: 'entry-id',
12});
13
fd9aa8df
DC
14Ext.define('PBS.TapeManagement.ChangerStatus', {
15 extend: 'Ext.panel.Panel',
16 alias: 'widget.pbsChangerStatus',
17
18 viewModel: {
19 data: {
20 changer: '',
21 },
22
23 formulas: {
24 changerSelected: (get) => get('changer') !== '',
25 },
26 },
27
28 controller: {
29 xclass: 'Ext.app.ViewController',
30
31 changerChange: function(field, value) {
32 let me = this;
33 let view = me.getView();
34 let vm = me.getViewModel();
35 vm.set('changer', value);
36 if (view.rendered) {
37 me.reload();
38 }
39 },
40
58791864
DC
41 importTape: function(view, rI, cI, button, el, record) {
42 let me = this;
43 let vm = me.getViewModel();
44 let from = record.data['entry-id'];
45 let changer = encodeURIComponent(vm.get('changer'));
46 Ext.create('Proxmox.window.Edit', {
47 title: gettext('Import'),
48 isCreate: true,
49 submitText: gettext('OK'),
50 method: 'POST',
51 url: `/api2/extjs/tape/changer/${changer}/transfer`,
52 items: [
53 {
54 xtype: 'displayfield',
55 name: 'from',
56 value: from,
57 submitValue: true,
58 fieldLabel: gettext('From Slot'),
59 },
60 {
61 xtype: 'proxmoxintegerfield',
62 name: 'to',
63 fieldLabel: gettext('To Slot'),
64 },
65 ],
66 listeners: {
67 destroy: function() {
68 me.reload();
69 },
70 },
71 }).show();
72 },
73
fd9aa8df
DC
74 slotTransfer: function(view, rI, cI, button, el, record) {
75 let me = this;
76 let vm = me.getViewModel();
77 let from = record.data['entry-id'];
78 let changer = encodeURIComponent(vm.get('changer'));
79 Ext.create('Proxmox.window.Edit', {
80 title: gettext('Transfer'),
81 isCreate: true,
82 submitText: gettext('OK'),
83 method: 'POST',
84 url: `/api2/extjs/tape/changer/${changer}/transfer`,
85 items: [
86 {
87 xtype: 'displayfield',
88 name: 'from',
89 value: from,
90 submitValue: true,
91 fieldLabel: gettext('From Slot'),
92 },
93 {
94 xtype: 'proxmoxintegerfield',
95 name: 'to',
96 fieldLabel: gettext('To Slot'),
97 },
98 ],
99 listeners: {
100 destroy: function() {
101 me.reload();
102 },
103 },
104 }).show();
105 },
106
85205bc2
DC
107 erase: function(view, rI, cI, button, el, record) {
108 let me = this;
109 let vm = me.getViewModel();
110 let label = record.data['label-text'];
111
112 let changer = vm.get('changer');
113 Ext.create('PBS.TapeManagement.EraseWindow', {
114 label,
115 changer,
116 listeners: {
117 destroy: function() {
118 me.reload();
119 },
120 },
121 }).show();
122 },
123
fd9aa8df
DC
124 load: function(view, rI, cI, button, el, record) {
125 let me = this;
126 let vm = me.getViewModel();
127 let label = record.data['label-text'];
128
129 let changer = vm.get('changer');
130
131 Ext.create('Proxmox.window.Edit', {
132 isCreate: true,
918a3672 133 autoShow: true,
fd9aa8df
DC
134 submitText: gettext('OK'),
135 title: gettext('Load Media into Drive'),
136 url: `/api2/extjs/tape/drive`,
918a3672 137 method: 'POST',
fd9aa8df
DC
138 submitUrl: function(url, values) {
139 let drive = values.drive;
140 delete values.drive;
141 return `${url}/${encodeURIComponent(drive)}/load-media`;
142 },
143 items: [
144 {
145 xtype: 'displayfield',
146 name: 'label-text',
147 value: label,
148 submitValue: true,
149 fieldLabel: gettext('Media'),
150 },
151 {
152 xtype: 'pbsDriveSelector',
153 fieldLabel: gettext('Drive'),
154 changer: changer,
155 name: 'drive',
156 },
157 ],
158 listeners: {
159 destroy: function() {
160 me.reload();
161 },
162 },
918a3672 163 });
fd9aa8df
DC
164 },
165
166 unload: async function(view, rI, cI, button, el, record) {
167 let me = this;
168 let drive = record.data.name;
fd9aa8df 169 try {
4f688e09 170 await PBS.Async.api2({
918a3672 171 method: 'POST',
b0338178 172 timeout: 5*60*1000,
fd9aa8df
DC
173 url: `/api2/extjs/tape/drive/${encodeURIComponent(drive)}/unload`,
174 });
fd9aa8df
DC
175 } catch (error) {
176 Ext.Msg.alert(gettext('Error'), error);
fd9aa8df 177 }
4f688e09 178 me.reload();
fd9aa8df
DC
179 },
180
181 driveCommand: function(driveid, command, callback, params, method) {
182 let me = this;
183 let view = me.getView();
184 params = params || {};
185 method = method || 'GET';
186 Proxmox.Utils.API2Request({
187 url: `/api2/extjs/tape/drive/${driveid}/${command}`,
b0338178 188 timeout: 5*60*1000,
fd9aa8df
DC
189 method,
190 waitMsgTarget: view,
191 params,
192 success: function(response) {
193 callback(response);
194 },
195 failure: function(response) {
196 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
197 },
198 });
199 },
200
201 cartridgeMemory: function(view, rI, cI, button, el, record) {
202 let me = this;
203 let drive = record.data.name;
204 me.driveCommand(drive, 'cartridge-memory', function(response) {
205 Ext.create('Ext.window.Window', {
206 title: gettext('Cartridge Memory'),
207 modal: true,
208 width: 600,
209 height: 450,
210 layout: 'fit',
211 scrollable: true,
212 items: [
213 {
214 xtype: 'grid',
215 store: {
216 data: response.result.data,
217 },
218 columns: [
219 {
220 text: gettext('ID'),
221 dataIndex: 'id',
222 width: 60,
223 },
224 {
225 text: gettext('Name'),
226 dataIndex: 'name',
227 flex: 2,
228 },
229 {
230 text: gettext('Value'),
231 dataIndex: 'value',
232 flex: 1,
233 },
234 ],
235 },
236 ],
237 }).show();
238 });
239 },
240
241 cleanDrive: function(view, rI, cI, button, el, record) {
242 let me = this;
60473d23
DC
243 me.driveCommand(record.data.name, 'clean', function(response) {
244 me.reload();
fd9aa8df
DC
245 }, {}, 'PUT');
246 },
247
248 volumeStatistics: function(view, rI, cI, button, el, record) {
249 let me = this;
250 let drive = record.data.name;
251 me.driveCommand(drive, 'volume-statistics', function(response) {
e1beaae4
DM
252 let list = [];
253 for (let [key, val] of Object.entries(response.result.data)) {
41685061
DM
254 if (key === 'total-native-capacity' ||
255 key === 'total-used-native-capacity' ||
256 key === 'lifetime-bytes-read' ||
257 key === 'lifetime-bytes-written' ||
258 key === 'last-mount-bytes-read' ||
259 key === 'last-mount-bytes-written')
260 {
261 val = Proxmox.Utils.format_size(val);
262 }
e1beaae4
DM
263 list.push({ key: key, value: val });
264 }
fd9aa8df
DC
265 Ext.create('Ext.window.Window', {
266 title: gettext('Volume Statistics'),
267 modal: true,
268 width: 600,
269 height: 450,
270 layout: 'fit',
271 scrollable: true,
272 items: [
273 {
274 xtype: 'grid',
275 store: {
e1beaae4 276 data: list,
fd9aa8df
DC
277 },
278 columns: [
279 {
e1beaae4
DM
280 text: gettext('Property'),
281 dataIndex: 'key',
282 flex: 1,
fd9aa8df
DC
283 },
284 {
285 text: gettext('Value'),
286 dataIndex: 'value',
287 flex: 1,
288 },
289 ],
290 },
291 ],
292 }).show();
293 });
294 },
295
296 readLabel: function(view, rI, cI, button, el, record) {
297 let me = this;
298 let drive = record.data.name;
299 me.driveCommand(drive, 'read-label', function(response) {
965bd586
DM
300 let list = [];
301 for (let [key, val] of Object.entries(response.result.data)) {
302 if (key === 'ctime' || key === 'media-set-ctime') {
303 val = Proxmox.Utils.render_timestamp(val);
304 }
305 list.push({ key: key, value: val });
fd9aa8df
DC
306 }
307
965bd586 308 Ext.create('Ext.window.Window', {
fd9aa8df 309 title: gettext('Label Information'),
965bd586
DM
310 modal: true,
311 width: 600,
312 height: 450,
313 layout: 'fit',
314 scrollable: true,
315 items: [
316 {
317 xtype: 'grid',
318 store: {
319 data: list,
320 },
321 columns: [
322 {
323 text: gettext('Property'),
324 dataIndex: 'key',
325 width: 120,
326 },
327 {
328 text: gettext('Value'),
329 dataIndex: 'value',
330 flex: 1,
331 },
332 ],
333 },
334 ],
335 }).show();
fd9aa8df
DC
336 });
337 },
338
339 status: function(view, rI, cI, button, el, record) {
340 let me = this;
341 let drive = record.data.name;
342 me.driveCommand(drive, 'status', function(response) {
35a7ab57
DM
343 let list = [];
344 for (let [key, val] of Object.entries(response.result.data)) {
345 if (key === 'manufactured') {
346 val = Proxmox.Utils.render_timestamp(val);
347 }
348 if (key === 'bytes-read' || key === 'bytes-written') {
349 val = Proxmox.Utils.format_size(val);
350 }
351 list.push({ key: key, value: val });
fd9aa8df
DC
352 }
353
35a7ab57 354 Ext.create('Ext.window.Window', {
4d651378 355 title: gettext('Status'),
35a7ab57
DM
356 modal: true,
357 width: 600,
358 height: 450,
359 layout: 'fit',
360 scrollable: true,
361 items: [
362 {
363 xtype: 'grid',
364 store: {
365 data: list,
366 },
367 columns: [
368 {
369 text: gettext('Property'),
370 dataIndex: 'key',
371 width: 120,
372 },
373 {
374 text: gettext('Value'),
375 dataIndex: 'value',
376 flex: 1,
377 },
378 ],
379 },
380 ],
381 }).show();
fd9aa8df
DC
382 });
383 },
384
385 reloadList: function() {
386 let me = this;
387 me.lookup('changerselector').getStore().load();
388 },
389
390 barcodeLabel: function() {
391 let me = this;
392 let vm = me.getViewModel();
393 let changer = vm.get('changer');
394 if (changer === '') {
395 return;
396 }
397
398 Ext.create('Proxmox.window.Edit', {
399 title: gettext('Barcode Label'),
400 showTaskViewer: true,
b8d526f1 401 method: 'POST',
fd9aa8df
DC
402 url: '/api2/extjs/tape/drive',
403 submitUrl: function(url, values) {
404 let drive = values.drive;
405 delete values.drive;
406 return `${url}/${encodeURIComponent(drive)}/barcode-label-media`;
407 },
408
409 items: [
410 {
411 xtype: 'pbsDriveSelector',
412 fieldLabel: gettext('Drive'),
413 name: 'drive',
414 changer: changer,
415 },
416 {
417 xtype: 'pbsMediaPoolSelector',
418 fieldLabel: gettext('Pool'),
419 name: 'pool',
420 skipEmptyText: true,
421 allowBlank: true,
422 },
423 ],
424 }).show();
425 },
426
e2225aa8
DC
427 inventory: function() {
428 let me = this;
429 let vm = me.getViewModel();
430 let changer = vm.get('changer');
431 if (changer === '') {
432 return;
433 }
434
435 Ext.create('Proxmox.window.Edit', {
436 title: gettext('Inventory'),
437 showTaskViewer: true,
438 method: 'PUT',
439 url: '/api2/extjs/tape/drive',
440 submitUrl: function(url, values) {
441 let drive = values.drive;
442 delete values.drive;
443 return `${url}/${encodeURIComponent(drive)}/inventory`;
444 },
445
446 items: [
447 {
448 xtype: 'pbsDriveSelector',
449 fieldLabel: gettext('Drive'),
450 name: 'drive',
451 changer: changer,
452 },
453 ],
454 }).show();
455 },
456
076afa61
DC
457 scheduleReload: function(time) {
458 let me = this;
459 if (me.reloadTimeout === undefined) {
460 me.reloadTimeout = setTimeout(function() {
461 me.reload();
462 }, time);
463 }
464 },
465
cbd98993 466 reload: function() {
076afa61
DC
467 let me = this;
468 if (me.reloadTimeout !== undefined) {
469 clearTimeout(me.reloadTimeout);
470 me.reloadTimeout = undefined;
471 }
472 me.reload_full(true);
cbd98993
DM
473 },
474
475 reload_no_cache: function() {
076afa61
DC
476 let me = this;
477 if (me.reloadTimeout !== undefined) {
478 clearTimeout(me.reloadTimeout);
479 me.reloadTimeout = undefined;
480 }
481 me.reload_full(false);
cbd98993
DM
482 },
483
484 reload_full: async function(use_cache) {
fd9aa8df
DC
485 let me = this;
486 let view = me.getView();
487 let vm = me.getViewModel();
488 let changer = vm.get('changer');
489 if (changer === '') {
490 return;
491 }
492
493 try {
423e3cbd
DC
494 if (!use_cache) {
495 Proxmox.Utils.setErrorMask(view, true);
496 Proxmox.Utils.setErrorMask(me.lookup('content'));
497 }
42eef145 498 let status_fut = PBS.Async.api2({
b0338178 499 timeout: 5*60*1000,
cbd98993 500 method: 'GET',
fd9aa8df 501 url: `/api2/extjs/tape/changer/${encodeURIComponent(changer)}/status`,
cbd98993
DM
502 params: {
503 cache: use_cache,
504 },
fd9aa8df 505 });
42eef145 506 let drives_fut = PBS.Async.api2({
b0338178 507 timeout: 5*60*1000,
fd9aa8df
DC
508 url: `/api2/extjs/tape/drive?changer=${encodeURIComponent(changer)}`,
509 });
510
42eef145 511 let tapes_fut = PBS.Async.api2({
b0338178 512 timeout: 5*60*1000,
42eef145 513 url: '/api2/extjs/tape/media/list',
aa16b7b2
DM
514 method: 'GET',
515 params: {
6ef8e290 516 "update-status": false,
6d62e69f 517 },
42eef145
DC
518 });
519
520 let [status, drives, tapes_list] = await Promise.all([status_fut, drives_fut, tapes_fut]);
521
fd9aa8df
DC
522 let data = {
523 slot: [],
524 'import-export': [],
525 drive: [],
526 };
527
42eef145
DC
528 let tapes = {};
529
530 for (const tape of tapes_list.result.data) {
bc02c278
DC
531 tapes[tape['label-text']] = {
532 labeled: true,
533 pool: tape.pool,
534 status: tape.expired ? 'expired' : tape.status,
535 };
42eef145
DC
536 }
537
fd9aa8df
DC
538 let drive_entries = {};
539
540 for (const entry of drives.result.data) {
541 drive_entries[entry['changer-drivenum'] || 0] = entry;
542 }
543
544 for (let entry of status.result.data) {
545 let type = entry['entry-kind'];
546
547 if (type === 'drive' && drive_entries[entry['entry-id']] !== undefined) {
548 entry = Ext.applyIf(entry, drive_entries[entry['entry-id']]);
549 }
550
bc02c278
DC
551 if (tapes[entry['label-text']] !== undefined) {
552 entry['is-labeled'] = true;
553 entry.pool = tapes[entry['label-text']].pool;
554 entry.status = tapes[entry['label-text']].status;
555 } else {
556 entry['is-labeled'] = false;
557 }
42eef145 558
fd9aa8df
DC
559 data[type].push(entry);
560 }
561
a83cedc2
DC
562 // the stores are diffstores and are only refreshed
563 // on a 'load' event, which does not trigger on 'setData'
564 // so we have to fire them ourselves
fd9aa8df 565
a83cedc2
DC
566 me.lookup('slots').getStore().rstore.setData(data.slot);
567 me.lookup('slots').getStore().rstore.fireEvent('load', me, [], true);
568
569 me.lookup('import_export').getStore().rstore.setData(data['import-export']);
570 me.lookup('import_export').getStore().rstore.fireEvent('load', me, [], true);
571
572 me.lookup('drives').getStore().rstore.setData(data.drive);
573 me.lookup('drives').getStore().rstore.fireEvent('load', me, [], true);
fd9aa8df 574
423e3cbd
DC
575 if (!use_cache) {
576 Proxmox.Utils.setErrorMask(view);
577 }
578 Proxmox.Utils.setErrorMask(me.lookup('content'));
fd9aa8df 579 } catch (err) {
423e3cbd
DC
580 if (!use_cache) {
581 Proxmox.Utils.setErrorMask(view);
582 }
583 Proxmox.Utils.setErrorMask(me.lookup('content'), err.toString());
fd9aa8df 584 }
076afa61
DC
585
586 me.scheduleReload(5000);
fd9aa8df 587 },
4094fe5a
DC
588
589 renderIsLabeled: function(value, mD, record) {
590 if (!record.data['label-text']) {
591 return "";
592 }
593
594 if (record.data['label-text'].startsWith("CLN")) {
595 return "";
596 }
597
598 if (!value) {
599 return gettext('Not Labeled');
600 }
601
602 let status = record.data.status;
603 if (record.data.pool) {
604 return `${status} (${record.data.pool})`;
605 }
606 return status;
607 },
0d890ec4
DC
608
609 renderState: function(value, md, record) {
610 if (!value) {
611 return gettext('Idle');
612 }
613
614 let icon = '<i class="fa fa-spinner fa-pulse fa-fw"></i>';
615
616 if (value.startsWith("UPID")) {
617 let upid = Proxmox.Utils.parse_task_upid(value);
618 md.tdCls = "pointer";
619 return `${icon} ${upid.desc}`;
620 }
621
622 return `${icon} ${value}`;
623 },
624
625 control: {
626 'grid[reference=drives]': {
627 cellclick: function(table, td, ci, rec, tr, ri, e) {
f26276bc 628 if (e.position.column.dataIndex !== 'state') {
0d890ec4
DC
629 return;
630 }
631
632 let upid = rec.data.state;
633 if (!upid || !upid.startsWith("UPID")) {
634 return;
635 }
636
637 Ext.create('Proxmox.window.TaskViewer', {
638 autoShow: true,
639 upid,
640 });
641 },
642 },
643 },
fd9aa8df
DC
644 },
645
646 listeners: {
647 activate: 'reload',
648 },
649
650 tbar: [
651 {
652 fieldLabel: gettext('Changer'),
653 xtype: 'pbsChangerSelector',
654 reference: 'changerselector',
655 autoSelect: true,
656 listeners: {
657 change: 'changerChange',
658 },
659 },
660 '-',
661 {
662 text: gettext('Reload'),
663 xtype: 'proxmoxButton',
cbd98993 664 handler: 'reload_no_cache',
fd9aa8df
DC
665 selModel: false,
666 },
667 '-',
fd9aa8df
DC
668 {
669 text: gettext('Barcode Label'),
670 xtype: 'proxmoxButton',
671 handler: 'barcodeLabel',
672 iconCls: 'fa fa-barcode',
673 bind: {
674 disabled: '{!changerSelected}',
675 },
676 },
e2225aa8
DC
677 {
678 text: gettext('Inventory'),
679 xtype: 'proxmoxButton',
680 handler: 'inventory',
681 iconCls: 'fa fa-book',
682 bind: {
683 disabled: '{!changerSelected}',
684 },
685 },
fd9aa8df
DC
686 ],
687
688 layout: 'auto',
689 bodyPadding: 5,
690 scrollable: true,
691
692 items: [
693 {
694 xtype: 'container',
695 reference: 'content',
696 layout: {
697 type: 'hbox',
698 aling: 'stretch',
699 },
700 items: [
701 {
702 xtype: 'grid',
703 reference: 'slots',
704 title: gettext('Slots'),
705 padding: 5,
706 flex: 1,
707 store: {
a83cedc2
DC
708 type: 'diff',
709 rstore: {
710 type: 'store',
711 model: 'pbs-slot-model',
712 },
fd9aa8df
DC
713 data: [],
714 },
715 columns: [
716 {
970a70b4 717 text: gettext('ID'),
fd9aa8df
DC
718 dataIndex: 'entry-id',
719 width: 50,
720 },
721 {
722 text: gettext("Content"),
723 dataIndex: 'label-text',
724 flex: 1,
725 renderer: (value) => value || '',
726 },
42eef145 727 {
bc02c278 728 text: gettext('Inventory'),
42eef145 729 dataIndex: 'is-labeled',
4094fe5a 730 renderer: 'renderIsLabeled',
bc02c278 731 flex: 1,
42eef145 732 },
fd9aa8df
DC
733 {
734 text: gettext('Actions'),
735 xtype: 'actioncolumn',
736 width: 100,
737 items: [
738 {
739 iconCls: 'fa fa-rotate-90 fa-exchange',
740 handler: 'slotTransfer',
d2edc68e 741 tooltip: gettext('Transfer'),
fd9aa8df
DC
742 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
743 },
85205bc2
DC
744 {
745 iconCls: 'fa fa-trash-o',
746 handler: 'erase',
747 tooltip: gettext('Erase'),
748 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
749 },
fd9aa8df
DC
750 {
751 iconCls: 'fa fa-rotate-90 fa-upload',
752 handler: 'load',
d2edc68e 753 tooltip: gettext('Load'),
fd9aa8df
DC
754 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
755 },
756 ],
757 },
758 ],
759 },
760 {
761 xtype: 'container',
762 flex: 2,
763 defaults: {
764 padding: 5,
765 },
766 items: [
767 {
768 xtype: 'grid',
769 reference: 'drives',
770 title: gettext('Drives'),
771 store: {
a83cedc2
DC
772 type: 'diff',
773 rstore: {
774 type: 'store',
775 model: 'pbs-slot-model',
776 },
fd9aa8df
DC
777 data: [],
778 },
779 columns: [
780 {
970a70b4 781 text: gettext('ID'),
fd9aa8df 782 dataIndex: 'entry-id',
970a70b4 783 hidden: true,
fd9aa8df
DC
784 width: 50,
785 },
786 {
787 text: gettext("Content"),
788 dataIndex: 'label-text',
789 flex: 1,
790 renderer: (value) => value || '',
791 },
4094fe5a
DC
792 {
793 text: gettext('Inventory'),
794 dataIndex: 'is-labeled',
795 renderer: 'renderIsLabeled',
0d890ec4 796 flex: 1.5,
4094fe5a 797 },
fd9aa8df
DC
798 {
799 text: gettext("Name"),
800 sortable: true,
801 dataIndex: 'name',
802 flex: 1,
803 renderer: Ext.htmlEncode,
804 },
0d890ec4
DC
805 {
806 text: gettext('State'),
807 dataIndex: 'state',
808 flex: 3,
809 renderer: 'renderState',
810 },
fd9aa8df
DC
811 {
812 text: gettext("Vendor"),
813 sortable: true,
814 dataIndex: 'vendor',
0d890ec4 815 hidden: true,
fd9aa8df
DC
816 flex: 1,
817 renderer: Ext.htmlEncode,
818 },
819 {
820 text: gettext("Model"),
821 sortable: true,
822 dataIndex: 'model',
0d890ec4 823 hidden: true,
fd9aa8df
DC
824 flex: 1,
825 renderer: Ext.htmlEncode,
826 },
827 {
828 text: gettext("Serial"),
829 sortable: true,
830 dataIndex: 'serial',
0d890ec4 831 hidden: true,
fd9aa8df
DC
832 flex: 1,
833 renderer: Ext.htmlEncode,
834 },
835 {
836 xtype: 'actioncolumn',
837 text: gettext('Actions'),
838 width: 140,
839 items: [
840 {
841 iconCls: 'fa fa-rotate-270 fa-upload',
842 handler: 'unload',
d2edc68e 843 tooltip: gettext('Unload'),
e63457b6 844 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'] || rec.data['is-blocked'],
fd9aa8df
DC
845 },
846 {
847 iconCls: 'fa fa-hdd-o',
848 handler: 'cartridgeMemory',
d2edc68e 849 tooltip: gettext('Cartridge Memory'),
e63457b6 850 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'] || rec.data['is-blocked'],
fd9aa8df
DC
851 },
852 {
853 iconCls: 'fa fa-line-chart',
854 handler: 'volumeStatistics',
d2edc68e 855 tooltip: gettext('Volume Statistics'),
e63457b6 856 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'] || rec.data['is-blocked'],
fd9aa8df
DC
857 },
858 {
859 iconCls: 'fa fa-tag',
860 handler: 'readLabel',
d2edc68e 861 tooltip: gettext('Read Label'),
e63457b6 862 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'] || rec.data['is-blocked'],
fd9aa8df
DC
863 },
864 {
865 iconCls: 'fa fa-info-circle',
d2edc68e 866 tooltip: gettext('Status'),
fd9aa8df 867 handler: 'status',
e63457b6 868 isDisabled: (v, r, c, i, rec) => rec.data['is-blocked'],
fd9aa8df
DC
869 },
870 {
871 iconCls: 'fa fa-shower',
d2edc68e 872 tooltip: gettext('Clean Drive'),
fd9aa8df 873 handler: 'cleanDrive',
e63457b6 874 isDisabled: (v, r, c, i, rec) => rec.data['is-blocked'],
fd9aa8df
DC
875 },
876 ],
877 },
878 ],
879 },
880 {
881 xtype: 'grid',
882 reference: 'import_export',
883 store: {
a83cedc2
DC
884 type: 'diff',
885 rstore: {
886 type: 'store',
887 model: 'pbs-slot-model',
888 },
fd9aa8df
DC
889 data: [],
890 },
970a70b4 891 title: gettext('Import-Export Slots'),
fd9aa8df
DC
892 columns: [
893 {
970a70b4 894 text: gettext('ID'),
fd9aa8df
DC
895 dataIndex: 'entry-id',
896 width: 50,
897 },
898 {
899 text: gettext("Content"),
900 dataIndex: 'label-text',
901 renderer: (value) => value || '',
902 flex: 1,
903 },
4094fe5a
DC
904 {
905 text: gettext('Inventory'),
906 dataIndex: 'is-labeled',
907 renderer: 'renderIsLabeled',
908 flex: 1,
909 },
fd9aa8df
DC
910 {
911 text: gettext('Actions'),
58791864
DC
912 xtype: 'actioncolumn',
913 items: [
914 {
915 iconCls: 'fa fa-rotate-270 fa-upload',
916 handler: 'importTape',
917 tooltip: gettext('Import'),
918 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
919 },
920 ],
fd9aa8df
DC
921 width: 80,
922 },
923 ],
924 },
925 ],
926 },
927 ],
928 },
929 ],
930});