+Ext.define('pbs-slot-model', {
+ extend: 'Ext.data.Model',
+ fields: ['entry-id', 'label-text', 'is-labeled', ' model', 'name', 'vendor', 'serial', 'state', 'status', 'pool',
+ {
+ name: 'is-blocked',
+ calculate: function(data) {
+ return data.state !== undefined;
+ },
+ },
+ ],
+ idProperty: 'entry-id',
+});
+
Ext.define('PBS.TapeManagement.ChangerStatus', {
extend: 'Ext.panel.Panel',
alias: 'widget.pbsChangerStatus',
}).show();
},
+ erase: function(view, rI, cI, button, el, record) {
+ let me = this;
+ let vm = me.getViewModel();
+ let label = record.data['label-text'];
+
+ let changer = vm.get('changer');
+ Ext.create('PBS.TapeManagement.EraseWindow', {
+ label,
+ changer,
+ listeners: {
+ destroy: function() {
+ me.reload();
+ },
+ },
+ }).show();
+ },
+
load: function(view, rI, cI, button, el, record) {
let me = this;
let vm = me.getViewModel();
Ext.create('Proxmox.window.Edit', {
isCreate: true,
+ autoShow: true,
submitText: gettext('OK'),
title: gettext('Load Media into Drive'),
url: `/api2/extjs/tape/drive`,
+ showProgress: true,
+ method: 'POST',
submitUrl: function(url, values) {
let drive = values.drive;
delete values.drive;
me.reload();
},
},
- }).show();
+ });
},
unload: async function(view, rI, cI, button, el, record) {
let me = this;
let drive = record.data.name;
- Proxmox.Utils.setErrorMask(view, true);
try {
- await PBS.Async.api2({
- method: 'PUT',
+ let response = await PBS.Async.api2({
+ method: 'POST',
+ timeout: 5*60*1000,
url: `/api2/extjs/tape/drive/${encodeURIComponent(drive)}/unload`,
});
- Proxmox.Utils.setErrorMask(view);
- me.reload();
+
+ Ext.create('Proxmox.window.TaskProgress', {
+ autoShow: true,
+ upid: response.result.data,
+ taskDone: () => me.reload(),
+ });
} catch (error) {
Ext.Msg.alert(gettext('Error'), error);
- Proxmox.Utils.setErrorMask(view);
me.reload();
}
},
method = method || 'GET';
Proxmox.Utils.API2Request({
url: `/api2/extjs/tape/drive/${driveid}/${command}`,
+ timeout: 5*60*1000,
method,
waitMsgTarget: view,
params,
}).show();
},
- reload: async function() {
+ scheduleReload: function(time) {
+ let me = this;
+ if (me.reloadTimeout === undefined) {
+ me.reloadTimeout = setTimeout(function() {
+ me.reload();
+ }, time);
+ }
+ },
+
+ reload: function() {
+ let me = this;
+ if (me.reloadTimeout !== undefined) {
+ clearTimeout(me.reloadTimeout);
+ me.reloadTimeout = undefined;
+ }
+ me.reload_full(true);
+ },
+
+ reload_no_cache: function() {
+ let me = this;
+ if (me.reloadTimeout !== undefined) {
+ clearTimeout(me.reloadTimeout);
+ me.reloadTimeout = undefined;
+ }
+ me.reload_full(false);
+ },
+
+ reload_full: async function(use_cache) {
let me = this;
let view = me.getView();
let vm = me.getViewModel();
}
try {
- Proxmox.Utils.setErrorMask(view, true);
- Proxmox.Utils.setErrorMask(me.lookup('content'));
+ if (!use_cache) {
+ Proxmox.Utils.setErrorMask(view, true);
+ Proxmox.Utils.setErrorMask(me.lookup('content'));
+ }
let status_fut = PBS.Async.api2({
+ timeout: 5*60*1000,
+ method: 'GET',
url: `/api2/extjs/tape/changer/${encodeURIComponent(changer)}/status`,
+ params: {
+ cache: use_cache,
+ },
});
let drives_fut = PBS.Async.api2({
+ timeout: 5*60*1000,
url: `/api2/extjs/tape/drive?changer=${encodeURIComponent(changer)}`,
});
let tapes_fut = PBS.Async.api2({
+ timeout: 5*60*1000,
url: '/api2/extjs/tape/media/list',
+ method: 'GET',
+ params: {
+ "update-status": false,
+ },
});
let [status, drives, tapes_list] = await Promise.all([status_fut, drives_fut, tapes_fut]);
let tapes = {};
for (const tape of tapes_list.result.data) {
- tapes[tape['label-text']] = true;
+ tapes[tape['label-text']] = {
+ labeled: true,
+ pool: tape.pool,
+ status: tape.expired ? 'expired' : tape.status,
+ };
}
let drive_entries = {};
entry = Ext.applyIf(entry, drive_entries[entry['entry-id']]);
}
- entry['is-labeled'] = !!tapes[entry['label-text']];
+ if (tapes[entry['label-text']] !== undefined) {
+ entry['is-labeled'] = true;
+ entry.pool = tapes[entry['label-text']].pool;
+ entry.status = tapes[entry['label-text']].status;
+ } else {
+ entry['is-labeled'] = false;
+ }
data[type].push(entry);
}
+ // the stores are diffstores and are only refreshed
+ // on a 'load' event, which does not trigger on 'setData'
+ // so we have to fire them ourselves
+
+ me.lookup('slots').getStore().rstore.setData(data.slot);
+ me.lookup('slots').getStore().rstore.fireEvent('load', me, [], true);
+
+ me.lookup('import_export').getStore().rstore.setData(data['import-export']);
+ me.lookup('import_export').getStore().rstore.fireEvent('load', me, [], true);
- me.lookup('slots').getStore().setData(data.slot);
- me.lookup('import_export').getStore().setData(data['import-export']);
- me.lookup('drives').getStore().setData(data.drive);
+ me.lookup('drives').getStore().rstore.setData(data.drive);
+ me.lookup('drives').getStore().rstore.fireEvent('load', me, [], true);
- Proxmox.Utils.setErrorMask(view);
+ if (!use_cache) {
+ Proxmox.Utils.setErrorMask(view);
+ }
+ Proxmox.Utils.setErrorMask(me.lookup('content'));
} catch (err) {
- Proxmox.Utils.setErrorMask(view);
- Proxmox.Utils.setErrorMask(me.lookup('content'), err);
+ if (!use_cache) {
+ Proxmox.Utils.setErrorMask(view);
+ }
+ Proxmox.Utils.setErrorMask(me.lookup('content'), err.toString());
}
+
+ me.scheduleReload(5000);
+ },
+
+ renderIsLabeled: function(value, mD, record) {
+ if (!record.data['label-text']) {
+ return "";
+ }
+
+ if (record.data['label-text'].startsWith("CLN")) {
+ return "";
+ }
+
+ if (!value) {
+ return gettext('Not Labeled');
+ }
+
+ let status = record.data.status;
+ if (record.data.pool) {
+ return `${status} (${record.data.pool})`;
+ }
+ return status;
+ },
+
+ renderState: function(value, md, record) {
+ if (!value) {
+ return gettext('Idle');
+ }
+
+ let icon = '<i class="fa fa-spinner fa-pulse fa-fw"></i>';
+
+ if (value.startsWith("UPID")) {
+ let upid = Proxmox.Utils.parse_task_upid(value);
+ md.tdCls = "pointer";
+ return `${icon} ${upid.desc}`;
+ }
+
+ return `${icon} ${value}`;
+ },
+
+ control: {
+ 'grid[reference=drives]': {
+ cellclick: function(table, td, ci, rec, tr, ri, e) {
+ if (e.position.column.dataIndex !== 'state') {
+ return;
+ }
+
+ let upid = rec.data.state;
+ if (!upid || !upid.startsWith("UPID")) {
+ return;
+ }
+
+ Ext.create('Proxmox.window.TaskViewer', {
+ autoShow: true,
+ upid,
+ });
+ },
+ },
},
},
{
text: gettext('Reload'),
xtype: 'proxmoxButton',
- handler: 'reload',
+ handler: 'reload_no_cache',
selModel: false,
},
'-',
padding: 5,
flex: 1,
store: {
+ type: 'diff',
+ rstore: {
+ type: 'store',
+ model: 'pbs-slot-model',
+ },
data: [],
},
columns: [
{
- text: gettext('Slot'),
+ text: gettext('ID'),
dataIndex: 'entry-id',
width: 50,
},
renderer: (value) => value || '',
},
{
- text: gettext('Labeled'),
+ text: gettext('Inventory'),
dataIndex: 'is-labeled',
- width: 80,
- renderer: function(value, mD, record) {
- if (!record.data['label-text']) {
- return "";
- }
-
- if (record.data['label-text'].startsWith("CLN")) {
- return "";
- }
-
- return Proxmox.Utils.format_boolean(value);
- },
+ renderer: 'renderIsLabeled',
+ flex: 1,
},
{
text: gettext('Actions'),
tooltip: gettext('Transfer'),
isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
},
+ {
+ iconCls: 'fa fa-trash-o',
+ handler: 'erase',
+ tooltip: gettext('Erase'),
+ isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+ },
{
iconCls: 'fa fa-rotate-90 fa-upload',
handler: 'load',
reference: 'drives',
title: gettext('Drives'),
store: {
- fields: ['entry-id', 'label-text', 'model', 'name', 'vendor', 'serial'],
+ type: 'diff',
+ rstore: {
+ type: 'store',
+ model: 'pbs-slot-model',
+ },
data: [],
},
columns: [
{
- text: gettext('Slot'),
+ text: gettext('ID'),
dataIndex: 'entry-id',
+ hidden: true,
width: 50,
},
{
flex: 1,
renderer: (value) => value || '',
},
+ {
+ text: gettext('Inventory'),
+ dataIndex: 'is-labeled',
+ renderer: 'renderIsLabeled',
+ flex: 1.5,
+ },
{
text: gettext("Name"),
sortable: true,
flex: 1,
renderer: Ext.htmlEncode,
},
+ {
+ text: gettext('State'),
+ dataIndex: 'state',
+ flex: 3,
+ renderer: 'renderState',
+ },
{
text: gettext("Vendor"),
sortable: true,
dataIndex: 'vendor',
+ hidden: true,
flex: 1,
renderer: Ext.htmlEncode,
},
text: gettext("Model"),
sortable: true,
dataIndex: 'model',
+ hidden: true,
flex: 1,
renderer: Ext.htmlEncode,
},
text: gettext("Serial"),
sortable: true,
dataIndex: 'serial',
+ hidden: true,
flex: 1,
renderer: Ext.htmlEncode,
},
iconCls: 'fa fa-rotate-270 fa-upload',
handler: 'unload',
tooltip: gettext('Unload'),
- isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+ isDisabled: (v, r, c, i, rec) => !rec.data['label-text'] || rec.data['is-blocked'],
},
{
iconCls: 'fa fa-hdd-o',
handler: 'cartridgeMemory',
tooltip: gettext('Cartridge Memory'),
- isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+ isDisabled: (v, r, c, i, rec) => !rec.data['label-text'] || rec.data['is-blocked'],
},
{
iconCls: 'fa fa-line-chart',
handler: 'volumeStatistics',
tooltip: gettext('Volume Statistics'),
- isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+ isDisabled: (v, r, c, i, rec) => !rec.data['label-text'] || rec.data['is-blocked'],
},
{
iconCls: 'fa fa-tag',
handler: 'readLabel',
tooltip: gettext('Read Label'),
- isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
+ isDisabled: (v, r, c, i, rec) => !rec.data['label-text'] || rec.data['is-blocked'],
},
{
iconCls: 'fa fa-info-circle',
tooltip: gettext('Status'),
handler: 'status',
+ isDisabled: (v, r, c, i, rec) => rec.data['is-blocked'],
},
{
iconCls: 'fa fa-shower',
tooltip: gettext('Clean Drive'),
handler: 'cleanDrive',
+ isDisabled: (v, r, c, i, rec) => rec.data['is-blocked'],
},
],
},
xtype: 'grid',
reference: 'import_export',
store: {
+ type: 'diff',
+ rstore: {
+ type: 'store',
+ model: 'pbs-slot-model',
+ },
data: [],
},
- title: gettext('Import-Export'),
+ title: gettext('Import-Export Slots'),
columns: [
{
- text: gettext('Slot'),
+ text: gettext('ID'),
dataIndex: 'entry-id',
width: 50,
},
renderer: (value) => value || '',
flex: 1,
},
+ {
+ text: gettext('Inventory'),
+ dataIndex: 'is-labeled',
+ renderer: 'renderIsLabeled',
+ flex: 1,
+ },
{
text: gettext('Actions'),
xtype: 'actioncolumn',