]> git.proxmox.com Git - proxmox-backup.git/blob - www/tape/window/TapeRestore.js
ui: tape/TapeRestore: fix small DataStoreMappingGrid bugs
[proxmox-backup.git] / www / tape / window / TapeRestore.js
1 Ext.define('PBS.TapeManagement.TapeRestoreWindow', {
2 extend: 'Proxmox.window.Edit',
3 alias: 'widget.pbsTapeRestoreWindow',
4 mixins: ['Proxmox.Mixin.CBind'],
5
6 width: 800,
7 title: gettext('Restore Media Set'),
8 submitText: gettext('Restore'),
9 url: '/api2/extjs/tape/restore',
10 method: 'POST',
11 showTaskViewer: true,
12 isCreate: true,
13
14 cbindData: function(config) {
15 let me = this;
16 me.isSingle = false;
17 me.listText = "";
18 if (me.list !== undefined) {
19 me.isSingle = true;
20 me.listText = me.list.join('<br>');
21 me.title = gettext('Restore Snapshot');
22 }
23 return {};
24 },
25
26 defaults: {
27 labelWidth: 120,
28 },
29
30 referenceHolder: true,
31
32 items: [
33 {
34 xtype: 'inputpanel',
35
36 onGetValues: function(values) {
37 let me = this;
38 let datastores = [];
39 if (values.store.toString() !== "") {
40 datastores.push(values.store);
41 delete values.store;
42 }
43
44 if (values.mapping.toString() !== "") {
45 datastores.push(values.mapping);
46 }
47 delete values.mapping;
48
49 if (me.up('window').list !== undefined) {
50 values.snapshots = me.up('window').list;
51 }
52
53 values.store = datastores.join(',');
54
55 return values;
56 },
57
58 column1: [
59 {
60 xtype: 'displayfield',
61 fieldLabel: gettext('Media Set'),
62 cbind: {
63 value: '{mediaset}',
64 },
65 },
66 {
67 xtype: 'displayfield',
68 fieldLabel: gettext('Media Set UUID'),
69 name: 'media-set',
70 submitValue: true,
71 cbind: {
72 value: '{uuid}',
73 },
74 },
75 {
76 xtype: 'displayfield',
77 fieldLabel: gettext('Snapshot(s)'),
78 submitValue: false,
79 cbind: {
80 hidden: '{!isSingle}',
81 value: '{listText}',
82 },
83 },
84 {
85 xtype: 'pbsDriveSelector',
86 fieldLabel: gettext('Drive'),
87 name: 'drive',
88 },
89 ],
90
91 column2: [
92 {
93 xtype: 'pbsUserSelector',
94 name: 'notify-user',
95 fieldLabel: gettext('Notify User'),
96 emptyText: gettext('Current User'),
97 value: null,
98 allowBlank: true,
99 skipEmptyText: true,
100 renderer: Ext.String.htmlEncode,
101 },
102 {
103 xtype: 'pbsUserSelector',
104 name: 'owner',
105 fieldLabel: gettext('Owner'),
106 emptyText: gettext('Current User'),
107 value: null,
108 allowBlank: true,
109 skipEmptyText: true,
110 renderer: Ext.String.htmlEncode,
111 },
112 {
113 xtype: 'pbsDataStoreSelector',
114 fieldLabel: gettext('Target Datastore'),
115 reference: 'defaultDatastore',
116 name: 'store',
117 listeners: {
118 change: function(field, value) {
119 let me = this;
120 let grid = me.up('window').lookup('mappingGrid');
121 grid.setNeedStores(!value);
122 },
123 },
124 },
125 ],
126
127 columnB: [
128 {
129 fieldLabel: gettext('Datastore Mapping'),
130 labelWidth: 200,
131 hidden: true,
132 reference: 'mappingLabel',
133 xtype: 'displayfield',
134 },
135 {
136 xtype: 'pbsDataStoreMappingField',
137 reference: 'mappingGrid',
138 name: 'mapping',
139 defaultBindProperty: 'value',
140 hidden: true,
141 },
142 ],
143 },
144 ],
145
146 setDataStores: function(datastores) {
147 let me = this;
148
149 let label = me.lookup('mappingLabel');
150 let grid = me.lookup('mappingGrid');
151 let defaultField = me.lookup('defaultDatastore');
152
153 if (!datastores || datastores.length <= 1) {
154 label.setVisible(false);
155 grid.setVisible(false);
156 defaultField.setFieldLabel(gettext('Target Datastore'));
157 defaultField.setAllowBlank(false);
158 defaultField.setEmptyText("");
159 return;
160 }
161
162 label.setVisible(true);
163 defaultField.setFieldLabel(gettext('Default Datastore'));
164 defaultField.setAllowBlank(true);
165 defaultField.setEmptyText(Proxmox.Utils.NoneText);
166
167 grid.setDataStores(datastores);
168 grid.setVisible(true);
169 },
170
171 initComponent: function() {
172 let me = this;
173
174 me.callParent();
175 if (me.datastores) {
176 me.setDataStores(me.datastores);
177 } else {
178 // use timeout so that the window is rendered already
179 // for correct masking
180 setTimeout(function() {
181 Proxmox.Utils.API2Request({
182 waitMsgTarget: me,
183 url: `/tape/media/content?media-set=${me.uuid}`,
184 success: function(response, opt) {
185 let datastores = {};
186 for (const content of response.result.data) {
187 datastores[content.store] = true;
188 }
189 me.setDataStores(Object.keys(datastores));
190 },
191 failure: function() {
192 // ignore failing api call, maybe catalog is missing
193 me.setDataStores();
194 },
195 });
196 }, 10);
197 }
198 },
199 });
200
201 Ext.define('PBS.TapeManagement.DataStoreMappingGrid', {
202 extend: 'Ext.grid.Panel',
203 alias: 'widget.pbsDataStoreMappingField',
204 mixins: ['Ext.form.field.Field'],
205
206 scrollable: true,
207
208 getValue: function() {
209 let me = this;
210 let datastores = [];
211 me.getStore().each((rec) => {
212 let source = rec.data.source;
213 let target = rec.data.target;
214 if (target && target !== "") {
215 datastores.push(`${source}=${target}`);
216 }
217 });
218
219 return datastores.join(',');
220 },
221
222 // this determines if we need at least one valid mapping
223 needStores: false,
224
225 setNeedStores: function(needStores) {
226 let me = this;
227 me.needStores = needStores;
228 me.checkChange();
229 me.validate();
230 },
231
232 setValue: function(value) {
233 let me = this;
234 me.setDataStores(value);
235 return me;
236 },
237
238 getErrors: function(value) {
239 let me = this;
240 let error = false;
241
242 if (me.needStores) {
243 error = true;
244 me.getStore().each((rec) => {
245 if (rec.data.target) {
246 error = false;
247 }
248 });
249 }
250
251 let el = me.getActionEl();
252 if (error) {
253 me.addCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
254 let errorMsg = gettext("Need at least one mapping");
255 if (el) {
256 el.dom.setAttribute('data-errorqtip', errorMsg);
257 }
258
259 return [errorMsg];
260 }
261 me.removeCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
262 if (el) {
263 el.dom.setAttribute('data-errorqtip', "");
264 }
265 return [];
266 },
267
268 setDataStores: function(datastores) {
269 let me = this;
270 let store = me.getStore();
271 let data = [];
272
273 for (const datastore of datastores) {
274 data.push({
275 source: datastore,
276 target: '',
277 });
278 }
279
280 store.setData(data);
281 },
282
283 viewConfig: {
284 markDirty: false,
285 },
286
287 store: { data: [] },
288
289 columns: [
290 {
291 text: gettext('Source Datastore'),
292 dataIndex: 'source',
293 flex: 1,
294 },
295 {
296 text: gettext('Target Datastore'),
297 xtype: 'widgetcolumn',
298 dataIndex: 'target',
299 flex: 1,
300 widget: {
301 xtype: 'pbsDataStoreSelector',
302 allowBlank: true,
303 emptyText: Proxmox.Utils.NoneText,
304 listeners: {
305 change: function(selector, value) {
306 let me = this;
307 let rec = me.getWidgetRecord();
308 if (!rec) {
309 return;
310 }
311 rec.set('target', value);
312 me.up('grid').checkChange();
313 },
314 },
315 },
316 },
317 ],
318 });
319
320 Ext.define('PBS.TapeManagement.SnapshotGrid', {
321 extend: 'Ext.grid.Panel',
322 alias: 'widget.pbsTapeSnapshotGrid',
323 mixins: ['Ext.form.field.Field'],
324
325 getValue: function() {
326 let me = this;
327 let snapshots = [];
328
329 me.getSelection().forEach((rec) => {
330 let id = rec.get('id');
331 let store = rec.data.store;
332 let snap = rec.data.snapshot;
333 // only add if not filtered
334 if (me.store.findExact('id', id) !== -1) {
335 snapshots.push(`${store}:${snap}`);
336 }
337 });
338
339 return snapshots;
340 },
341
342 setValue: function(value) {
343 let me = this;
344 // not implemented
345 return me;
346 },
347
348 getErrors: function(value) {
349 let me = this;
350 if (me.getSelection() < 1) {
351 me.addCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
352 let errorMsg = gettext("Need at least one snapshot");
353 me.getActionEl().dom.setAttribute('data-errorqtip', errorMsg);
354
355 return [errorMsg];
356 }
357 me.removeCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
358 me.getActionEl().dom.setAttribute('data-errorqtip', "");
359 return [];
360 },
361
362 scrollable: true,
363 height: 350,
364 plugins: 'gridfilters',
365
366 viewConfig: {
367 emptyText: gettext('No Snapshots'),
368 markDirty: false,
369 },
370
371 selModel: 'checkboxmodel',
372 store: {
373 sorters: ['store', 'snapshot'],
374 data: [],
375 filters: [],
376 },
377
378 listeners: {
379 selectionchange: function() {
380 // to trigger validity and error checks
381 this.checkChange();
382 },
383 },
384
385 checkChangeEvents: [
386 'selectionchange',
387 'change',
388 ],
389
390 columns: [
391 {
392 text: gettext('Source Datastore'),
393 dataIndex: 'store',
394 filter: {
395 type: 'list',
396 },
397 flex: 1,
398 },
399 {
400 text: gettext('Snapshot'),
401 dataIndex: 'snapshot',
402 filter: {
403 type: 'string',
404 },
405 flex: 2,
406 },
407 ],
408
409 initComponent: function() {
410 let me = this;
411 me.callParent();
412 if (me.prefilter !== undefined) {
413 me.store.filters.add(
414 {
415 id: 'x-gridfilter-store',
416 property: 'store',
417 operator: 'in',
418 value: [me.prefilter.store],
419 },
420 {
421 id: 'x-gridfilter-snapshot',
422 property: 'snapshot',
423 value: me.prefilter.snapshot,
424 },
425 );
426 }
427 },
428 });