chore(react2angular): remove orphaned bridges [BE-13186] (#3142)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Chaim Lev-Ari
2026-07-15 11:27:46 +03:00
committed by GitHub
parent cde2c55ba8
commit 71a0290bb1
17 changed files with 1 additions and 823 deletions
-17
View File
@@ -6,11 +6,7 @@ import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { withReactQuery } from '@/react-tools/withReactQuery'; import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter'; import { withUIRouter } from '@/react-tools/withUIRouter';
import { DockerfileDetails } from '@/react/docker/images/ItemView/DockerfileDetails'; import { DockerfileDetails } from '@/react/docker/images/ItemView/DockerfileDetails';
import { HealthStatus } from '@/react/docker/containers/ItemView/HealthStatus';
import { GpusList } from '@/react/docker/host/SetupView/GpusList'; import { GpusList } from '@/react/docker/host/SetupView/GpusList';
import { InsightsBox } from '@/react/components/InsightsBox';
import { BetaAlert } from '@/react/portainer/environments/update-schedules/common/BetaAlert';
import { EventsDatatable } from '@/react/docker/events/EventsDatatables';
import { AgentHostBrowser } from '@/react/docker/host/BrowseView/AgentHostBrowser'; import { AgentHostBrowser } from '@/react/docker/host/BrowseView/AgentHostBrowser';
import { AgentVolumeBrowser } from '@/react/docker/volumes/BrowseView/AgentVolumeBrowser'; import { AgentVolumeBrowser } from '@/react/docker/volumes/BrowseView/AgentVolumeBrowser';
import { ProcessesDatatable } from '@/react/docker/containers/StatsView/ProcessesDatatable'; import { ProcessesDatatable } from '@/react/docker/containers/StatsView/ProcessesDatatable';
@@ -35,22 +31,10 @@ const ngModule = angular
templatesModule, templatesModule,
]) ])
.component('dockerfileDetails', r2a(DockerfileDetails, ['image'])) .component('dockerfileDetails', r2a(DockerfileDetails, ['image']))
.component('dockerHealthStatus', r2a(HealthStatus, ['health']))
.component( .component(
'gpusList', 'gpusList',
r2a(withControlledInput(GpusList), ['value', 'onChange']) r2a(withControlledInput(GpusList), ['value', 'onChange'])
) )
.component(
'insightsBox',
r2a(InsightsBox, [
'header',
'content',
'insightCloseId',
'type',
'className',
])
)
.component('betaAlert', r2a(BetaAlert, ['className', 'message', 'isHtml']))
.component( .component(
'agentHostBrowserReact', 'agentHostBrowserReact',
r2a(withUIRouter(withCurrentUser(AgentHostBrowser)), [ r2a(withUIRouter(withCurrentUser(AgentHostBrowser)), [
@@ -84,7 +68,6 @@ const ngModule = angular
'dockerContainerProcessesDatatable', 'dockerContainerProcessesDatatable',
r2a(withUIRouter(withReactQuery(withCurrentUser(ProcessesDatatable))), []) r2a(withUIRouter(withReactQuery(withCurrentUser(ProcessesDatatable))), [])
) )
.component('dockerEventsDatatable', r2a(EventsDatatable, ['dataset']))
.component( .component(
'dockerSecretsDatatable', 'dockerSecretsDatatable',
r2a(withUIRouter(withCurrentUser(SecretsDatatable)), [ r2a(withUIRouter(withCurrentUser(SecretsDatatable)), [
-43
View File
@@ -3,12 +3,8 @@ import angular from 'angular';
import { r2a } from '@/react-tools/react2angular'; import { r2a } from '@/react-tools/react2angular';
import { withReactQuery } from '@/react-tools/withReactQuery'; import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter'; import { withUIRouter } from '@/react-tools/withUIRouter';
import { AssociatedEdgeEnvironmentsSelector } from '@/react/edge/components/AssociatedEdgeEnvironmentsSelector';
import { EdgeAsyncIntervalsForm } from '@/react/edge/components/EdgeAsyncIntervalsForm';
import { EdgeCheckinIntervalField } from '@/react/edge/components/EdgeCheckInIntervalField';
import { EdgeScriptForm } from '@/react/edge/components/EdgeScriptForm'; import { EdgeScriptForm } from '@/react/edge/components/EdgeScriptForm';
import { EdgeGroupsSelector } from '@/react/edge/edge-stacks/components/EdgeGroupsSelector'; import { EdgeGroupsSelector } from '@/react/edge/edge-stacks/components/EdgeGroupsSelector';
import { AssociatedEdgeGroupEnvironmentsSelector } from '@/react/edge/components/AssociatedEdgeGroupEnvironmentsSelector';
const ngModule = angular const ngModule = angular
.module('portainer.edge.react.components', []) .module('portainer.edge.react.components', [])
@@ -32,45 +28,6 @@ const ngModule = angular
'asyncMode', 'asyncMode',
'showMetaFields', 'showMetaFields',
]) ])
)
.component(
'edgeCheckinIntervalField',
r2a(withReactQuery(EdgeCheckinIntervalField), [
'value',
'onChange',
'isDefaultHidden',
'tooltip',
'label',
'readonly',
'size',
])
)
.component(
'edgeAsyncIntervalsForm',
r2a(withReactQuery(EdgeAsyncIntervalsForm), [
'values',
'onChange',
'isDefaultHidden',
'readonly',
'fieldSettings',
])
)
.component(
'associatedEdgeEnvironmentsSelector',
r2a(withReactQuery(AssociatedEdgeEnvironmentsSelector), [
'onChange',
'value',
'error',
])
)
.component(
'associatedEdgeGroupEnvironmentsSelector',
r2a(withReactQuery(AssociatedEdgeGroupEnvironmentsSelector), [
'onChange',
'value',
'error',
'edgeGroupId',
])
); );
export const componentsModule = ngModule.name; export const componentsModule = ngModule.name;
@@ -3,16 +3,11 @@ import angular from 'angular';
import { r2a } from '@/react-tools/react2angular'; import { r2a } from '@/react-tools/react2angular';
import { withUIRouter } from '@/react-tools/withUIRouter'; import { withUIRouter } from '@/react-tools/withUIRouter';
import { withCurrentUser } from '@/react-tools/withCurrentUser'; import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { NodeApplicationsDatatable } from '@/react/kubernetes/cluster/NodeView/NodeApplicationsDatatable/NodeApplicationsDatatable';
import { ResourceEventsDatatable } from '@/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable'; import { ResourceEventsDatatable } from '@/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable';
import { withReactQuery } from '@/react-tools/withReactQuery'; import { withReactQuery } from '@/react-tools/withReactQuery';
export const clusterManagementModule = angular export const clusterManagementModule = angular
.module('portainer.kubernetes.react.components.clusterManagement', []) .module('portainer.kubernetes.react.components.clusterManagement', [])
.component(
'kubernetesNodeApplicationsDatatable',
r2a(withUIRouter(withCurrentUser(NodeApplicationsDatatable)), [])
)
.component( .component(
'resourceEventsDatatable', 'resourceEventsDatatable',
r2a( r2a(
-89
View File
@@ -1,19 +1,11 @@
import angular from 'angular'; import angular from 'angular';
import { r2a } from '@/react-tools/react2angular'; import { r2a } from '@/react-tools/react2angular';
import { IngressClassDatatableAngular } from '@/react/kubernetes/cluster/ingressClass/IngressClassDatatable/IngressClassDatatableAngular';
import { NamespacesSelector } from '@/react/kubernetes/cluster/RegistryAccessView/NamespacesSelector'; import { NamespacesSelector } from '@/react/kubernetes/cluster/RegistryAccessView/NamespacesSelector';
import { NamespaceAccessUsersSelector } from '@/react/kubernetes/namespaces/AccessView/NamespaceAccessUsersSelector';
import { KubeServicesForm } from '@/react/kubernetes/applications/CreateView/application-services/KubeServicesForm'; import { KubeServicesForm } from '@/react/kubernetes/applications/CreateView/application-services/KubeServicesForm';
import { kubeServicesValidation } from '@/react/kubernetes/applications/CreateView/application-services/kubeServicesValidation'; import { kubeServicesValidation } from '@/react/kubernetes/applications/CreateView/application-services/kubeServicesValidation';
import { withReactQuery } from '@/react-tools/withReactQuery'; import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter'; import { withUIRouter } from '@/react-tools/withUIRouter';
import {
ApplicationSummaryWidget,
ApplicationDetailsWidget,
ApplicationEventsDatatable,
} from '@/react/kubernetes/applications/DetailsView';
import { ApplicationContainersDatatable } from '@/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable';
import { import {
PlacementFormSection, PlacementFormSection,
placementValidation, placementValidation,
@@ -22,7 +14,6 @@ import { ApplicationSummarySection } from '@/react/kubernetes/applications/compo
import { withFormValidation } from '@/react-tools/withFormValidation'; import { withFormValidation } from '@/react-tools/withFormValidation';
import { withCurrentUser } from '@/react-tools/withCurrentUser'; import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { YAMLInspector } from '@/react/kubernetes/components/YAMLInspector'; import { YAMLInspector } from '@/react/kubernetes/components/YAMLInspector';
import { NodesDatatable } from '@/react/kubernetes/cluster/HomeView/NodesDatatable';
import { StackName } from '@/react/kubernetes/DeployView/StackName/StackName'; import { StackName } from '@/react/kubernetes/DeployView/StackName/StackName';
import { StackNameLabelInsight } from '@/react/kubernetes/DeployView/StackName/StackNameLabelInsight'; import { StackNameLabelInsight } from '@/react/kubernetes/DeployView/StackName/StackNameLabelInsight';
import { SecretsFormSection } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/SecretsFormSection'; import { SecretsFormSection } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/SecretsFormSection';
@@ -59,33 +50,17 @@ import { EnvironmentVariablesFormSection } from '@/react/kubernetes/applications
import { kubeEnvVarValidationSchema } from '@/react/kubernetes/applications/components/EnvironmentVariablesFormSection/kubeEnvVarValidationSchema'; import { kubeEnvVarValidationSchema } from '@/react/kubernetes/applications/components/EnvironmentVariablesFormSection/kubeEnvVarValidationSchema';
import { IntegratedAppsDatatable } from '@/react/kubernetes/components/IntegratedAppsDatatable/IntegratedAppsDatatable'; import { IntegratedAppsDatatable } from '@/react/kubernetes/components/IntegratedAppsDatatable/IntegratedAppsDatatable';
import { K8sRegistryAccessNotice } from '@/react/kubernetes/components/K8sRegistryAccessNotice'; import { K8sRegistryAccessNotice } from '@/react/kubernetes/components/K8sRegistryAccessNotice';
import { HelmTemplates } from '@/react/kubernetes/helm/HelmTemplates/HelmTemplates';
import { SecretDetailsTable } from '@/react/kubernetes/configs/secrets/ItemView/SecretDetailsTable';
import { KubernetesSummaryView } from '@/react/kubernetes/summary/KubernetesSummaryView'; import { KubernetesSummaryView } from '@/react/kubernetes/summary/KubernetesSummaryView';
import { SecretItemTabsWidget } from '@/react/kubernetes/configs/secrets/ItemView/SecretItemTabsWidget'; import { SecretItemTabsWidget } from '@/react/kubernetes/configs/secrets/ItemView/SecretItemTabsWidget';
import { namespacesModule } from './namespaces';
import { clusterManagementModule } from './clusterManagement'; import { clusterManagementModule } from './clusterManagement';
import { registriesModule } from './registries'; import { registriesModule } from './registries';
export const ngModule = angular export const ngModule = angular
.module('portainer.kubernetes.react.components', [ .module('portainer.kubernetes.react.components', [
namespacesModule,
clusterManagementModule, clusterManagementModule,
registriesModule, registriesModule,
]) ])
.component(
'ingressClassDatatable',
r2a(IngressClassDatatableAngular, [
'onChangeControllers',
'description',
'ingressControllers',
'initialIngressControllers',
'allowNoneIngressClass',
'isLoading',
'view',
])
)
.component( .component(
'namespacesSelector', 'namespacesSelector',
r2a(NamespacesSelector, [ r2a(NamespacesSelector, [
@@ -99,22 +74,6 @@ export const ngModule = angular
'allowSelectAll', 'allowSelectAll',
]) ])
) )
.component(
'namespaceAccessUsersSelector',
r2a(NamespaceAccessUsersSelector, [
'inputId',
'onChange',
'options',
'value',
'dataCy',
'placeholder',
'name',
])
)
.component(
'kubeNodesDatatable',
r2a(withUIRouter(withReactQuery(withCurrentUser(NodesDatatable))), [])
)
.component( .component(
'accessPolicyFormSection', 'accessPolicyFormSection',
r2a(DataAccessPolicyFormSection, [ r2a(DataAccessPolicyFormSection, [
@@ -166,36 +125,6 @@ export const ngModule = angular
'isComposeFormat', 'isComposeFormat',
]) ])
) )
.component(
'applicationSummaryWidget',
r2a(
withUIRouter(withReactQuery(withCurrentUser(ApplicationSummaryWidget))),
[]
)
)
.component(
'applicationContainersDatatable',
r2a(
withUIRouter(
withReactQuery(withCurrentUser(ApplicationContainersDatatable))
),
[]
)
)
.component(
'applicationDetailsWidget',
r2a(
withUIRouter(withReactQuery(withCurrentUser(ApplicationDetailsWidget))),
[]
)
)
.component(
'applicationEventsDatatable',
r2a(
withUIRouter(withReactQuery(withCurrentUser(ApplicationEventsDatatable))),
[]
)
)
.component( .component(
'applicationSummarySection', 'applicationSummarySection',
r2a( r2a(
@@ -214,24 +143,6 @@ export const ngModule = angular
'dataCy', 'dataCy',
]) ])
) )
.component(
'helmTemplatesView',
r2a(withUIRouter(withCurrentUser(HelmTemplates)), [
'onSelectHelmChart',
'namespace',
'name',
])
)
.component(
'secretDetailsTable',
r2a(withUIRouter(withCurrentUser(withReactQuery(SecretDetailsTable))), [
'name',
'namespace',
'secretTypeLabel',
'isSystem',
'registryId',
])
)
.component( .component(
'secretItemTabsWidget', 'secretItemTabsWidget',
r2a(withUIRouter(withReactQuery(withCurrentUser(SecretItemTabsWidget))), [ r2a(withUIRouter(withReactQuery(withCurrentUser(SecretItemTabsWidget))), [
@@ -1,13 +0,0 @@
import angular from 'angular';
import { r2a } from '@/react-tools/react2angular';
import { withUIRouter } from '@/react-tools/withUIRouter';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { NamespacesDatatable } from '@/react/kubernetes/namespaces/ListView/NamespacesDatatable';
export const namespacesModule = angular
.module('portainer.kubernetes.react.components.namespaces', [])
.component(
'kubernetesNamespacesDatatable',
r2a(withUIRouter(withCurrentUser(NamespacesDatatable)), [])
).name;
@@ -1,69 +0,0 @@
<form class="form-horizontal" name="endpointGroupForm" ng-if="$ctrl.model">
<!-- name-input -->
<div class="form-group">
<label for="group_name" class="col-sm-3 col-lg-2 control-label required text-left">Name</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="group_name" ng-model="$ctrl.model.Name" placeholder="e.g. my-group" required auto-focus data-cy="group-name-input" />
<div class="help-block" ng-show="endpointGroupForm.group_name.$invalid">
<div class="small text-warning">
<div ng-messages="endpointGroupForm.group_name.$error">
<p ng-message="required" class="vertical-center">
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
This field is required.
</p>
</div>
</div>
</div>
</div>
</div>
<!-- !name-input -->
<!-- description-input -->
<div class="form-group">
<label for="group_description" class="col-sm-3 col-lg-2 control-label text-left">Description</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="group_description"
ng-model="$ctrl.model.Description"
placeholder="e.g. production environments..."
data-cy="group-description-input"
/>
</div>
</div>
<!-- !description-input -->
<div class="col-sm-12 form-section-title"> Metadata </div>
<tag-selector ng-if="$ctrl.model.TagIds" value="$ctrl.model.TagIds" on-change="($ctrl.onChangeTags)" allow-create="$ctrl.state.allowCreateTag"> </tag-selector>
<div class="form-group" ng-if="$ctrl.model.Id !== 1">
<associated-endpoints-selector value="$ctrl.associatedEndpoints" on-change="($ctrl.onChangeEnvironments)"></associated-endpoints-selector>
</div>
<div class="-mx-[15px]">
<group-association-table
ng-if="$ctrl.model.Id === 1"
title="'Unassociated environments'"
empty-content-message="'No environment available'"
query="$ctrl.unassociatedQuery"
></group-association-table>
</div>
<!-- actions -->
<div class="col-sm-12 form-section-title"> Actions </div>
<div class="form-group">
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm"
ng-click="$ctrl.formAction()"
ng-disabled="$ctrl.actionInProgress || !endpointGroupForm.$valid"
button-spinner="$ctrl.actionInProgress"
>
<span ng-hide="$ctrl.actionInProgress">{{ $ctrl.formActionLabel }}</span>
<span ng-show="$ctrl.actionInProgress">In progress...</span>
</button>
</div>
</div>
<!-- !actions -->
</form>
@@ -1,31 +0,0 @@
import angular from 'angular';
class GroupFormController {
/* @ngInject */
constructor($async, $scope, GroupService, Notifications, Authentication) {
this.$async = $async;
this.$scope = $scope;
this.GroupService = GroupService;
this.Notifications = Notifications;
this.Authentication = Authentication;
this.state = {
allowCreateTag: this.Authentication.isAdmin(),
};
this.unassociatedQuery = {
groupIds: [1],
};
this.onChangeTags = this.onChangeTags.bind(this);
}
onChangeTags(value) {
return this.$scope.$evalAsync(() => {
this.model.TagIds = value;
});
}
}
angular.module('portainer.app').controller('GroupFormController', GroupFormController);
export default GroupFormController;
@@ -1,7 +1,6 @@
import angular from 'angular'; import angular from 'angular';
import { r2a } from '@/react-tools/react2angular'; import { r2a } from '@/react-tools/react2angular';
import { CustomTemplatesVariablesDefinitionField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField';
import { CustomTemplatesVariablesField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField'; import { CustomTemplatesVariablesField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField';
import { withControlledInput } from '@/react-tools/withControlledInput'; import { withControlledInput } from '@/react-tools/withControlledInput';
@@ -18,15 +17,6 @@ export const ngModule = angular
'errors', 'errors',
]) ])
) )
.component('customTemplatesVariablesField', VariablesFieldAngular) .component('customTemplatesVariablesField', VariablesFieldAngular);
.component(
'customTemplatesVariablesDefinitionField',
r2a(withControlledInput(CustomTemplatesVariablesDefinitionField), [
'onChange',
'value',
'errors',
'isVariablesNamesFromParent',
])
);
export const customTemplatesModule = ngModule.name; export const customTemplatesModule = ngModule.name;
-109
View File
@@ -11,32 +11,21 @@ import { withControlledInput } from '@/react-tools/withControlledInput';
import { import {
EnvironmentVariablesFieldset, EnvironmentVariablesFieldset,
EnvironmentVariablesPanel, EnvironmentVariablesPanel,
StackEnvironmentVariablesPanel,
envVarValidation, envVarValidation,
} from '@@/form-components/EnvironmentVariablesFieldset'; } from '@@/form-components/EnvironmentVariablesFieldset';
import { Icon } from '@@/Icon'; import { Icon } from '@@/Icon';
import { ReactQueryDevtoolsWrapper } from '@@/ReactQueryDevtoolsWrapper'; import { ReactQueryDevtoolsWrapper } from '@@/ReactQueryDevtoolsWrapper';
import { PageHeader } from '@@/PageHeader'; import { PageHeader } from '@@/PageHeader';
import { TagSelector } from '@@/TagSelector';
import { Loading } from '@@/Widget/Loading'; import { Loading } from '@@/Widget/Loading';
import { PasswordCheckHint } from '@@/PasswordCheckHint'; import { PasswordCheckHint } from '@@/PasswordCheckHint';
import { Tooltip } from '@@/Tip/Tooltip'; import { Tooltip } from '@@/Tip/Tooltip';
import { TableColumnHeaderAngular } from '@@/datatables/TableHeaderCell'; import { TableColumnHeaderAngular } from '@@/datatables/TableHeaderCell';
import { DashboardItem } from '@@/DashboardItem';
import { SearchBar } from '@@/datatables/SearchBar';
import { FallbackImage } from '@@/FallbackImage';
import { BadgeIcon } from '@@/BadgeIcon';
import { TeamsSelector } from '@@/TeamsSelector';
import { TerminalTooltip } from '@@/TerminalTooltip'; import { TerminalTooltip } from '@@/TerminalTooltip';
import { Terminal } from '@@/Terminal/Terminal'; import { Terminal } from '@@/Terminal/Terminal';
import { PortainerSelect } from '@@/form-components/PortainerSelect'; import { PortainerSelect } from '@@/form-components/PortainerSelect';
import { Slider } from '@@/form-components/Slider';
import { TagButton } from '@@/TagButton';
import { BETeaserButton } from '@@/BETeaserButton'; import { BETeaserButton } from '@@/BETeaserButton';
import { CodeEditor } from '@@/CodeEditor'; import { CodeEditor } from '@@/CodeEditor';
import { HelpLink } from '@@/HelpLink';
import { TextTip } from '@@/Tip/TextTip'; import { TextTip } from '@@/Tip/TextTip';
import { InlineLoader } from '@@/InlineLoader/InlineLoader';
import { fileUploadField } from './file-upload-field'; import { fileUploadField } from './file-upload-field';
import { switchField } from './switch-field'; import { switchField } from './switch-field';
@@ -68,15 +57,6 @@ export const ngModule = angular
rbacModule, rbacModule,
stacksModule, stacksModule,
]) ])
.component(
'tagSelector',
r2a(withUIRouter(withReactQuery(TagSelector)), [
'allowCreate',
'onChange',
'value',
'errors',
])
)
.component( .component(
'beTeaserButton', 'beTeaserButton',
r2a(BETeaserButton, [ r2a(BETeaserButton, [
@@ -89,11 +69,6 @@ export const ngModule = angular
'data-cy', 'data-cy',
]) ])
) )
.component(
'tagButton',
r2a(TagButton, ['value', 'label', 'title', 'onRemove'])
)
.component( .component(
'portainerTooltip', 'portainerTooltip',
r2a(Tooltip, ['message', 'position', 'className', 'setHtmlMessage', 'size']) r2a(Tooltip, ['message', 'position', 'className', 'setHtmlMessage', 'size'])
@@ -130,64 +105,11 @@ export const ngModule = angular
'showTitle', 'showTitle',
]) ])
) )
.component(
'fallbackImage',
r2a(FallbackImage, ['src', 'fallbackIcon', 'alt', 'className'])
)
.component('prIcon', r2a(Icon, ['className', 'icon', 'mode', 'size', 'spin'])) .component('prIcon', r2a(Icon, ['className', 'icon', 'mode', 'size', 'spin']))
.component( .component(
'reactQueryDevTools', 'reactQueryDevTools',
r2a(withReactQuery(ReactQueryDevtoolsWrapper), []) r2a(withReactQuery(ReactQueryDevtoolsWrapper), [])
) )
.component(
'helpLink',
r2a(withUIRouter(withReactQuery(HelpLink)), [
'docLink',
'target',
'children',
])
)
.component(
'dashboardItem',
r2a(DashboardItem, [
'icon',
'type',
'value',
'to',
'params',
'children',
'pluralType',
'isLoading',
'isRefetching',
'data-cy',
'iconClass',
])
)
.component(
'datatableSearchbar',
r2a(SearchBar, [
'data-cy',
'onChange',
'value',
'placeholder',
'children',
'className',
])
)
.component('badgeIcon', r2a(BadgeIcon, ['icon', 'size', 'iconClass']))
.component(
'teamsSelector',
r2a(TeamsSelector, [
'onChange',
'value',
'dataCy',
'inputId',
'name',
'placeholder',
'teams',
'disabled',
])
)
.component( .component(
'porSelect', 'porSelect',
r2a(PortainerSelect, [ r2a(PortainerSelect, [
@@ -213,19 +135,6 @@ export const ngModule = angular
'onBlur', 'onBlur',
]) ])
) )
.component(
'porSlider',
r2a(Slider, [
'min',
'max',
'step',
'value',
'onChange',
'visibleTooltip',
'dataCy',
'disabled',
])
)
.component( .component(
'reactCodeEditor', 'reactCodeEditor',
r2a(CodeEditor, [ r2a(CodeEditor, [
@@ -257,10 +166,6 @@ export const ngModule = angular
'childrenWrapperClassName', 'childrenWrapperClassName',
]) ])
) )
.component(
'inlineLoader',
r2a(InlineLoader, ['children', 'className', 'size'])
)
.component('annotationsBeTeaser', r2a(AnnotationsBeTeaser, [])) .component('annotationsBeTeaser', r2a(AnnotationsBeTeaser, []))
.component( .component(
'shellTerminal', 'shellTerminal',
@@ -290,17 +195,3 @@ withFormValidation(
['explanation', 'showHelpMessage', 'isFoldable'], ['explanation', 'showHelpMessage', 'isFoldable'],
envVarValidation envVarValidation
); );
withFormValidation(
ngModule,
withUIRouter(
withReactQuery(
withControlledInput(StackEnvironmentVariablesPanel, {
values: 'onChange',
})
)
),
'stackEnvironmentVariablesPanel',
['showHelpMessage', 'isFoldable'],
envVarValidation
);
@@ -2,27 +2,11 @@ import angular from 'angular';
import { r2a } from '@/react-tools/react2angular'; import { r2a } from '@/react-tools/react2angular';
import { withReactQuery } from '@/react-tools/withReactQuery'; import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter';
import { RepositoriesDatatable } from '@/react/portainer/registries/repositories/ListView/RepositoriesDatatable';
import { TagsDatatable } from '@/react/portainer/registries/repositories/ItemView/TagsDatatable/TagsDatatable';
import { GitlabProjectTable } from '@/react/portainer/registries/CreateView/GitlabProjectsTable/GitlabProjectsTable'; import { GitlabProjectTable } from '@/react/portainer/registries/CreateView/GitlabProjectsTable/GitlabProjectsTable';
import { RegistryFormDockerhub } from '@/react/portainer/registries/CreateView/RegistryFormDockerhub/RegistryFormDockerhub'; import { RegistryFormDockerhub } from '@/react/portainer/registries/CreateView/RegistryFormDockerhub/RegistryFormDockerhub';
export const registriesModule = angular export const registriesModule = angular
.module('portainer.app.react.components.registries', []) .module('portainer.app.react.components.registries', [])
.component(
'registryRepositoriesDatatable',
r2a(withUIRouter(withReactQuery(RepositoriesDatatable)), ['dataset'])
)
.component(
'registriesRepositoryTagsDatatable',
r2a(withUIRouter(withReactQuery(TagsDatatable)), [
'dataset',
'advancedFeaturesAvailable',
'onRemove',
'onRetag',
])
)
.component( .component(
'gitlabProjectSelector', 'gitlabProjectSelector',
r2a(GitlabProjectTable, ['dataset', 'onChange', 'value']) r2a(GitlabProjectTable, ['dataset', 'onChange', 'value'])
@@ -4,14 +4,8 @@ import { InternalAuth } from '@/react/portainer/settings/AuthenticationView/Inte
import { AuthenticationMethodSelector } from '@/react/portainer/settings/AuthenticationView/AuthenticationMethodSelector'; import { AuthenticationMethodSelector } from '@/react/portainer/settings/AuthenticationView/AuthenticationMethodSelector';
import { r2a } from '@/react-tools/react2angular'; import { r2a } from '@/react-tools/react2angular';
import { withReactQuery } from '@/react-tools/withReactQuery'; import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter';
import { LDAPUsersTable } from '@/react/portainer/settings/AuthenticationView/LDAPAuth/LDAPUsersTable'; import { LDAPUsersTable } from '@/react/portainer/settings/AuthenticationView/LDAPAuth/LDAPUsersTable';
import { LDAPGroupsTable } from '@/react/portainer/settings/AuthenticationView/LDAPAuth/LDAPGroupsTable'; import { LDAPGroupsTable } from '@/react/portainer/settings/AuthenticationView/LDAPAuth/LDAPGroupsTable';
import { ApplicationSettingsPanel } from '@/react/portainer/settings/SettingsView/ApplicationSettingsPanel';
import { KubeSettingsPanel } from '@/react/portainer/settings/SettingsView/KubeSettingsPanel';
import { HelmCertPanel } from '@/react/portainer/settings/SettingsView/HelmCertPanel';
import { HiddenContainersPanel } from '@/react/portainer/settings/SettingsView/HiddenContainersPanel/HiddenContainersPanel';
import { SSLSettingsPanelWrapper } from '@/react/portainer/settings/SettingsView/SSLSettingsPanel/SSLSettingsPanel';
import { AuthStyleField } from '@/react/portainer/settings/AuthenticationView/OAuth'; import { AuthStyleField } from '@/react/portainer/settings/AuthenticationView/OAuth';
import { AutoUserProvisionToggle } from '@/react/portainer/settings/AuthenticationView/AutoUserProvisionToggle/AutoUserProvisionToggle'; import { AutoUserProvisionToggle } from '@/react/portainer/settings/AuthenticationView/AutoUserProvisionToggle/AutoUserProvisionToggle';
import { LdapSettingsTestLogin } from '@/react/portainer/settings/AuthenticationView/LDAPAuth/LdapSettingsTestLogin/LdapSettingsTestLogin'; import { LdapSettingsTestLogin } from '@/react/portainer/settings/AuthenticationView/LDAPAuth/LdapSettingsTestLogin/LdapSettingsTestLogin';
@@ -53,23 +47,6 @@ export const settingsModule = angular
'limitedFeatureId', 'limitedFeatureId',
]) ])
) )
.component(
'applicationSettingsPanel',
r2a(withReactQuery(ApplicationSettingsPanel), ['onSuccess', 'settings'])
)
.component(
'sslSettingsPanel',
r2a(withReactQuery(SSLSettingsPanelWrapper), [])
)
.component('helmCertPanel', r2a(withReactQuery(HelmCertPanel), []))
.component(
'hiddenContainersPanel',
r2a(withUIRouter(withReactQuery(HiddenContainersPanel)), [])
)
.component(
'kubeSettingsPanel',
r2a(withUIRouter(withReactQuery(KubeSettingsPanel)), ['settings'])
)
.component( .component(
'oauthAuthStyle', 'oauthAuthStyle',
r2a(AuthStyleField, [ r2a(AuthStyleField, [
-7
View File
@@ -4,18 +4,11 @@ import { r2a } from '@/react-tools/react2angular';
import { withUIRouter } from '@/react-tools/withUIRouter'; import { withUIRouter } from '@/react-tools/withUIRouter';
import { withReactQuery } from '@/react-tools/withReactQuery'; import { withReactQuery } from '@/react-tools/withReactQuery';
import { withCurrentUser } from '@/react-tools/withCurrentUser'; import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { EffectiveAccessViewerDatatable } from '@/react/portainer/users/RolesView/AccessViewer/EffectiveAccessViewerDatatable';
import { EffectiveAccessViewer } from '@/react/portainer/users/RolesView/AccessViewer/EffectiveAccessViewer'; import { EffectiveAccessViewer } from '@/react/portainer/users/RolesView/AccessViewer/EffectiveAccessViewer';
import { RbacRolesDatatable } from '@/react/portainer/users/RolesView/RbacRolesDatatable'; import { RbacRolesDatatable } from '@/react/portainer/users/RolesView/RbacRolesDatatable';
export const usersModule = angular export const usersModule = angular
.module('portainer.app.react.components.users', []) .module('portainer.app.react.components.users', [])
.component(
'effectiveAccessViewerDatatable',
r2a(withUIRouter(withCurrentUser(EffectiveAccessViewerDatatable)), [
'dataset',
])
)
.component( .component(
'effectiveAccessViewer', 'effectiveAccessViewer',
r2a(withUIRouter(withReactQuery(withCurrentUser(EffectiveAccessViewer))), [ r2a(withUIRouter(withReactQuery(withCurrentUser(EffectiveAccessViewer))), [
-6
View File
@@ -7,7 +7,6 @@ import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter'; import { withUIRouter } from '@/react-tools/withUIRouter';
import { CreateUserAccessToken } from '@/react/portainer/account/CreateAccessTokenView'; import { CreateUserAccessToken } from '@/react/portainer/account/CreateAccessTokenView';
import { EdgeComputeSettingsView } from '@/react/portainer/settings/EdgeComputeView/EdgeComputeSettingsView'; import { EdgeComputeSettingsView } from '@/react/portainer/settings/EdgeComputeView/EdgeComputeSettingsView';
import { BackupSettingsPanel } from '@/react/portainer/settings/SettingsView/BackupSettingsView/BackupSettingsPanel';
import { SettingsView } from '@/react/portainer/settings/SettingsView/SettingsView'; import { SettingsView } from '@/react/portainer/settings/SettingsView/SettingsView';
import { CreateHelmRepositoriesView } from '@/react/portainer/account/helm-repositories/CreateHelmRepositoryView'; import { CreateHelmRepositoriesView } from '@/react/portainer/account/helm-repositories/CreateHelmRepositoryView';
@@ -53,11 +52,6 @@ export const viewsModule = angular
['onSubmit', 'settings'] ['onSubmit', 'settings']
) )
) )
.component(
'backupSettingsPanel',
r2a(withUIRouter(withReactQuery(withCurrentUser(BackupSettingsPanel))), [])
)
.component( .component(
'settingsView', 'settingsView',
r2a(withUIRouter(withReactQuery(withCurrentUser(SettingsView))), []) r2a(withUIRouter(withReactQuery(withCurrentUser(SettingsView))), [])
@@ -1,8 +1,5 @@
import { array, object, string } from 'yup'; import { array, object, string } from 'yup';
import { r2a } from '@/react-tools/react2angular';
import { withControlledInput } from '@/react-tools/withControlledInput';
import { InputList } from '@@/form-components/InputList'; import { InputList } from '@@/form-components/InputList';
import { ItemProps } from '@@/form-components/InputList/InputList'; import { ItemProps } from '@@/form-components/InputList/InputList';
import { InputGroup } from '@@/form-components/InputGroup'; import { InputGroup } from '@@/form-components/InputGroup';
@@ -69,8 +66,3 @@ export function gpusListValidation() {
}); });
return array().of(gpuShape).default([]); return array().of(gpuShape).default([]);
} }
export const GpusListAngular = r2a(withControlledInput(GpusList), [
'value',
'onChange',
]);
@@ -1,106 +0,0 @@
import { useCurrentStateAndParams } from '@uirouter/react';
import { useMemo } from 'react';
import { compact } from 'lodash';
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { useTableState } from '@@/datatables/useTableState';
import { EventsDatatable } from '../../components/EventsDatatable';
import { useEvents } from '../../queries/useEvents';
import { AppKind } from '../types';
import { useApplication } from '../queries/useApplication';
import { useApplicationServices } from '../queries/useApplicationServices';
import { useApplicationPods } from '../queries/useApplicationPods';
const storageKey = 'k8sAppEventsDatatable';
const settingsStore = createStore(storageKey, { id: 'Date', desc: true });
export function ApplicationEventsDatatable() {
const tableState = useTableState(settingsStore, storageKey);
const {
params: {
namespace,
name,
'resource-type': appKind,
endpointId: environmentId,
},
} = useCurrentStateAndParams();
const { relatedEvents, isInitialLoading } = useApplicationEvents(
environmentId,
namespace,
name,
appKind,
{
autoRefreshRate: tableState.autoRefreshRate,
}
);
return (
<EventsDatatable
dataset={relatedEvents}
tableState={tableState}
isLoading={isInitialLoading}
data-cy="k8sAppDetail-eventsTable"
noWidget
/>
);
}
export function useApplicationEvents(
environmentId: EnvironmentId,
namespace: string,
name: string,
appKind?: AppKind,
options?: { autoRefreshRate?: number; yaml?: boolean }
) {
const { data: application, ...applicationQuery } = useApplication(
environmentId,
namespace,
name,
appKind
);
const servicesQuery = useApplicationServices(
environmentId,
namespace,
name,
application
);
const podsQuery = useApplicationPods(
environmentId,
namespace,
name,
application
);
// related events are events that have the application id, or the id of a service or pod from the application
const relatedUids = useMemo(() => {
const serviceIds = compact(
servicesQuery.data?.map((service) => service?.metadata?.uid)
);
const podIds = compact(podsQuery.data?.map((pod) => pod?.metadata?.uid));
return [application?.metadata?.uid, ...serviceIds, ...podIds];
}, [application?.metadata?.uid, podsQuery.data, servicesQuery.data]);
const relatedUidsSet = useMemo(() => new Set(relatedUids), [relatedUids]);
const { data: events, ...eventsQuery } = useEvents(environmentId, {
namespace,
queryOptions: {
autoRefreshRate: options?.autoRefreshRate
? options.autoRefreshRate * 1000
: undefined,
select: (data) =>
data.filter((event) => relatedUidsSet.has(event.involvedObject.uid)),
},
});
const isInitialLoading =
applicationQuery.isInitialLoading ||
servicesQuery.isInitialLoading ||
podsQuery.isInitialLoading ||
eventsQuery.isInitialLoading;
return { relatedEvents: events || [], isInitialLoading };
}
@@ -1,3 +1,2 @@
export { ApplicationSummaryWidget } from './ApplicationSummaryWidget'; export { ApplicationSummaryWidget } from './ApplicationSummaryWidget';
export { ApplicationDetailsWidget } from './ApplicationDetailsWidget/ApplicationDetailsWidget'; export { ApplicationDetailsWidget } from './ApplicationDetailsWidget/ApplicationDetailsWidget';
export { ApplicationEventsDatatable } from './ApplicationEventsDatatable';
@@ -1,269 +0,0 @@
import { useEffect, useState } from 'react';
import Route from '@/assets/ico/route.svg?c';
import { confirm } from '@@/modals/confirm';
import { ModalType } from '@@/modals';
import { Datatable } from '@@/datatables';
import { Button, ButtonGroup } from '@@/buttons';
import { createPersistedStore } from '@@/datatables/types';
import { buildConfirmButton } from '@@/modals/utils';
import { useTableState } from '@@/datatables/useTableState';
import { TextTip } from '@@/Tip/TextTip';
import { IngressControllerClassMap } from '../types';
import { columns } from './columns';
const storageKey = 'ingressClasses';
const settingsStore = createPersistedStore(storageKey, 'name');
interface Props {
onChangeControllers: (
controllerClassMap: IngressControllerClassMap[]
) => void; // angular function to save the ingress class list
description: string;
ingressControllers: IngressControllerClassMap[] | undefined;
initialIngressControllers: IngressControllerClassMap[] | undefined;
allowNoneIngressClass: boolean;
isLoading: boolean;
view: string;
}
// This is a legacy component that has more state logic than the new one, for angular views
// Delete this component when the namespace edit view is migrated to react
export function IngressClassDatatableAngular({
onChangeControllers,
description,
initialIngressControllers,
ingressControllers,
allowNoneIngressClass,
isLoading,
view,
}: Props) {
const tableState = useTableState(settingsStore, storageKey);
const [ingControllerFormValues, setIngControllerFormValues] = useState(
ingressControllers || []
);
// set the ingress controller form values when the ingress controller list changes
// and the ingress controller form values are not set
useEffect(() => {
if (
ingressControllers &&
ingControllerFormValues.length !== ingressControllers.length
) {
setIngControllerFormValues(ingressControllers);
}
}, [ingressControllers, ingControllerFormValues]);
useEffect(() => {
if (allowNoneIngressClass === undefined || isLoading) {
return;
}
let newIngFormValues: IngressControllerClassMap[];
const isCustomTypeExist = ingControllerFormValues.some(
(ic) => ic.Type === 'custom'
);
if (allowNoneIngressClass) {
newIngFormValues = [...ingControllerFormValues];
// add the ingress controller type 'custom' with a 'none' ingress class name
if (!isCustomTypeExist) {
newIngFormValues.push({
Name: 'none',
ClassName: 'none',
Type: 'custom',
Availability: true,
New: false,
Used: false,
});
}
} else {
newIngFormValues = ingControllerFormValues.filter(
(ingController) => ingController.ClassName !== 'none'
);
}
setIngControllerFormValues(newIngFormValues);
onChangeControllers(newIngFormValues);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allowNoneIngressClass, onChangeControllers]);
return (
<div className="-mx-[15px]">
<Datatable
settingsManager={tableState}
dataset={ingControllerFormValues || []}
columns={columns}
isLoading={isLoading}
title="Ingress Controllers"
titleIcon={Route}
getRowId={(row) => `${row.Name}-${row.ClassName}-${row.Type}`}
renderTableActions={(selectedRows) => renderTableActions(selectedRows)}
description={renderIngressClassDescription()}
data-cy="k8s-ingress-classes-datatable"
/>
</div>
);
function renderTableActions(selectedRows: IngressControllerClassMap[]) {
return (
<div className="flex items-start">
<ButtonGroup>
<Button
disabled={
selectedRows.filter((row) => row.Availability === true).length ===
0
}
color="dangerlight"
size="small"
onClick={() =>
updateIngressControllers(
selectedRows,
ingControllerFormValues || [],
false
)
}
data-cy="k8s-disallow-selected-ingress-controllers-button"
>
Disallow selected
</Button>
<Button
disabled={
selectedRows.filter((row) => row.Availability === false)
.length === 0
}
color="default"
size="small"
onClick={() =>
updateIngressControllers(
selectedRows,
ingControllerFormValues || [],
true
)
}
data-cy="k8s-allow-selected-ingress-controllers-button"
>
Allow selected
</Button>
</ButtonGroup>
</div>
);
}
function renderIngressClassDescription() {
return (
<div className="text-muted flex w-full flex-col !text-xs">
<div className="mt-1">{description}</div>
{initialIngressControllers &&
ingControllerFormValues &&
isUnsavedChanges(
initialIngressControllers,
ingControllerFormValues
) && <TextTip>Unsaved changes.</TextTip>}
</div>
);
}
async function updateIngressControllers(
selectedRows: IngressControllerClassMap[],
ingControllerFormValues: IngressControllerClassMap[],
availability: boolean
) {
const updatedIngressControllers = getUpdatedIngressControllers(
selectedRows,
ingControllerFormValues || [],
availability
);
if (ingressControllers && ingressControllers.length) {
const newAllowed = updatedIngressControllers.map(
(ingController) => ingController.Availability
);
if (view === 'namespace') {
setIngControllerFormValues(updatedIngressControllers);
onChangeControllers(updatedIngressControllers);
return;
}
const usedControllersToDisallow = ingressControllers.filter(
(ingController, index) => {
// if any of the current controllers are allowed, and are used, then become disallowed, then add the controller to a new list
if (
ingController.Availability &&
ingController.Used &&
!newAllowed[index]
) {
return true;
}
return false;
}
);
if (usedControllersToDisallow.length > 0) {
const confirmed = await confirm({
title: 'Disallow in-use ingress controllers?',
modalType: ModalType.Warn,
message: (
<div>
<p>
There are ingress controllers you want to disallow that are in
use:
</p>
<ul className="ml-6">
{usedControllersToDisallow.map((controller) => (
<li key={controller.ClassName}>{controller.ClassName}</li>
))}
</ul>
<p>
No new ingress rules can be created for the disallowed
controllers.
</p>
</div>
),
confirmButton: buildConfirmButton('Disallow', 'warning'),
});
if (!confirmed) {
return;
}
}
setIngControllerFormValues(updatedIngressControllers);
onChangeControllers(updatedIngressControllers);
}
}
}
function isUnsavedChanges(
oldIngressControllers: IngressControllerClassMap[],
newIngressControllers: IngressControllerClassMap[]
) {
if (oldIngressControllers.length !== newIngressControllers.length) {
return true;
}
for (let i = 0; i < newIngressControllers.length; i += 1) {
if (
oldIngressControllers[i]?.Availability !==
newIngressControllers[i]?.Availability
) {
return true;
}
}
return false;
}
function getUpdatedIngressControllers(
selectedRows: IngressControllerClassMap[],
allRows: IngressControllerClassMap[],
allow: boolean
) {
const selectedRowClassNames = selectedRows.map((row) => row.ClassName);
const updatedIngressControllers = allRows?.map((row) => {
if (selectedRowClassNames.includes(row.ClassName)) {
return { ...row, Availability: allow };
}
return row;
});
return updatedIngressControllers;
}