iguazio.dashboard-controls 1.2.13 → 1.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iguazio.dashboard-controls",
3
- "version": "1.2.13",
3
+ "version": "1.2.14",
4
4
  "main": "dist/js/iguazio.dashboard-controls.js",
5
5
  "description": "Collection of resources (such as CSS styles, fonts and images) and AngularJs 1.x components and services to share among different Iguazio repos.",
6
6
  "repository": {
@@ -6,7 +6,6 @@
6
6
 
7
7
  function ControlPanelLogsDataService($q, lodash, ElasticsearchService) {
8
8
  return {
9
- collectLogs: collectLogs,
10
9
  entriesPaginated: search,
11
10
  logsPaginated: logsWidthReplicas
12
11
  };
@@ -150,58 +149,6 @@
150
149
  });
151
150
  }
152
151
 
153
- /**
154
- * Collects all the logs using chunks, and saved them as an array of strings.
155
- * @param {Object} queryParams - additional parameters
156
- * @param {string} queryParams.query - search query text
157
- * @param {string} queryParams.timeFrame - selected time period to show results for
158
- * @returns {Promise.<Array>} a promise resolving to an array of logs.
159
- */
160
- function collectLogs(queryParams) {
161
- var keepAlive = '5m';
162
- var size = 10000;
163
- var downloadLogsData = [];
164
- var MAX_DOWNLOAD_LOGS = 100000;
165
-
166
- return createSearch(size, keepAlive, queryParams).then(function (response) {
167
- var hits = response.hits.hits;
168
-
169
- if (hits.length > 0) {
170
- downloadLogsData = downloadLogsData.concat(prepareLogs(hits));
171
-
172
- return getNextChunk(response._scroll_id);
173
- }
174
- });
175
-
176
- function getNextChunk(scroll) {
177
- return getNextScrollLogs(keepAlive, scroll)
178
- .then(function (response) {
179
- var hits = response.hits.hits;
180
-
181
- if (hits.length > 0 && downloadLogsData.length < MAX_DOWNLOAD_LOGS - size) {
182
- downloadLogsData = downloadLogsData.concat(prepareLogs(hits));
183
-
184
- return getNextChunk(response._scroll_id);
185
- } else {
186
- return downloadLogsData;
187
- }
188
- }).catch(function (error) {
189
- throw error;
190
- });
191
- }
192
-
193
- function prepareLogs(logs) {
194
- return logs.map(function (logData) {
195
- var log = lodash.get(logData, '_source', {});
196
- var level = log.level ? ' (' + log.level + ') ' : '';
197
- var name = lodash.get(log, 'kubernetes.pod.name', lodash.get(log, 'name', ''));
198
-
199
- return log['@timestamp'] + ' ' + name + level +
200
- lodash.get(log, 'message', '') + ' ' + JSON.stringify(lodash.get(log, 'more', {}));
201
- });
202
- }
203
- }
204
-
205
152
  /**
206
153
  * Mocks the real search function without the need for a running Elasticsearch service, for development
207
154
  * purposes.
@@ -0,0 +1,200 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ angular.module('iguazio.dashboard-controls')
5
+ .factory('ExecutionLogsDataService', ExecutionLogsDataService);
6
+
7
+ function ExecutionLogsDataService($rootScope, $i18next, $q, i18next, lodash, NuclioRestangular) {
8
+ var lng = i18next.language;
9
+
10
+ return {
11
+ collectLogs: collectLogs,
12
+ logsPaginated: logsPaginated,
13
+ getReplicasList: getReplicasList
14
+ };
15
+
16
+ //
17
+ // Public methods
18
+ //
19
+
20
+ /**
21
+ * Collects all the logs using chunks, and saved them as an array of strings.
22
+ * @param {Object} queryParams - additional parameters
23
+ * @returns {Promise.<Array>} a promise resolving to an array of logs.
24
+ */
25
+ function collectLogs(queryParams) {
26
+ var size = 10000;
27
+ var downloadLogsData = [];
28
+ var MAX_DOWNLOAD_LOGS = 100000;
29
+
30
+ return getLogsList(0, size, queryParams).then(function (response) {
31
+ var hits = response.hits.hits;
32
+
33
+ if (hits.length > 0) {
34
+ downloadLogsData = downloadLogsData.concat(prepareLogs(hits));
35
+
36
+ return getNextChunk(lodash.get(lodash.last(hits), 'sort'));
37
+ }
38
+ });
39
+
40
+ function getNextChunk(searchAfter) {
41
+ return getLogsList(0, size, Object.assign(queryParams, {searchAfter}))
42
+ .then(function (response) {
43
+ var hits = response.hits.hits;
44
+
45
+ if (hits.length > 0 && downloadLogsData.length < MAX_DOWNLOAD_LOGS - size) {
46
+ downloadLogsData = downloadLogsData.concat(prepareLogs(hits));
47
+
48
+ return getNextChunk(lodash.get(lodash.last(response.hits.hits), 'sort'));
49
+ } else {
50
+ if (hits.length > 0) {
51
+ downloadLogsData = downloadLogsData.concat(prepareLogs(hits));
52
+ }
53
+
54
+ return downloadLogsData;
55
+ }
56
+ }).catch(function (error) {
57
+ throw error;
58
+ });
59
+ }
60
+
61
+ function prepareLogs(logs) {
62
+ return logs.map(function (logData) {
63
+ var log = lodash.get(logData, '_source', {});
64
+ var level = log.level ? ' (' + log.level + ') ' : '';
65
+ var name = lodash.get(log, 'kubernetes.pod.name', lodash.get(log, 'name', ''));
66
+
67
+ return log['@timestamp'] + ' ' + name + level +
68
+ lodash.get(log, 'message', '') + ' ' + JSON.stringify(lodash.get(log, 'more', {}));
69
+ });
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Get latest log entries (used for pagination)
75
+ * @param {number} page - current page
76
+ * @param {number} perPage - max items count on a page
77
+ * @param {Object} queryParams - additional parameters
78
+ * update
79
+ * @returns {Promise} array of log entries
80
+ */
81
+ function logsPaginated(page, perPage, queryParams) {
82
+ return getLogsList(page, perPage, queryParams).then(function (response) {
83
+ // Saved log entry can be found in `_source` property
84
+ // For now all additional data from Elasticsearch is not used, so only entries are returned
85
+ var logs = lodash.map(response.hits.hits, '_source')
86
+
87
+ logs.total_logs_count = lodash.get(response, 'hits.total.value', 0);
88
+ logs.total_pages = Math.ceil(lodash.get(response, 'hits.total.value', 0) / perPage);
89
+
90
+ return logs;
91
+ })
92
+ .catch(function (error) {
93
+ return $q.reject(error);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Provides filtered replicas list
99
+ * @param {string} projectId - project name
100
+ * @param {string} funcName - function name
101
+ * @param {Object} queryParams - additional parameters
102
+ * update
103
+ * @returns {Promise} array of log entries
104
+ */
105
+ function getReplicasList(projectId, funcName, queryParams) {
106
+ var headers = {
107
+ 'Cache-Control': 'no-cache',
108
+ 'Content-Type': 'application/json',
109
+ 'x-nuclio-project-name': projectId
110
+ };
111
+ var requestParams = Object.assign(queryParams, {includeOffline: true});
112
+
113
+ if (queryParams.timeFilter) {
114
+ queryParams.timeFilter = {
115
+ since: queryParams.timeFilter.from,
116
+ until: queryParams.timeFilter.to,
117
+ sort: queryParams.timeFilter.sort
118
+ }
119
+ }
120
+
121
+ return NuclioRestangular.one('functions', funcName)
122
+ .one('replicas')
123
+ .get(requestParams, headers)
124
+ .then(function (response) {
125
+ return response.replicas.names.concat(response.offlineReplicas.names);
126
+ })
127
+ .catch(function (error) {
128
+ var errorMessages = {
129
+ '400': $i18next.t('common:ERROR_MSG.PAGINATION.400', { lng: lng }),
130
+ '403': $i18next.t('common:ERROR_MSG.PAGINATION.403', { lng: lng }),
131
+ '500': $i18next.t('common:ERROR_MSG.ERROR_ON_SERVER_SIDE', { lng: lng }),
132
+ 'default': $i18next.t('common:ERROR_MSG.UNKNOWN_ERROR', { lng: lng })
133
+ };
134
+
135
+ $rootScope.$broadcast('splash-screen_show-error', {
136
+ alertText: lodash.get(errorMessages, String(error.status), errorMessages.default) +
137
+ ' ' + $i18next.t('common:ERROR_MSG.TRY_REFRESHING_THE_PAGE', { lng: lng })
138
+ });
139
+
140
+ return $q.reject(error);
141
+ });
142
+ }
143
+
144
+ //
145
+ // Private methods
146
+ //
147
+
148
+ /**
149
+ * Gets the list of logs based on parameters
150
+ * @param {number} page - current page
151
+ * @param {number} perPage - max items count on a page
152
+ * @param {Object} queryParams - additional parameters
153
+ * update
154
+ * @returns {Promise} array of log entries
155
+ */
156
+ function getLogsList(page, perPage, queryParams) {
157
+ var headers = {
158
+ 'Cache-Control': 'no-cache',
159
+ 'Content-Type': 'application/json',
160
+ 'x-nuclio-project-name': queryParams.projectName
161
+ };
162
+
163
+ var sizeParams = {}
164
+
165
+ if (queryParams.searchAfter) {
166
+ queryParams.searchAfter[1] = '"' + queryParams.searchAfter[1] + '"';
167
+
168
+ sizeParams = {
169
+ size: perPage,
170
+ searchAfter: queryParams.searchAfter
171
+ }
172
+ } else {
173
+ sizeParams = {
174
+ size: perPage,
175
+ from: page * perPage,
176
+ }
177
+ }
178
+
179
+ var filters = lodash.omitBy(Object.assign(sizeParams, queryParams.filters), function (value) {
180
+ return lodash.isObject(value) || lodash.isString(value) ? lodash.isEmpty(value) : false;
181
+ });
182
+
183
+ if (filters.timeFilter) {
184
+ filters.timeFilter = {
185
+ since: filters.timeFilter.from,
186
+ until: filters.timeFilter.to,
187
+ sort: filters.timeFilter.sort
188
+ }
189
+ }
190
+
191
+ return NuclioRestangular.one('functions', queryParams.functionName)
192
+ .one('proxy-logs')
193
+ .get(filters, headers)
194
+ .catch(function (error) {
195
+ return $q.reject(error);
196
+ });
197
+ }
198
+
199
+ }
200
+ }());
@@ -28,16 +28,12 @@ such restriction.
28
28
  controller: NclVersionExecutionLogController
29
29
  });
30
30
 
31
- function NclVersionExecutionLogController(lodash, $interval, i18next, $i18next, $rootScope, moment, ControlPanelLogsDataService,
32
- ConfigService, ExportService, LoginService, PaginationService) {
31
+ function NclVersionExecutionLogController(lodash, moment, $interval, i18next, $i18next, $rootScope, ExecutionLogsDataService,
32
+ ExportService, LoginService, PaginationService) {
33
33
  var ctrl = this;
34
34
  var lng = i18next.language;
35
35
 
36
36
  var refreshInterval = null;
37
- var initialTimeRange = {
38
- from: null,
39
- to: null
40
- };
41
37
  var initialDatePreset = '7d';
42
38
  var initialReplicas = [];
43
39
  var defaultFilter = {
@@ -50,12 +46,12 @@ such restriction.
50
46
  error: false
51
47
  }
52
48
  };
49
+ var projectName = '';
53
50
 
54
51
  ctrl.downloadButtonIsDisabled = false;
55
52
  ctrl.isSplashShowed = {
56
53
  value: false
57
54
  };
58
- ctrl.lastEntryTimestamp = null;
59
55
  ctrl.logs = {};
60
56
  ctrl.replicasList = [];
61
57
  ctrl.filter = {};
@@ -69,7 +65,11 @@ such restriction.
69
65
  };
70
66
  ctrl.datePreset = initialDatePreset;
71
67
  ctrl.logsAreDownloading = false;
72
- ctrl.timeRange = initialTimeRange;
68
+ ctrl.timeRange = {
69
+ from: null,
70
+ to: null,
71
+ sort: 'desc'
72
+ };
73
73
  ctrl.searchStates = {};
74
74
  ctrl.selectedReplicas = [];
75
75
  ctrl.isFiltersShowed = {
@@ -187,11 +187,27 @@ such restriction.
187
187
  defaultFilter.name = ctrl.version.metadata.name;
188
188
  ctrl.filter = lodash.cloneDeep(defaultFilter);
189
189
 
190
- PaginationService.addPagination(ctrl, 'logs', 'ControlPanelLogsDataService', onChangePageCallback, true);
190
+ projectName = lodash.get(ctrl.version, ['metadata', 'labels', 'nuclio.io/project-name']);
191
191
 
192
- ctrl.page.size = ctrl.perPageValues[0].id;
192
+ PaginationService.addPagination(ctrl, 'logs', 'ExecutionLogsDataService', onChangePageCallback, true);
193
193
 
194
- applyFilters();
194
+ ctrl.timeRange = getInitialTimeRange();
195
+
196
+ ctrl.isSplashShowed.value = true;
197
+ ExecutionLogsDataService.getReplicasList(projectName, ctrl.version.metadata.name, {timeFilter: ctrl.timeRange}).then(function (replicas) {
198
+ ctrl.replicasList = replicas.map(function (replica) {
199
+ return {
200
+ label: replica,
201
+ id: replica,
202
+ value: replica,
203
+ checked: true
204
+ }
205
+ });
206
+ ctrl.selectedReplicas = angular.copy(replicas);
207
+ initialReplicas = replicas;
208
+
209
+ applyFilters();
210
+ });
195
211
  }
196
212
 
197
213
  /**
@@ -211,7 +227,6 @@ such restriction.
211
227
  function applyFilters() {
212
228
  stopAutoUpdate();
213
229
  calcActiveFilters();
214
- generateFilterQuery();
215
230
  searchWithParams(0, ctrl.page.size);
216
231
  }
217
232
 
@@ -222,7 +237,8 @@ such restriction.
222
237
  stopAutoUpdate();
223
238
 
224
239
  ctrl.downloadButtonIsDisabled = true;
225
- return ControlPanelLogsDataService.collectLogs(queryParams())
240
+
241
+ return ExecutionLogsDataService.collectLogs(queryParams())
226
242
  .then(function (response) {
227
243
  ExportService.exportLogs(response, ctrl.version.metadata.name);
228
244
  }).finally(function () {
@@ -273,8 +289,7 @@ such restriction.
273
289
  ctrl.timeRange = lodash.mapValues(dateTimeRange, function (range) {
274
290
  return new Date(range).toISOString();
275
291
  });
276
-
277
- ctrl.lastEntryTimestamp = null;
292
+ ctrl.timeRange.sort = 'desc';
278
293
  }
279
294
 
280
295
  /**
@@ -283,11 +298,30 @@ such restriction.
283
298
  function refreshLogs() {
284
299
  startAutoUpdate();
285
300
 
286
- ControlPanelLogsDataService.logsPaginated(ctrl.page.number, ctrl.page.size, queryParams())
287
- .then(function (logs) {
288
- if (logs.length > 0) {
289
- ctrl.logs = lodash.cloneDeep(logs);
290
- }
301
+ ctrl.isSplashShowed.value = true
302
+
303
+ ExecutionLogsDataService.getReplicasList(projectName, ctrl.version.metadata.name, {timeFilter: ctrl.timeRange})
304
+ .then(function (replicas) {
305
+ ctrl.replicasList = replicas.map(function (replica) {
306
+ return {
307
+ label: replica,
308
+ id: replica,
309
+ value: replica,
310
+ checked: true
311
+ }
312
+ });
313
+
314
+ initialReplicas = replicas;
315
+
316
+ return ExecutionLogsDataService.logsPaginated(ctrl.page.number, ctrl.page.size, queryParams())
317
+ .then(function (logs) {
318
+ if (logs.length > 0) {
319
+ ctrl.logs = lodash.cloneDeep(logs);
320
+ }
321
+ });
322
+ })
323
+ .finally(function () {
324
+ ctrl.isSplashShowed.value = false;
291
325
  });
292
326
  }
293
327
 
@@ -296,7 +330,7 @@ such restriction.
296
330
  */
297
331
  function resetFilters() {
298
332
  ctrl.applyIsDisabled = false;
299
- ctrl.timeRange = initialTimeRange;
333
+ ctrl.timeRange = getInitialTimeRange();
300
334
  ctrl.datePreset = initialDatePreset;
301
335
  ctrl.selectedReplicas = initialReplicas;
302
336
 
@@ -336,7 +370,7 @@ such restriction.
336
370
  return;
337
371
  }
338
372
 
339
- ControlPanelLogsDataService.logsPaginated(ctrl.page.number, ctrl.page.size, queryParamsAutoUpdate())
373
+ ExecutionLogsDataService.logsPaginated(ctrl.page.number, ctrl.page.size, queryParams())
340
374
  .then(function (logs) {
341
375
  if (logs.length > 0) {
342
376
  ctrl.logs = lodash.cloneDeep(logs);
@@ -365,79 +399,27 @@ such restriction.
365
399
  * Updates latest timestamp when new data received and generates replicas list
366
400
  */
367
401
  function onChangePageCallback() {
368
- if (ctrl.logs.length > 0) {
369
- ctrl.lastEntryTimestamp = ctrl.logs[0].time;
370
-
371
- if (ctrl.logs.replicas) {
372
- ctrl.replicasList = ctrl.logs.replicas.map(function (replica) {
373
- return {
374
- label: replica,
375
- id: replica,
376
- value: replica,
377
- checked: true
378
- }
379
- });
380
- ctrl.selectedReplicas = angular.copy(ctrl.logs.replicas);
381
- initialReplicas = ctrl.logs.replicas;
382
- }
383
- }
384
-
385
402
  startAutoUpdate();
386
403
  }
387
404
 
388
- /**
389
- * Generates query string from all filters
390
- */
391
- function generateFilterQuery() {
392
- var levels = lodash.chain(ctrl.filter.level).pickBy().keys().join(' OR ').value();
393
- var projectName = lodash.get(ctrl.version, ['metadata', 'labels', 'nuclio.io/project-name']);
394
- var projectFilter = '(nuclio.project_name.keyword:' + projectName + ' OR nuclio.project_name:' + projectName + ')';
395
- var queries = ['system-id:"' + ConfigService.systemId + '"', '_exists_:nuclio', projectFilter];
396
-
397
- if (ctrl.selectedReplicas.length && ctrl.selectedReplicas.length !== initialReplicas.length) {
398
- var replicas = ctrl.selectedReplicas.join(' OR ');
399
-
400
- queries.push('kubernetes.pod.name:(' + replicas + ')');
401
- }
402
-
403
- if (!lodash.isEmpty(ctrl.filter.message)) {
404
- // Escape reserved characters: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
405
- var escapedMessage = ctrl.filter.message.replace(/[+\-=&|!(){}[\]^"~*?:\\/]/g, '\\$&')
406
- .replace(/<|>/g, '');
407
- queries.push('(message:' + escapedMessage + ' OR more:' + escapedMessage + ')');
408
- }
409
-
410
- if (!lodash.isEmpty(levels)) {
411
- queries.push('level:(' + levels + ')');
412
- }
413
-
414
- ctrl.filterQuery = lodash.join(queries, ' AND ');
415
- ctrl.filterName = ctrl.version.metadata.name;
416
- }
417
-
418
405
  /**
419
406
  * Generates query params for ordinary request, for example, when page was changed
420
407
  * @returns {Object}
421
408
  */
422
409
  function queryParams() {
423
410
  return {
424
- query: ctrl.filterQuery,
425
- filterName: ctrl.filterName,
426
- timeFrame: ctrl.datePreset,
427
- customTimeFrame: ctrl.timeRange
411
+ functionName: ctrl.version.metadata.name,
412
+ projectName,
413
+ filters: {
414
+ substring: ctrl.filter.message.replace(/[+\-=&|!(){}[\]^"~*?:\\/]/g, '\\$&')
415
+ .replace(/<|>/g, ''),
416
+ logLevels: lodash.chain(ctrl.filter.level).pickBy().keys().value(),
417
+ replicaNames: ctrl.selectedReplicas,
418
+ timeFilter: ctrl.timeRange
419
+ }
428
420
  };
429
421
  }
430
422
 
431
- /**
432
- * Generates query params for auto update request
433
- * @returns {Object}
434
- */
435
- function queryParamsAutoUpdate() {
436
- return lodash.defaults(queryParams(), {
437
- lastEntryTimestamp: ctrl.lastEntryTimestamp
438
- });
439
- }
440
-
441
423
  /**
442
424
  * Starts auto update process
443
425
  * @param {bool} [force] - used for making first update immediately after refreshInterval is created
@@ -466,5 +448,17 @@ such restriction.
466
448
  refreshInterval = null;
467
449
  }
468
450
  }
451
+
452
+ /**
453
+ * Provides initial time range
454
+ */
455
+ function getInitialTimeRange() {
456
+ var weekDate = ctrl.customDatePresets['7d'].getRange();
457
+
458
+ return {
459
+ from: weekDate.from.toISOString(),
460
+ sort: 'desc'
461
+ };
462
+ }
469
463
  }
470
464
  }());
@@ -28,8 +28,7 @@
28
28
  data-type="control"
29
29
  data-rule-type="message"
30
30
  data-search-states="$ctrl.searchStates"
31
- data-search-callback="$ctrl.onQueryChanged(searchQuery, ruleType)"
32
- data-on-search-submit="$ctrl.applyFilters()">
31
+ data-search-callback="$ctrl.onQueryChanged(searchQuery, ruleType)">
33
32
  </igz-search-input>
34
33
  </div>
35
34
 
@@ -125,8 +124,7 @@
125
124
  <div class="actions-bar-left">
126
125
  <igz-action-item-refresh
127
126
  data-is-disabled="$ctrl.isFunctionDeploying()"
128
-
129
- data-refresh="$ctrl.searchWithParams($ctrl.page.number, $ctrl.page.size)">
127
+ data-refresh="$ctrl.refreshLogs()">
130
128
  </igz-action-item-refresh>
131
129
  </div>
132
130
  <div class="actions-bar-left">
@@ -35,13 +35,12 @@ such restriction.
35
35
  controller: NclVersionController
36
36
  });
37
37
 
38
- function NclVersionController($i18next, $interval, $injector, $rootScope, $scope, $state, $stateParams, $transitions, $timeout,
38
+ function NclVersionController($i18next, $interval, $rootScope, $scope, $state, $stateParams, $transitions, $timeout,
39
39
  i18next, lodash, ngDialog, ConfigService, DialogsService, ExportService,
40
40
  FunctionsService, GeneralDataService, NuclioHeaderService,
41
41
  VersionHelperService) {
42
42
  var ctrl = this;
43
43
  var deregisterFunction = null;
44
- var servicesService = null;
45
44
  var interval = null;
46
45
  var lng = i18next.language;
47
46
 
@@ -228,10 +227,6 @@ such restriction.
228
227
  }
229
228
  });
230
229
 
231
- if ($injector.has('ServicesService')) {
232
- servicesService = $injector.get('ServicesService')
233
- }
234
-
235
230
  setImageNamePrefixTemplate();
236
231
  setIngressHost();
237
232
  initLogTabs();
@@ -687,18 +682,11 @@ such restriction.
687
682
  * Checks if the "Execution log" tab should be shown
688
683
  */
689
684
  function initLogTabs() {
690
- if (lodash.get(ConfigService, 'url.elasticsearch.path', '') && servicesService) {
691
- servicesService.getServices().then(function (result) {
692
- var services = result.services;
693
- var logForwarderService = lodash.find(services, ['spec.name', 'log-forwarder']);
694
-
695
- if (servicesService.isEnabled(logForwarderService)) {
696
- ctrl.navigationTabsConfig.push({
697
- tabName: $i18next.t('functions:EXECUTION_LOG', { lng: lng }),
698
- id: 'execution-log',
699
- uiRoute: 'app.project.function.edit.execution-log'
700
- });
701
- }
685
+ if (lodash.get(ConfigService, 'nuclio.defaultProxyLogsSource', '') === 'elasticsearch') {
686
+ ctrl.navigationTabsConfig.push({
687
+ tabName: $i18next.t('functions:EXECUTION_LOG', { lng: lng }),
688
+ id: 'execution-log',
689
+ uiRoute: 'app.project.function.edit.execution-log'
702
690
  });
703
691
  }
704
692
  }