-
-
- Dark and High-contrast theme are experimental. Some UI components might not display properly.
-
diff --git a/app/portainer/models/user.js b/app/portainer/models/user.js
index 8f16e08c1..4c5fa6dc4 100644
--- a/app/portainer/models/user.js
+++ b/app/portainer/models/user.js
@@ -2,7 +2,7 @@ export function UserViewModel(data) {
this.Id = data.Id;
this.Username = data.Username;
this.Role = data.Role;
- this.UserTheme = data.UserTheme;
+ this.ThemeSettings = data.ThemeSettings;
if (data.Role === 1) {
this.RoleName = 'administrator';
} else {
diff --git a/app/portainer/services/api/userService.js b/app/portainer/services/api/userService.js
index 705c3f065..efa798760 100644
--- a/app/portainer/services/api/userService.js
+++ b/app/portainer/services/api/userService.js
@@ -67,8 +67,8 @@ export function UserService($q, Users, TeamService, TeamMembershipService) {
return Users.updatePassword({ id: id }, payload).$promise;
};
- service.updateUserTheme = function (id, userTheme) {
- return Users.updateTheme({ id }, { userTheme }).$promise;
+ service.updateUserTheme = function (id, theme) {
+ return Users.updateTheme({ id }, { theme }).$promise;
};
service.userMemberships = function (id) {
diff --git a/app/portainer/services/authentication.js b/app/portainer/services/authentication.js
index a3c8bed4f..1ffc9b1a1 100644
--- a/app/portainer/services/authentication.js
+++ b/app/portainer/services/authentication.js
@@ -102,7 +102,7 @@ angular.module('portainer.app').factory('Authentication', [
const data = await UserService.user(user.ID);
// Initialize user theme base on UserTheme from database
- const userTheme = data.UserTheme;
+ const userTheme = data.ThemeSettings ? data.ThemeSettings.color : 'auto';
if (userTheme === 'auto' || !userTheme) {
ThemeManager.autoTheme();
} else {
diff --git a/app/portainer/services/themeManager.js b/app/portainer/services/themeManager.js
index b5b634645..a950977d2 100644
--- a/app/portainer/services/themeManager.js
+++ b/app/portainer/services/themeManager.js
@@ -1,7 +1,6 @@
angular.module('portainer.app').service('ThemeManager', ThemeManager);
/* @ngInject */
-
export function ThemeManager(StateManager) {
return {
setTheme,
diff --git a/app/portainer/users/queries/queryKeys.ts b/app/portainer/users/queries/queryKeys.ts
new file mode 100644
index 000000000..c6f335d98
--- /dev/null
+++ b/app/portainer/users/queries/queryKeys.ts
@@ -0,0 +1,6 @@
+import { UserId } from '../types';
+
+export const queryKeys = {
+ base: () => ['users'] as const,
+ user: (id: UserId) => [...queryKeys.base(), id] as const,
+};
diff --git a/app/portainer/users/queries/useUser.ts b/app/portainer/users/queries/useUser.ts
index ec1915f5b..8986270a6 100644
--- a/app/portainer/users/queries/useUser.ts
+++ b/app/portainer/users/queries/useUser.ts
@@ -6,11 +6,13 @@ import { withError } from '@/react-tools/react-query';
import { buildUrl } from '../user.service';
import { User, UserId } from '../types';
+import { queryKeys } from './queryKeys';
+
export function useUser(
id: UserId,
{ staleTime }: { staleTime?: number } = {}
) {
- return useQuery(['users', id], () => getUser(id), {
+ return useQuery(queryKeys.user(id), () => getUser(id), {
...withError('Unable to retrieve user details'),
staleTime,
});
diff --git a/app/portainer/users/types.ts b/app/portainer/users/types.ts
index a02cc031e..5b5a9ecf4 100644
--- a/app/portainer/users/types.ts
+++ b/app/portainer/users/types.ts
@@ -20,15 +20,8 @@ export type User = {
EndpointAuthorizations: {
[endpointId: EnvironmentId]: AuthorizationMap;
};
- // UserTheme: string;
- // this.EndpointAuthorizations = data.EndpointAuthorizations;
- // this.PortainerAuthorizations = data.PortainerAuthorizations;
- // if (data.Role === 1) {
- // this.RoleName = 'administrator';
- // } else {
- // this.RoleName = 'user';
- // }
- // this.AuthenticationMethod = data.AuthenticationMethod;
- // this.Checked = false;
- // this.EndpointAuthorizations = data.EndpointAuthorizations;
+ ThemeSettings: {
+ color: 'dark' | 'light' | 'highcontrast' | 'auto';
+ subtleUpgradeButton: boolean;
+ };
};
diff --git a/app/portainer/views/account/accountController.js b/app/portainer/views/account/accountController.js
index a53b41f05..b1c9c1885 100644
--- a/app/portainer/views/account/accountController.js
+++ b/app/portainer/views/account/accountController.js
@@ -10,13 +10,11 @@ angular.module('portainer.app').controller('AccountController', [
'Notifications',
'SettingsService',
'StateManager',
- 'ThemeManager',
- function ($scope, $state, Authentication, UserService, Notifications, SettingsService, StateManager, ThemeManager) {
+ function ($scope, $state, Authentication, UserService, Notifications, SettingsService, StateManager) {
$scope.formValues = {
currentPassword: '',
newPassword: '',
confirmPassword: '',
- userTheme: '',
};
$scope.updatePassword = async function () {
@@ -98,24 +96,6 @@ angular.module('portainer.app').controller('AccountController', [
});
};
- // Update DOM for theme attribute & LocalStorage
- $scope.setTheme = function (theme) {
- ThemeManager.setTheme(theme);
- StateManager.updateTheme(theme);
- };
-
- // Rest API Call to update theme with userID in DB
- $scope.updateTheme = function () {
- UserService.updateUserTheme($scope.userID, $scope.formValues.userTheme)
- .then(function success() {
- Notifications.success('Success', 'User theme successfully updated');
- $state.reload();
- })
- .catch(function error(err) {
- Notifications.error('Failure', err, err.msg);
- });
- };
-
async function initView() {
const state = StateManager.getState();
const userDetails = Authentication.getUserDetails();
@@ -128,10 +108,6 @@ angular.module('portainer.app').controller('AccountController', [
$scope.isDemoUser = state.application.demoEnvironment.users.includes($scope.userID);
}
- const data = await UserService.user($scope.userID);
-
- $scope.formValues.userTheme = data.UserTheme;
-
SettingsService.publicSettings()
.then(function success(data) {
$scope.AuthenticationMethod = data.AuthenticationMethod;
diff --git a/app/react-tools/test-mocks.ts b/app/react-tools/test-mocks.ts
index 07b5a0923..0f1d478ab 100644
--- a/app/react-tools/test-mocks.ts
+++ b/app/react-tools/test-mocks.ts
@@ -12,12 +12,15 @@ export function createMockUsers(
Id: value,
Username: `user${value}`,
Role: getRoles(roles, value),
- UserTheme: '',
RoleName: '',
AuthenticationMethod: '',
Checked: false,
EndpointAuthorizations: {},
PortainerAuthorizations: {},
+ ThemeSettings: {
+ color: 'auto',
+ subtleUpgradeButton: false,
+ },
}));
}
diff --git a/app/react/components/UsersSelector/UsersSelector.mocks.ts b/app/react/components/UsersSelector/UsersSelector.mocks.ts
index 793f8c1f4..2ebc35b7f 100644
--- a/app/react/components/UsersSelector/UsersSelector.mocks.ts
+++ b/app/react/components/UsersSelector/UsersSelector.mocks.ts
@@ -5,7 +5,6 @@ export function createMockUser(id: number, username: string): UserViewModel {
Id: id,
Username: username,
Role: 2,
- UserTheme: '',
EndpointAuthorizations: {},
PortainerAuthorizations: {
PortainerDockerHubInspect: true,
@@ -25,5 +24,9 @@ export function createMockUser(id: number, username: string): UserViewModel {
RoleName: 'user',
Checked: false,
AuthenticationMethod: '',
+ ThemeSettings: {
+ color: 'auto',
+ subtleUpgradeButton: false,
+ },
};
}
diff --git a/app/react/portainer/users/teams/ListView/CreateTeamForm/CreateTeamForm.mocks.ts b/app/react/portainer/users/teams/ListView/CreateTeamForm/CreateTeamForm.mocks.ts
index 33f7366a7..e2e196cea 100644
--- a/app/react/portainer/users/teams/ListView/CreateTeamForm/CreateTeamForm.mocks.ts
+++ b/app/react/portainer/users/teams/ListView/CreateTeamForm/CreateTeamForm.mocks.ts
@@ -20,7 +20,10 @@ export function mockExampleData() {
Id: 10,
Username: 'user1',
Role: 2,
- UserTheme: '',
+ ThemeSettings: {
+ color: 'auto',
+ subtleUpgradeButton: false,
+ },
EndpointAuthorizations: {},
PortainerAuthorizations: {
PortainerDockerHubInspect: true,
@@ -45,7 +48,10 @@ export function mockExampleData() {
Id: 13,
Username: 'user2',
Role: 2,
- UserTheme: '',
+ ThemeSettings: {
+ color: 'auto',
+ subtleUpgradeButton: false,
+ },
EndpointAuthorizations: {},
PortainerAuthorizations: {
PortainerDockerHubInspect: true,
diff --git a/app/react/sidebar/UpgradeBEBanner/GetLicenseDialog.tsx b/app/react/sidebar/UpgradeBEBanner/GetLicenseDialog.tsx
index f88da9f19..4ae25e95a 100644
--- a/app/react/sidebar/UpgradeBEBanner/GetLicenseDialog.tsx
+++ b/app/react/sidebar/UpgradeBEBanner/GetLicenseDialog.tsx
@@ -1,3 +1,5 @@
+import { useAnalytics } from '@/angulartics.matomo/analytics-services';
+
import { HubspotForm } from '@@/HubspotForm';
import { Modal } from '@@/modals/Modal';
@@ -11,6 +13,7 @@ export function GetLicenseDialog({
// form is loaded from hubspot, so it won't have the same styling as the rest of the app
// since it won't support darkmode, we enforce a white background and black text for the components we use
// (Modal, CloseButton, loading text)
+ const { trackEvent } = useAnalytics();
return (