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