1 Ext
.define('pbs-slot-model', {
2 extend
: 'Ext.data.Model',
3 fields
: ['entry-id', 'label-text', 'is-labeled', ' model', 'name', 'vendor', 'serial', 'state', 'status', 'pool',
6 calculate: function(data
) {
7 return data
.state
!== undefined;
11 idProperty
: 'entry-id',
14 Ext
.define('PBS.TapeManagement.FreeSlotSelector', {
15 extend
: 'Proxmox.form.ComboGrid',
16 alias
: 'widget.pbsFreeSlotSelector',
30 text
: gettext('Type'),
37 Ext
.define('PBS.TapeManagement.ChangerStatus', {
38 extend
: 'Ext.panel.Panel',
39 alias
: 'widget.pbsChangerStatus',
41 tools
: [PBS
.Utils
.get_help_tool("tape_backup")],
44 xclass
: 'Ext.app.ViewController',
46 importTape: function(v
, rI
, cI
, button
, el
, record
) {
48 let view
= me
.getView();
49 let from = record
.data
['entry-id'];
50 let changer
= encodeURIComponent(view
.changer
);
51 Ext
.create('Proxmox.window.Edit', {
52 title
: gettext('Import'),
54 submitText
: gettext('OK'),
56 url
: `/api2/extjs/tape/changer/${changer}/transfer`,
59 xtype
: 'displayfield',
63 fieldLabel
: gettext('From Slot'),
66 xtype
: 'pbsFreeSlotSelector',
68 fieldLabel
: gettext('To Slot'),
82 slotTransfer: function(v
, rI
, cI
, button
, el
, record
) {
84 let view
= me
.getView();
85 let from = record
.data
['entry-id'];
86 let changer
= encodeURIComponent(view
.changer
);
87 Ext
.create('Proxmox.window.Edit', {
88 title
: gettext('Transfer'),
90 submitText
: gettext('OK'),
92 url
: `/api2/extjs/tape/changer/${changer}/transfer`,
95 xtype
: 'displayfield',
99 fieldLabel
: gettext('From Slot'),
102 xtype
: 'pbsFreeSlotSelector',
104 fieldLabel
: gettext('To Slot'),
106 data
: me
.free_slots
.concat(me
.free_ie_slots
),
111 destroy: function() {
118 labelMedia: function(button
, event
, record
) {
120 Ext
.create('PBS.TapeManagement.LabelMediaWindow', {
121 driveid
: record
.data
.name
,
122 label
: record
.data
["label-text"],
126 catalog: function(button
, event
, record
) {
129 let view
= me
.getView();
130 PBS
.Utils
.driveCommand(record
.data
.name
, 'catalog', {
133 success: function(response
) {
134 Ext
.create('Proxmox.window.TaskViewer', {
135 upid
: response
.result
.data
,
141 erase: function(v
, rI
, cI
, button
, el
, record
) {
143 let view
= me
.getView();
144 let label
= record
.data
['label-text'];
146 let changer
= encodeURIComponent(view
.changer
);
147 let singleDrive
= me
.drives
.length
=== 1 ? me
.drives
[0] : undefined;
148 Ext
.create('PBS.TapeManagement.EraseWindow', {
153 destroy: function() {
160 load: function(v
, rI
, cI
, button
, el
, record
) {
162 let view
= me
.getView();
163 let label
= record
.data
['label-text'];
165 let changer
= encodeURIComponent(view
.changer
);
166 let singleDrive
= me
.drives
.length
=== 1 ? me
.drives
[0] : undefined;
168 if (singleDrive
!== undefined) {
169 Proxmox
.Utils
.API2Request({
174 url
: `/api2/extjs/tape/drive/${singleDrive}/load-media`,
175 success: function(response
, opt
) {
176 Ext
.create('Proxmox.window.TaskProgress', {
177 upid
: response
.result
.data
,
178 taskDone: function(success
) {
183 failure: function(response
, opt
) {
184 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
188 Ext
.create('Proxmox.window.Edit', {
191 submitText
: gettext('OK'),
192 title
: gettext('Load Media into Drive'),
193 url
: `/api2/extjs/tape/drive`,
195 submitUrl: function(url
, values
) {
196 let drive
= values
.drive
;
198 return `${url}/${encodeURIComponent(drive)}/load-media`;
202 xtype
: 'displayfield',
206 fieldLabel
: gettext('Media'),
209 xtype
: 'pbsDriveSelector',
210 fieldLabel
: gettext('Drive'),
216 destroy: function() {
224 unload
: async
function(v
, rI
, cI
, button
, el
, record
) {
226 let drive
= record
.data
.name
;
228 await PBS
.Async
.api2({
231 url
: `/api2/extjs/tape/drive/${encodeURIComponent(drive)}/unload`,
234 Ext
.Msg
.alert(gettext('Error'), error
);
239 cartridgeMemory: function(view
, rI
, cI
, button
, el
, record
) {
241 let drive
= record
.data
.name
;
242 PBS
.Utils
.driveCommand(drive
, 'cartridge-memory', {
243 waitMsgTarget
: me
.getView(),
244 success
: PBS
.Utils
.showCartridgeMemoryWindow
,
248 cleanDrive: function(button
, event
, record
) {
250 PBS
.Utils
.driveCommand(record
.data
.name
, 'clean', {
251 waitMsgTarget
: me
.getView(),
253 success: function(response
) {
254 Ext
.create('Proxmox.window.TaskProgress', {
255 upid
: response
.result
.data
,
256 taskDone: function() {
264 volumeStatistics: function(view
, rI
, cI
, button
, el
, record
) {
266 let drive
= record
.data
.name
;
267 PBS
.Utils
.driveCommand(drive
, 'volume-statistics', {
268 waitMsgTarget
: me
.getView(),
269 success
: PBS
.Utils
.showVolumeStatisticsWindow
,
273 readLabel: function(view
, rI
, cI
, button
, el
, record
) {
275 let drive
= record
.data
.name
;
277 PBS
.Utils
.driveCommand(drive
, 'read-label', {
278 waitMsgTarget
: me
.getView(),
279 success
: PBS
.Utils
.showMediaLabelWindow
,
283 status: function(view
, rI
, cI
, button
, el
, record
) {
285 let drive
= record
.data
.name
;
286 PBS
.Utils
.driveCommand(drive
, 'status', {
287 waitMsgTarget
: me
.getView(),
288 success
: PBS
.Utils
.showDriveStatusWindow
,
292 reloadList: function() {
294 me
.lookup('changerselector').getStore().load();
297 barcodeLabel: function() {
299 let view
= me
.getView();
300 let changer
= view
.changer
;
301 if (changer
=== '') {
305 let singleDrive
= me
.drives
.length
=== 1 ? me
.drives
[0] : undefined;
307 Ext
.create('Proxmox.window.Edit', {
308 title
: gettext('Barcode Label'),
309 showTaskViewer
: true,
311 url
: '/api2/extjs/tape/drive',
312 submitUrl: function(url
, values
) {
313 let drive
= values
.drive
;
315 return `${url}/${encodeURIComponent(drive)}/barcode-label-media`;
320 xtype
: singleDrive
=== undefined ? 'pbsDriveSelector' : 'displayfield',
321 fieldLabel
: gettext('Drive'),
328 xtype
: 'pbsMediaPoolSelector',
329 fieldLabel
: gettext('Pool'),
338 inventory: function() {
340 let view
= me
.getView();
341 let changer
= view
.changer
;
342 if (changer
=== '') {
346 let singleDrive
= me
.drives
.length
=== 1 ? me
.drives
[0] : undefined;
348 if (singleDrive
!== undefined) {
349 Proxmox
.Utils
.API2Request({
351 url
: `/api2/extjs/tape/drive/${singleDrive}/inventory`,
352 success: function(response
, opt
) {
353 Ext
.create('Proxmox.window.TaskViewer', {
354 upid
: response
.result
.data
,
355 taskDone: function(success
) {
360 failure: function(response
, opt
) {
361 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
365 Ext
.create('Proxmox.window.Edit', {
366 title
: gettext('Inventory'),
367 showTaskViewer
: true,
369 url
: '/api2/extjs/tape/drive',
370 submitUrl: function(url
, values
) {
371 let drive
= values
.drive
;
373 return `${url}/${encodeURIComponent(drive)}/inventory`;
378 xtype
: 'pbsDriveSelector',
379 fieldLabel
: gettext('Drive'),
388 scheduleReload: function(time
) {
390 if (me
.reloadTimeout
=== undefined) {
391 me
.reloadTimeout
= setTimeout(function() {
397 cancelReload: function() {
399 if (me
.reloadTimeout
!== undefined) {
400 clearTimeout(me
.reloadTimeout
);
401 me
.reloadTimeout
= undefined;
408 me
.reload_full(true);
411 reload_no_cache: function() {
413 if (me
.reloadTimeout
!== undefined) {
414 clearTimeout(me
.reloadTimeout
);
415 me
.reloadTimeout
= undefined;
417 me
.reload_full(false);
422 updateDrives: function(drives
) {
430 updateFreeSlots: function(free_slots
, free_ie_slots
) {
432 me
.free_slots
= free_slots
;
433 me
.free_ie_slots
= free_ie_slots
;
436 reload_full
: async
function(use_cache
) {
438 let view
= me
.getView();
439 let changer
= view
.changer
;
440 if (changer
=== '') {
446 Proxmox
.Utils
.setErrorMask(view
, true);
447 Proxmox
.Utils
.setErrorMask(me
.lookup('content'));
449 let status_fut
= PBS
.Async
.api2({
452 url
: `/api2/extjs/tape/changer/${encodeURIComponent(changer)}/status`,
457 let drives_fut
= PBS
.Async
.api2({
459 url
: `/api2/extjs/tape/drive?changer=${encodeURIComponent(changer)}`,
462 let tapes_fut
= PBS
.Async
.api2({
464 url
: '/api2/extjs/tape/media/list',
467 "update-status": false,
471 let [status
, drives
, tapes_list
] = await Promise
.all([status_fut
, drives_fut
, tapes_fut
]);
481 for (const tape
of tapes_list
.result
.data
) {
482 tapes
[tape
['label-text']] = {
485 status
: tape
.expired
? 'expired' : tape
.status
,
489 let drive_entries
= {};
491 for (const entry
of drives
.result
.data
) {
492 drive_entries
[entry
['changer-drivenum'] || 0] = entry
;
496 let free_ie_slots
= [];
498 let valid_drives
= [];
500 for (let entry
of status
.result
.data
) {
501 let type
= entry
['entry-kind'];
502 let id
= entry
['entry-id'];
504 if (type
=== 'drive' && drive_entries
[id
] !== undefined) {
505 entry
= Ext
.applyIf(entry
, drive_entries
[id
]);
506 valid_drives
.push(drive_entries
[id
].name
);
509 if (tapes
[entry
['label-text']] !== undefined) {
510 entry
['is-labeled'] = true;
511 entry
.pool
= tapes
[entry
['label-text']].pool
;
512 entry
.status
= tapes
[entry
['label-text']].status
;
514 entry
['is-labeled'] = false;
517 if (!entry
['label-text'] && type
!== 'drive') {
518 if (type
=== 'slot') {
530 data
[type
].push(entry
);
533 // the stores are diffstores and are only refreshed
534 // on a 'load' event, which does not trigger on 'setData'
535 // so we have to fire them ourselves
537 me
.lookup('slots').getStore().rstore
.setData(data
.slot
);
538 me
.lookup('slots').getStore().rstore
.fireEvent('load', me
, [], true);
540 me
.lookup('import_export').getStore().rstore
.setData(data
['import-export']);
541 me
.lookup('import_export').getStore().rstore
.fireEvent('load', me
, [], true);
543 me
.lookup('drives').getStore().rstore
.setData(data
.drive
);
544 me
.lookup('drives').getStore().rstore
.fireEvent('load', me
, [], true);
546 // manually fire selectionchange to update button status
547 me
.lookup('drives').getSelectionModel().fireEvent('selectionchange', me
);
549 me
.updateFreeSlots(free_slots
, free_ie_slots
);
550 me
.updateDrives(valid_drives
);
553 Proxmox
.Utils
.setErrorMask(view
);
555 Proxmox
.Utils
.setErrorMask(me
.lookup('content'));
557 if (!view
|| view
.isDestroyed
) {
562 Proxmox
.Utils
.setErrorMask(view
);
564 Proxmox
.Utils
.setErrorMask(me
.lookup('content'), err
.toString());
567 me
.scheduleReload(5000);
570 renderIsLabeled: function(value
, mD
, record
) {
571 if (!record
.data
['label-text']) {
575 if (record
.data
['label-text'].startsWith("CLN")) {
580 return gettext('Not Labeled');
583 let status
= record
.data
.status
;
584 if (record
.data
.pool
) {
585 return `${status} (${record.data.pool})`;
591 'grid[reference=drives]': {
592 cellclick: function(table
, td
, ci
, rec
, tr
, ri
, e
) {
593 if (e
.position
.column
.dataIndex
!== 'state') {
597 let upid
= rec
.data
.state
;
598 if (!upid
|| !upid
.startsWith("UPID")) {
602 Ext
.create('Proxmox.window.TaskViewer', {
610 init: function(view
) {
613 throw "no changer given";
616 view
.title
= `${gettext("Changer")}: ${view.changer}`;
622 deactivate
: 'cancelReload',
623 destroy
: 'cancelReload',
628 text
: gettext('Reload'),
629 xtype
: 'proxmoxButton',
630 handler
: 'reload_no_cache',
635 text
: gettext('Barcode Label'),
636 xtype
: 'proxmoxButton',
637 handler
: 'barcodeLabel',
638 iconCls
: 'fa fa-barcode',
641 text
: gettext('Inventory'),
642 xtype
: 'proxmoxButton',
643 handler
: 'inventory',
644 iconCls
: 'fa fa-book',
655 reference
: 'content',
664 title
: gettext('Slots'),
671 model
: 'pbs-slot-model',
678 dataIndex
: 'entry-id',
682 text
: gettext("Content"),
683 dataIndex
: 'label-text',
685 renderer
: (value
) => value
|| '',
688 text
: gettext('Inventory'),
689 dataIndex
: 'is-labeled',
690 renderer
: 'renderIsLabeled',
694 text
: gettext('Actions'),
695 xtype
: 'actioncolumn',
699 iconCls
: 'fa fa-rotate-90 fa-exchange',
700 handler
: 'slotTransfer',
701 tooltip
: gettext('Transfer'),
702 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
['label-text'],
705 iconCls
: 'fa fa-trash-o',
707 tooltip
: gettext('Erase'),
708 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
['label-text'],
711 iconCls
: 'fa fa-rotate-90 fa-upload',
713 tooltip
: gettext('Load'),
714 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
['label-text'],
730 title
: gettext('Drives'),
735 model
: 'pbs-slot-model',
741 text
: gettext('Label Media'),
742 xtype
: 'proxmoxButton',
743 handler
: 'labelMedia',
744 iconCls
: 'fa fa-barcode',
746 enableFn
: (rec
) => rec
.data
["label-text"] !== undefined,
749 text
: gettext('Catalog'),
750 xtype
: 'proxmoxButton',
752 iconCls
: 'fa fa-book',
754 enableFn
: (rec
) => rec
.data
["label-text"] !== undefined,
757 text
: gettext('Clean Drive'),
758 xtype
: 'proxmoxButton',
759 handler
: 'cleanDrive',
760 iconCls
: 'fa fa-shower',
767 dataIndex
: 'entry-id',
772 text
: gettext("Content"),
773 dataIndex
: 'label-text',
775 renderer
: (value
) => value
|| '',
778 text
: gettext('Inventory'),
779 dataIndex
: 'is-labeled',
780 renderer
: 'renderIsLabeled',
784 text
: gettext("Name"),
788 renderer
: Ext
.htmlEncode
,
791 text
: gettext('State'),
794 renderer
: PBS
.Utils
.renderDriveState
,
797 text
: gettext("Vendor"),
802 renderer
: Ext
.htmlEncode
,
805 text
: gettext("Model"),
810 renderer
: Ext
.htmlEncode
,
813 text
: gettext("Serial"),
818 renderer
: Ext
.htmlEncode
,
821 xtype
: 'actioncolumn',
822 text
: gettext('Actions'),
826 iconCls
: 'fa fa-rotate-270 fa-upload',
828 tooltip
: gettext('Unload'),
829 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
['label-text'] || rec
.data
['is-blocked'],
832 iconCls
: 'fa fa-hdd-o',
833 handler
: 'cartridgeMemory',
834 tooltip
: gettext('Cartridge Memory'),
835 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
['label-text'] || rec
.data
['is-blocked'],
838 iconCls
: 'fa fa-line-chart',
839 handler
: 'volumeStatistics',
840 tooltip
: gettext('Volume Statistics'),
841 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
['label-text'] || rec
.data
['is-blocked'],
844 iconCls
: 'fa fa-tag',
845 handler
: 'readLabel',
846 tooltip
: gettext('Read Label'),
847 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
['label-text'] || rec
.data
['is-blocked'],
850 iconCls
: 'fa fa-info-circle',
851 tooltip
: gettext('Status'),
853 isDisabled
: (v
, r
, c
, i
, rec
) => rec
.data
['is-blocked'],
861 reference
: 'import_export',
866 model
: 'pbs-slot-model',
870 title
: gettext('Import-Export Slots'),
874 dataIndex
: 'entry-id',
878 text
: gettext("Content"),
879 dataIndex
: 'label-text',
880 renderer
: (value
) => value
|| '',
884 text
: gettext('Inventory'),
885 dataIndex
: 'is-labeled',
886 renderer
: 'renderIsLabeled',
890 text
: gettext('Actions'),
891 xtype
: 'actioncolumn',
894 iconCls
: 'fa fa-rotate-270 fa-upload',
895 handler
: 'importTape',
896 tooltip
: gettext('Import'),
897 isDisabled
: (v
, r
, c
, i
, rec
) => !rec
.data
['label-text'],