]> git.proxmox.com Git - proxmox-backup.git/blame - www/tape/ChangerStatus.js
ui: tape: add Changer config grid
[proxmox-backup.git] / www / tape / ChangerStatus.js
CommitLineData
fd9aa8df
DC
1Ext.define('PBS.TapeManagement.ChangerStatus', {
2 extend: 'Ext.panel.Panel',
3 alias: 'widget.pbsChangerStatus',
4
5 viewModel: {
6 data: {
7 changer: '',
8 },
9
10 formulas: {
11 changerSelected: (get) => get('changer') !== '',
12 },
13 },
14
15 controller: {
16 xclass: 'Ext.app.ViewController',
17
18 changerChange: function(field, value) {
19 let me = this;
20 let view = me.getView();
21 let vm = me.getViewModel();
22 vm.set('changer', value);
23 if (view.rendered) {
24 me.reload();
25 }
26 },
27
28 onAdd: function() {
29 let me = this;
30 Ext.create('PBS.TapeManagement.ChangerEditWindow', {
31 listeners: {
32 destroy: function() {
33 me.reloadList();
34 },
35 },
36 }).show();
37 },
38
39 onEdit: function() {
40 let me = this;
41 let vm = me.getViewModel();
42 let changerid = vm.get('changer');
43 Ext.create('PBS.TapeManagement.ChangerEditWindow', {
44 changerid,
45 autoLoad: true,
46 listeners: {
47 destroy: () => me.reload(),
48 },
49 }).show();
50 },
51
52 slotTransfer: function(view, rI, cI, button, el, record) {
53 let me = this;
54 let vm = me.getViewModel();
55 let from = record.data['entry-id'];
56 let changer = encodeURIComponent(vm.get('changer'));
57 Ext.create('Proxmox.window.Edit', {
58 title: gettext('Transfer'),
59 isCreate: true,
60 submitText: gettext('OK'),
61 method: 'POST',
62 url: `/api2/extjs/tape/changer/${changer}/transfer`,
63 items: [
64 {
65 xtype: 'displayfield',
66 name: 'from',
67 value: from,
68 submitValue: true,
69 fieldLabel: gettext('From Slot'),
70 },
71 {
72 xtype: 'proxmoxintegerfield',
73 name: 'to',
74 fieldLabel: gettext('To Slot'),
75 },
76 ],
77 listeners: {
78 destroy: function() {
79 me.reload();
80 },
81 },
82 }).show();
83 },
84
85 load: function(view, rI, cI, button, el, record) {
86 let me = this;
87 let vm = me.getViewModel();
88 let label = record.data['label-text'];
89
90 let changer = vm.get('changer');
91
92 Ext.create('Proxmox.window.Edit', {
93 isCreate: true,
94 submitText: gettext('OK'),
95 title: gettext('Load Media into Drive'),
96 url: `/api2/extjs/tape/drive`,
97 submitUrl: function(url, values) {
98 let drive = values.drive;
99 delete values.drive;
100 return `${url}/${encodeURIComponent(drive)}/load-media`;
101 },
102 items: [
103 {
104 xtype: 'displayfield',
105 name: 'label-text',
106 value: label,
107 submitValue: true,
108 fieldLabel: gettext('Media'),
109 },
110 {
111 xtype: 'pbsDriveSelector',
112 fieldLabel: gettext('Drive'),
113 changer: changer,
114 name: 'drive',
115 },
116 ],
117 listeners: {
118 destroy: function() {
119 me.reload();
120 },
121 },
122 }).show();
123 },
124
125 unload: async function(view, rI, cI, button, el, record) {
126 let me = this;
127 let drive = record.data.name;
128 Proxmox.Utils.setErrorMask(view, true);
129 try {
130 await PBS.Async.api2({
131 method: 'PUT',
132 url: `/api2/extjs/tape/drive/${encodeURIComponent(drive)}/unload`,
133 });
134 Proxmox.Utils.setErrorMask(view);
135 me.reload();
136 } catch (error) {
137 Ext.Msg.alert(gettext('Error'), error);
138 Proxmox.Utils.setErrorMask(view);
139 me.reload();
140 }
141 },
142
143 driveCommand: function(driveid, command, callback, params, method) {
144 let me = this;
145 let view = me.getView();
146 params = params || {};
147 method = method || 'GET';
148 Proxmox.Utils.API2Request({
149 url: `/api2/extjs/tape/drive/${driveid}/${command}`,
150 method,
151 waitMsgTarget: view,
152 params,
153 success: function(response) {
154 callback(response);
155 },
156 failure: function(response) {
157 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
158 },
159 });
160 },
161
162 cartridgeMemory: function(view, rI, cI, button, el, record) {
163 let me = this;
164 let drive = record.data.name;
165 me.driveCommand(drive, 'cartridge-memory', function(response) {
166 Ext.create('Ext.window.Window', {
167 title: gettext('Cartridge Memory'),
168 modal: true,
169 width: 600,
170 height: 450,
171 layout: 'fit',
172 scrollable: true,
173 items: [
174 {
175 xtype: 'grid',
176 store: {
177 data: response.result.data,
178 },
179 columns: [
180 {
181 text: gettext('ID'),
182 dataIndex: 'id',
183 width: 60,
184 },
185 {
186 text: gettext('Name'),
187 dataIndex: 'name',
188 flex: 2,
189 },
190 {
191 text: gettext('Value'),
192 dataIndex: 'value',
193 flex: 1,
194 },
195 ],
196 },
197 ],
198 }).show();
199 });
200 },
201
202 cleanDrive: function(view, rI, cI, button, el, record) {
203 let me = this;
204 let drive = record.data.name;
205 me.driveCommand(drive, 'clean', function(response) {
206 Ext.create('Proxmox.window.TaskProgress', {
207 upid: response.result.data,
208 taskDone: function() {
209 me.reload();
210 },
211 }).show();
212 }, {}, 'PUT');
213 },
214
215 volumeStatistics: function(view, rI, cI, button, el, record) {
216 let me = this;
217 let drive = record.data.name;
218 me.driveCommand(drive, 'volume-statistics', function(response) {
219 Ext.create('Ext.window.Window', {
220 title: gettext('Volume Statistics'),
221 modal: true,
222 width: 600,
223 height: 450,
224 layout: 'fit',
225 scrollable: true,
226 items: [
227 {
228 xtype: 'grid',
229 store: {
230 data: response.result.data,
231 },
232 columns: [
233 {
234 text: gettext('ID'),
235 dataIndex: 'id',
236 width: 60,
237 },
238 {
239 text: gettext('Name'),
240 dataIndex: 'name',
241 flex: 2,
242 },
243 {
244 text: gettext('Value'),
245 dataIndex: 'value',
246 flex: 1,
247 },
248 ],
249 },
250 ],
251 }).show();
252 });
253 },
254
255 readLabel: function(view, rI, cI, button, el, record) {
256 let me = this;
257 let drive = record.data.name;
258 me.driveCommand(drive, 'read-label', function(response) {
259 let lines = [];
260 for (const [key, val] of Object.entries(response.result.data)) {
261 lines.push(`${key}: ${val}`);
262 }
263
264 let txt = lines.join('<br>');
265
266 Ext.Msg.show({
267 title: gettext('Label Information'),
268 message: txt,
269 icon: undefined,
270 });
271 });
272 },
273
274 status: function(view, rI, cI, button, el, record) {
275 let me = this;
276 let drive = record.data.name;
277 me.driveCommand(drive, 'status', function(response) {
278 let lines = [];
279 for (const [key, val] of Object.entries(response.result.data)) {
280 lines.push(`${key}: ${val}`);
281 }
282
283 let txt = lines.join('<br>');
284
285 Ext.Msg.show({
286 title: gettext('Label Information'),
287 message: txt,
288 icon: undefined,
289 });
290 });
291 },
292
293 reloadList: function() {
294 let me = this;
295 me.lookup('changerselector').getStore().load();
296 },
297
298 barcodeLabel: function() {
299 let me = this;
300 let vm = me.getViewModel();
301 let changer = vm.get('changer');
302 if (changer === '') {
303 return;
304 }
305
306 Ext.create('Proxmox.window.Edit', {
307 title: gettext('Barcode Label'),
308 showTaskViewer: true,
309 url: '/api2/extjs/tape/drive',
310 submitUrl: function(url, values) {
311 let drive = values.drive;
312 delete values.drive;
313 return `${url}/${encodeURIComponent(drive)}/barcode-label-media`;
314 },
315
316 items: [
317 {
318 xtype: 'pbsDriveSelector',
319 fieldLabel: gettext('Drive'),
320 name: 'drive',
321 changer: changer,
322 },
323 {
324 xtype: 'pbsMediaPoolSelector',
325 fieldLabel: gettext('Pool'),
326 name: 'pool',
327 skipEmptyText: true,
328 allowBlank: true,
329 },
330 ],
331 }).show();
332 },
333
334 reload: async function() {
335 let me = this;
336 let view = me.getView();
337 let vm = me.getViewModel();
338 let changer = vm.get('changer');
339 if (changer === '') {
340 return;
341 }
342
343 try {
344 Proxmox.Utils.setErrorMask(view, true);
345 Proxmox.Utils.setErrorMask(me.lookup('content'));
346 let status = await PBS.Async.api2({
347 url: `/api2/extjs/tape/changer/${encodeURIComponent(changer)}/status`,
348 });
349 let drives = await PBS.Async.api2({
350 url: `/api2/extjs/tape/drive?changer=${encodeURIComponent(changer)}`,
351 });
352
353 let data = {
354 slot: [],
355 'import-export': [],
356 drive: [],
357 };
358
359 let drive_entries = {};
360
361 for (const entry of drives.result.data) {
362 drive_entries[entry['changer-drivenum'] || 0] = entry;
363 }
364
365 for (let entry of status.result.data) {
366 let type = entry['entry-kind'];
367
368 if (type === 'drive' && drive_entries[entry['entry-id']] !== undefined) {
369 entry = Ext.applyIf(entry, drive_entries[entry['entry-id']]);
370 }
371
372 data[type].push(entry);
373 }
374
375
376 me.lookup('slots').getStore().setData(data.slot);
377 me.lookup('import_export').getStore().setData(data['import-export']);
378 me.lookup('drives').getStore().setData(data.drive);
379
380 Proxmox.Utils.setErrorMask(view);
381 } catch (err) {
382 Proxmox.Utils.setErrorMask(view);
383 Proxmox.Utils.setErrorMask(me.lookup('content'), err);
384 }
385 },
386 },
387
388 listeners: {
389 activate: 'reload',
390 },
391
392 tbar: [
393 {
394 fieldLabel: gettext('Changer'),
395 xtype: 'pbsChangerSelector',
396 reference: 'changerselector',
397 autoSelect: true,
398 listeners: {
399 change: 'changerChange',
400 },
401 },
402 '-',
403 {
404 text: gettext('Reload'),
405 xtype: 'proxmoxButton',
406 handler: 'reload',
407 selModel: false,
408 },
409 '-',
410 {
411 text: gettext('Add'),
412 xtype: 'proxmoxButton',
413 handler: 'onAdd',
414 selModel: false,
415 },
416 {
417 text: gettext('Edit'),
418 xtype: 'proxmoxButton',
419 handler: 'onEdit',
420 bind: {
421 disabled: '{!changerSelected}',
422 },
423 },
424 {
425 xtype: 'proxmoxStdRemoveButton',
426 baseurl: '/api2/extjs/config/changer',
427 callback: 'reloadList',
428 selModel: false,
429 getRecordName: function() {
430 let me = this;
431 let vm = me.up('panel').getViewModel();
432 return vm.get('changer');
433 },
434 getUrl: function() {
435 let me = this;
436 let vm = me.up('panel').getViewModel();
437 return `/api2/extjs/config/changer/${vm.get('changer')}`;
438 },
439 bind: {
440 disabled: '{!changerSelected}',
441 },
442 },
443 '-',
444 {
445 text: gettext('Barcode Label'),
446 xtype: 'proxmoxButton',
447 handler: 'barcodeLabel',
448 iconCls: 'fa fa-barcode',
449 bind: {
450 disabled: '{!changerSelected}',
451 },
452 },
453 ],
454
455 layout: 'auto',
456 bodyPadding: 5,
457 scrollable: true,
458
459 items: [
460 {
461 xtype: 'container',
462 reference: 'content',
463 layout: {
464 type: 'hbox',
465 aling: 'stretch',
466 },
467 items: [
468 {
469 xtype: 'grid',
470 reference: 'slots',
471 title: gettext('Slots'),
472 padding: 5,
473 flex: 1,
474 store: {
475 data: [],
476 },
477 columns: [
478 {
479 text: gettext('Slot'),
480 dataIndex: 'entry-id',
481 width: 50,
482 },
483 {
484 text: gettext("Content"),
485 dataIndex: 'label-text',
486 flex: 1,
487 renderer: (value) => value || '',
488 },
489 {
490 text: gettext('Actions'),
491 xtype: 'actioncolumn',
492 width: 100,
493 items: [
494 {
495 iconCls: 'fa fa-rotate-90 fa-exchange',
496 handler: 'slotTransfer',
497 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
498 },
499 {
500 iconCls: 'fa fa-rotate-90 fa-upload',
501 handler: 'load',
502 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
503 },
504 ],
505 },
506 ],
507 },
508 {
509 xtype: 'container',
510 flex: 2,
511 defaults: {
512 padding: 5,
513 },
514 items: [
515 {
516 xtype: 'grid',
517 reference: 'drives',
518 title: gettext('Drives'),
519 store: {
520 fields: ['entry-id', 'label-text', 'model', 'name', 'vendor', 'serial'],
521 data: [],
522 },
523 columns: [
524 {
525 text: gettext('Slot'),
526 dataIndex: 'entry-id',
527 width: 50,
528 },
529 {
530 text: gettext("Content"),
531 dataIndex: 'label-text',
532 flex: 1,
533 renderer: (value) => value || '',
534 },
535 {
536 text: gettext("Name"),
537 sortable: true,
538 dataIndex: 'name',
539 flex: 1,
540 renderer: Ext.htmlEncode,
541 },
542 {
543 text: gettext("Vendor"),
544 sortable: true,
545 dataIndex: 'vendor',
546 flex: 1,
547 renderer: Ext.htmlEncode,
548 },
549 {
550 text: gettext("Model"),
551 sortable: true,
552 dataIndex: 'model',
553 flex: 1,
554 renderer: Ext.htmlEncode,
555 },
556 {
557 text: gettext("Serial"),
558 sortable: true,
559 dataIndex: 'serial',
560 flex: 1,
561 renderer: Ext.htmlEncode,
562 },
563 {
564 xtype: 'actioncolumn',
565 text: gettext('Actions'),
566 width: 140,
567 items: [
568 {
569 iconCls: 'fa fa-rotate-270 fa-upload',
570 handler: 'unload',
571 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
572 },
573 {
574 iconCls: 'fa fa-hdd-o',
575 handler: 'cartridgeMemory',
576 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
577 },
578 {
579 iconCls: 'fa fa-line-chart',
580 handler: 'volumeStatistics',
581 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
582 },
583 {
584 iconCls: 'fa fa-tag',
585 handler: 'readLabel',
586 isDisabled: (v, r, c, i, rec) => !rec.data['label-text'],
587 },
588 {
589 iconCls: 'fa fa-info-circle',
590 handler: 'status',
591 },
592 {
593 iconCls: 'fa fa-shower',
594 handler: 'cleanDrive',
595 },
596 ],
597 },
598 ],
599 },
600 {
601 xtype: 'grid',
602 reference: 'import_export',
603 store: {
604 data: [],
605 },
606 title: gettext('Import-Export'),
607 columns: [
608 {
609 text: gettext('Slot'),
610 dataIndex: 'entry-id',
611 width: 50,
612 },
613 {
614 text: gettext("Content"),
615 dataIndex: 'label-text',
616 renderer: (value) => value || '',
617 flex: 1,
618 },
619 {
620 text: gettext('Actions'),
621 items: [],
622 width: 80,
623 },
624 ],
625 },
626 ],
627 },
628 ],
629 },
630 ],
631});