mirror of https://github.com/portainer/portainer
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
parent
7aaf9d0eb7
commit
2158cc5157
|
@ -6,7 +6,6 @@ env:
|
|||
|
||||
globals:
|
||||
angular: true
|
||||
__CONFIG_GA_ID: true
|
||||
|
||||
extends:
|
||||
- 'eslint:recommended'
|
||||
|
|
|
@ -15,6 +15,7 @@ func (m *Migrator) updateSettingsToDB25() error {
|
|||
}
|
||||
|
||||
legacySettings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||
legacySettings.EnableTelemetry = true
|
||||
|
||||
legacySettings.AllowContainerCapabilitiesForRegularUsers = true
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
5088
api/swagger.yaml
5088
api/swagger.yaml
File diff suppressed because it is too large
Load Diff
|
@ -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) {
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
});
|
||||
},
|
||||
]);
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
|
@ -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())
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export function StatusViewModel(data) {
|
||||
this.Authentication = data.Authentication;
|
||||
this.Snapshot = data.Snapshot;
|
||||
this.Analytics = data.Analytics;
|
||||
this.Version = data.Version;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
})
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue