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