Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 49df590a

Přidáno uživatelem Jan Kohlíček před téměř 7 roky(ů)

refs #6647: Vytvoření grafů průměrná rychlost a počet vozidel, lepší zapsání a mazání dat z url, přidaná možnost jít zpět, validace časového rozsahu

Zobrazit rozdíly:

frontend/app.js
1
var app = angular.module('pvpk', ['ngRoute', 'ngResource', 'ngSanitize']);
1
let app = angular.module('pvpk', ['ngRoute', 'ngResource', 'ngSanitize']);
2 2

  
3 3
app.constant('config', {
4 4
    APP_NAME: 'PVPK',
......
32 32
// });
33 33

  
34 34

  
35
app.controller('mainController', function ($rootScope, $scope, $http, $window) {
35
app.controller('mainController', function ($rootScope, $scope, $location, $window) {
36 36

  
37 37
    this.$onInit = function () {
38

  
39
    };
40

  
41
    $window.onload = function () {
42
        let params = $location.search();
43
        if (params.deviceId) {
44
            $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
45
            $rootScope.$emit('activeMarker', {id: params.deviceId});
46
        }
47

  
38 48
        $scope.showLoadingScreen = false;
39 49
    };
40 50

  
51
    $rootScope.$on('$locationChangeSuccess', function (event, newUrl, oldUrl) {
52

  
53
        if (newUrl !== oldUrl && $scope.historyUrl) {
54
            let params = $location.search();
55

  
56
            if ($scope.historyUrl.q !== $scope.historyUrl.q || $scope.historyUrl.isDirection != params.isDirection) {
57
                $rootScope.$emit('setSearchFromUrl', null);
58
            }
59

  
60
            if ($scope.historyUrl.fromDate !== params.fromDate || $scope.historyUrl.toDate !== params.toDate ||
61
                $scope.historyUrl.fromTime !== params.fromTime || $scope.historyUrl.toTime !== params.toTime) {
62
                $rootScope.$emit('setRangeFromUrl', null);
63
                if (params.deviceId) {
64
                    $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
65
                }
66
            } else if (params.deviceId && ($scope.historyUrl.deviceId !== params.deviceId || $scope.historyUrl.direction !== params.direction)) {
67
                $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
68
                $rootScope.$emit('activeMarker', {id: params.deviceId});
69
            }
70
        }
71

  
72
        $scope.historyUrl = $location.search();
73
    });
74

  
41 75
    $rootScope.handleErrorResponse = function (response) {
76

  
77
        let modalError = jQuery('#modalError');
42 78
        switch (response.status) {
43 79
            case 400:
44 80
                console.log('API ERROR 400');
......
47 83
                    body: 'Požadavek nemůže být vyřízen, poněvadž byl syntakticky nesprávně zapsán.',
48 84
                    button: 'OK'
49 85
                };
50
                jQuery('#modalError').modal('show');
86
                modalError.modal('show');
51 87
                break;
52 88
            case 401:
53 89
                $scope.modalError = {
......
56 92
                    button: 'Obnovit',
57 93
                    clickButton: $scope.reloadApp
58 94
                };
59
                jQuery('#modalError').modal({backdrop: 'static', keyboard: false});
95
                modalError.modal({backdrop: 'static', keyboard: false});
60 96
                break;
61 97
            case 404:
62 98
                console.log('API ERROR 404');
63 99
                $scope.modalError = {title: 'Nenalezen', body: 'Záznam nebyl nalezen.', button: 'OK'};
64
                jQuery('#modalError').modal('show');
100
                modalError.modal('show');
65 101
                break;
66 102
            case 500:
67 103
                console.log('API ERROR 500');
68 104
                $scope.modalError = {title: 'Chyba', body: 'Chyba serveru. Zopakujte akci později.', button: 'OK'};
69
                jQuery('#modalError').modal('show');
105
                modalError.modal('show');
70 106
                break;
71 107
            case -1:
72 108
                console.log('API NOT CONNECTED');
......
75 111
                    body: 'Nejste připojeni k internetu. Zkontrolujte připojení.',
76 112
                    button: 'OK'
77 113
                };
78
                jQuery('#modalError').modal('show');
114
                modalError.modal('show');
79 115
                break;
80 116
            default:
81 117
                console.log('API UNKNOWN ERROR');
82 118
                $scope.modalError = {title: 'Neočekávaná chyba', body: 'Nastala neočekávaná chyba.', button: 'OK'};
83
                jQuery('#modalError').modal('show');
119
                modalError.modal('show');
84 120
                break;
85 121
        }
86 122
    };
......
94 130
app.controller('searchController', function ($rootScope, $scope, $location, config, Device) {
95 131

  
96 132
    this.$onInit = function () {
97
        var fromTime = new Date();
98
        fromTime.setHours(7, 0, 0, 0);
99

  
100
        var toTime = new Date();
101
        toTime.setHours(16, 0, 0, 0);
102

  
103

  
104
        var toDate = new Date(new Date().getTime() - (1 * 24 * 60 * 60 * 1000));
105

  
106
        //DODELAT OMEZENI
107
        $scope.maxDate = toDate;
108
        var fromDate = new Date(toDate.getTime() - (30 * 24 * 60 * 60 * 1000));
109

  
110
        var params = $location.search();
111

  
112
        $scope.search = {
113
            location: params.location,
114
            fromDate: params.fromDate == null ? fromDate : new Date(parseInt(params.fromDate)),
115
            toDate: params.toDate == null ? toDate : new Date(parseInt(params.toDate)),
116
            fromTime: params.fromTime == null ? fromTime : new Date(parseInt(params.fromTime)),
117
            toTime: params.toTime == null ? toTime : new Date(parseInt(params.toTime)),
118
            direction: params.direction == null ? true : !!+params.direction
119
        };
120

  
121 133
        $scope.locations = [];
122 134
        $scope.showSearchLoading = false;
123 135

  
124
        if (params.location != null && params.location.length > 2) {
125
            $scope.searchLocations(false);
126
        }
136
        $rootScope.$emit('setSearchFromUrl', null);
127 137
    };
128 138

  
129

  
130
    $scope.searchLocations = function (saveToUrl) {
131
        if (!$scope.search.location || $scope.search.location.length <= 1) {
139
    $scope.searchLocations = function () {
140
        if (!$scope.search.q || $scope.search.q.length <= 1) {
132 141
            $scope.locations = [];
133 142
            return;
134 143
        }
135 144

  
136 145
        $scope.showSearchLoading = true;
137 146

  
138
        if (saveToUrl)
139
            $location.search({
140
                location: $scope.search.location,
141
                // fromDate: $scope.search.fromDate.getTime(),
142
                // toDate: $scope.search.toDate.getTime(),
143
                // fromTime: $scope.search.fromTime.getTime(),
144
                // toTime: $scope.search.toTime.getTime(),
145
                direction: $scope.search.direction ? 1 : 0
146
            });
147
        let params = $location.search();
148
        params.q = $scope.search.q;
149
        params.isDirection = $scope.search.isDirection ? 1 : 0;
150
        $location.search(params);
147 151

  
148 152
        Device.query({
149
            address: $scope.search.location,
150
            showDirection: $scope.search.direction ? 1 : 0
153
            address: $scope.search.q,
154
            showDirection: $scope.search.isDirection ? 1 : 0
151 155
        }, function (data) {
152 156
            $scope.locations = data;
153 157
            $scope.showSearchLoading = false;
154

  
155
            var params = $location.search();
156
            if (!saveToUrl && jQuery.grep($scope.locations, function (e) {
157
                return e.id === params.deviceId;
158
            }).length > 0) {
159
                $scope.selectDevice(params.deviceId);
160
            }
161

  
162 158
        }, function (response) {
163 159
            $scope.showSearchLoading = false;
164 160
            console.log('Error api all Devices');
......
166 162
        });
167 163
    };
168 164

  
169
    $scope.selectDevice = function (id) {
170
        var searchObject = $location.search();
171
        searchObject.deviceId = id;
172
        $location.search(searchObject);
165
    $rootScope.$on('setSearchFromUrl', function (event, args) {
166
        let params = $location.search();
173 167

  
168
        $scope.search = {
169
            q: params.q,
170
            isDirection: params.isDirection ? !!+params.isDirection : false
171
        };
172
        $scope.searchLocations();
173
    });
174

  
175
    $scope.selectDevice = function (id, direction) {
174 176
        $rootScope.$emit('activeMarker', {id: id});
175
        $rootScope.$emit('infoLocation', {id: id});
177
        $rootScope.$emit('infoLocation', {id: id, direction: direction});
176 178
    };
177 179

  
178 180
});
179 181

  
180 182

  
181
app.controller('infoController', function ($rootScope, $scope, config, Device, Vehicle) {
183
app.controller('infoController', function ($rootScope, $scope, $location, config, Device, Vehicle) {
182 184

  
183 185
    this.$onInit = function () {
184 186
        $rootScope.selectDevice = null;
185
        $scope.vehicles = [];
186 187
        $scope.showInfoLoading = false;
188
        $scope.vehicles = [];
189
        $scope.typeVehicle = null;
190
        $scope.filterVehicles = [];
187 191

  
188 192
        Vehicle.query(null, function (data) {
189 193
            $scope.vehicles = data;
......
192 196
            console.log('Error api all Vehicles');
193 197
            $rootScope.handleErrorResponse(response);
194 198
        });
199

  
200
        $rootScope.$emit('setRangeFromUrl', null);
195 201
    };
196 202

  
203
    $rootScope.$on('setRangeFromUrl', function (event, args) {
204
        let params = $location.search();
205
        let defaultRange = $scope.defaultRange();
206

  
207
        $scope.range = {
208
            fromDate: moment(params.fromDate, 'YYYY-MM-DD').isValid() ? moment(params.fromDate).toDate() : defaultRange.fromDate.toDate(),
209
            toDate: moment(params.toDate, 'YYYY-MM-DD').isValid() ? moment(params.toDate).toDate() : defaultRange.toDate.toDate(),
210
            fromTime: moment(params.fromTime, 'HH:mm').isValid() ? moment(params.fromTime, 'HH:mm').toDate() : defaultRange.fromTime.toDate(),
211
            toTime: moment(params.toTime, 'HH:mm').isValid() ? moment(params.toTime, 'HH:mm').toDate() : defaultRange.toTime.toDate()
212
        };
213

  
214
    });
215

  
197 216
    $rootScope.$on('infoLocation', function (event, args) {
198 217
        $scope.showInfoLoading = true;
199 218

  
219
        let params = $location.search();
220
        params.deviceId = args.id;
221
        params.direction = args.direction;
222
        $location.search(params);
223

  
224
        let range = $scope.getRange();
225

  
200 226
        Device.get({
201
            id: args.id
202
            // dateFrom: $scope.search.fromDate.getTime(),
203
            // dateTo: $scope.search.toDate.getTime(),
204
            // timeFrom: $scope.search.fromTime.getTime(),
205
            // timeTo: $scope.search.toTime.getTime(),
206
            // direction: $scope.search.direction ? 1 : 0
227
            id: args.id,
228
            direction: args.direction,
229
            dateFrom: range.fromDate.format('YYYY-MM-DD'),
230
            dateTo: range.toDate.format('YYYY-MM-DD'),
231
            timeFrom: range.fromTime.format('HH:mm'),
232
            timeTo: range.toTime.format('HH:mm'),
207 233
        }, function (data) {
208 234
            $rootScope.selectDevice = data;
235

  
236
            $scope.typeVehicle = null;
237
            $scope.renderGraphAverageSpeed();
238
            $scope.renderGraphNumberVehicles();
239

  
209 240
            $scope.showInfoLoading = false;
210 241
        }, function (response) {
211 242
            $rootScope.selectDevice = null;
......
216 247

  
217 248
    });
218 249

  
250
    $scope.changeRange = function () {
251
        if ($scope.range.fromDate >= $scope.range.toDate || $scope.range.fromTime >= $scope.range.toTime) {
252
            $rootScope.selectDevice.traffics = [];
253
            return;
254
        }
255

  
256
        let range = $scope.getRange();
257

  
258
        let params = $location.search();
259
        params.fromDate = range.fromDate.format('YYYY-MM-DD');
260
        params.toDate = range.toDate.format('YYYY-MM-DD');
261
        params.fromTime = range.fromTime.format('HH:mm');
262
        params.toTime = range.toTime.format('HH:mm');
263
        $location.search(params);
264

  
265
        if ($rootScope.selectDevice)
266
            $rootScope.$emit('infoLocation', {
267
                id: $rootScope.selectDevice.id,
268
                direction: $rootScope.selectDevice.direction
269
            });
270
    };
271

  
272
    $scope.getRange = function () {
273
        let defaultRange = $scope.defaultRange();
274

  
275
        return {
276
            fromDate: moment($scope.range.fromDate).isValid() ? moment($scope.range.fromDate) : defaultRange.fromDate,
277
            toDate: moment($scope.range.toDate).isValid() ? moment($scope.range.toDate) : defaultRange.toDate,
278
            fromTime: moment($scope.range.fromTime).isValid() ? moment($scope.range.fromTime) : defaultRange.fromTime,
279
            toTime: moment($scope.range.toTime).isValid() ? moment($scope.range.toTime) : defaultRange.toTime
280
        };
281
    };
282

  
283
    $scope.defaultRange = function () {
284
        return {
285
            fromDate: moment().day(-30),
286
            toDate: moment().day(-1),
287
            fromTime: moment({hour: 7}),
288
            toTime: moment({hour: 16})
289
        };
290
    };
291

  
292

  
293
    $scope.renderGraphAverageSpeed = function () {
294

  
295
        let t = $rootScope.selectDevice.traffics.reduce(function (l, r) {
296
            let key = r.timeFrom;
297
            if (typeof l[key] === 'undefined') {
298
                l[key] = {
299
                    numberVehicle: 0,
300
                    speedSum: 0
301
                };
302
            }
303

  
304
            if (r.speedAverage > 0) {
305
                l[key].numberVehicle += r.numberVehicle;
306
                l[key].speedSum += r.speedAverage * r.numberVehicle;
307
            }
308
            return l;
309
        }, {});
310

  
311
        let labels = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
312
            return d.timeFrom;
313
        }));
314
        let data = Object.values(t).map(function (d) {
315
            return Math.round(d.speedSum / d.numberVehicle);
316
        });
317

  
318

  
319
        let canvasGraphAverageSpeed = document.getElementById('graphAverageSpeed').getContext('2d');
320

  
321
        if ($scope.graphAverageSpeed)
322
            $scope.graphAverageSpeed.destroy();
323

  
324
        $scope.graphAverageSpeed = new Chart(canvasGraphAverageSpeed, {
325
            type: 'line',
326
            data: {
327
                labels: labels,
328
                datasets: [{
329
                    data: data,
330
                    borderWidth: 2,
331
                    label: "Rychlost",
332
                    fill: 'start',
333
                    backgroundColor: 'rgba(0, 123, 255, 0.3)',
334
                    borderColor: 'rgba(0, 123, 255,1)',
335
                    cubicInterpolationMode: 'monotone',
336
                    radius: 0
337
                }]
338
            },
339
            options: {
340
                responsive: true,
341
                pointDot: false,
342
                scales: {
343
                    xAxes: [{
344
                        ticks: {
345
                            autoSkip: true,
346
                            maxTicksLimit: 15
347
                        }
348
                    }],
349
                    yAxes: [{
350
                        scaleLabel: {
351
                            display: true,
352
                            labelString: 'km/h'
353
                        },
354
                        ticks: {
355
                            beginAtZero: true
356
                        }
357
                    }]
358
                },
359
                tooltips: {
360
                    enabled: true,
361
                    mode: 'single',
362
                    callbacks: {
363
                        label: function (tooltipItems) {
364
                            return tooltipItems.yLabel + ' km/h';
365
                        }
366
                    }
367
                }
368
            }
369
        });
370
    };
371

  
372

  
373
    $scope.renderGraphNumberVehicles = function () {
374
        let color = ['rgba(158, 158, 158, #alpha)', 'rgba(213, 0, 0, #alpha)', 'rgba(0, 123, 255, #alpha)', 'rgba(170, 0, 255, #alpha)',
375
            'rgba(0, 200, 83, #alpha)', 'rgba(255, 214, 0, #alpha)', 'rgba(255, 109, 0, #alpha)',
376
            'rgba(174, 234, 0, #alpha)', 'rgba(98, 0, 234, #alpha)', 'rgba(255, 171, 0, #alpha)', 'rgba(100, 221, 23, #alpha)', 'rgba(0, 184, 212, #alpha)'];
377

  
378

  
379
        let labels = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
380
            return d.timeFrom;
381
        }));
382

  
383
        let useVehiclesIds = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
384
            return d.typeVehicleId;
385
        }));
386

  
387
        $scope.filterVehicles = jQuery.grep($scope.vehicles, function (n) {
388
            return useVehiclesIds.indexOf(n.id) >= 0;
389
        });
390

  
391
        let datasets = [];
392
        for (let i = 0, vehicle; vehicle = $scope.filterVehicles[i]; i++) {
393
            if ($scope.typeVehicle == null || $scope.typeVehicle === vehicle.id) {
394
                let dataset = {
395
                    label: vehicle.name,
396
                    backgroundColor: color[vehicle.id].replace("#alpha", "0.3"),
397
                    borderColor: color[vehicle.id].replace("#alpha", "1"),
398
                    borderWidth: 2,
399
                    data: []
400
                };
401

  
402
                let l = 0;
403
                for (let j = 0, traffic; traffic = $rootScope.selectDevice.traffics[j]; j++) {
404
                    if (labels[l] !== traffic.timeFrom) {
405
                        l++;
406
                        if (dataset.data.length < l) {
407
                            dataset.data.push(0);
408
                        }
409
                    }
410
                    if (traffic.typeVehicleId === vehicle.id) {
411
                        dataset.data.push(traffic.numberVehicleAverage);
412
                    }
413
                }
414
                datasets.push(dataset);
415
            }
416
        }
417

  
418
        let canvasGraphNumberVehicles = document.getElementById('graphNumberVehicles').getContext('2d');
419

  
420
        if ($scope.graphNumberVehicles)
421
            $scope.graphNumberVehicles.destroy();
422

  
423
        $scope.graphNumberVehicles = new Chart(canvasGraphNumberVehicles, {
424
            type: 'bar',
425
            data: {
426
                labels: labels,
427
                datasets: datasets
428
            },
429
            options: {
430
                tooltips: {
431
                    mode: 'index',
432
                    intersect: false
433
                },
434
                responsive: true,
435
                scales: {
436
                    xAxes: [{
437
                        stacked: true,
438
                        ticks: {
439
                            autoSkip: true,
440
                            maxTicksLimit: 15
441
                        }
442
                    }],
443
                    yAxes: [{
444
                        scaleLabel: {
445
                            display: true,
446
                            labelString: "počet vozidel"
447
                        },
448
                        stacked: true
449
                    }]
450
                }
451
            }
452
        });
453
    };
454

  
455

  
219 456
    $scope.infoClose = function () {
220 457
        $rootScope.selectDevice = null;
221 458

  
459
        let params = $location.search();
460
        params.deviceId = null;
461
        params.direction = null;
462
        $location.search(params);
463

  
222 464
        $rootScope.$emit('setDefaultMap', null);
223 465
    };
224 466
});
......
239 481
            mapTypeId: 'roadmap',
240 482
            zoom: config.DEFAULT_ZOOM,
241 483
            lat: config.DEFAULT_POSITION.LAT,
242
            lng: config.DEFAULT_POSITION.LNG
484
            lng: config.DEFAULT_POSITION.LNG,
485
            // styles: [
486
            //     {
487
            //         featureType: "poi",
488
            //         elementType: "labels",
489
            //         stylers: [{ visibility: "off" }]
490
            //     }
491
            // ]
243 492
        });
244 493

  
245 494
        Device.query({showDirection: 0}, function (data) {
246
            $scope.createMarkerNext(data, 0);
495
            for (let i = 0, lctn; lctn = data[i]; i++) {
496
                $scope.createMarker(lctn);
497
            }
247 498
        }, function (response) {
248 499
            console.log('Error api all Devices');
249 500
            $rootScope.handleErrorResponse(response);
250 501
        });
251 502
    };
252 503

  
253
    $scope.createMarkerNext = function (data, i) {
254
        var lctn = data[i];
255

  
256
        GMaps.geocode({
257
            address: lctn.street + ', ' + lctn.town + ', Plzeňský kraj',
258
            callback: function (results, status) {
259
                if (status === 'OK') {
260
                    latlng = results[0].geometry.location;
261

  
262
                    var marker = $scope.map.addMarker({
263
                        lat: latlng.lat(),
264
                        lng: latlng.lng(),
265
                        title: lctn.name,
266
                        click: function (e) {
267
                            $rootScope.$emit('infoLocation', {id: lctn.id});
268
                            //alert("asdfas");
269
                        },
270
                        infoWindow: {
271
                            content: '<h6 class="mb-1">' + lctn.name + '</h6>'
272
                            + '<address>' + lctn.street + ', ' + lctn.town + '</address>'
273
                        },
274
                        id: lctn.id
275
                    });
276

  
277
                } else if (status === 'ZERO_RESULTS') {
278
                    console.log('No results found address');
279
                }
280

  
281
                i++;
282
                if (i < data.length) {
283
                    setTimeout(function () {
284
                        $scope.createMarkerNext(data, i);
285
                    }, 900);
286
                }
287
            }
288
        });
289 504

  
505
    $scope.createMarker = function (lctn) {
506
        if (lctn.lat && lctn.lng) {
507
            $scope.map.addMarker({
508
                lat: lctn.lat,
509
                lng: lctn.lng,
510
                title: lctn.name,
511
                click: function () {
512
                    $rootScope.$emit('infoLocation', {id: lctn.id});
513
                },
514
                infoWindow: {
515
                    content: '<h6 class="mb-1">' + lctn.name + '</h6>'
516
                    + '<address>' + lctn.street + ', ' + lctn.town + '</address>'
517
                },
518
                id: lctn.id
519
            });
520
        }
290 521
    };
291 522

  
292 523
    $rootScope.$on('activeMarker', function (event, args) {
293
        var id = args.id;
294
        for (var i = 0, marker; marker = $scope.map.markers[i]; i++) {
524
        let id = args.id;
525
        for (let i = 0, marker; marker = $scope.map.markers[i]; i++) {
295 526
            if (marker.id && marker.id === id && marker.infoWindow) {
296 527
                $scope.map.setCenter(marker.position.lat(), marker.position.lng());
297 528
                $scope.map.setZoom(12);
529
                $scope.map.hideInfoWindows();
298 530
                marker.infoWindow.open($scope.map, marker);
299 531
                return;
300 532
            }
......
306 538
        $scope.map.setZoom(config.DEFAULT_ZOOM);
307 539
        $scope.map.hideInfoWindows();
308 540
    });
309

  
310 541
});
311 542

  
312 543

  
313
app.factory("Device", function ($resource, config) {
314
    return $resource(config.API_URL + "/devices/:id", {id: '@id'}, {
544
app.factory('Device', function ($resource, config) {
545
    return $resource(config.API_URL + '/devices/:id', {id: '@id'}, {
315 546
        'get': {
316
            url: config.API_URL + '/devices/:id',
547
            url: config.API_URL + '/devices/:id/time-period',
317 548
            method: 'GET',
318
            headers: {jwt: config.API_TOKEN}
549
            headers: {
550
                'Content-Type': 'application/json',
551
                'Accept': 'application/json',
552
                'jwt': config.API_TOKEN
553
            }
319 554
        },
320 555
        'query': {
321 556
            url: config.API_URL + '/devices',
322 557
            method: 'GET',
323 558
            isArray: true,
324
            headers: {jwt: config.API_TOKEN}
559
            headers: {
560
                'Content-Type': 'application/json',
561
                'Accept': 'application/json',
562
                'jwt': config.API_TOKEN
563
            }
325 564
        }
326 565
    });
327 566
});
328 567

  
329
app.factory("Vehicle", function ($resource, config) {
330
    return $resource(config.API_URL + "/vehicles", null, {
568
app.factory('Vehicle', function ($resource, config) {
569
    return $resource(config.API_URL + '/vehicles', null, {
331 570
        'query': {
332 571
            url: config.API_URL + '/vehicles',
333 572
            method: 'GET',
334 573
            isArray: true,
335
            headers: {jwt: config.API_TOKEN}
574
            headers: {
575
                'Content-Type': 'application/json',
576
                'Accept': 'application/json',
577
                'jwt': config.API_TOKEN
578
            }
336 579
        }
337 580
    });
338 581
});

Také k dispozici: Unified diff