]> git.proxmox.com Git - proxmox-backup.git/blob - www/window/TrafficControlEdit.js
f81c19883d9e550e26670711b489292cdff58c82
[proxmox-backup.git] / www / window / TrafficControlEdit.js
1 Ext.define('PBS.window.TrafficControlEdit', {
2 extend: 'Proxmox.window.Edit',
3 alias: 'widget.pbsTrafficControlEdit',
4 mixins: ['Proxmox.Mixin.CBind'],
5
6 onlineHelp: 'sysadmin_traffic_control',
7 width: 800,
8 height: 600,
9
10 isAdd: true,
11
12 subject: gettext('Traffic Control Rule'),
13
14 fieldDefaults: { labelWidth: 120 },
15
16 cbindData: function(initialConfig) {
17 let me = this;
18
19 let baseurl = '/api2/extjs/config/traffic-control';
20 let name = initialConfig.name;
21
22 me.isCreate = !name;
23 me.url = name ? `${baseurl}/${name}` : baseurl;
24 me.method = name ? 'PUT' : 'POST';
25 return { };
26 },
27
28 controller: {
29 xclass: 'Ext.app.ViewController',
30
31 weekdays: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'],
32
33 dowChanged: function(field, value) {
34 let me = this;
35 let record = field.getWidgetRecord();
36 if (record === undefined) {
37 // this is sometimes called before a record/column is initialized
38 return;
39 }
40 let col = field.getWidgetColumn();
41 record.set(col.dataIndex, value);
42 record.commit();
43
44 me.updateTimeframeField();
45 },
46
47 timeChanged: function(field, value) {
48 let me = this;
49 if (value === null) {
50 return;
51 }
52 let record = field.getWidgetRecord();
53 if (record === undefined) {
54 // this is sometimes called before a record/column is initialized
55 return;
56 }
57 let col = field.getWidgetColumn();
58 let hours = value.getHours().toString().padStart(2, '0');
59 let minutes = value.getMinutes().toString().padStart(2, '0');
60 record.set(col.dataIndex, `${hours}:${minutes}`);
61 record.commit();
62
63 me.updateTimeframeField();
64 },
65
66 addTimeframe: function() {
67 let me = this;
68 me.lookup('timeframes').getStore().add({
69 start: "00:00",
70 end: "23:59",
71 mon: true,
72 tue: true,
73 wed: true,
74 thu: true,
75 fri: true,
76 sat: true,
77 sun: true,
78 });
79
80 me.updateTimeframeField();
81 },
82
83 updateTimeframeField: function() {
84 let me = this;
85
86 let timeframes = [];
87 me.lookup('timeframes').getStore().each((rec) => {
88 let timeframe = '';
89 let days = me.weekdays.filter(day => rec.data[day]);
90 if (days.length < 7 && days.length > 0) {
91 timeframe += days.join(',') + ' ';
92 }
93 let { start, end } = rec.data;
94
95 timeframe += `${start}-${end}`;
96 timeframes.push(timeframe);
97 });
98
99 let field = me.lookup('timeframe');
100 field.suspendEvent('change');
101 field.setValue(timeframes.join(';'));
102 field.resumeEvent('change');
103 },
104
105 removeTimeFrame: function(field) {
106 let me = this;
107 let record = field.getWidgetRecord();
108 if (record === undefined) {
109 // this is sometimes called before a record/column is initialized
110 return;
111 }
112
113 me.lookup('timeframes').getStore().remove(record);
114 me.updateTimeframeField();
115 },
116
117 parseTimeframe: function(timeframe) {
118 let me = this;
119 let [, days, start, end] = /^(?:(\S*)\s+)?([0-9:]+)-([0-9:]+)$/.exec(timeframe) || [];
120
121 if (start === '0') {
122 start = "00:00";
123 }
124
125 let record = {
126 start,
127 end,
128 };
129
130 if (!days) {
131 days = 'mon..sun';
132 }
133
134 days = days.split(',');
135 days.forEach((day) => {
136 if (record[day]) {
137 return;
138 }
139
140 if (me.weekdays.indexOf(day) !== -1) {
141 record[day] = true;
142 } else {
143 // we have a range 'xxx..yyy'
144 let [startDay, endDay] = day.split('..');
145 let startIdx = me.weekdays.indexOf(startDay);
146 let endIdx = me.weekdays.indexOf(endDay);
147
148 if (endIdx < startIdx) {
149 endIdx += me.weekdays.length;
150 }
151
152 for (let dayIdx = startIdx; dayIdx <= endIdx; dayIdx++) {
153 let curDay = me.weekdays[dayIdx%me.weekdays.length];
154 if (!record[curDay]) {
155 record[curDay] = true;
156 }
157 }
158 }
159 });
160
161 return record;
162 },
163
164 setGridData: function(field, value) {
165 let me = this;
166 if (!value) {
167 return;
168 }
169
170 value = value.split(';');
171 let records = value.map((timeframe) => me.parseTimeframe(timeframe));
172 me.lookup('timeframes').getStore().setData(records);
173 },
174
175 control: {
176 'grid checkbox': {
177 change: 'dowChanged',
178 },
179 'grid timefield': {
180 change: 'timeChanged',
181 },
182 'grid button': {
183 click: 'removeTimeFrame',
184 },
185 'field[name=timeframe]': {
186 change: 'setGridData',
187 },
188 },
189 },
190
191 items: {
192 xtype: 'inputpanel',
193 onGetValues: function(values) {
194 let me = this;
195 let isCreate = me.up('window').isCreate;
196
197 if (!values['network-select']) {
198 values.network = '0.0.0.0/0';
199 } else if (values.network) {
200 values.network = values.network.split(/\s*,\s*/);
201 }
202
203 if ('timeframe' in values && !values.timeframe) {
204 delete values.timeframe;
205 }
206 if (values.timeframe && !Ext.isArray(values.timeframe)) {
207 let timeframe = [], seen = {};
208 values.timeframe.split(';').forEach(tf => {
209 if (!seen[tf]) {
210 timeframe.push(tf);
211 seen[tf] = true;
212 }
213 });
214 values.timeframe = timeframe;
215 }
216
217 delete values['network-select'];
218
219 if (!isCreate) {
220 PBS.Utils.delete_if_default(values, 'timeframe');
221 PBS.Utils.delete_if_default(values, 'rate-in');
222 PBS.Utils.delete_if_default(values, 'rate-out');
223 PBS.Utils.delete_if_default(values, 'burst-in');
224 PBS.Utils.delete_if_default(values, 'burst-out');
225 if (typeof values.delete === 'string') {
226 values.delete = values.delete.split(',');
227 }
228 }
229
230 return values;
231 },
232 column1: [
233 {
234 xtype: 'pmxDisplayEditField',
235 name: 'name',
236 fieldLabel: gettext('Name'),
237 renderer: Ext.htmlEncode,
238 allowBlank: false,
239 minLength: 4,
240 cbind: {
241 editable: '{isCreate}',
242 },
243 },
244 {
245 xtype: 'pmxBandwidthField',
246 name: 'rate-in',
247 fieldLabel: gettext('Rate In'),
248 emptyText: gettext('Unlimited'),
249 submitAutoScaledSizeUnit: true,
250 },
251 {
252 xtype: 'pmxBandwidthField',
253 name: 'rate-out',
254 fieldLabel: gettext('Rate Out'),
255 emptyText: gettext('Unlimited'),
256 submitAutoScaledSizeUnit: true,
257 },
258 ],
259
260 column2: [
261 {
262 xtype: 'proxmoxtextfield',
263 name: 'comment',
264 cbind: {
265 deleteEmpty: '{!isCreate}',
266 },
267 fieldLabel: gettext('Comment'),
268 },
269 {
270 xtype: 'pmxBandwidthField',
271 name: 'burst-in',
272 fieldLabel: gettext('Burst In'),
273 emptyText: gettext('Same as Rate'),
274 submitAutoScaledSizeUnit: true,
275 },
276 {
277 xtype: 'pmxBandwidthField',
278 name: 'burst-out',
279 fieldLabel: gettext('Burst Out'),
280 emptyText: gettext('Same as Rate'),
281 submitAutoScaledSizeUnit: true,
282 },
283 ],
284
285 columnB: [
286 {
287 xtype: 'proxmoxtextfield',
288 fieldLabel: gettext('Network(s)'),
289 name: 'network',
290 emptyText: gettext('0.0.0.0/0 (Apply on all Networks)'),
291 autoEl: {
292 tag: 'div',
293 'data-qtip': gettext('A comma-separated list of networks to apply the (shared) limit.'),
294
295 },
296 },
297 {
298 xtype: 'displayfield',
299 fieldLabel: gettext('Timeframes'),
300 },
301 {
302 xtype: 'fieldcontainer',
303 items: [
304 {
305 xtype: 'grid',
306 height: 300,
307 scrollable: true,
308 reference: 'timeframes',
309 viewConfig: {
310 emptyText: gettext('Apply Always'),
311 },
312 store: {
313 fields: ['start', 'end', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'],
314 data: [],
315 },
316 columns: [
317 {
318 text: gettext('Time Start'),
319 xtype: 'widgetcolumn',
320 dataIndex: 'start',
321 widget: {
322 xtype: 'timefield',
323 isFormField: false,
324 format: 'H:i',
325 formatText: 'HH:MM',
326 },
327 flex: 1,
328 },
329 {
330 text: gettext('Time End'),
331 xtype: 'widgetcolumn',
332 dataIndex: 'end',
333 widget: {
334 xtype: 'timefield',
335 isFormField: false,
336 format: 'H:i',
337 formatText: 'HH:MM',
338 maxValue: '23:59',
339 },
340 flex: 1,
341 },
342 {
343 text: gettext('Mon'),
344 xtype: 'widgetcolumn',
345 dataIndex: 'mon',
346 width: 60,
347 widget: {
348 xtype: 'checkbox',
349 isFormField: false,
350 },
351 },
352 {
353 text: gettext('Tue'),
354 xtype: 'widgetcolumn',
355 dataIndex: 'tue',
356 width: 60,
357 widget: {
358 xtype: 'checkbox',
359 isFormField: false,
360 },
361 },
362 {
363 text: gettext('Wed'),
364 xtype: 'widgetcolumn',
365 dataIndex: 'wed',
366 width: 60,
367 widget: {
368 xtype: 'checkbox',
369 isFormField: false,
370 },
371 },
372 {
373 text: gettext('Thu'),
374 xtype: 'widgetcolumn',
375 dataIndex: 'thu',
376 width: 60,
377 widget: {
378 xtype: 'checkbox',
379 isFormField: false,
380 },
381 },
382 {
383 text: gettext('Fri'),
384 xtype: 'widgetcolumn',
385 dataIndex: 'fri',
386 width: 60,
387 widget: {
388 xtype: 'checkbox',
389 isFormField: false,
390 },
391 },
392 {
393 text: gettext('Sat'),
394 xtype: 'widgetcolumn',
395 dataIndex: 'sat',
396 width: 60,
397 widget: {
398 xtype: 'checkbox',
399 isFormField: false,
400 },
401 },
402 {
403 text: gettext('Sun'),
404 xtype: 'widgetcolumn',
405 dataIndex: 'sun',
406 width: 60,
407 widget: {
408 xtype: 'checkbox',
409 isFormField: false,
410 },
411 },
412 {
413 xtype: 'widgetcolumn',
414 width: 40,
415 widget: {
416 xtype: 'button',
417 iconCls: 'fa fa-trash-o',
418 },
419 },
420 ],
421 },
422 ],
423 },
424 {
425 xtype: 'button',
426 text: gettext('Add'),
427 iconCls: 'fa fa-plus-circle',
428 handler: 'addTimeframe',
429 },
430 {
431 xtype: 'hidden',
432 reference: 'timeframe',
433 name: 'timeframe',
434 },
435 ],
436 },
437
438 initComponent: function() {
439 let me = this;
440 me.callParent();
441 if (!me.isCreate) {
442 me.load({
443 success: function(response) {
444 let data = response.result.data;
445 if (data.network?.length === 1 && data.network[0] === '0.0.0.0/0') {
446 data['network-select'] = 'all';
447 delete data.network;
448 } else {
449 data['network-select'] = 'limit';
450 }
451
452 if (Ext.isArray(data.timeframe)) {
453 data.timeframe = data.timeframe.join(';');
454 }
455
456 me.setValues(data);
457 },
458 });
459 }
460 },
461 });