diff --git a/app/assets/ico/theme/auto.svg b/app/assets/ico/theme/auto.svg
deleted file mode 100644
index d7ed3b96d..000000000
--- a/app/assets/ico/theme/auto.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/app/assets/ico/theme/darkmode.svg b/app/assets/ico/theme/darkmode.svg
deleted file mode 100644
index 34357e419..000000000
--- a/app/assets/ico/theme/darkmode.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/app/assets/ico/theme/highcontrastmode.svg b/app/assets/ico/theme/highcontrastmode.svg
deleted file mode 100644
index 5b8551c68..000000000
--- a/app/assets/ico/theme/highcontrastmode.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/app/assets/ico/theme/lightmode.svg b/app/assets/ico/theme/lightmode.svg
deleted file mode 100644
index f81b64062..000000000
--- a/app/assets/ico/theme/lightmode.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/app/docker/components/network-macvlan-form/networkMacvlanFormController.js b/app/docker/components/network-macvlan-form/networkMacvlanFormController.js
index ec63b4eeb..c654f979d 100644
--- a/app/docker/components/network-macvlan-form/networkMacvlanFormController.js
+++ b/app/docker/components/network-macvlan-form/networkMacvlanFormController.js
@@ -1,4 +1,4 @@
-import { getOptions } from './options';
+import { getOptions } from '@/react/docker/networks/CreateView/macvlanOptions';
angular.module('portainer.docker').controller('NetworkMacvlanFormController', [
'$q',
diff --git a/app/docker/views/images/build/buildImageController.js b/app/docker/views/images/build/buildImageController.js
index 965f8d668..e52994ba1 100644
--- a/app/docker/views/images/build/buildImageController.js
+++ b/app/docker/views/images/build/buildImageController.js
@@ -1,12 +1,12 @@
import { confirmWebEditorDiscard } from '@@/modals/confirm';
-import { options } from './options';
+import { editor, upload, url } from '@@/BoxSelector/common-options/build-methods';
angular.module('portainer.docker').controller('BuildImageController', BuildImageController);
/* @ngInject */
function BuildImageController($scope, $async, $window, BuildService, Notifications, HttpRequestHelper, endpoint) {
$scope.endpoint = endpoint;
- $scope.options = options;
+ $scope.options = [editor, upload, url];
$scope.state = {
BuildType: 'editor',
diff --git a/app/docker/views/images/build/options.tsx b/app/docker/views/images/build/options.tsx
deleted file mode 100644
index 96e242c59..000000000
--- a/app/docker/views/images/build/options.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import {
- editor,
- upload,
- url,
-} from '@@/BoxSelector/common-options/build-methods';
-
-export const options = [editor, upload, url] as const;
diff --git a/app/edge/components/edge-job-form/edgeJobFormController.js b/app/edge/components/edge-job-form/edgeJobFormController.js
index 454a31ee6..68fecebb6 100644
--- a/app/edge/components/edge-job-form/edgeJobFormController.js
+++ b/app/edge/components/edge-job-form/edgeJobFormController.js
@@ -2,7 +2,7 @@ import _ from 'lodash-es';
import moment from 'moment';
import { editor, upload } from '@@/BoxSelector/common-options/build-methods';
-import { cronMethodOptions } from './cron-method-options';
+import { cronMethodOptions } from '@/react/edge/edge-jobs/CreateView/cron-method-options';
export class EdgeJobFormController {
/* @ngInject */
diff --git a/app/edge/components/group-form/groupFormController.js b/app/edge/components/group-form/groupFormController.js
index d7358e291..253c7ea66 100644
--- a/app/edge/components/group-form/groupFormController.js
+++ b/app/edge/components/group-form/groupFormController.js
@@ -5,8 +5,8 @@ import { getEnvironments } from '@/react/portainer/environments/environment.serv
import { getTags } from '@/portainer/tags/tags.service';
import { notifyError } from '@/portainer/services/notifications';
import { buildConfirmButton } from '@@/modals/utils';
-import { groupTypeOptions } from './group-type-options';
-import { tagOptions } from './tag-options';
+import { tagOptions } from '@/react/edge/edge-groups/CreateView/tag-options';
+import { groupTypeOptions } from '@/react/edge/edge-groups/CreateView/group-type-options';
export class EdgeGroupFormController {
/* @ngInject */
diff --git a/app/kubernetes/views/applications/create/createApplicationController.js b/app/kubernetes/views/applications/create/createApplicationController.js
index b3cb053f0..22fcdef0b 100644
--- a/app/kubernetes/views/applications/create/createApplicationController.js
+++ b/app/kubernetes/views/applications/create/createApplicationController.js
@@ -37,7 +37,7 @@ import { confirmUpdateAppIngress } from '@/react/kubernetes/applications/CreateV
import { confirm, confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confirm';
import { buildConfirmButton } from '@@/modals/utils';
import { ModalType } from '@@/modals';
-import { placementOptions } from './placementTypes';
+import { placementOptions } from '@/react/kubernetes/applications/CreateView/placementTypes';
class KubernetesCreateApplicationController {
/* #region CONSTRUCTOR */
diff --git a/app/kubernetes/views/configurations/create/createConfigurationController.js b/app/kubernetes/views/configurations/create/createConfigurationController.js
index 8c58a348d..7be02d396 100644
--- a/app/kubernetes/views/configurations/create/createConfigurationController.js
+++ b/app/kubernetes/views/configurations/create/createConfigurationController.js
@@ -5,10 +5,10 @@ import { KubernetesConfigurationKinds, KubernetesSecretTypeOptions } from 'Kuber
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
import { getServiceAccounts } from 'Kubernetes/rest/serviceAccount';
+import { typeOptions } from '@/react/kubernetes/configs/CreateView/options';
import { confirmWebEditorDiscard } from '@@/modals/confirm';
import { isConfigurationFormValid } from '../validation';
-import { typeOptions } from './options';
class KubernetesCreateConfigurationController {
/* @ngInject */
diff --git a/app/kubernetes/views/configure/configureController.js b/app/kubernetes/views/configure/configureController.js
index 4f2045e83..dc2248340 100644
--- a/app/kubernetes/views/configure/configureController.js
+++ b/app/kubernetes/views/configure/configureController.js
@@ -7,9 +7,9 @@ import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { getIngressControllerClassMap, updateIngressControllerClassMap } from '@/react/kubernetes/cluster/ingressClass/utils';
-import { getIsRBACEnabled } from '@/react/kubernetes/cluster/service';
import { buildConfirmButton } from '@@/modals/utils';
import { confirm } from '@@/modals/confirm';
+import { getIsRBACEnabled } from '@/react/kubernetes/cluster/getIsRBACEnabled';
class KubernetesConfigureController {
/* #region CONSTRUCTOR */
diff --git a/app/kubernetes/views/resource-pools/access/resourcePoolAccessController.js b/app/kubernetes/views/resource-pools/access/resourcePoolAccessController.js
index 1ce6ef6b0..d9802586f 100644
--- a/app/kubernetes/views/resource-pools/access/resourcePoolAccessController.js
+++ b/app/kubernetes/views/resource-pools/access/resourcePoolAccessController.js
@@ -3,7 +3,7 @@ import _ from 'lodash-es';
import { KubernetesPortainerConfigMapConfigName, KubernetesPortainerConfigMapNamespace, KubernetesPortainerConfigMapAccessKey } from 'Kubernetes/models/config-map/models';
import { UserAccessViewModel, TeamAccessViewModel } from 'Portainer/models/access';
import KubernetesConfigMapHelper from 'Kubernetes/helpers/configMapHelper';
-import { getIsRBACEnabled } from '@/react/kubernetes/cluster/service';
+import { getIsRBACEnabled } from '@/react/kubernetes/cluster/getIsRBACEnabled';
class KubernetesResourcePoolAccessController {
/* @ngInject */
diff --git a/app/portainer/components/BoxSelector/BoxSelectorAngular.tsx b/app/portainer/components/BoxSelector/BoxSelectorAngular.ts
similarity index 100%
rename from app/portainer/components/BoxSelector/BoxSelectorAngular.tsx
rename to app/portainer/components/BoxSelector/BoxSelectorAngular.ts
diff --git a/app/portainer/components/endpointSecurity/porEndpointSecurityController.js b/app/portainer/components/endpointSecurity/porEndpointSecurityController.js
index 32facf119..56d11a562 100644
--- a/app/portainer/components/endpointSecurity/porEndpointSecurityController.js
+++ b/app/portainer/components/endpointSecurity/porEndpointSecurityController.js
@@ -1,4 +1,4 @@
-import { tlsOptions } from './tls-options';
+import { tlsOptions } from '@/react/portainer/environments/ItemView/tls-options';
angular.module('portainer.app').controller('porEndpointSecurityController', [
'$scope',
diff --git a/app/portainer/components/forms/git-form/git-form-auth-fieldset.controller.ts b/app/portainer/components/forms/git-form/git-form-auth-fieldset.controller.ts
index 3b89e4b9a..1c8350c85 100644
--- a/app/portainer/components/forms/git-form/git-form-auth-fieldset.controller.ts
+++ b/app/portainer/components/forms/git-form/git-form-auth-fieldset.controller.ts
@@ -5,8 +5,8 @@ import { notifyError } from '@/portainer/services/notifications';
import { IAuthenticationService } from '@/portainer/services/types';
import { GitAuthModel } from '@/react/portainer/gitops/types';
import { gitAuthValidation } from '@/react/portainer/gitops/AuthFieldset';
-import { getGitCredentials } from '@/portainer/views/account/git-credential/gitCredential.service';
-import { GitCredential } from '@/portainer/views/account/git-credential/types';
+import { GitCredential } from '@/react/portainer/account/git-credentials/types';
+import { getGitCredentials } from '@/react/portainer/account/git-credentials/git-credentials.service';
import { validateForm } from '@@/form-components/validate-form';
diff --git a/app/portainer/components/forms/git-form/git-form.controller.ts b/app/portainer/components/forms/git-form/git-form.controller.ts
index a30319412..63836da31 100644
--- a/app/portainer/components/forms/git-form/git-form.controller.ts
+++ b/app/portainer/components/forms/git-form/git-form.controller.ts
@@ -5,8 +5,8 @@ import { GitFormModel } from '@/react/portainer/gitops/types';
import { validateGitForm } from '@/react/portainer/gitops/GitForm';
import { notifyError } from '@/portainer/services/notifications';
import { IAuthenticationService } from '@/portainer/services/types';
-import { getGitCredentials } from '@/portainer/views/account/git-credential/gitCredential.service';
-import { GitCredential } from '@/portainer/views/account/git-credential/types';
+import { getGitCredentials } from '@/react/portainer/account/git-credentials/git-credentials.service';
+import { GitCredential } from '@/react/portainer/account/git-credentials/types';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
export default class GitFormController {
diff --git a/app/portainer/components/theme/theme-settings.controller.js b/app/portainer/components/theme/theme-settings.controller.js
index 97f4ad6de..3c6bb47eb 100644
--- a/app/portainer/components/theme/theme-settings.controller.js
+++ b/app/portainer/components/theme/theme-settings.controller.js
@@ -1,7 +1,7 @@
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
import { queryKeys } from '@/portainer/users/queries/queryKeys';
import { queryClient } from '@/react-tools/react-query';
-import { options } from './options';
+import { options } from '@/react/portainer/account/AccountView/theme-options';
export default class ThemeSettingsController {
/* @ngInject */
diff --git a/app/portainer/license-management/license.service.ts b/app/portainer/license-management/license.service.ts
index 55f93ea76..338506299 100644
--- a/app/portainer/license-management/license.service.ts
+++ b/app/portainer/license-management/license.service.ts
@@ -1,112 +1,11 @@
-import _ from 'lodash';
-import { AxiosError } from 'axios';
-
-import axios from '@/portainer/services/axios';
-
-import { License, LicenseInfo } from './types';
-
-type Listener = (info: LicenseInfo) => void;
-
-interface Store {
- data?: LicenseInfo;
- lastLoaded?: number;
- invalidated: boolean;
- listeners: Listener[];
-}
-
-const store: Store = {
- listeners: [],
- invalidated: true,
-};
-
-export async function getLicenses() {
- try {
- const { data } = await axios.get(buildUrl());
-
- return data;
- } catch (e) {
- const axiosError = e as AxiosError;
- throw new Error(axiosError.response?.data.message);
- }
-}
-
-interface AttachResponse {
- licenses: License[];
- failedKeys: Record;
-}
-
-export async function attachLicense(licenseKeys: string[]) {
- try {
- const { data } = await axios.post(buildUrl(), {
- licenseKeys,
- });
-
- if (Object.keys(data.failedKeys).length === licenseKeys.length) {
- return data;
- }
-
- store.invalidated = true;
- getLicenseInfo();
- return data;
- } catch (e) {
- const axiosError = e as AxiosError;
- throw new Error(axiosError.response?.data.message);
- }
-}
-
-interface RemoveResponse {
- failedKeys: Record;
-}
-
-export async function removeLicense(licenseKeys: string[]) {
- try {
- const { data } = await axios.post(buildUrl('remove'), {
- licenseKeys,
- });
- if (Object.keys(data.failedKeys).length === licenseKeys.length) {
- return data;
- }
-
- store.invalidated = true;
- getLicenseInfo();
- return data;
- } catch (e) {
- const axiosError = e as AxiosError;
- throw new Error(axiosError.response?.data.message);
- }
-}
-
-export async function getLicenseInfo() {
- try {
- if (
- store.data &&
- !store.invalidated &&
- store.lastLoaded &&
- Math.abs(store.lastLoaded - Date.now()) < 1000 * 30
- ) {
- return store.data;
- }
-
- const { data: info } = await axios.get(buildUrl('info'));
- store.data = info;
- store.lastLoaded = Date.now();
- store.invalidated = false;
- store.listeners.forEach((listener) => listener(info));
-
- return info;
- } catch (e) {
- const axiosError = e as AxiosError;
- throw new Error(axiosError.response?.data.message);
- }
-}
-
-export function subscribe(listener: Listener) {
- store.listeners.push(listener);
-}
-
-export function unsubscribe(listener: Listener) {
- _.remove(store.listeners, listener);
-}
+import {
+ getLicenses,
+ attachLicense,
+ removeLicense,
+ getLicenseInfo,
+ unsubscribe,
+ subscribe,
+} from '@/react/portainer/licenses/license.service';
/* @ngInject */
export function LicenseService() {
@@ -119,12 +18,3 @@ export function LicenseService() {
unsubscribe,
};
}
-
-function buildUrl(action = '') {
- let url = 'licenses';
-
- if (action) {
- url += `/${action}`;
- }
- return url;
-}
diff --git a/app/portainer/oauth/components/oauth-providers-selector/oauth-provider-selector.controller.js b/app/portainer/oauth/components/oauth-providers-selector/oauth-provider-selector.controller.js
index 027be408d..f7dceabae 100644
--- a/app/portainer/oauth/components/oauth-providers-selector/oauth-provider-selector.controller.js
+++ b/app/portainer/oauth/components/oauth-providers-selector/oauth-provider-selector.controller.js
@@ -1,4 +1,4 @@
-import { options } from './oauth-options';
+import { options } from '@/react/portainer/settings/AuthenticationView/oauth-options';
export default class OAuthProviderSelectorController {
constructor() {
diff --git a/app/portainer/react/components/access-control.ts b/app/portainer/react/components/access-control.ts
new file mode 100644
index 000000000..bad17418c
--- /dev/null
+++ b/app/portainer/react/components/access-control.ts
@@ -0,0 +1,58 @@
+import angular from 'angular';
+
+import { r2a } from '@/react-tools/react2angular';
+import { withCurrentUser } from '@/react-tools/withCurrentUser';
+import { withReactQuery } from '@/react-tools/withReactQuery';
+import { withUIRouter } from '@/react-tools/withUIRouter';
+import { PorAccessControlFormTeamSelector } from '@/react/portainer/access-control/PorAccessControlForm/TeamsSelector';
+import { PorAccessControlFormUserSelector } from '@/react/portainer/access-control/PorAccessControlForm/UsersSelector';
+import { PorAccessManagementUsersSelector } from '@/react/portainer/access-control/AccessManagement/PorAccessManagementUsersSelector';
+import { AccessTypeSelector } from '@/react/portainer/access-control/EditDetails/AccessTypeSelector';
+import { AccessControlPanel } from '@/react/portainer/access-control';
+
+export const accessControlModule = angular
+ .module('portainer.app.react.components.access-control', [])
+ .component(
+ 'accessControlPanel',
+ r2a(withUIRouter(withReactQuery(withCurrentUser(AccessControlPanel))), [
+ 'disableOwnershipChange',
+ 'onUpdateSuccess',
+ 'resourceControl',
+ 'resourceId',
+ 'resourceType',
+ 'environmentId',
+ ])
+ )
+ .component(
+ 'accessTypeSelector',
+ r2a(AccessTypeSelector, [
+ 'isAdmin',
+ 'isPublicVisible',
+ 'name',
+ 'onChange',
+ 'value',
+ 'teams',
+ ])
+ )
+ .component(
+ 'porAccessControlFormTeamSelector',
+ r2a(PorAccessControlFormTeamSelector, [
+ 'inputId',
+ 'onChange',
+ 'options',
+ 'value',
+ ])
+ )
+ .component(
+ 'porAccessControlFormUserSelector',
+ r2a(PorAccessControlFormUserSelector, [
+ 'inputId',
+ 'onChange',
+ 'options',
+ 'value',
+ ])
+ )
+ .component(
+ 'porAccessManagementUsersSelector',
+ r2a(PorAccessManagementUsersSelector, ['onChange', 'options', 'value'])
+ ).name;
diff --git a/app/portainer/react/components/envronments.ts b/app/portainer/react/components/envronments.ts
new file mode 100644
index 000000000..4e7f9ae68
--- /dev/null
+++ b/app/portainer/react/components/envronments.ts
@@ -0,0 +1,16 @@
+import angular from 'angular';
+
+import { r2a } from '@/react-tools/react2angular';
+import { withControlledInput } from '@/react-tools/withControlledInput';
+import { EdgeKeyDisplay } from '@/react/portainer/environments/ItemView/EdgeKeyDisplay';
+import { KVMControl } from '@/react/portainer/environments/KvmView/KVMControl';
+import { GpusList } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
+
+export const environmentsModule = angular
+ .module('portainer.app.react.components.environments', [])
+ .component('edgeKeyDisplay', r2a(EdgeKeyDisplay, ['edgeKey']))
+ .component('kvmControl', r2a(KVMControl, ['deviceId', 'server', 'token']))
+ .component(
+ 'gpusList',
+ r2a(withControlledInput(GpusList), ['value', 'onChange'])
+ ).name;
diff --git a/app/portainer/react/components/git-form.ts b/app/portainer/react/components/git-form.ts
index 3a12101bb..98537ed36 100644
--- a/app/portainer/react/components/git-form.ts
+++ b/app/portainer/react/components/git-form.ts
@@ -9,6 +9,7 @@ import { GitForm } from '@/react/portainer/gitops/GitForm';
import { AuthFieldset } from '@/react/portainer/gitops/AuthFieldset';
import { InfoPanel } from '@/react/portainer/gitops/InfoPanel';
import { RefField } from '@/react/portainer/gitops/RefField';
+import { TimeWindowDisplay } from '@/react/portainer/gitops/TimeWindowDisplay';
export const gitFormModule = angular
.module('portainer.app.components.forms.git', [])
@@ -66,4 +67,8 @@ export const gitFormModule = angular
'value',
'isUrlValid',
])
+ )
+ .component(
+ 'timeWindowDisplay',
+ r2a(withReactQuery(withUIRouter(TimeWindowDisplay)), [])
).name;
diff --git a/app/portainer/react/components/index.ts b/app/portainer/react/components/index.ts
index df0a623d8..c014c7315 100644
--- a/app/portainer/react/components/index.ts
+++ b/app/portainer/react/components/index.ts
@@ -1,26 +1,12 @@
import angular from 'angular';
import { r2a } from '@/react-tools/react2angular';
-import {
- DefaultRegistryAction,
- DefaultRegistryDomain,
- DefaultRegistryName,
-} from '@/react/portainer/registries/ListView/DefaultRegistry';
-import { Icon } from '@/react/components/Icon';
-import { ReactQueryDevtoolsWrapper } from '@/react/components/ReactQueryDevtoolsWrapper';
-import { AccessControlPanel } from '@/react/portainer/access-control';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter';
-import { SettingsFDO } from '@/react/portainer/settings/EdgeComputeView/SettingsFDO';
-import { SettingsOpenAMT } from '@/react/portainer/settings/EdgeComputeView/SettingsOpenAMT';
-import { InternalAuth } from '@/react/portainer/settings/AuthenticationView/InternalAuth';
-import { PorAccessControlFormTeamSelector } from '@/react/portainer/access-control/PorAccessControlForm/TeamsSelector';
-import { PorAccessControlFormUserSelector } from '@/react/portainer/access-control/PorAccessControlForm/UsersSelector';
-import { PorAccessManagementUsersSelector } from '@/react/portainer/access-control/AccessManagement/PorAccessManagementUsersSelector';
-import { AccessTypeSelector } from '@/react/portainer/access-control/EditDetails/AccessTypeSelector';
-import { EdgeKeyDisplay } from '@/react/portainer/environments/ItemView/EdgeKeyDisplay';
+import { Icon } from '@@/Icon';
+import { ReactQueryDevtoolsWrapper } from '@@/ReactQueryDevtoolsWrapper';
import { PageHeader } from '@@/PageHeader';
import { TagSelector } from '@@/TagSelector';
import { Loading } from '@@/Widget/Loading';
@@ -38,18 +24,21 @@ import { PortainerSelect } from '@@/form-components/PortainerSelect';
import { Slider } from '@@/form-components/Slider';
import { TagButton } from '@@/TagButton';
import { BETeaserButton } from '@@/BETeaserButton';
-import { TimeWindowDisplay } from '@@/TimeWindowDisplay';
import { CodeEditor } from '@@/CodeEditor';
import { fileUploadField } from './file-upload-field';
import { switchField } from './switch-field';
import { customTemplatesModule } from './custom-templates';
import { gitFormModule } from './git-form';
+import { settingsModule } from './settings';
+import { accessControlModule } from './access-control';
export const componentsModule = angular
.module('portainer.app.react.components', [
customTemplatesModule,
gitFormModule,
+ settingsModule,
+ accessControlModule,
])
.component(
'tagSelector',
@@ -74,17 +63,7 @@ export const componentsModule = angular
'tagButton',
r2a(TagButton, ['value', 'label', 'title', 'onRemove'])
)
- .component(
- 'accessTypeSelector',
- r2a(AccessTypeSelector, [
- 'isAdmin',
- 'isPublicVisible',
- 'name',
- 'onChange',
- 'value',
- 'teams',
- ])
- )
+
.component(
'portainerTooltip',
r2a(Tooltip, ['message', 'position', 'className', 'setHtmlMessage'])
@@ -143,38 +122,6 @@ export const componentsModule = angular
])
)
.component('badgeIcon', r2a(BadgeIcon, ['icon', 'size']))
- .component(
- 'accessControlPanel',
- r2a(withUIRouter(withReactQuery(withCurrentUser(AccessControlPanel))), [
- 'disableOwnershipChange',
- 'onUpdateSuccess',
- 'resourceControl',
- 'resourceId',
- 'resourceType',
- 'environmentId',
- ])
- )
- .component(
- 'defaultRegistryName',
- r2a(withReactQuery(DefaultRegistryName), [])
- )
- .component(
- 'defaultRegistryAction',
- r2a(withReactQuery(DefaultRegistryAction), [])
- )
- .component(
- 'defaultRegistryDomain',
- r2a(withReactQuery(DefaultRegistryDomain), [])
- )
- .component(
- 'settingsFdo',
- r2a(withUIRouter(withReactQuery(SettingsFDO)), ['onSubmit', 'settings'])
- )
- .component('settingsOpenAmt', r2a(SettingsOpenAMT, ['onSubmit', 'settings']))
- .component(
- 'internalAuth',
- r2a(InternalAuth, ['onSaveSettings', 'isLoading', 'value', 'onChange'])
- )
.component(
'teamsSelector',
r2a(TeamsSelector, [
@@ -188,24 +135,6 @@ export const componentsModule = angular
'disabled',
])
)
- .component(
- 'porAccessControlFormTeamSelector',
- r2a(PorAccessControlFormTeamSelector, [
- 'inputId',
- 'onChange',
- 'options',
- 'value',
- ])
- )
- .component(
- 'porAccessControlFormUserSelector',
- r2a(PorAccessControlFormUserSelector, [
- 'inputId',
- 'onChange',
- 'options',
- 'value',
- ])
- )
.component(
'porSelect',
r2a(PortainerSelect, [
@@ -234,15 +163,7 @@ export const componentsModule = angular
'dataCy',
])
)
- .component(
- 'porAccessManagementUsersSelector',
- r2a(PorAccessManagementUsersSelector, ['onChange', 'options', 'value'])
- )
- .component('edgeKeyDisplay', r2a(EdgeKeyDisplay, ['edgeKey']))
- .component(
- 'timeWindowDisplay',
- r2a(withReactQuery(withUIRouter(TimeWindowDisplay)), [])
- )
+
.component(
'reactCodeEditor',
r2a(CodeEditor, [
diff --git a/app/portainer/react/components/registries.ts b/app/portainer/react/components/registries.ts
new file mode 100644
index 000000000..94502103d
--- /dev/null
+++ b/app/portainer/react/components/registries.ts
@@ -0,0 +1,24 @@
+import angular from 'angular';
+
+import { r2a } from '@/react-tools/react2angular';
+import { withReactQuery } from '@/react-tools/withReactQuery';
+import {
+ DefaultRegistryAction,
+ DefaultRegistryDomain,
+ DefaultRegistryName,
+} from '@/react/portainer/registries/ListView/DefaultRegistry';
+
+export const registriesModule = angular
+ .module('portainer.app.react.components.registries', [])
+ .component(
+ 'defaultRegistryName',
+ r2a(withReactQuery(DefaultRegistryName), [])
+ )
+ .component(
+ 'defaultRegistryAction',
+ r2a(withReactQuery(DefaultRegistryAction), [])
+ )
+ .component(
+ 'defaultRegistryDomain',
+ r2a(withReactQuery(DefaultRegistryDomain), [])
+ ).name;
diff --git a/app/portainer/react/components/settings.ts b/app/portainer/react/components/settings.ts
new file mode 100644
index 000000000..2bb90e9d9
--- /dev/null
+++ b/app/portainer/react/components/settings.ts
@@ -0,0 +1,20 @@
+import angular from 'angular';
+
+import { SettingsFDO } from '@/react/portainer/settings/EdgeComputeView/SettingsFDO';
+import { SettingsOpenAMT } from '@/react/portainer/settings/EdgeComputeView/SettingsOpenAMT';
+import { InternalAuth } from '@/react/portainer/settings/AuthenticationView/InternalAuth';
+import { r2a } from '@/react-tools/react2angular';
+import { withReactQuery } from '@/react-tools/withReactQuery';
+import { withUIRouter } from '@/react-tools/withUIRouter';
+
+export const settingsModule = angular
+ .module('portainer.app.react.components.settings', [])
+ .component(
+ 'settingsFdo',
+ r2a(withUIRouter(withReactQuery(SettingsFDO)), ['onSubmit', 'settings'])
+ )
+ .component('settingsOpenAmt', r2a(SettingsOpenAMT, ['onSubmit', 'settings']))
+ .component(
+ 'internalAuth',
+ r2a(InternalAuth, ['onSaveSettings', 'isLoading', 'value', 'onChange'])
+ ).name;
diff --git a/app/portainer/settings/authentication/ldap/ldap-settings/ldap-settings.controller.js b/app/portainer/settings/authentication/ldap/ldap-settings/ldap-settings.controller.js
index 390faa4d5..3d13509e1 100644
--- a/app/portainer/settings/authentication/ldap/ldap-settings/ldap-settings.controller.js
+++ b/app/portainer/settings/authentication/ldap/ldap-settings/ldap-settings.controller.js
@@ -1,5 +1,5 @@
import { buildLdapSettingsModel, buildOpenLDAPSettingsModel } from '@/portainer/settings/authentication/ldap/ldap-settings.model';
-import { options } from './ldap-options';
+import { options } from '@/react/portainer/settings/AuthenticationView/ldap-options';
const SERVER_TYPES = {
CUSTOM: 0,
diff --git a/app/portainer/views/endpoints/kvm/index.js b/app/portainer/views/endpoints/kvm/index.js
deleted file mode 100644
index 7842d3581..000000000
--- a/app/portainer/views/endpoints/kvm/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import angular from 'angular';
-
-import { KVMControlAngular } from '@/portainer/views/endpoints/kvm/KVMControl';
-
-angular.module('portainer.app').component('kvmControl', KVMControlAngular).name;
diff --git a/app/portainer/views/init/admin/initAdminController.js b/app/portainer/views/init/admin/initAdminController.js
index fdc0304aa..e985d6d10 100644
--- a/app/portainer/views/init/admin/initAdminController.js
+++ b/app/portainer/views/init/admin/initAdminController.js
@@ -1,5 +1,5 @@
import { getEnvironments } from '@/react/portainer/environments/environment.service';
-import { restoreOptions } from './restore-options';
+import { restoreOptions } from '@/react/portainer/init/InitAdminView/restore-options';
angular.module('portainer.app').controller('InitAdminController', [
'$scope',
diff --git a/app/portainer/views/registries/create/createRegistryController.js b/app/portainer/views/registries/create/createRegistryController.js
index dfab3640c..45080c238 100644
--- a/app/portainer/views/registries/create/createRegistryController.js
+++ b/app/portainer/views/registries/create/createRegistryController.js
@@ -1,7 +1,7 @@
import _ from 'lodash';
import { RegistryTypes } from 'Portainer/models/registryTypes';
import { RegistryCreateFormValues } from 'Portainer/models/registry';
-import { options } from './options';
+import { options } from '@/react/portainer/registries/CreateView/options';
class CreateRegistryController {
/* @ngInject */
diff --git a/app/portainer/views/settings/authentication/settingsAuthenticationController.js b/app/portainer/views/settings/authentication/settingsAuthenticationController.js
index 8214e8cf8..64f73bc80 100644
--- a/app/portainer/views/settings/authentication/settingsAuthenticationController.js
+++ b/app/portainer/views/settings/authentication/settingsAuthenticationController.js
@@ -2,7 +2,7 @@ import angular from 'angular';
import _ from 'lodash-es';
import { buildLdapSettingsModel, buildAdSettingsModel } from '@/portainer/settings/authentication/ldap/ldap-settings.model';
-import { options } from './options';
+import { options } from '@/react/portainer/settings/AuthenticationView/InternalAuth/options';
angular.module('portainer.app').controller('SettingsAuthenticationController', SettingsAuthenticationController);
diff --git a/app/portainer/views/settings/settingsController.js b/app/portainer/views/settings/settingsController.js
index 80284345f..5ad50e716 100644
--- a/app/portainer/views/settings/settingsController.js
+++ b/app/portainer/views/settings/settingsController.js
@@ -1,7 +1,7 @@
import angular from 'angular';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
-import { options } from './options';
+import { options } from '@/react/portainer/settings/SettingsView/backup-options';
angular.module('portainer.app').controller('SettingsController', [
'$scope',
diff --git a/app/react/components/Svg.tsx b/app/react/components/Svg.tsx
index 00dd8f706..5aa055d64 100644
--- a/app/react/components/Svg.tsx
+++ b/app/react/components/Svg.tsx
@@ -1,8 +1,3 @@
-// theme icons
-import automode from '@/assets/ico/theme/auto.svg?c';
-import darkmode from '@/assets/ico/theme/darkmode.svg?c';
-import lightmode from '@/assets/ico/theme/lightmode.svg?c';
-import highcontrastmode from '@/assets/ico/theme/highcontrastmode.svg?c';
// general icons
import heartbeatup from '@/assets/ico/heartbeat-up.svg?c';
import heartbeatdown from '@/assets/ico/heartbeat-down.svg?c';
@@ -47,10 +42,6 @@ const placeholder = Placeholder;
export const SvgIcons = {
heartbeatup,
heartbeatdown,
- automode,
- darkmode,
- lightmode,
- highcontrastmode,
dataflow,
dockericon,
git,
diff --git a/app/docker/components/network-macvlan-form/options.tsx b/app/react/docker/networks/CreateView/macvlanOptions.tsx
similarity index 100%
rename from app/docker/components/network-macvlan-form/options.tsx
rename to app/react/docker/networks/CreateView/macvlanOptions.tsx
diff --git a/app/react/edge/edge-devices/WaitingRoomView/queries.ts b/app/react/edge/edge-devices/WaitingRoomView/queries.ts
index ddf9a866a..f20d1d9c9 100644
--- a/app/react/edge/edge-devices/WaitingRoomView/queries.ts
+++ b/app/react/edge/edge-devices/WaitingRoomView/queries.ts
@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from 'react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { promiseSequence } from '@/portainer/helpers/promise-utils';
-import { useIntegratedLicenseInfo } from '@/portainer/license-management/use-license.service';
+import { useIntegratedLicenseInfo } from '@/react/portainer/licenses/use-license.service';
export function useAssociateDeviceMutation() {
const queryClient = useQueryClient();
diff --git a/app/edge/components/group-form/group-type-options.tsx b/app/react/edge/edge-groups/CreateView/group-type-options.tsx
similarity index 100%
rename from app/edge/components/group-form/group-type-options.tsx
rename to app/react/edge/edge-groups/CreateView/group-type-options.tsx
diff --git a/app/edge/components/group-form/tag-options.tsx b/app/react/edge/edge-groups/CreateView/tag-options.tsx
similarity index 100%
rename from app/edge/components/group-form/tag-options.tsx
rename to app/react/edge/edge-groups/CreateView/tag-options.tsx
diff --git a/app/edge/components/edge-job-form/cron-method-options.tsx b/app/react/edge/edge-jobs/CreateView/cron-method-options.tsx
similarity index 100%
rename from app/edge/components/edge-job-form/cron-method-options.tsx
rename to app/react/edge/edge-jobs/CreateView/cron-method-options.tsx
diff --git a/app/kubernetes/views/applications/create/placementTypes.tsx b/app/react/kubernetes/applications/CreateView/placementTypes.tsx
similarity index 100%
rename from app/kubernetes/views/applications/create/placementTypes.tsx
rename to app/react/kubernetes/applications/CreateView/placementTypes.tsx
diff --git a/app/react/kubernetes/cluster/service.ts b/app/react/kubernetes/cluster/getIsRBACEnabled.ts
similarity index 89%
rename from app/react/kubernetes/cluster/service.ts
rename to app/react/kubernetes/cluster/getIsRBACEnabled.ts
index 7302ca568..972016d42 100644
--- a/app/react/kubernetes/cluster/service.ts
+++ b/app/react/kubernetes/cluster/getIsRBACEnabled.ts
@@ -4,7 +4,7 @@ import { EnvironmentId } from '@/react/portainer/environments/types';
export async function getIsRBACEnabled(environmentId: EnvironmentId) {
try {
- const { data } = await axios.get(
+ const { data } = await axios.get(
`kubernetes/${environmentId}/rbac_enabled`
);
return data;
diff --git a/app/kubernetes/views/configurations/create/options.tsx b/app/react/kubernetes/configs/CreateView/options.tsx
similarity index 100%
rename from app/kubernetes/views/configurations/create/options.tsx
rename to app/react/kubernetes/configs/CreateView/options.tsx
diff --git a/app/react/portainer/HomeView/LicenseNodePanel.test.tsx b/app/react/portainer/HomeView/LicenseNodePanel.test.tsx
index 04c50c3ac..498b46778 100644
--- a/app/react/portainer/HomeView/LicenseNodePanel.test.tsx
+++ b/app/react/portainer/HomeView/LicenseNodePanel.test.tsx
@@ -1,6 +1,7 @@
import { server, rest } from '@/setup-tests/server';
import { renderWithQueryClient } from '@/react-tools/test-utils';
-import { LicenseType } from '@/portainer/license-management/types';
+
+import { LicenseType } from '../licenses/types';
import { LicenseNodePanel } from './LicenseNodePanel';
diff --git a/app/react/portainer/HomeView/LicenseNodePanel.tsx b/app/react/portainer/HomeView/LicenseNodePanel.tsx
index 05cffc140..e0f130dcc 100644
--- a/app/react/portainer/HomeView/LicenseNodePanel.tsx
+++ b/app/react/portainer/HomeView/LicenseNodePanel.tsx
@@ -1,10 +1,9 @@
-import { LicenseType } from '@/portainer/license-management/types';
-import { useLicenseInfo } from '@/portainer/license-management/use-license.service';
-
import { TextTip } from '@@/Tip/TextTip';
import { InformationPanel } from '@@/InformationPanel';
import { useNodesCount } from '../system/useNodesCount';
+import { useLicenseInfo } from '../licenses/use-license.service';
+import { LicenseType } from '../licenses/types';
export function LicenseNodePanel() {
const nodesValid = useNodesValid();
diff --git a/app/portainer/components/theme/options.tsx b/app/react/portainer/account/AccountView/theme-options.tsx
similarity index 59%
rename from app/portainer/components/theme/options.tsx
rename to app/react/portainer/account/AccountView/theme-options.tsx
index 2c821d381..c76ddaa2f 100644
--- a/app/portainer/components/theme/options.tsx
+++ b/app/react/portainer/account/AccountView/theme-options.tsx
@@ -1,33 +1,32 @@
-import Lightmode from '@/assets/ico/theme/lightmode.svg?c';
-import Darkmode from '@/assets/ico/theme/darkmode.svg?c';
-import Highcontrastmode from '@/assets/ico/theme/highcontrastmode.svg?c';
-import Automode from '@/assets/ico/theme/auto.svg?c';
+import { Eye, Moon, Sun, RefreshCw } from 'lucide-react';
+
+import { BadgeIcon } from '@@/BadgeIcon';
export const options = [
{
id: 'light',
- icon: Lightmode,
+ icon: ,
label: 'Light Theme',
description: 'Default color mode',
value: 'light',
},
{
id: 'dark',
- icon: Darkmode,
+ icon: ,
label: 'Dark Theme',
description: 'Dark color mode',
value: 'dark',
},
{
id: 'highcontrast',
- icon: Highcontrastmode,
+ icon: ,
label: 'High Contrast',
description: 'High contrast color mode',
value: 'highcontrast',
},
{
id: 'auto',
- icon: Automode,
+ icon: ,
label: 'Auto',
description: 'Sync with system theme',
value: 'auto',
diff --git a/app/portainer/views/account/git-credential/gitCredential.service.ts b/app/react/portainer/account/git-credentials/git-credentials.service.ts
similarity index 100%
rename from app/portainer/views/account/git-credential/gitCredential.service.ts
rename to app/react/portainer/account/git-credentials/git-credentials.service.ts
diff --git a/app/portainer/views/account/git-credential/types.ts b/app/react/portainer/account/git-credentials/types.ts
similarity index 100%
rename from app/portainer/views/account/git-credential/types.ts
rename to app/react/portainer/account/git-credentials/types.ts
diff --git a/app/portainer/components/endpointSecurity/tls-options.tsx b/app/react/portainer/environments/ItemView/tls-options.tsx
similarity index 100%
rename from app/portainer/components/endpointSecurity/tls-options.tsx
rename to app/react/portainer/environments/ItemView/tls-options.tsx
diff --git a/app/portainer/views/endpoints/kvm/KVMControl.css b/app/react/portainer/environments/KvmView/KVMControl/KVMControl.css
similarity index 100%
rename from app/portainer/views/endpoints/kvm/KVMControl.css
rename to app/react/portainer/environments/KvmView/KVMControl/KVMControl.css
diff --git a/app/portainer/views/endpoints/kvm/KVMControl.tsx b/app/react/portainer/environments/KvmView/KVMControl/KVMControl.tsx
similarity index 77%
rename from app/portainer/views/endpoints/kvm/KVMControl.tsx
rename to app/react/portainer/environments/KvmView/KVMControl/KVMControl.tsx
index c04e31bf7..7511646d4 100644
--- a/app/portainer/views/endpoints/kvm/KVMControl.tsx
+++ b/app/react/portainer/environments/KvmView/KVMControl/KVMControl.tsx
@@ -1,7 +1,5 @@
import { KVM } from '@open-amt-cloud-toolkit/ui-toolkit-react/reactjs/src/kvm.bundle';
-import { react2angular } from '@/react-tools/react2angular';
-
import './KVMControl.css';
export interface KVMControlProps {
@@ -24,9 +22,3 @@ export function KVMControl({ deviceId, server, token }: KVMControlProps) {
/>
);
}
-
-export const KVMControlAngular = react2angular(KVMControl, [
- 'deviceId',
- 'server',
- 'token',
-]);
diff --git a/app/react/portainer/environments/KvmView/KVMControl/index.ts b/app/react/portainer/environments/KvmView/KVMControl/index.ts
new file mode 100644
index 000000000..54f3fe6d0
--- /dev/null
+++ b/app/react/portainer/environments/KvmView/KVMControl/index.ts
@@ -0,0 +1 @@
+export { KVMControl } from './KVMControl';
diff --git a/app/react/portainer/gitops/AuthFieldset/AuthFieldset.tsx b/app/react/portainer/gitops/AuthFieldset/AuthFieldset.tsx
index 7cdc5050d..a0a923f6c 100644
--- a/app/react/portainer/gitops/AuthFieldset/AuthFieldset.tsx
+++ b/app/react/portainer/gitops/AuthFieldset/AuthFieldset.tsx
@@ -3,7 +3,7 @@ import { boolean, number, object, SchemaOf, string } from 'yup';
import { GitAuthModel } from '@/react/portainer/gitops/types';
import { useDebounce } from '@/react/hooks/useDebounce';
-import { GitCredential } from '@/portainer/views/account/git-credential/types';
+import { GitCredential } from '@/react/portainer/account/git-credentials/types';
import { SwitchField } from '@@/form-components/SwitchField';
import { Input } from '@@/form-components/Input';
diff --git a/app/react/portainer/gitops/AuthFieldset/CredentialSelector.tsx b/app/react/portainer/gitops/AuthFieldset/CredentialSelector.tsx
index 824a04602..76c350298 100644
--- a/app/react/portainer/gitops/AuthFieldset/CredentialSelector.tsx
+++ b/app/react/portainer/gitops/AuthFieldset/CredentialSelector.tsx
@@ -1,5 +1,5 @@
-import { useGitCredentials } from '@/portainer/views/account/git-credential/gitCredential.service';
-import { GitCredential } from '@/portainer/views/account/git-credential/types';
+import { GitCredential } from '@/react/portainer/account/git-credentials/types';
+import { useGitCredentials } from '@/react/portainer/account/git-credentials/git-credentials.service';
import { useUser } from '@/react/hooks/useUser';
import { FormControl } from '@@/form-components/FormControl';
diff --git a/app/react/portainer/gitops/GitForm.stories.tsx b/app/react/portainer/gitops/GitForm.stories.tsx
index fa8e56d67..8e9c29035 100644
--- a/app/react/portainer/gitops/GitForm.stories.tsx
+++ b/app/react/portainer/gitops/GitForm.stories.tsx
@@ -3,7 +3,7 @@ import { Form, Formik } from 'formik';
import { rest } from 'msw';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
-import { GitCredential } from '@/portainer/views/account/git-credential/types';
+import { GitCredential } from '@/react/portainer/account/git-credentials/types';
import { GitForm, buildGitValidationSchema } from './GitForm';
import { GitFormModel } from './types';
diff --git a/app/react/portainer/gitops/GitForm.tsx b/app/react/portainer/gitops/GitForm.tsx
index 4ce42fe4e..ce3da1412 100644
--- a/app/react/portainer/gitops/GitForm.tsx
+++ b/app/react/portainer/gitops/GitForm.tsx
@@ -5,12 +5,13 @@ import { ComposePathField } from '@/react/portainer/gitops/ComposePathField';
import { RefField } from '@/react/portainer/gitops/RefField';
import { GitFormUrlField } from '@/react/portainer/gitops/GitFormUrlField';
import { GitFormModel } from '@/react/portainer/gitops/types';
-import { GitCredential } from '@/portainer/views/account/git-credential/types';
+import { TimeWindowDisplay } from '@/react/portainer/gitops/TimeWindowDisplay';
import { FormSection } from '@@/form-components/FormSection';
-import { TimeWindowDisplay } from '@@/TimeWindowDisplay';
import { validateForm } from '@@/form-components/validate-form';
+import { GitCredential } from '../account/git-credentials/types';
+
import { AdditionalFileField } from './AdditionalFilesField';
import { gitAuthValidation, AuthFieldset } from './AuthFieldset';
import { AutoUpdateFieldset } from './AutoUpdateFieldset';
diff --git a/app/react/components/TimeWindowDisplay.tsx b/app/react/portainer/gitops/TimeWindowDisplay.tsx
similarity index 96%
rename from app/react/components/TimeWindowDisplay.tsx
rename to app/react/portainer/gitops/TimeWindowDisplay.tsx
index 8c49471b8..8318485d0 100644
--- a/app/react/components/TimeWindowDisplay.tsx
+++ b/app/react/portainer/gitops/TimeWindowDisplay.tsx
@@ -5,7 +5,7 @@ import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
import { TextTip } from '@@/Tip/TextTip';
-import { withEdition } from '../portainer/feature-flags/withEdition';
+import { withEdition } from '../feature-flags/withEdition';
const TimeWindowDisplayWrapper = withEdition(TimeWindowDisplay, 'BE');
diff --git a/app/portainer/views/init/admin/restore-options.tsx b/app/react/portainer/init/InitAdminView/restore-options.tsx
similarity index 100%
rename from app/portainer/views/init/admin/restore-options.tsx
rename to app/react/portainer/init/InitAdminView/restore-options.tsx
diff --git a/app/portainer/license-management/license.service.test.ts b/app/react/portainer/licenses/license.service.test.ts
similarity index 100%
rename from app/portainer/license-management/license.service.test.ts
rename to app/react/portainer/licenses/license.service.test.ts
diff --git a/app/react/portainer/licenses/license.service.ts b/app/react/portainer/licenses/license.service.ts
new file mode 100644
index 000000000..7d7dd250b
--- /dev/null
+++ b/app/react/portainer/licenses/license.service.ts
@@ -0,0 +1,128 @@
+import _ from 'lodash';
+import { AxiosError } from 'axios';
+
+import axios from '@/portainer/services/axios';
+
+import { License, LicenseInfo } from './types';
+
+type Listener = (info: LicenseInfo) => void;
+
+interface Store {
+ data?: LicenseInfo;
+ lastLoaded?: number;
+ invalidated: boolean;
+ listeners: Listener[];
+}
+
+const store: Store = {
+ listeners: [],
+ invalidated: true,
+};
+
+export async function getLicenses() {
+ try {
+ const { data } = await axios.get(buildUrl());
+
+ return data;
+ } catch (e) {
+ const axiosError = e as AxiosError;
+ throw new Error(axiosError.response?.data.message);
+ }
+}
+
+interface AttachResponse {
+ licenses: License[];
+ failedKeys: Record;
+}
+
+export async function attachLicense(licenseKeys: string[]) {
+ try {
+ const { data } = await axios.post(buildUrl(), {
+ licenseKeys,
+ });
+
+ if (Object.keys(data.failedKeys).length === licenseKeys.length) {
+ return data;
+ }
+
+ store.invalidated = true;
+ getLicenseInfo();
+ return data;
+ } catch (e) {
+ const axiosError = e as AxiosError;
+ if (axiosError.response?.status === 401) {
+ throw new Error(
+ 'Your session has expired, please refresh the browser and log in again.'
+ );
+ }
+ throw new Error(axiosError.response?.data.message);
+ }
+}
+
+interface RemoveResponse {
+ failedKeys: Record;
+}
+
+export async function removeLicense(licenseKeys: string[]) {
+ try {
+ const { data } = await axios.post(buildUrl('remove'), {
+ licenseKeys,
+ });
+ if (Object.keys(data.failedKeys).length === licenseKeys.length) {
+ return data;
+ }
+
+ store.invalidated = true;
+ getLicenseInfo();
+ return data;
+ } catch (e) {
+ const axiosError = e as AxiosError;
+ throw new Error(axiosError.response?.data.message);
+ }
+}
+
+export function resetState() {
+ store.invalidated = true;
+ store.data = undefined;
+}
+
+export async function getLicenseInfo() {
+ try {
+ if (
+ store.data &&
+ !store.invalidated &&
+ store.lastLoaded &&
+ Math.abs(store.lastLoaded - Date.now()) < 1000 * 30
+ ) {
+ return store.data;
+ }
+
+ const { data: info } = await axios.get(buildUrl('info'));
+ store.data = info;
+ store.lastLoaded = Date.now();
+ store.invalidated = false;
+ store.listeners.forEach((listener) => listener(info));
+
+ return info;
+ } catch (e) {
+ const axiosError = e as AxiosError;
+ throw new Error(axiosError.response?.data.message);
+ }
+}
+
+export function subscribe(listener: Listener) {
+ store.listeners.push(listener);
+}
+
+export function unsubscribe(listener: Listener) {
+ _.remove(store.listeners, listener);
+}
+
+function buildUrl(action = '') {
+ let url = 'licenses';
+
+ if (action) {
+ url += `/${action}`;
+ }
+ return url;
+}
diff --git a/app/portainer/license-management/types.ts b/app/react/portainer/licenses/types.ts
similarity index 100%
rename from app/portainer/license-management/types.ts
rename to app/react/portainer/licenses/types.ts
diff --git a/app/portainer/license-management/use-license.service.ts b/app/react/portainer/licenses/use-license.service.ts
similarity index 100%
rename from app/portainer/license-management/use-license.service.ts
rename to app/react/portainer/licenses/use-license.service.ts
diff --git a/app/portainer/views/registries/create/options.tsx b/app/react/portainer/registries/CreateView/options.tsx
similarity index 100%
rename from app/portainer/views/registries/create/options.tsx
rename to app/react/portainer/registries/CreateView/options.tsx
diff --git a/app/portainer/views/settings/authentication/options.tsx b/app/react/portainer/settings/AuthenticationView/InternalAuth/options.tsx
similarity index 100%
rename from app/portainer/views/settings/authentication/options.tsx
rename to app/react/portainer/settings/AuthenticationView/InternalAuth/options.tsx
diff --git a/app/portainer/settings/authentication/ldap/ldap-settings/ldap-options.tsx b/app/react/portainer/settings/AuthenticationView/ldap-options.tsx
similarity index 100%
rename from app/portainer/settings/authentication/ldap/ldap-settings/ldap-options.tsx
rename to app/react/portainer/settings/AuthenticationView/ldap-options.tsx
diff --git a/app/portainer/oauth/components/oauth-providers-selector/oauth-options.tsx b/app/react/portainer/settings/AuthenticationView/oauth-options.tsx
similarity index 100%
rename from app/portainer/oauth/components/oauth-providers-selector/oauth-options.tsx
rename to app/react/portainer/settings/AuthenticationView/oauth-options.tsx
diff --git a/app/portainer/views/settings/options.tsx b/app/react/portainer/settings/SettingsView/backup-options.tsx
similarity index 100%
rename from app/portainer/views/settings/options.tsx
rename to app/react/portainer/settings/SettingsView/backup-options.tsx
diff --git a/app/setup-tests/server-handlers.ts b/app/setup-tests/server-handlers.ts
index 8b5920ff0..94e1590e9 100644
--- a/app/setup-tests/server-handlers.ts
+++ b/app/setup-tests/server-handlers.ts
@@ -4,7 +4,7 @@ import {
Edition,
LicenseInfo,
LicenseType,
-} from '@/portainer/license-management/types';
+} from '@/react/portainer/licenses/types';
import { EnvironmentGroup } from '@/react/portainer/environments/environment-groups/types';
import { Tag } from '@/portainer/tags/types';
import { StatusResponse } from '@/react/portainer/system/useSystemStatus';