feat(telemetry): replace GA with matomo (#4140)

* feat(core/telemetry): add posthog

* feat(core/telemetry): add posthog

* feat(core/telemetry): add matomo

* feat(core/telemetry): update matomo

* feat(core/telemetry): update matomo

* feat(core/telemetry): update matomo

* feat(telemetry): remove google analytics code

* refactor(telemetry): move matomo code to bundle

* refactor(telemetry): move matomo lib to assets

* refactor(telemetry): depreciate --no-analytics

* feat(settings): introduce a setting to enable telemetry

* fix(cli): fix typo

* feat(settings): allow toggle telemetry from settings

* fix(settings): handle case where AuthenticationMethod is missing

* feat(admin): set telemetry on admin init

* refactor(app); revert file

* refactor(state-manager): move optout to state manager

* feat(telemetry): set matomo url

* feat(core/settings): minor UI update

* feat(core/telemetry): update custom URL

* feat(core/telemetry): add placeholder for privacy policy

* feat(core/telemetry): add privacy policy link

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
pull/4169/head
Chaim Lev-Ari 2020-08-07 01:46:25 +03:00 committed by GitHub
parent 7aaf9d0eb7
commit 2158cc5157
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 2879 additions and 2605 deletions

View File

@ -6,7 +6,6 @@ env:
globals:
angular: true
__CONFIG_GA_ID: true
extends:
- 'eslint:recommended'

View File

@ -15,6 +15,7 @@ func (m *Migrator) updateSettingsToDB25() error {
}
legacySettings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
legacySettings.EnableTelemetry = true
legacySettings.AllowContainerCapabilitiesForRegularUsers = true

View File

@ -2,6 +2,7 @@ package cli
import (
"errors"
"log"
"time"
"github.com/portainer/portainer/api"
@ -35,7 +36,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app").Default(defaultNoAnalytics).Bool(),
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app (deprecated)").Default(defaultNoAnalytics).Bool(),
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
TLSSkipVerify: kingpin.Flag("tlsskipverify", "Disable TLS server verification").Default(defaultTLSSkipVerify).Bool(),
TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String(),
@ -88,7 +89,9 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
}
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
if flags.NoAnalytics != nil {
log.Println("Warning: The --no-analytics has been deprecated and will be removed in a future version of Portainer. It currently has no effect, telemetry settings are available in the Portainer settings.")
}
}
func validateEndpointURL(endpointURL string) error {

View File

@ -154,8 +154,7 @@ func loadEdgeJobsFromDatabase(dataStore portainer.DataStore, reverseTunnelServic
func initStatus(flags *portainer.CLIFlags) *portainer.Status {
return &portainer.Status{
Analytics: !*flags.NoAnalytics,
Version: portainer.APIVersion,
Version: portainer.APIVersion,
}
}
@ -168,6 +167,7 @@ func updateSettingsFromFlags(dataStore portainer.DataStore, flags *portainer.CLI
settings.LogoURL = *flags.Logo
settings.SnapshotInterval = *flags.SnapshotInterval
settings.EnableEdgeComputeFeatures = *flags.EnableEdgeComputeFeatures
settings.EnableTelemetry = true
if *flags.Templates != "" {
settings.TemplatesURL = *flags.Templates

View File

@ -22,6 +22,7 @@ type publicSettingsResponse struct {
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
OAuthLoginURI string `json:"OAuthLoginURI"`
EnableTelemetry bool `json:"EnableTelemetry"`
}
// GET request on /api/settings/public
@ -43,6 +44,7 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
AllowContainerCapabilitiesForRegularUsers: settings.AllowContainerCapabilitiesForRegularUsers,
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
EnableTelemetry: settings.EnableTelemetry,
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
settings.OAuthSettings.AuthorizationURI,
settings.OAuthSettings.ClientID,

View File

@ -33,10 +33,11 @@ type settingsUpdatePayload struct {
EdgeAgentCheckinInterval *int
EnableEdgeComputeFeatures *bool
UserSessionTimeout *string
EnableTelemetry *bool
}
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
if *payload.AuthenticationMethod != 1 && *payload.AuthenticationMethod != 2 && *payload.AuthenticationMethod != 3 {
if payload.AuthenticationMethod != nil && *payload.AuthenticationMethod != 1 && *payload.AuthenticationMethod != 2 && *payload.AuthenticationMethod != 3 {
return errors.New("Invalid authentication method value. Value must be one of: 1 (internal), 2 (LDAP/AD) or 3 (OAuth)")
}
if payload.LogoURL != nil && *payload.LogoURL != "" && !govalidator.IsURL(*payload.LogoURL) {
@ -164,6 +165,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
settings.AllowDeviceMappingForRegularUsers = *payload.AllowDeviceMappingForRegularUsers
}
if payload.EnableTelemetry != nil {
settings.EnableTelemetry = *payload.EnableTelemetry
}
tlsError := handler.updateTLS(settings)
if tlsError != nil {
return tlsError

View File

@ -532,6 +532,7 @@ type (
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
UserSessionTimeout string `json:"UserSessionTimeout"`
EnableTelemetry bool `json:"EnableTelemetry"`
// Deprecated fields
DisplayDonationHeader bool
@ -566,8 +567,7 @@ type (
// Status represents the application status
Status struct {
Analytics bool `json:"Analytics"`
Version string `json:"Version"`
Version string `json:"Version"`
}
// Tag represents a tag that can be associated to a resource

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,9 @@ import '@babel/polyfill';
import angular from 'angular';
import './matomo-setup';
import './assets/js/angulartics-matomo';
import './agent';
import './azure/_module';
import './docker/__module';
@ -21,7 +24,6 @@ angular.module('portainer', [
'angularUtils.directives.dirPagination',
'LocalStorageModule',
'angular-jwt',
'angular-google-analytics',
'angular-json-tree',
'angular-loading-bar',
'angular-clipboard',
@ -37,6 +39,8 @@ angular.module('portainer', [
'portainer.integrations',
'rzModule',
'moment-picker',
'angulartics',
'angulartics.matomo',
]);
if (require) {

223
app/assets/js/angulartics-matomo.js vendored Normal file
View File

@ -0,0 +1,223 @@
import angular from 'angular';
// forked from https://github.com/angulartics/angulartics-piwik/blob/master/src/angulartics-piwik.js
/* global _paq */
/**
* @ngdoc overview
* @name angulartics.piwik
* Enables analytics support for Piwik/Matomo (http://piwik.org/docs/tracking-api/)
*/
angular.module('angulartics.matomo', ['angulartics']).config([
'$analyticsProvider',
'$windowProvider',
function ($analyticsProvider, $windowProvider) {
var $window = $windowProvider.$get();
$analyticsProvider.settings.pageTracking.trackRelativePath = true;
// Add piwik specific trackers to angulartics API
// Requires the CustomDimensions plugin for Piwik.
$analyticsProvider.api.setCustomDimension = function (dimensionId, value) {
if ($window._paq) {
$window._paq.push(['setCustomDimension', dimensionId, value]);
}
};
// Requires the CustomDimensions plugin for Piwik.
$analyticsProvider.api.deleteCustomDimension = function (dimensionId) {
if ($window._paq) {
$window._paq.push(['deleteCustomDimension', dimensionId]);
}
};
// scope: visit or page. Defaults to 'page'
$analyticsProvider.api.setCustomVariable = function (varIndex, varName, value, scope) {
if ($window._paq) {
scope = scope || 'page';
$window._paq.push(['setCustomVariable', varIndex, varName, value, scope]);
}
};
// scope: visit or page. Defaults to 'page'
$analyticsProvider.api.deleteCustomVariable = function (varIndex, scope) {
if ($window._paq) {
scope = scope || 'page';
$window._paq.push(['deleteCustomVariable', varIndex, scope]);
}
};
// trackSiteSearch(keyword, category, [searchCount])
$analyticsProvider.api.trackSiteSearch = function (keyword, category, searchCount) {
// keyword is required
if ($window._paq && keyword) {
var params = ['trackSiteSearch', keyword, category || false];
// searchCount is optional
if (angular.isDefined(searchCount)) {
params.push(searchCount);
}
$window._paq.push(params);
}
};
// logs a conversion for goal 1. revenue is optional
// trackGoal(goalID, [revenue]);
$analyticsProvider.api.trackGoal = function (goalID, revenue) {
if ($window._paq) {
_paq.push(['trackGoal', goalID, revenue || 0]);
}
};
// track outlink or download
// linkType is 'link' or 'download', 'link' by default
// trackLink(url, [linkType]);
$analyticsProvider.api.trackLink = function (url, linkType) {
var type = linkType || 'link';
if ($window._paq) {
$window._paq.push(['trackLink', url, type]);
}
};
// Set default angulartics page and event tracking
$analyticsProvider.registerSetUsername(function (username) {
if ($window._paq) {
$window._paq.push(['setUserId', username]);
}
});
// locationObj is the angular $location object
$analyticsProvider.registerPageTrack(function (path) {
if ($window._paq) {
$window._paq.push(['setDocumentTitle', $window.document.title]);
$window._paq.push(['setReferrerUrl', '']);
$window._paq.push(['setCustomUrl', 'http://portainer-ce.app' + path]);
$window._paq.push(['trackPageView']);
}
});
/**
* @name eventTrack
* Track a basic event in Piwik, or send an ecommerce event.
*
* @param {string} action A string corresponding to the type of event that needs to be tracked.
* @param {object} properties The properties that need to be logged with the event.
*/
$analyticsProvider.registerEventTrack(function (action, properties) {
if ($window._paq) {
properties = properties || {};
switch (action) {
/**
* @description Sets the current page view as a product or category page view. When you call
* setEcommerceView it must be followed by a call to trackPageView to record the product or
* category page view.
*
* @link https://piwik.org/docs/ecommerce-analytics/#tracking-product-page-views-category-page-views-optional
* @link https://developer.piwik.org/api-reference/tracking-javascript#ecommerce
*
* @property productSKU (required) SKU: Product unique identifier
* @property productName (optional) Product name
* @property categoryName (optional) Product category, or array of up to 5 categories
* @property price (optional) Product Price as displayed on the page
*/
case 'setEcommerceView':
$window._paq.push(['setEcommerceView', properties.productSKU, properties.productName, properties.categoryName, properties.price]);
break;
/**
* @description Adds a product into the ecommerce order. Must be called for each product in
* the order.
*
* @link https://piwik.org/docs/ecommerce-analytics/#tracking-ecommerce-orders-items-purchased-required
* @link https://developer.piwik.org/api-reference/tracking-javascript#ecommerce
*
* @property productSKU (required) SKU: Product unique identifier
* @property productName (optional) Product name
* @property categoryName (optional) Product category, or array of up to 5 categories
* @property price (recommended) Product price
* @property quantity (optional, default to 1) Product quantity
*/
case 'addEcommerceItem':
$window._paq.push(['addEcommerceItem', properties.productSKU, properties.productName, properties.productCategory, properties.price, properties.quantity]);
break;
/**
* @description Tracks a shopping cart. Call this javascript function every time a user is
* adding, updating or deleting a product from the cart.
*
* @link https://piwik.org/docs/ecommerce-analytics/#tracking-add-to-cart-items-added-to-the-cart-optional
* @link https://developer.piwik.org/api-reference/tracking-javascript#ecommerce
*
* @property grandTotal (required) Cart amount
*/
case 'trackEcommerceCartUpdate':
$window._paq.push(['trackEcommerceCartUpdate', properties.grandTotal]);
break;
/**
* @description Tracks an Ecommerce order, including any ecommerce item previously added to
* the order. orderId and grandTotal (ie. revenue) are required parameters.
*
* @link https://piwik.org/docs/ecommerce-analytics/#tracking-ecommerce-orders-items-purchased-required
* @link https://developer.piwik.org/api-reference/tracking-javascript#ecommerce
*
* @property orderId (required) Unique Order ID
* @property grandTotal (required) Order Revenue grand total (includes tax, shipping, and subtracted discount)
* @property subTotal (optional) Order sub total (excludes shipping)
* @property tax (optional) Tax amount
* @property shipping (optional) Shipping amount
* @property discount (optional) Discount offered (set to false for unspecified parameter)
*/
case 'trackEcommerceOrder':
$window._paq.push(['trackEcommerceOrder', properties.orderId, properties.grandTotal, properties.subTotal, properties.tax, properties.shipping, properties.discount]);
break;
/**
* @description Logs an event with an event category (Videos, Music, Games...), an event
* action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...), and an optional
* event name and optional numeric value.
*
* @link https://piwik.org/docs/event-tracking/
* @link https://developer.piwik.org/api-reference/tracking-javascript#using-the-tracker-object
*
* @property category
* @property action
* @property name (optional, recommended)
* @property value (optional)
*/
default:
// PAQ requires that eventValue be an integer, see: http://piwik.org/docs/event-tracking
if (properties.value) {
var parsed = parseInt(properties.value, 10);
properties.value = isNaN(parsed) ? 0 : parsed;
}
$window._paq.push([
'trackEvent',
properties.category,
action,
properties.name || properties.label, // Changed in favour of Piwik documentation. Added fallback so it's backwards compatible.
properties.value,
]);
}
}
});
/**
* @name exceptionTrack
* Sugar on top of the eventTrack method for easily handling errors
*
* @param {object} error An Error object to track: error.toString() used for event 'action', error.stack used for event 'label'.
* @param {object} cause The cause of the error given from $exceptionHandler, not used.
*/
$analyticsProvider.registerExceptionTrack(function (error) {
if ($window._paq) {
$window._paq.push(['trackEvent', 'Exceptions', error.toString(), error.stack, 0]);
}
});
},
]);

View File

@ -7,11 +7,10 @@ angular.module('portainer').config([
'$httpProvider',
'localStorageServiceProvider',
'jwtOptionsProvider',
'AnalyticsProvider',
'$uibTooltipProvider',
'$compileProvider',
'cfpLoadingBarProvider',
function ($urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider, AnalyticsProvider, $uibTooltipProvider, $compileProvider, cfpLoadingBarProvider) {
function ($urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider, $uibTooltipProvider, $compileProvider, cfpLoadingBarProvider) {
'use strict';
var environment = '@@ENVIRONMENT';
@ -52,9 +51,6 @@ angular.module('portainer').config([
},
]);
AnalyticsProvider.setAccount({ tracker: __CONFIG_GA_ID, set: { anonymizeIp: true } });
AnalyticsProvider.startOffline(true);
toastr.options.timeOut = 3000;
Terminal.applyAddon(fit);

14
app/matomo-setup.js Normal file
View File

@ -0,0 +1,14 @@
const _paq = (window._paq = window._paq || []);
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['enableLinkTracking']);
var u = 'https://portainer-ce.matomo.cloud/';
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '1']);
var d = document,
g = d.createElement('script'),
s = d.getElementsByTagName('script')[0];
g.type = 'text/javascript';
g.async = true;
g.src = '//cdn.matomo.cloud/portainer-ce.matomo.cloud/matomo.js';
s.parentNode.insertBefore(g, s);

View File

@ -15,16 +15,6 @@ async function initAuthentication(authManager, Authentication, $rootScope, $stat
await Authentication.init();
}
function initAnalytics(Analytics, $rootScope) {
Analytics.offline(false);
Analytics.registerScriptTags();
Analytics.registerTrackers();
$rootScope.$on('$stateChangeSuccess', function (event, toState) {
Analytics.trackPage(toState.url);
Analytics.pageView();
});
}
angular.module('portainer.app', ['portainer.oauth']).config([
'$stateRegistryProvider',
function ($stateRegistryProvider) {
@ -38,23 +28,19 @@ angular.module('portainer.app', ['portainer.oauth']).config([
'StateManager',
'Authentication',
'Notifications',
'Analytics',
'authManager',
'$rootScope',
'$state',
'$async',
'$q',
(StateManager, Authentication, Notifications, Analytics, authManager, $rootScope, $state, $async, $q) => {
(StateManager, Authentication, Notifications, authManager, $rootScope, $state, $async, $q) => {
const deferred = $q.defer();
const appState = StateManager.getState();
if (!appState.loading) {
deferred.resolve();
} else {
StateManager.initialize()
.then(function success(state) {
if (state.application.analytics) {
initAnalytics(Analytics, $rootScope);
}
.then(function success() {
return $async(initAuthentication, authManager, Authentication, $rootScope, $state);
})
.then(() => deferred.resolve())

View File

@ -17,6 +17,7 @@ export function SettingsViewModel(data) {
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
this.UserSessionTimeout = data.UserSessionTimeout;
this.EnableTelemetry = data.EnableTelemetry;
}
export function PublicSettingsViewModel(settings) {
@ -32,6 +33,7 @@ export function PublicSettingsViewModel(settings) {
this.EnableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
this.LogoURL = settings.LogoURL;
this.OAuthLoginURI = settings.OAuthLoginURI;
this.EnableTelemetry = settings.EnableTelemetry;
}
export function LDAPSettingsViewModel(data) {

View File

@ -1,7 +1,6 @@
export function StatusViewModel(data) {
this.Authentication = data.Authentication;
this.Snapshot = data.Snapshot;
this.Analytics = data.Analytics;
this.Version = data.Version;
}

View File

@ -11,7 +11,19 @@ angular.module('portainer.app').factory('StateManager', [
'StatusService',
'APPLICATION_CACHE_VALIDITY',
'AgentPingService',
function StateManagerFactory($q, SystemService, InfoHelper, EndpointProvider, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY, AgentPingService) {
'$analytics',
function StateManagerFactory(
$q,
SystemService,
InfoHelper,
EndpointProvider,
LocalStorage,
SettingsService,
StatusService,
APPLICATION_CACHE_VALIDITY,
AgentPingService,
$analytics
) {
'use strict';
var manager = {};
@ -106,9 +118,15 @@ angular.module('portainer.app').factory('StateManager', [
LocalStorage.storeApplicationState(state.application);
};
manager.updateEnableTelemetry = function updateEnableTelemetry(enableTelemetry) {
state.application.enableTelemetry = enableTelemetry;
$analytics.setOptOut(!enableTelemetry);
LocalStorage.storeApplicationState(state.application);
};
function assignStateFromStatusAndSettings(status, settings) {
state.application.analytics = status.Analytics;
state.application.version = status.Version;
state.application.enableTelemetry = settings.EnableTelemetry;
state.application.logo = settings.LogoURL;
state.application.snapshotInterval = settings.SnapshotInterval;
state.application.enableHostManagementFeatures = settings.EnableHostManagementFeatures;
@ -134,6 +152,7 @@ angular.module('portainer.app').factory('StateManager', [
var status = data.status;
var settings = data.settings;
assignStateFromStatusAndSettings(status, settings);
$analytics.setOptOut(!settings.EnableTelemetry);
LocalStorage.storeApplicationState(state.application);
deferred.resolve(state);
})
@ -176,6 +195,7 @@ angular.module('portainer.app').factory('StateManager', [
} else {
state.application = applicationState;
state.loading = false;
$analytics.setOptOut(!state.application.enableTelemetry);
deferred.resolve(state);
}
} else {

View File

@ -29,20 +29,16 @@
<div>
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>Opt-out</u>
<ul>
<li>You may opt-out by passing the <em>--no-analytics</em> flag as part of the docker run command when starting Portainer.</li>
<li
>If you believe that we could improve our analytics approach make sure to let us know! There is an open discussion on
<a target="_blank" href="https://github.com/portainer/portainer/issues/3310"><i class="fab fa-github" aria-hidden="true"></i> Github</a></li
>
<li>You may opt-out by turning off the analytics in the settings page.</li>
</ul>
</div>
<div>
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i> <u>What we collect & GDPR</u>
<ul>
<li
>We dont know who uses Portainer, where its used, to what scale its used, all we know (from analytics) is how often Portainer is used and which pages within the app
are most frequently used.</li
>
<li>
We don't know who uses Portainer, where its used, to what scale its used, all we know (from analytics) is how often Portainer is used and which pages within the app
are most frequently used.
</li>
<li>As we are only collecting a very small amount of totally anonymous data, it is deemed that opt-in is not required.</li>
</ul>
</div>

View File

@ -82,6 +82,17 @@
</div>
</div>
<!-- !actions -->
<!-- enableTelemetry-->
<div class="form-group">
<div class="col-sm-12">
<input type="checkbox" name="toggle_enableTelemetry" ng-model="formValues.enableTelemetry" />
<span class="text-muted small" style="margin-left: 2px;"
>Allow collection of anonymous statistics. You can find more information about this in our
<a href="https://www.portainer.io/documentation/in-app-analytics-and-privacy-policy/" target="_blank">privacy policy</a>.</span
>
</div>
</div>
<!-- !enableTelemetry-->
</form>
<!-- !init password form -->
</div>

View File

@ -5,16 +5,18 @@ angular.module('portainer.app').controller('InitAdminController', [
'Notifications',
'Authentication',
'StateManager',
'SettingsService',
'UserService',
'EndpointService',
'ExtensionService',
function ($async, $scope, $state, Notifications, Authentication, StateManager, UserService, EndpointService, ExtensionService) {
function ($async, $scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService, ExtensionService) {
$scope.logo = StateManager.getState().application.logo;
$scope.formValues = {
Username: 'admin',
Password: '',
ConfirmPassword: '',
enableTelemetry: true,
};
$scope.state = {
@ -45,6 +47,10 @@ angular.module('portainer.app').controller('InitAdminController', [
.then(function success() {
return retrieveAndSaveEnabledExtensions();
})
.then(function success() {
StateManager.updateEnableTelemetry($scope.formValues.enableTelemetry);
return SettingsService.update({ enableTelemetry: $scope.formValues.enableTelemetry });
})
.then(function () {
return EndpointService.endpoints(0, 100);
})

View File

@ -26,6 +26,18 @@
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" name="toggle_logo" ng-model="formValues.customLogo" /><i></i> </label>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<label for="toggle_enableTelemetry" class="control-label text-left">
Allow the collection of anonymous statistics
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" name="toggle_enableTelemetry" ng-model="formValues.enableTelemetry" /><i></i> </label>
</div>
<div class="col-sm-12 text-muted small" style="margin-top: 10px;">
You can find more information about this in our
<a href="https://www.portainer.io/documentation/in-app-analytics-and-privacy-policy/" target="_blank">privacy policy</a>.
</div>
</div>
<div ng-if="formValues.customLogo">
<div class="form-group">
<span class="col-sm-12 text-muted small">

View File

@ -36,6 +36,7 @@ angular.module('portainer.app').controller('SettingsController', [
allowDeviceMappingForRegularUsers: false,
allowStackManagementForRegularUsers: false,
disableContainerCapabilitiesForRegularUsers: false,
enableTelemetry: false,
};
$scope.isContainerEditDisabled = function isContainerEditDisabled() {
@ -85,6 +86,7 @@ angular.module('portainer.app').controller('SettingsController', [
settings.AllowDeviceMappingForRegularUsers = !$scope.formValues.disableDeviceMappingForRegularUsers;
settings.AllowStackManagementForRegularUsers = !$scope.formValues.disableStackManagementForRegularUsers;
settings.AllowContainerCapabilitiesForRegularUsers = !$scope.formValues.disableContainerCapabilitiesForRegularUsers;
settings.EnableTelemetry = $scope.formValues.enableTelemetry;
$scope.state.actionInProgress = true;
updateSettings(settings);
@ -105,6 +107,7 @@ angular.module('portainer.app').controller('SettingsController', [
StateManager.updateAllowContainerCapabilitiesForRegularUsers(settings.AllowContainerCapabilitiesForRegularUsers);
StateManager.updateAllowPrivilegedModeForRegularUsers(settings.AllowPrivilegedModeForRegularUsers);
StateManager.updateAllowBindMountsForRegularUsers(settings.AllowBindMountsForRegularUsers);
StateManager.updateEnableTelemetry(settings.EnableTelemetry);
$state.reload();
})
.catch(function error(err) {
@ -133,6 +136,7 @@ angular.module('portainer.app').controller('SettingsController', [
$scope.formValues.disableDeviceMappingForRegularUsers = !settings.AllowDeviceMappingForRegularUsers;
$scope.formValues.disableStackManagementForRegularUsers = !settings.AllowStackManagementForRegularUsers;
$scope.formValues.disableContainerCapabilitiesForRegularUsers = !settings.AllowContainerCapabilitiesForRegularUsers;
$scope.formValues.enableTelemetry = settings.EnableTelemetry;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve application settings');

View File

@ -25,7 +25,6 @@ import 'angular-resource';
import 'angular-utils-pagination';
import 'angular-local-storage';
import 'angular-jwt';
import 'angular-google-analytics';
import 'angular-json-tree';
import 'angular-loading-bar';
import 'angular-clipboard';
@ -37,5 +36,6 @@ import 'js-yaml/dist/js-yaml.js';
import 'angular-ui-bootstrap';
import 'angular-moment-picker';
import 'angular-multiselect/isteven-multi-select.js';
import 'angulartics/dist/angulartics.min.js';
window.angular = angular;

View File

@ -177,12 +177,12 @@ function shell_run_container() {
'docker rm -f portainer',
'docker run -d -p 8000:8000 -p 9000:9000 -v $(pwd)/dist:/app -v ' +
portainer_data +
':/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics',
':/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer',
].join(';');
}
function shell_run_localserver() {
return './dist/portainer --no-analytics';
return './dist/portainer';
}
function shell_install_yarndeps() {

View File

@ -10,9 +10,6 @@
"bugs": {
"url": "https://github.com/portainer/portainer/issues"
},
"config": {
"GA_ID": "UA-84944922-2"
},
"licenses": [
{
"type": "Zlib",
@ -56,7 +53,6 @@
"angular": "1.8.0",
"angular-clipboard": "^1.6.2",
"angular-file-saver": "^1.1.3",
"angular-google-analytics": "github:revolunet/angular-google-analytics#semver:~1.1.9",
"angular-json-tree": "1.0.1",
"angular-jwt": "~0.1.8",
"angular-loading-bar": "~0.9.0",
@ -71,6 +67,7 @@
"angular-utils-pagination": "~0.11.1",
"angularjs-scroll-glue": "^2.2.0",
"angularjs-slider": "^6.4.0",
"angulartics": "^1.6.0",
"babel-plugin-angularjs-annotate": "^0.10.0",
"bootbox": "^5.4.0",
"bootstrap": "^3.4.0",
@ -162,4 +159,4 @@
"*.js": "eslint --cache --fix",
"*.{js,css,md,html}": "prettier --write"
}
}
}

View File

@ -1,5 +1,5 @@
const path = require('path');
const { ProvidePlugin, IgnorePlugin, DefinePlugin } = require('webpack');
const { ProvidePlugin, IgnorePlugin } = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackBuildNotifierPlugin = require('webpack-build-notifier');
const CleanTerminalPlugin = require('clean-terminal-webpack-plugin');
@ -92,9 +92,6 @@ module.exports = {
collections: true,
paths: true,
}),
new DefinePlugin({
__CONFIG_GA_ID: JSON.stringify(pkg.config.GA_ID),
}),
],
optimization: {
splitChunks: {

View File

@ -1205,10 +1205,6 @@ angular-file-saver@^1.1.3:
blob-tmp "^1.0.0"
file-saver "^1.3.3"
"angular-google-analytics@github:revolunet/angular-google-analytics#semver:~1.1.9":
version "1.1.8"
resolved "https://codeload.github.com/revolunet/angular-google-analytics/tar.gz/92768a525870bc066dcf85fbe9d9f115358a6d91"
angular-json-tree@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/angular-json-tree/-/angular-json-tree-1.0.1.tgz#61b6e76ab165130335d9ec46fa572eb99604de51"
@ -1301,6 +1297,11 @@ angularjs-slider@^6.4.0:
resolved "https://registry.yarnpkg.com/angularjs-slider/-/angularjs-slider-6.7.0.tgz#eb2229311b81b79315a36e7b5eb700e128f50319"
integrity sha512-Cizsuax65wN2Y+htmA3safE5ALOSCyWcKyWkziaO8vCVymi26bQQs6kKDhkYc8GFix/KE7Oc9gH3QLlTUgD38w==
angulartics@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/angulartics/-/angulartics-1.6.0.tgz#a89c17ef8ea2334ebced65d6265951846f848172"
integrity sha512-fywhCi1InawcX+rpLv9NQ32Ed87KoZeH20SUIsRUz9dYJSxuk4/uxiKiopITveGxTC8THYHFEATj9Y/X+BvMqA==
ansi-colors@^3.0.0, ansi-colors@^3.2.1:
version "3.2.4"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"