diff --git a/docs/changelog.rst b/docs/changelog.rst
index a6a18926..e5ea9f1b 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
This project adheres to `Semantic Versioning `_.
Please note that the changes before version 1.10.0 have not been documented.
+v3.3.1
+----------
+Changed
+
+- Telemetry now uses dynamic server-ip
+- Removed survey
+- FollowUp questions refactored
+
v3.3.0
----------
Changed
diff --git a/docs/functionality.rst b/docs/functionality.rst
index 5d9bd8e4..ee9cf44b 100644
--- a/docs/functionality.rst
+++ b/docs/functionality.rst
@@ -346,6 +346,8 @@ Telemetry
The Dashboard is setup to be able to collect telemetric-data.
This data-collection can be toggled on and off under the "Configuration" route.
+The collected data is released weekly at https://flask-dashboard.github.io/fmd-telemetry.
+
You can find detailed information about what and how data is collected below.
What:
@@ -356,7 +358,7 @@ What:
3. **Aggregated monitoring levels:** To determine the most frequently utilized monitoring level, we aggregate the levels set from each endpoint.
-4. **Table size:** In order to determine how fast the data accumulates, we collect the size of the database and its tables.
+4. **Version number:** In order to determine how often people update their dashboards, we collect the build number.
5. **Route visits:** Which routes you use in the dashboard.
@@ -368,19 +370,29 @@ This is most of the logic behind the telemetry:
.. code-block:: python
def post_to_back_if_telemetry_enabled(class_name='Endpoints', **kwargs):
- """
- Function to send telemetry data to remote database
- """
- if telemetry_config.telemetry_consent:
- back4app_endpoint = f'https://parseapi.back4app.com/classes/{class_name}'
-
- headers = telemetry_config.telemetry_headers
- data = {'fmd_id': telemetry_config.fmd_user, 'session': telemetry_config.telemetry_session} # fmd_id is the random uuid of the user, session is amount of times app was initialized
-
- for key, value in kwargs.items():
- data[key] = value
-
- requests.post(back4app_endpoint, json=data, headers=headers)
+ """
+ Function to send data to server, with dynamic IP fetching.
+ If the IP cannot be fetched, the function will silently exit without sending data.
+ """
+ if telemetry_config.telemetry_consent or class_name == 'FollowUp':
+ github_file_url = 'https://raw.githubusercontent.com/flask-dashboard/fmd-telemetry/master/ip_address'
+ parse_server_ip = fetch_ip_from_github(github_file_url)
+ if parse_server_ip is None:
+ return # Exit silently if no IP is fetched
+
+
+
+ parse_server_endpoint = f'http://{parse_server_ip}/parse/classes/{class_name}'
+ headers = telemetry_config.telemetry_headers
+ data = {'fmd_id': telemetry_config.fmd_user, 'session': telemetry_config.telemetry_session}
+ for key, value in kwargs.items():
+ data[key] = value
+
+ try:
+ response = requests.post(parse_server_endpoint, json=data, headers=headers, timeout=1)
+ return response
+ except requests.exceptions.ConnectionError as e:
+ return None
Need more information?
diff --git a/flask_monitoringdashboard/constants.json b/flask_monitoringdashboard/constants.json
index 0f6d3e6d..121934a1 100644
--- a/flask_monitoringdashboard/constants.json
+++ b/flask_monitoringdashboard/constants.json
@@ -1,5 +1,5 @@
{
- "version": "3.3.0",
- "author": "Krzysztof Wielicki, Johannes Lind Christiansen",
+ "version": "3.3.1",
+ "author": "Johannes Lind Christiansen",
"email": "flask.monitoringdashboard@gmail.com"
}
diff --git a/flask_monitoringdashboard/core/config/__init__.py b/flask_monitoringdashboard/core/config/__init__.py
index 1bc777ca..af6d712d 100644
--- a/flask_monitoringdashboard/core/config/__init__.py
+++ b/flask_monitoringdashboard/core/config/__init__.py
@@ -199,7 +199,7 @@ def init_from(self, file=None, envvar=None, log_verbose=False):
class TelemetryConfig(object):
"""Configuration for the telemetry feature"""
- # constants for defining survey and telemetry answers
+ # constants for defining telemetry answers
NOT_ANSWERED = 1
REJECTED = 2
ACCEPTED = 3
@@ -210,7 +210,6 @@ def __init__(self):
self.telemetry_consent = False
self.telemetry_session = 0
self.telemetry_headers = {
- 'X-Parse-Application-Id': '4nHPABwkHqOZzNrFduzNyKH8q7wmPFdOWvajfWU2',
- 'X-Parse-REST-API-Key': 'zjv0WLI2K3UvpfzrfG4sPA6EykYyzZM4KxQk07Hs',
+ 'X-Parse-Application-Id': 'fmd-md',
'Content-Type': 'application/json'
}
diff --git a/flask_monitoringdashboard/core/telemetry.py b/flask_monitoringdashboard/core/telemetry.py
index c7bed58b..9daa9bd1 100644
--- a/flask_monitoringdashboard/core/telemetry.py
+++ b/flask_monitoringdashboard/core/telemetry.py
@@ -1,5 +1,6 @@
import datetime
import requests
+import functools
from sqlalchemy import func
from sqlalchemy.exc import NoResultFound, MultipleResultsFound, SQLAlchemyError
@@ -7,6 +8,7 @@
from flask_monitoringdashboard import telemetry_config
from flask_monitoringdashboard.core.config import TelemetryConfig
from flask_monitoringdashboard.core.blueprints import get_blueprint
+from flask_monitoringdashboard.core.utils import get_details
from flask_monitoringdashboard.database import TelemetryUser, Endpoint
@@ -49,6 +51,10 @@ def collect_general_telemetry_data(session, telemetry_user):
level_twos_count = counts_dict.get(2, 0)
level_threes_count = counts_dict.get(3, 0)
+ # Get details including the dashboard version
+ details = get_details(session)
+ dashboard_version = details['dashboard-version']
+
data = {'endpoints': no_of_endpoints,
'blueprints': no_of_blueprints,
'time_initialized': telemetry_user.last_initialized.strftime('%Y-%m-%d %H:%M:%S'),
@@ -56,6 +62,7 @@ def collect_general_telemetry_data(session, telemetry_user):
'monitoring_1': level_ones_count,
'monitoring_2': level_twos_count,
'monitoring_3': level_threes_count,
+ 'dashboard_version': dashboard_version,
}
# post user data
@@ -78,13 +85,10 @@ def initialize_telemetry_session(session):
telemetry_user.last_initialized = datetime.datetime.utcnow()
session.commit()
- # reset telemetry and survey prompt if declined in previous session
+ # reset telemetry if declined in previous session
if telemetry_user.monitoring_consent == TelemetryConfig.REJECTED:
telemetry_user.monitoring_consent = TelemetryConfig.NOT_ANSWERED
session.commit()
- if telemetry_user.survey_filled == TelemetryConfig.REJECTED:
- telemetry_user.survey_filled = TelemetryConfig.NOT_ANSWERED
- session.commit()
# check if telemetry's been agreed on
telemetry_config.telemetry_consent = True if telemetry_user.monitoring_consent == TelemetryConfig.ACCEPTED else False
@@ -107,17 +111,46 @@ def initialize_telemetry_session(session):
session.rollback()
-def post_to_back_if_telemetry_enabled(class_name='Endpoints', **kwargs):
+@functools.cache
+def fetch_ip_from_github(file_url):
"""
- Function to send telemetry data to remote database
+ Fetches the IP address from a text file hosted on GitHub and caches the result.
+
+ Args:
+ file_url (str): URL to the raw version of the GitHub hosted text file containing the IP address.
+
+ Returns:
+ str: IP address and port as a string, or None if unable to fetch.
"""
- if telemetry_config.telemetry_consent:
- back4app_endpoint = f'https://parseapi.back4app.com/classes/{class_name}'
+ try:
+ response = requests.get(file_url)
+ response.raise_for_status() # Raises an HTTPError for bad responses
+ return response.text.strip() # Assuming the file contains the IP address and port in the format "IP:PORT"
+ except requests.RequestException:
+ return None
+
+def post_to_back_if_telemetry_enabled(class_name='Endpoints', **kwargs):
+ """
+ Function to send data to server, with dynamic IP fetching.
+ If the IP cannot be fetched, the function will silently exit without sending data.
+ """
+ if telemetry_config.telemetry_consent or class_name == 'FollowUp':
+ github_file_url = 'https://raw.githubusercontent.com/flask-dashboard/fmd-telemetry/master/ip_address'
+ parse_server_ip = fetch_ip_from_github(github_file_url)
+ if parse_server_ip is None:
+ return # Exit silently if no IP is fetched
+
+ parse_server_endpoint = f'http://{parse_server_ip}/parse/classes/{class_name}'
headers = telemetry_config.telemetry_headers
data = {'fmd_id': telemetry_config.fmd_user, 'session': telemetry_config.telemetry_session}
-
for key, value in kwargs.items():
data[key] = value
- requests.post(back4app_endpoint, json=data, headers=headers)
+ try:
+ response = requests.post(parse_server_endpoint, json=data, headers=headers, timeout=1)
+ return response
+ except requests.exceptions.ConnectionError as e:
+ return None
+
+
\ No newline at end of file
diff --git a/flask_monitoringdashboard/core/utils.py b/flask_monitoringdashboard/core/utils.py
index 9ea71251..878be793 100644
--- a/flask_monitoringdashboard/core/utils.py
+++ b/flask_monitoringdashboard/core/utils.py
@@ -12,6 +12,7 @@
get_date_of_first_request,
get_date_of_first_request_version,
)
+from flask_monitoringdashboard import telemetry_config
def get_endpoint_details(session, endpoint_id):
@@ -57,6 +58,7 @@ def get_details(session):
'first-request': get_date_of_first_request(session),
'first-request-version': get_date_of_first_request_version(session, config.version),
'total-requests': count_total_requests(session),
+ 'fmd-id': telemetry_config.fmd_user,
}
diff --git a/flask_monitoringdashboard/database/__init__.py b/flask_monitoringdashboard/database/__init__.py
index f48289fd..8bf545af 100644
--- a/flask_monitoringdashboard/database/__init__.py
+++ b/flask_monitoringdashboard/database/__init__.py
@@ -70,9 +70,6 @@ class TelemetryUser(Base):
last_initialized = Column(DateTime, default=datetime.datetime.utcnow)
"""Check when was the last time user accessed FMD"""
- survey_filled = Column(Integer, default=1)
- """If user filled the survey 1 - not responded 2 - declined 3 - filled"""
-
monitoring_consent = Column(Integer, default=1)
"""If user agrees to share data 1 - not responded 2 - declined 3 - accepted"""
diff --git a/flask_monitoringdashboard/frontend/js/app.js b/flask_monitoringdashboard/frontend/js/app.js
index 1397f4b3..823213e0 100644
--- a/flask_monitoringdashboard/frontend/js/app.js
+++ b/flask_monitoringdashboard/frontend/js/app.js
@@ -41,7 +41,6 @@ import { DatabaseManagementController } from './controllers/databaseManagementCo
import { EndpointVersionIPController } from './controllers/endpointVersionIP';
import { EndpointVersionController } from "./controllers/endpointVersion";
import { MonitorLevelController } from "./controllers/monitorLevel";
-import { SurveyController } from "./controllers/surveyController";
import { TelemetryController } from "./controllers/telemetryController";
@@ -85,10 +84,6 @@ app.controller('EndpointController', ['$scope', 'endpointService', EndpointContr
app.controller('PaginationController', ['$scope', 'paginationService', PaginationController]);
app.controller('ModalController', ['$scope', '$window', '$browser', 'modalService', ModalController]);
-app.component('surveyComponent', {
- templateUrl: 'static/pages/survey.html',
- controller: SurveyController
-});
app.component('telemetryComponent', {
templateUrl: 'static/pages/telemetry.html',
controller: TelemetryController
diff --git a/flask_monitoringdashboard/frontend/js/controllers/surveyController.js b/flask_monitoringdashboard/frontend/js/controllers/surveyController.js
deleted file mode 100644
index b7e07b47..00000000
--- a/flask_monitoringdashboard/frontend/js/controllers/surveyController.js
+++ /dev/null
@@ -1,52 +0,0 @@
-export function SurveyController($scope, $http, $sce) {
- $scope.surveyShow = false;
- $scope.surveyCompleted = false;
-
- // Fetch local storage variation index
- const storedIndex = localStorage.getItem('surveyVariationIndex');
- $scope.surveyVariationIndex = storedIndex && !isNaN(parseInt(storedIndex)) ? parseInt(storedIndex) : 0;
-
- // Variations of the survey prompt
- $scope.surveyVariations = [
- 'Please take a moment to fill out our survey .',
- 'Your feedback is valuable! Take our quick survey. ',
- 'We value your opinion! Click here to share your thoughts.',
- 'Help us improve! Participate in our short survey .'
- ];
-
- // Mark as trusted HTML
- $scope.surveyVariations = $scope.surveyVariations.map(variation =>
- $sce.trustAsHtml(variation)
- );
-
- // Fetches to check if the survey is filled from database
- $scope.fetchSurveyFilled = function () {
- $http.get('/dashboard/telemetry/get_is_survey_filled')
- .then(function (response) {
- $scope.surveyCompleted = response.data.is_survey_filled;
- $scope.surveyShow = !$scope.surveyCompleted && ($scope.surveyVariationIndex < $scope.surveyVariations.length);
- }, function (error) {
- console.error('Error fetching survey status:', error);
- });
- };
- $scope.fetchSurveyFilled();
-
- // Increment surveyVariation in localStorage
- $scope.closeSurvey = function () {
- if (!$scope.surveyCompleted) {
- $scope.surveyVariationIndex++;
- localStorage.setItem('surveyVariationIndex', $scope.surveyVariationIndex.toString());
- }
- };
-
- // Mark survey as filled in database
- $scope.surveyFilled = function () {
- $http.get('/dashboard/telemetry/survey_has_been_filled')
- .then(function (response) {
- }, function (error) {
- console.error('Error:', error);
- });
- $scope.surveyCompleted = true;
- $scope.surveyShow = false;
- };
-}
\ No newline at end of file
diff --git a/flask_monitoringdashboard/frontend/js/controllers/telemetryController.js b/flask_monitoringdashboard/frontend/js/controllers/telemetryController.js
index 64aa6bdb..7e259043 100644
--- a/flask_monitoringdashboard/frontend/js/controllers/telemetryController.js
+++ b/flask_monitoringdashboard/frontend/js/controllers/telemetryController.js
@@ -41,34 +41,22 @@ export function TelemetryController($scope, $http, $window) {
};
$scope.customReason = '';
- // Configuration for HTTP requests to Back4App
- var config = {
- headers: {
- 'X-Parse-Application-Id': '4nHPABwkHqOZzNrFduzNyKH8q7wmPFdOWvajfWU2',
- 'X-Parse-REST-API-Key': 'zjv0WLI2K3UvpfzrfG4sPA6EykYyzZM4KxQk07Hs',
- 'Content-Type': 'application/json'
- }
- };
-
// Function to submit follow-up feedback
$scope.submitFollowUp = function () {
$scope.followUpShow = false;
-
+
var feedback = [];
for (var key in $scope.reasons) {
if ($scope.reasons[key]) {
- if (key === 'other') {
- feedback.push(key);
- if ($scope.customReason.trim() !== '') {
- feedback.push({ other: $scope.customReason });
- }
+ if (key === 'other' && $scope.customReason.trim() !== '') {
+ feedback.push({ key: 'other', other_reason: $scope.customReason });
} else {
- feedback.push(key);
+ feedback.push({ key: key });
}
}
}
-
- $http.post('https://parseapi.back4app.com/classes/FollowUp', { reasons: feedback }, config)
+
+ $http.post('/dashboard/telemetry/submit_follow_up', { feedback: feedback })
.then(function (response) {
}, function (error) {
console.error('Error sending feedback:', error);
diff --git a/flask_monitoringdashboard/static/css/main.css b/flask_monitoringdashboard/static/css/main.css
index a38b9ca8..bc62aeaa 100644
--- a/flask_monitoringdashboard/static/css/main.css
+++ b/flask_monitoringdashboard/static/css/main.css
@@ -1360,7 +1360,6 @@ progress {
margin-top: calc(-1 * var(--bs-gutter-y));
margin-right: calc(-0.5 * var(--bs-gutter-x));
margin-left: calc(-0.5 * var(--bs-gutter-x));
- overflow: auto;
}
.row > * {
flex-shrink: 0;
@@ -4315,13 +4314,6 @@ textarea.form-control-lg {
list-style: none;
}
-.navbar-btn {
- display: none;
- position: absolute;
- right: 11px;
- top: 13px;
-}
-
.nav-link {
display: block;
padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);
@@ -6193,12 +6185,6 @@ textarea.form-control-lg {
}
}
@media (max-width: 991.98px) {
-
-
- .navbar-btn {
- display: block !important;
- }
-
.modal-fullscreen-lg-down {
width: 100vw;
max-width: none;
diff --git a/flask_monitoringdashboard/static/js/app.js b/flask_monitoringdashboard/static/js/app.js
index d34c1867..5170befd 100644
--- a/flask_monitoringdashboard/static/js/app.js
+++ b/flask_monitoringdashboard/static/js/app.js
@@ -16,7 +16,7 @@
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _fortawesome_fontawesome_free_webfonts_fa_solid_900_woff2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 */ \"./node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2\");\n/* harmony import */ var plotly_js_cartesian_dist__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! plotly.js-cartesian-dist */ \"./node_modules/plotly.js-cartesian-dist/plotly-cartesian.js\");\n/* harmony import */ var plotly_js_cartesian_dist__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(plotly_js_cartesian_dist__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\");\n/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! moment */ \"./node_modules/moment/moment.js\");\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(moment__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _controllers_OverviewController__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./controllers/OverviewController */ \"./js/controllers/OverviewController.js\");\n/* harmony import */ var _controllers_hourlyLoad__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./controllers/hourlyLoad */ \"./js/controllers/hourlyLoad.js\");\n/* harmony import */ var _controllers_multiVersion__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./controllers/multiVersion */ \"./js/controllers/multiVersion.js\");\n/* harmony import */ var _controllers_dailyUtilization__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./controllers/dailyUtilization */ \"./js/controllers/dailyUtilization.js\");\n/* harmony import */ var _controllers_apiPerformance__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./controllers/apiPerformance */ \"./js/controllers/apiPerformance.js\");\n/* harmony import */ var _controllers_reporting__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./controllers/reporting */ \"./js/controllers/reporting.js\");\n/* harmony import */ var _controllers_endpointHourlyLoad__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./controllers/endpointHourlyLoad */ \"./js/controllers/endpointHourlyLoad.js\");\n/* harmony import */ var _controllers_endpointVersionUser__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./controllers/endpointVersionUser */ \"./js/controllers/endpointVersionUser.js\");\n/* harmony import */ var _controllers_endpointUsers__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./controllers/endpointUsers */ \"./js/controllers/endpointUsers.js\");\n/* harmony import */ var _controllers_endpointProfiler__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./controllers/endpointProfiler */ \"./js/controllers/endpointProfiler.js\");\n/* harmony import */ var _controllers_endpointGroupedProfiler__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./controllers/endpointGroupedProfiler */ \"./js/controllers/endpointGroupedProfiler.js\");\n/* harmony import */ var _controllers_endpointOutlier__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./controllers/endpointOutlier */ \"./js/controllers/endpointOutlier.js\");\n/* harmony import */ var _controllers_statusCodeDistribution__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./controllers/statusCodeDistribution */ \"./js/controllers/statusCodeDistribution.js\");\n/* harmony import */ var _controllers_customGraph__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./controllers/customGraph */ \"./js/controllers/customGraph.js\");\n/* harmony import */ var _controllers_configuration__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./controllers/configuration */ \"./js/controllers/configuration.js\");\n/* harmony import */ var _controllers_databaseManagementController__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./controllers/databaseManagementController */ \"./js/controllers/databaseManagementController.js\");\n/* harmony import */ var _controllers_endpointVersionIP__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./controllers/endpointVersionIP */ \"./js/controllers/endpointVersionIP.js\");\n/* harmony import */ var _controllers_endpointVersion__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! ./controllers/endpointVersion */ \"./js/controllers/endpointVersion.js\");\n/* harmony import */ var _controllers_monitorLevel__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! ./controllers/monitorLevel */ \"./js/controllers/monitorLevel.js\");\n/* harmony import */ var _controllers_surveyController__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! ./controllers/surveyController */ \"./js/controllers/surveyController.js\");\n/* harmony import */ var _controllers_telemetryController__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! ./controllers/telemetryController */ \"./js/controllers/telemetryController.js\");\n/* harmony import */ var _services_form__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! ./services/form */ \"./js/services/form.js\");\n/* harmony import */ var _services_info__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! ./services/info */ \"./js/services/info.js\");\n/* harmony import */ var _services_endpoint__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! ./services/endpoint */ \"./js/services/endpoint.js\");\n/* harmony import */ var _services_menu__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! ./services/menu */ \"./js/services/menu.js\");\n/* harmony import */ var _services_pagination__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! ./services/pagination */ \"./js/services/pagination.js\");\n/* harmony import */ var _services_plotly__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! ./services/plotly */ \"./js/services/plotly.js\");\n/* harmony import */ var _services_modal__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! ./services/modal */ \"./js/services/modal.js\");\n/* harmony import */ var _controllers_util__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(/*! ./controllers/util */ \"./js/controllers/util.js\");\n/* harmony import */ var _filters__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(/*! ./filters */ \"./js/filters.js\");\n/* harmony import */ var _directives__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(/*! ./directives */ \"./js/directives.js\");\n// Importing this font here will make it pass through the file loader, moving it to fonts/ directory\r\n\r\n\r\n// Plotly\r\n\r\n\r\nwindow.Plotly = (plotly_js_cartesian_dist__WEBPACK_IMPORTED_MODULE_1___default());\r\n\r\n// jQuery\r\n\r\n\r\nwindow.$ = window.jQuery = (jquery__WEBPACK_IMPORTED_MODULE_2___default());\r\n\r\n// Popper.js\r\n\r\n\r\n\r\n__webpack_require__(/*! bootstrap */ \"./node_modules/bootstrap/dist/js/bootstrap.esm.js\");\r\n\r\n// Moment\r\n\r\n\r\nwindow.moment = (moment__WEBPACK_IMPORTED_MODULE_3___default());\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nlet app = angular.module('fmdApp', ['ngRoute']);\r\n(0,_filters__WEBPACK_IMPORTED_MODULE_33__.applyFilters)(app);\r\n(0,_directives__WEBPACK_IMPORTED_MODULE_34__[\"default\"])(app);\r\n\r\napp.service('formService', ['$http', 'endpointService', '$filter', _services_form__WEBPACK_IMPORTED_MODULE_25__[\"default\"]]);\r\napp.service('infoService', _services_info__WEBPACK_IMPORTED_MODULE_26__[\"default\"]);\r\napp.service('endpointService', ['$http', '$routeParams', _services_endpoint__WEBPACK_IMPORTED_MODULE_27__[\"default\"]]);\r\napp.service('menuService', ['$http', 'endpointService', _services_menu__WEBPACK_IMPORTED_MODULE_28__[\"default\"]]);\r\napp.service('paginationService', ['$http', 'endpointService', _services_menu__WEBPACK_IMPORTED_MODULE_28__[\"default\"]]);\r\napp.service('paginationService', _services_pagination__WEBPACK_IMPORTED_MODULE_29__[\"default\"]);\r\napp.service('plotlyService', ['formService', _services_plotly__WEBPACK_IMPORTED_MODULE_30__[\"default\"]]);\r\napp.service('modalService', _services_modal__WEBPACK_IMPORTED_MODULE_31__[\"default\"]);\r\n\r\napp.controller('MonitorLevelController', ['$scope', '$http', _controllers_monitorLevel__WEBPACK_IMPORTED_MODULE_22__.MonitorLevelController]);\r\n\r\napp.controller('MenuController', ['$scope', 'menuService', _controllers_util__WEBPACK_IMPORTED_MODULE_32__.MenuController]);\r\napp.controller('InfoController', ['$scope', 'infoService', _controllers_util__WEBPACK_IMPORTED_MODULE_32__.InfoController]);\r\napp.controller('FormController', ['$scope', 'formService', _controllers_util__WEBPACK_IMPORTED_MODULE_32__.FormController]);\r\napp.controller('EndpointController', ['$scope', 'endpointService', _controllers_util__WEBPACK_IMPORTED_MODULE_32__.EndpointController]);\r\napp.controller('PaginationController', ['$scope', 'paginationService', _controllers_util__WEBPACK_IMPORTED_MODULE_32__.PaginationController]);\r\napp.controller('ModalController', ['$scope', '$window', '$browser', 'modalService', _controllers_util__WEBPACK_IMPORTED_MODULE_32__.ModalController]);\r\n\r\napp.component('surveyComponent', {\r\n templateUrl: 'static/pages/survey.html',\r\n controller: _controllers_surveyController__WEBPACK_IMPORTED_MODULE_23__.SurveyController\r\n});\r\napp.component('telemetryComponent', {\r\n templateUrl: 'static/pages/telemetry.html',\r\n controller: _controllers_telemetryController__WEBPACK_IMPORTED_MODULE_24__.TelemetryController\r\n});\r\n\r\napp.config(['$locationProvider', '$routeProvider', function ($locationProvider, $routeProvider) {\r\n $routeProvider\r\n .when('/overview', {\r\n templateUrl: 'static/pages/overview.html',\r\n controller: ['$scope', '$http', '$location', 'menuService', 'endpointService', _controllers_OverviewController__WEBPACK_IMPORTED_MODULE_4__.OverviewController]\r\n })\r\n .when('/hourly_load', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'plotlyService', 'infoService',\r\n 'formService', 'endpointService', '$filter', _controllers_hourlyLoad__WEBPACK_IMPORTED_MODULE_5__.HourlyLoadController]\r\n })\r\n .when('/multi_version', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'formService', 'infoService', 'plotlyService', 'endpointService', _controllers_multiVersion__WEBPACK_IMPORTED_MODULE_6__.MultiVersionController]\r\n })\r\n .when('/daily_utilization', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'formService', 'infoService',\r\n 'plotlyService', 'endpointService', _controllers_dailyUtilization__WEBPACK_IMPORTED_MODULE_7__.DailyUtilizationController]\r\n })\r\n .when('/api_performance', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'formService', 'infoService',\r\n 'plotlyService', 'endpointService', _controllers_apiPerformance__WEBPACK_IMPORTED_MODULE_8__.ApiPerformanceController]\r\n })\r\n .when('/reporting', {\r\n templateUrl: 'static/pages/reporting.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService', 'plotlyService', _controllers_reporting__WEBPACK_IMPORTED_MODULE_9__.ReportingController]\r\n })\r\n .when('/endpoint/:endpointId/hourly_load', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService',\r\n 'infoService', 'formService', 'plotlyService', '$filter', _controllers_endpointHourlyLoad__WEBPACK_IMPORTED_MODULE_10__.EndpointHourlyLoadController]\r\n })\r\n .when('/endpoint/:endpointId/version_user', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: [\r\n '$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', '$filter', _controllers_endpointVersionUser__WEBPACK_IMPORTED_MODULE_11__.EndpointVersionUserController]\r\n })\r\n .when('/endpoint/:endpointId/version_ip', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: [\r\n '$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', '$filter', _controllers_endpointVersionIP__WEBPACK_IMPORTED_MODULE_20__.EndpointVersionIPController]\r\n })\r\n .when('/endpoint/:endpointId/versions', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', '$filter', _controllers_endpointVersion__WEBPACK_IMPORTED_MODULE_21__.EndpointVersionController]\r\n })\r\n .when('/endpoint/:endpointId/users', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', _controllers_endpointUsers__WEBPACK_IMPORTED_MODULE_12__.EndpointUsersController]\r\n })\r\n .when('/endpoint/:endpointId/profiler', {\r\n templateUrl: 'static/pages/profiler.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService',\r\n 'paginationService', 'formService', _controllers_endpointProfiler__WEBPACK_IMPORTED_MODULE_13__.EndpointProfilerController]\r\n })\r\n .when('/endpoint/:endpointId/grouped-profiler', {\r\n templateUrl: 'static/pages/grouped_profiler.html',\r\n controller: ['$scope', '$http', 'menuService',\r\n 'endpointService', 'formService', _controllers_endpointGroupedProfiler__WEBPACK_IMPORTED_MODULE_14__.EndpointGroupedProfilerController]\r\n })\r\n .when('/endpoint/:endpointId/outliers', {\r\n templateUrl: 'static/pages/outliers.html',\r\n controller: ['$scope', '$http', 'endpointService', 'menuService',\r\n 'paginationService', 'plotlyService', _controllers_endpointOutlier__WEBPACK_IMPORTED_MODULE_15__.OutlierController]\r\n })\r\n .when('/endpoint/:endpointId/status_code_distribution', {\r\n templateUrl: 'static/pages/status_code_distribution.html',\r\n controller: [\r\n '$scope', '$http', 'infoService', 'endpointService', 'menuService', 'formService', 'plotlyService', _controllers_statusCodeDistribution__WEBPACK_IMPORTED_MODULE_16__.StatusCodeDistributionController],\r\n })\r\n .when('/custom_graph/:graphId', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', _controllers_customGraph__WEBPACK_IMPORTED_MODULE_17__.CustomGraphController]\r\n })\r\n .when('/configuration', {\r\n templateUrl: 'static/pages/configuration.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService', 'modalService', _controllers_configuration__WEBPACK_IMPORTED_MODULE_18__.ConfigurationController]\r\n })\r\n .when('/database_management', {\r\n templateUrl: 'static/pages/database_management.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService', 'modalService', _controllers_databaseManagementController__WEBPACK_IMPORTED_MODULE_19__.DatabaseManagementController]\r\n })\r\n .otherwise({\r\n redirectTo: '/overview'\r\n });\r\n\r\n $locationProvider.html5Mode({\r\n enabled: true,\r\n requireBase: true\r\n });\r\n}]);\r\n\r\n// Toggle the side navigation\r\njquery__WEBPACK_IMPORTED_MODULE_2___default()(\"#sidenavToggler\").click(function (e) {\r\n e.preventDefault();\r\n jquery__WEBPACK_IMPORTED_MODULE_2___default()(\"body\").toggleClass(\"sidenav-toggled\");\r\n jquery__WEBPACK_IMPORTED_MODULE_2___default()(\".navbar-sidenav .nav-link-collapse\").addClass(\"collapsed\");\r\n jquery__WEBPACK_IMPORTED_MODULE_2___default()(\".navbar-sidenav .sidenav-second-level, .navbar-sidenav .sidenav-third-level\").removeClass(\"show\");\r\n});\r\n// Force the toggled class to be removed when a collapsible nav link is clicked\r\njquery__WEBPACK_IMPORTED_MODULE_2___default()(\".navbar-sidenav .nav-link-collapse\").click(function (e) {\r\n e.preventDefault();\r\n jquery__WEBPACK_IMPORTED_MODULE_2___default()(\"body\").removeClass(\"sidenav-toggled\");\r\n});\r\n\r\n\r\nwindow.app = app;\n\n//# sourceURL=webpack://frontend/./js/app.js?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _fortawesome_fontawesome_free_webfonts_fa_solid_900_woff2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 */ \"./node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2\");\n/* harmony import */ var plotly_js_cartesian_dist__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! plotly.js-cartesian-dist */ \"./node_modules/plotly.js-cartesian-dist/plotly-cartesian.js\");\n/* harmony import */ var plotly_js_cartesian_dist__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(plotly_js_cartesian_dist__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\");\n/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! moment */ \"./node_modules/moment/moment.js\");\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(moment__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _controllers_OverviewController__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./controllers/OverviewController */ \"./js/controllers/OverviewController.js\");\n/* harmony import */ var _controllers_hourlyLoad__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./controllers/hourlyLoad */ \"./js/controllers/hourlyLoad.js\");\n/* harmony import */ var _controllers_multiVersion__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./controllers/multiVersion */ \"./js/controllers/multiVersion.js\");\n/* harmony import */ var _controllers_dailyUtilization__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./controllers/dailyUtilization */ \"./js/controllers/dailyUtilization.js\");\n/* harmony import */ var _controllers_apiPerformance__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./controllers/apiPerformance */ \"./js/controllers/apiPerformance.js\");\n/* harmony import */ var _controllers_reporting__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./controllers/reporting */ \"./js/controllers/reporting.js\");\n/* harmony import */ var _controllers_endpointHourlyLoad__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./controllers/endpointHourlyLoad */ \"./js/controllers/endpointHourlyLoad.js\");\n/* harmony import */ var _controllers_endpointVersionUser__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./controllers/endpointVersionUser */ \"./js/controllers/endpointVersionUser.js\");\n/* harmony import */ var _controllers_endpointUsers__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./controllers/endpointUsers */ \"./js/controllers/endpointUsers.js\");\n/* harmony import */ var _controllers_endpointProfiler__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./controllers/endpointProfiler */ \"./js/controllers/endpointProfiler.js\");\n/* harmony import */ var _controllers_endpointGroupedProfiler__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./controllers/endpointGroupedProfiler */ \"./js/controllers/endpointGroupedProfiler.js\");\n/* harmony import */ var _controllers_endpointOutlier__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./controllers/endpointOutlier */ \"./js/controllers/endpointOutlier.js\");\n/* harmony import */ var _controllers_statusCodeDistribution__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./controllers/statusCodeDistribution */ \"./js/controllers/statusCodeDistribution.js\");\n/* harmony import */ var _controllers_customGraph__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./controllers/customGraph */ \"./js/controllers/customGraph.js\");\n/* harmony import */ var _controllers_configuration__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./controllers/configuration */ \"./js/controllers/configuration.js\");\n/* harmony import */ var _controllers_databaseManagementController__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./controllers/databaseManagementController */ \"./js/controllers/databaseManagementController.js\");\n/* harmony import */ var _controllers_endpointVersionIP__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./controllers/endpointVersionIP */ \"./js/controllers/endpointVersionIP.js\");\n/* harmony import */ var _controllers_endpointVersion__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! ./controllers/endpointVersion */ \"./js/controllers/endpointVersion.js\");\n/* harmony import */ var _controllers_monitorLevel__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! ./controllers/monitorLevel */ \"./js/controllers/monitorLevel.js\");\n/* harmony import */ var _controllers_telemetryController__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! ./controllers/telemetryController */ \"./js/controllers/telemetryController.js\");\n/* harmony import */ var _services_form__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! ./services/form */ \"./js/services/form.js\");\n/* harmony import */ var _services_info__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! ./services/info */ \"./js/services/info.js\");\n/* harmony import */ var _services_endpoint__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! ./services/endpoint */ \"./js/services/endpoint.js\");\n/* harmony import */ var _services_menu__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! ./services/menu */ \"./js/services/menu.js\");\n/* harmony import */ var _services_pagination__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! ./services/pagination */ \"./js/services/pagination.js\");\n/* harmony import */ var _services_plotly__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! ./services/plotly */ \"./js/services/plotly.js\");\n/* harmony import */ var _services_modal__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! ./services/modal */ \"./js/services/modal.js\");\n/* harmony import */ var _controllers_util__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! ./controllers/util */ \"./js/controllers/util.js\");\n/* harmony import */ var _filters__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(/*! ./filters */ \"./js/filters.js\");\n/* harmony import */ var _directives__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(/*! ./directives */ \"./js/directives.js\");\n// Importing this font here will make it pass through the file loader, moving it to fonts/ directory\r\n\r\n\r\n// Plotly\r\n\r\n\r\nwindow.Plotly = (plotly_js_cartesian_dist__WEBPACK_IMPORTED_MODULE_1___default());\r\n\r\n// jQuery\r\n\r\n\r\nwindow.$ = window.jQuery = (jquery__WEBPACK_IMPORTED_MODULE_2___default());\r\n\r\n// Popper.js\r\n\r\n\r\n\r\n__webpack_require__(/*! bootstrap */ \"./node_modules/bootstrap/dist/js/bootstrap.esm.js\");\r\n\r\n// Moment\r\n\r\n\r\nwindow.moment = (moment__WEBPACK_IMPORTED_MODULE_3___default());\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nlet app = angular.module('fmdApp', ['ngRoute']);\r\n(0,_filters__WEBPACK_IMPORTED_MODULE_32__.applyFilters)(app);\r\n(0,_directives__WEBPACK_IMPORTED_MODULE_33__[\"default\"])(app);\r\n\r\napp.service('formService', ['$http', 'endpointService', '$filter', _services_form__WEBPACK_IMPORTED_MODULE_24__[\"default\"]]);\r\napp.service('infoService', _services_info__WEBPACK_IMPORTED_MODULE_25__[\"default\"]);\r\napp.service('endpointService', ['$http', '$routeParams', _services_endpoint__WEBPACK_IMPORTED_MODULE_26__[\"default\"]]);\r\napp.service('menuService', ['$http', 'endpointService', _services_menu__WEBPACK_IMPORTED_MODULE_27__[\"default\"]]);\r\napp.service('paginationService', ['$http', 'endpointService', _services_menu__WEBPACK_IMPORTED_MODULE_27__[\"default\"]]);\r\napp.service('paginationService', _services_pagination__WEBPACK_IMPORTED_MODULE_28__[\"default\"]);\r\napp.service('plotlyService', ['formService', _services_plotly__WEBPACK_IMPORTED_MODULE_29__[\"default\"]]);\r\napp.service('modalService', _services_modal__WEBPACK_IMPORTED_MODULE_30__[\"default\"]);\r\n\r\napp.controller('MonitorLevelController', ['$scope', '$http', _controllers_monitorLevel__WEBPACK_IMPORTED_MODULE_22__.MonitorLevelController]);\r\n\r\napp.controller('MenuController', ['$scope', 'menuService', _controllers_util__WEBPACK_IMPORTED_MODULE_31__.MenuController]);\r\napp.controller('InfoController', ['$scope', 'infoService', _controllers_util__WEBPACK_IMPORTED_MODULE_31__.InfoController]);\r\napp.controller('FormController', ['$scope', 'formService', _controllers_util__WEBPACK_IMPORTED_MODULE_31__.FormController]);\r\napp.controller('EndpointController', ['$scope', 'endpointService', _controllers_util__WEBPACK_IMPORTED_MODULE_31__.EndpointController]);\r\napp.controller('PaginationController', ['$scope', 'paginationService', _controllers_util__WEBPACK_IMPORTED_MODULE_31__.PaginationController]);\r\napp.controller('ModalController', ['$scope', '$window', '$browser', 'modalService', _controllers_util__WEBPACK_IMPORTED_MODULE_31__.ModalController]);\r\n\r\napp.component('telemetryComponent', {\r\n templateUrl: 'static/pages/telemetry.html',\r\n controller: _controllers_telemetryController__WEBPACK_IMPORTED_MODULE_23__.TelemetryController\r\n});\r\n\r\napp.config(['$locationProvider', '$routeProvider', function ($locationProvider, $routeProvider) {\r\n $routeProvider\r\n .when('/overview', {\r\n templateUrl: 'static/pages/overview.html',\r\n controller: ['$scope', '$http', '$location', 'menuService', 'endpointService', _controllers_OverviewController__WEBPACK_IMPORTED_MODULE_4__.OverviewController]\r\n })\r\n .when('/hourly_load', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'plotlyService', 'infoService',\r\n 'formService', 'endpointService', '$filter', _controllers_hourlyLoad__WEBPACK_IMPORTED_MODULE_5__.HourlyLoadController]\r\n })\r\n .when('/multi_version', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'formService', 'infoService', 'plotlyService', 'endpointService', _controllers_multiVersion__WEBPACK_IMPORTED_MODULE_6__.MultiVersionController]\r\n })\r\n .when('/daily_utilization', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'formService', 'infoService',\r\n 'plotlyService', 'endpointService', _controllers_dailyUtilization__WEBPACK_IMPORTED_MODULE_7__.DailyUtilizationController]\r\n })\r\n .when('/api_performance', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'formService', 'infoService',\r\n 'plotlyService', 'endpointService', _controllers_apiPerformance__WEBPACK_IMPORTED_MODULE_8__.ApiPerformanceController]\r\n })\r\n .when('/reporting', {\r\n templateUrl: 'static/pages/reporting.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService', 'plotlyService', _controllers_reporting__WEBPACK_IMPORTED_MODULE_9__.ReportingController]\r\n })\r\n .when('/endpoint/:endpointId/hourly_load', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService',\r\n 'infoService', 'formService', 'plotlyService', '$filter', _controllers_endpointHourlyLoad__WEBPACK_IMPORTED_MODULE_10__.EndpointHourlyLoadController]\r\n })\r\n .when('/endpoint/:endpointId/version_user', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: [\r\n '$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', '$filter', _controllers_endpointVersionUser__WEBPACK_IMPORTED_MODULE_11__.EndpointVersionUserController]\r\n })\r\n .when('/endpoint/:endpointId/version_ip', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: [\r\n '$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', '$filter', _controllers_endpointVersionIP__WEBPACK_IMPORTED_MODULE_20__.EndpointVersionIPController]\r\n })\r\n .when('/endpoint/:endpointId/versions', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', '$filter', _controllers_endpointVersion__WEBPACK_IMPORTED_MODULE_21__.EndpointVersionController]\r\n })\r\n .when('/endpoint/:endpointId/users', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', _controllers_endpointUsers__WEBPACK_IMPORTED_MODULE_12__.EndpointUsersController]\r\n })\r\n .when('/endpoint/:endpointId/profiler', {\r\n templateUrl: 'static/pages/profiler.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService',\r\n 'paginationService', 'formService', _controllers_endpointProfiler__WEBPACK_IMPORTED_MODULE_13__.EndpointProfilerController]\r\n })\r\n .when('/endpoint/:endpointId/grouped-profiler', {\r\n templateUrl: 'static/pages/grouped_profiler.html',\r\n controller: ['$scope', '$http', 'menuService',\r\n 'endpointService', 'formService', _controllers_endpointGroupedProfiler__WEBPACK_IMPORTED_MODULE_14__.EndpointGroupedProfilerController]\r\n })\r\n .when('/endpoint/:endpointId/outliers', {\r\n templateUrl: 'static/pages/outliers.html',\r\n controller: ['$scope', '$http', 'endpointService', 'menuService',\r\n 'paginationService', 'plotlyService', _controllers_endpointOutlier__WEBPACK_IMPORTED_MODULE_15__.OutlierController]\r\n })\r\n .when('/endpoint/:endpointId/status_code_distribution', {\r\n templateUrl: 'static/pages/status_code_distribution.html',\r\n controller: [\r\n '$scope', '$http', 'infoService', 'endpointService', 'menuService', 'formService', 'plotlyService', _controllers_statusCodeDistribution__WEBPACK_IMPORTED_MODULE_16__.StatusCodeDistributionController],\r\n })\r\n .when('/custom_graph/:graphId', {\r\n templateUrl: 'static/pages/plotly_graph.html',\r\n controller: ['$scope', '$http', 'infoService', 'endpointService',\r\n 'menuService', 'formService', 'plotlyService', _controllers_customGraph__WEBPACK_IMPORTED_MODULE_17__.CustomGraphController]\r\n })\r\n .when('/configuration', {\r\n templateUrl: 'static/pages/configuration.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService', 'modalService', _controllers_configuration__WEBPACK_IMPORTED_MODULE_18__.ConfigurationController]\r\n })\r\n .when('/database_management', {\r\n templateUrl: 'static/pages/database_management.html',\r\n controller: ['$scope', '$http', 'menuService', 'endpointService', 'modalService', _controllers_databaseManagementController__WEBPACK_IMPORTED_MODULE_19__.DatabaseManagementController]\r\n })\r\n .otherwise({\r\n redirectTo: '/overview'\r\n });\r\n\r\n $locationProvider.html5Mode({\r\n enabled: true,\r\n requireBase: true\r\n });\r\n}]);\r\n\r\n// Toggle the side navigation\r\njquery__WEBPACK_IMPORTED_MODULE_2___default()(\"#sidenavToggler\").click(function (e) {\r\n e.preventDefault();\r\n jquery__WEBPACK_IMPORTED_MODULE_2___default()(\"body\").toggleClass(\"sidenav-toggled\");\r\n jquery__WEBPACK_IMPORTED_MODULE_2___default()(\".navbar-sidenav .nav-link-collapse\").addClass(\"collapsed\");\r\n jquery__WEBPACK_IMPORTED_MODULE_2___default()(\".navbar-sidenav .sidenav-second-level, .navbar-sidenav .sidenav-third-level\").removeClass(\"show\");\r\n});\r\n// Force the toggled class to be removed when a collapsible nav link is clicked\r\njquery__WEBPACK_IMPORTED_MODULE_2___default()(\".navbar-sidenav .nav-link-collapse\").click(function (e) {\r\n e.preventDefault();\r\n jquery__WEBPACK_IMPORTED_MODULE_2___default()(\"body\").removeClass(\"sidenav-toggled\");\r\n});\r\n\r\n\r\nwindow.app = app;\n\n//# sourceURL=webpack://frontend/./js/app.js?");
/***/ }),
@@ -229,17 +229,6 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
-/***/ "./js/controllers/surveyController.js":
-/*!********************************************!*\
- !*** ./js/controllers/surveyController.js ***!
- \********************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ SurveyController: () => (/* binding */ SurveyController)\n/* harmony export */ });\nfunction SurveyController($scope, $http, $sce) {\r\n $scope.surveyShow = false;\r\n $scope.surveyCompleted = false;\r\n\r\n // Fetch local storage variation index\r\n const storedIndex = localStorage.getItem('surveyVariationIndex');\r\n $scope.surveyVariationIndex = storedIndex && !isNaN(parseInt(storedIndex)) ? parseInt(storedIndex) : 0;\r\n\r\n // Variations of the survey prompt\r\n $scope.surveyVariations = [\r\n 'Please take a moment to fill out our survey .',\r\n 'Your feedback is valuable! Take our quick survey. ',\r\n 'We value your opinion! Click here to share your thoughts.',\r\n 'Help us improve! Participate in our short survey .'\r\n ];\r\n\r\n // Mark as trusted HTML\r\n $scope.surveyVariations = $scope.surveyVariations.map(variation =>\r\n $sce.trustAsHtml(variation)\r\n );\r\n\r\n // Fetches to check if the survey is filled from database\r\n $scope.fetchSurveyFilled = function () {\r\n $http.get('/dashboard/telemetry/get_is_survey_filled')\r\n .then(function (response) {\r\n $scope.surveyCompleted = response.data.is_survey_filled;\r\n $scope.surveyShow = !$scope.surveyCompleted && ($scope.surveyVariationIndex < $scope.surveyVariations.length);\r\n }, function (error) {\r\n console.error('Error fetching survey status:', error);\r\n });\r\n };\r\n $scope.fetchSurveyFilled();\r\n\r\n // Increment surveyVariation in localStorage\r\n $scope.closeSurvey = function () {\r\n if (!$scope.surveyCompleted) {\r\n $scope.surveyVariationIndex++;\r\n localStorage.setItem('surveyVariationIndex', $scope.surveyVariationIndex.toString());\r\n }\r\n };\r\n\r\n // Mark survey as filled in database\r\n $scope.surveyFilled = function () {\r\n $http.get('/dashboard/telemetry/survey_has_been_filled')\r\n .then(function (response) {\r\n }, function (error) {\r\n console.error('Error:', error);\r\n });\r\n $scope.surveyCompleted = true;\r\n $scope.surveyShow = false;\r\n };\r\n}\n\n//# sourceURL=webpack://frontend/./js/controllers/surveyController.js?");
-
-/***/ }),
-
/***/ "./js/controllers/telemetryController.js":
/*!***********************************************!*\
!*** ./js/controllers/telemetryController.js ***!
@@ -247,7 +236,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ TelemetryController: () => (/* binding */ TelemetryController)\n/* harmony export */ });\nfunction TelemetryController($scope, $http, $window) {\r\n\r\n // Check if telemetry response is already stored in local storage\r\n const telemetryAnswered = $window.localStorage.getItem('telemetryAnswered') === 'true';\r\n\r\n // Control the visibility of the telemetry prompt based on previous response\r\n $scope.telemetryShow = !telemetryAnswered;\r\n $scope.followUpShow = false;\r\n\r\n // Function to fetch telemetry consent status from database \r\n $scope.fetchTelemetryConsent = function () {\r\n $http.get(`/dashboard/telemetry/get_is_telemetry_answered`)\r\n .then(function (response) {\r\n $scope.telemetryShow = !response.data.is_telemetry_answered;\r\n }, function (error) {\r\n console.error('Error fetching telemetry consent:', error);\r\n });\r\n };\r\n $scope.fetchTelemetryConsent();\r\n\r\n // Function to handle user response to telemetry prompt\r\n $scope.handleTelemetry = function (consent) {\r\n $scope.telemetryShow = false;\r\n $scope.followUpShow = !consent;\r\n\r\n $http.post('/dashboard/telemetry/accept_telemetry_consent', { 'consent': consent })\r\n .then(function (response) {\r\n $scope.telemetryShow = false;\r\n $window.localStorage.setItem('telemetryAnswered', 'true');\r\n }, function (error) {\r\n console.error('Error updating telemetry consent:', error);\r\n });\r\n };\r\n\r\n // Object to track reasons for declining telemetry\r\n $scope.reasons = {\r\n privacy: false,\r\n performance: false,\r\n trust: false,\r\n other: false\r\n };\r\n $scope.customReason = '';\r\n\r\n // Configuration for HTTP requests to Back4App\r\n var config = {\r\n headers: {\r\n 'X-Parse-Application-Id': '4nHPABwkHqOZzNrFduzNyKH8q7wmPFdOWvajfWU2',\r\n 'X-Parse-REST-API-Key': 'zjv0WLI2K3UvpfzrfG4sPA6EykYyzZM4KxQk07Hs',\r\n 'Content-Type': 'application/json'\r\n }\r\n };\r\n\r\n // Function to submit follow-up feedback\r\n $scope.submitFollowUp = function () {\r\n $scope.followUpShow = false;\r\n\r\n var feedback = [];\r\n for (var key in $scope.reasons) {\r\n if ($scope.reasons[key]) {\r\n if (key === 'other') {\r\n feedback.push(key);\r\n if ($scope.customReason.trim() !== '') {\r\n feedback.push({ other: $scope.customReason });\r\n }\r\n } else {\r\n feedback.push(key);\r\n }\r\n }\r\n }\r\n\r\n $http.post('https://parseapi.back4app.com/classes/FollowUp', { reasons: feedback }, config)\r\n .then(function (response) {\r\n }, function (error) {\r\n console.error('Error sending feedback:', error);\r\n });\r\n };\r\n}\n\n//# sourceURL=webpack://frontend/./js/controllers/telemetryController.js?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ TelemetryController: () => (/* binding */ TelemetryController)\n/* harmony export */ });\nfunction TelemetryController($scope, $http, $window) {\r\n\r\n // Check if telemetry response is already stored in local storage\r\n const telemetryAnswered = $window.localStorage.getItem('telemetryAnswered') === 'true';\r\n\r\n // Control the visibility of the telemetry prompt based on previous response\r\n $scope.telemetryShow = !telemetryAnswered;\r\n $scope.followUpShow = false;\r\n\r\n // Function to fetch telemetry consent status from database \r\n $scope.fetchTelemetryConsent = function () {\r\n $http.get(`/dashboard/telemetry/get_is_telemetry_answered`)\r\n .then(function (response) {\r\n $scope.telemetryShow = !response.data.is_telemetry_answered;\r\n }, function (error) {\r\n console.error('Error fetching telemetry consent:', error);\r\n });\r\n };\r\n $scope.fetchTelemetryConsent();\r\n\r\n // Function to handle user response to telemetry prompt\r\n $scope.handleTelemetry = function (consent) {\r\n $scope.telemetryShow = false;\r\n $scope.followUpShow = !consent;\r\n\r\n $http.post('/dashboard/telemetry/accept_telemetry_consent', { 'consent': consent })\r\n .then(function (response) {\r\n $scope.telemetryShow = false;\r\n $window.localStorage.setItem('telemetryAnswered', 'true');\r\n }, function (error) {\r\n console.error('Error updating telemetry consent:', error);\r\n });\r\n };\r\n\r\n // Object to track reasons for declining telemetry\r\n $scope.reasons = {\r\n privacy: false,\r\n performance: false,\r\n trust: false,\r\n other: false\r\n };\r\n $scope.customReason = '';\r\n\r\n // Function to submit follow-up feedback\r\n $scope.submitFollowUp = function () {\r\n $scope.followUpShow = false;\r\n \r\n var feedback = [];\r\n for (var key in $scope.reasons) {\r\n if ($scope.reasons[key]) {\r\n if (key === 'other' && $scope.customReason.trim() !== '') {\r\n feedback.push({ key: 'other', other_reason: $scope.customReason });\r\n } else {\r\n feedback.push({ key: key });\r\n }\r\n }\r\n }\r\n \r\n $http.post('/dashboard/telemetry/submit_follow_up', { feedback: feedback })\r\n .then(function (response) {\r\n }, function (error) {\r\n console.error('Error sending feedback:', error);\r\n });\r\n };\r\n}\n\n//# sourceURL=webpack://frontend/./js/controllers/telemetryController.js?");
/***/ }),
diff --git a/flask_monitoringdashboard/static/pages/configuration.html b/flask_monitoringdashboard/static/pages/configuration.html
index c768cf80..5de5a87e 100644
--- a/flask_monitoringdashboard/static/pages/configuration.html
+++ b/flask_monitoringdashboard/static/pages/configuration.html
@@ -184,6 +184,11 @@
Deployment details
Total amount of monitored requests
{{ details['total-requests'] | number }}
+
+
+ fmd_id
+ {{ details['fmd-id'] }}
+
diff --git a/flask_monitoringdashboard/static/pages/overview.html b/flask_monitoringdashboard/static/pages/overview.html
index 43a265d8..dc437d86 100644
--- a/flask_monitoringdashboard/static/pages/overview.html
+++ b/flask_monitoringdashboard/static/pages/overview.html
@@ -11,8 +11,6 @@
- {{ telemetryShow }}
-
diff --git a/flask_monitoringdashboard/static/pages/survey.html b/flask_monitoringdashboard/static/pages/survey.html
deleted file mode 100644
index 31b4715f..00000000
--- a/flask_monitoringdashboard/static/pages/survey.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/flask_monitoringdashboard/static/pages/telemetry.html b/flask_monitoringdashboard/static/pages/telemetry.html
index c720a9d1..c377fb71 100644
--- a/flask_monitoringdashboard/static/pages/telemetry.html
+++ b/flask_monitoringdashboard/static/pages/telemetry.html
@@ -1,16 +1,22 @@
Telemetry Consent:
Help us enhance our services by allowing anonymous usage collection. This data helps us understand which features
- are useful, and which can be retired - we are a small research project, and we can not afford to maintain
+ are useful, and which can be retired - we are a small research project, and we cannot afford to maintain
features that are not useful. You have full control and can modify your choice at any time in the
"Configuration" section.
- If you can't help us with usage information, you can maybe help us with
- adopting and fixing some
issues ? We are an open-source project!
-
- Want to know more?
-
Learn about what
- we collect .
-
+
+ The anonymous telemetry data (learn about what
+ we collect ) is published weekly on our GitHub Pages .
+
+
+
+ If you can't help us with usage information, you can maybe help us with
+ adopting and fixing some issues ? We are an open-source project!
+
Agree
Decline
@@ -39,7 +45,7 @@
Other:
+ maxlength="2000" placeholder="Please specify your reason...">
Characters: {{ customReason.length || 0 }} / 2000
@@ -48,7 +54,7 @@
Submit Feedback
+ ng-disabled="feedbackForm.$invalid">Submit Feedback
Accept Telemetry
diff --git a/flask_monitoringdashboard/templates/fmd_base.html b/flask_monitoringdashboard/templates/fmd_base.html
index 319fdc2b..00558acc 100644
--- a/flask_monitoringdashboard/templates/fmd_base.html
+++ b/flask_monitoringdashboard/templates/fmd_base.html
@@ -29,7 +29,6 @@
>
diff --git a/flask_monitoringdashboard/views/telemetry.py b/flask_monitoringdashboard/views/telemetry.py
index 115b055a..3b72ede3 100644
--- a/flask_monitoringdashboard/views/telemetry.py
+++ b/flask_monitoringdashboard/views/telemetry.py
@@ -3,7 +3,7 @@
from flask_monitoringdashboard.core.auth import secure
from flask_monitoringdashboard import blueprint, telemetry_config
from flask_monitoringdashboard.core.config import TelemetryConfig
-from flask_monitoringdashboard.core.telemetry import get_telemetry_user
+from flask_monitoringdashboard.core.telemetry import get_telemetry_user, post_to_back_if_telemetry_enabled
from flask_monitoringdashboard.database import session_scope
@@ -29,22 +29,6 @@ def accept_telemetry_consent():
return '', 204
-@blueprint.route('/telemetry/survey_has_been_filled', methods=['GET'])
-def survey_has_been_filled():
- with session_scope() as session:
- try:
- telemetry_user = get_telemetry_user(session)
- telemetry_user.survey_filled = TelemetryConfig.ACCEPTED
- session.commit()
-
- except SQLAlchemyError as e:
- print('error committing survey consent to database', e)
- session.rollback()
-
- # Return no content
- return '', 204
-
-
@blueprint.route('/telemetry/get_is_telemetry_answered', methods=['GET'])
def get_is_telemetry_answered():
with session_scope() as session:
@@ -52,10 +36,13 @@ def get_is_telemetry_answered():
res = True if telemetry_user.monitoring_consent in (TelemetryConfig.REJECTED, TelemetryConfig.ACCEPTED) else False
return {'is_telemetry_answered': res}
+@blueprint.route('/telemetry/submit_follow_up', methods=['POST'])
+def submit_follow_up():
+ data = request.json
+ feedback = data.get('feedback')
+
+ post_to_back_if_telemetry_enabled('FollowUp', feedback=feedback)
+
+ return jsonify({'message': 'Feedback submitted successfully'}), 200
+
-@blueprint.route('/telemetry/get_is_survey_filled', methods=['GET'])
-def get_is_survey_filled():
- with session_scope() as session:
- telemetry_user = get_telemetry_user(session)
- res = True if telemetry_user.survey_filled in (TelemetryConfig.REJECTED, TelemetryConfig.ACCEPTED) else False
- return {'is_survey_filled': res}