mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
refactor(apps): migrate applications view to react [r8s-124] (#28)
This commit is contained in:
parent
cc75167437
commit
959c527be7
42 changed files with 1378 additions and 1293 deletions
|
@ -207,7 +207,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
|
|||
|
||||
const applications = {
|
||||
name: 'kubernetes.applications',
|
||||
url: '/applications',
|
||||
url: '/applications?tab',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'kubernetesApplicationsView',
|
||||
|
@ -233,7 +233,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
|
|||
|
||||
const application = {
|
||||
name: 'kubernetes.applications.application',
|
||||
url: '/:namespace/:name?resource-type&tab',
|
||||
url: '/:namespace/:name?resource-type',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'applicationDetailsView',
|
||||
|
|
|
@ -1,21 +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 { ApplicationsDatatable } from '@/react/kubernetes/applications/ListView/ApplicationsDatatable/ApplicationsDatatable';
|
||||
|
||||
export const applicationsModule = angular
|
||||
.module('portainer.kubernetes.react.components.applications', [])
|
||||
|
||||
.component(
|
||||
'kubernetesApplicationsDatatable',
|
||||
r2a(withUIRouter(withCurrentUser(ApplicationsDatatable)), [
|
||||
'namespace',
|
||||
'namespaces',
|
||||
'onNamespaceChange',
|
||||
'onRefresh',
|
||||
'onRemove',
|
||||
'hideStacks',
|
||||
])
|
||||
).name;
|
|
@ -23,7 +23,6 @@ import { ApplicationSummarySection } from '@/react/kubernetes/applications/compo
|
|||
import { withFormValidation } from '@/react-tools/withFormValidation';
|
||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
||||
import { YAMLInspector } from '@/react/kubernetes/components/YAMLInspector';
|
||||
import { ApplicationsStacksDatatable } from '@/react/kubernetes/applications/ListView/ApplicationsStacksDatatable';
|
||||
import { NodesDatatable } from '@/react/kubernetes/cluster/HomeView/NodesDatatable';
|
||||
import { StackName } from '@/react/kubernetes/DeployView/StackName/StackName';
|
||||
import { StackNameLabelInsight } from '@/react/kubernetes/DeployView/StackName/StackNameLabelInsight';
|
||||
|
@ -61,14 +60,12 @@ import { EnvironmentVariablesFormSection } from '@/react/kubernetes/applications
|
|||
import { kubeEnvVarValidationSchema } from '@/react/kubernetes/applications/components/EnvironmentVariablesFormSection/kubeEnvVarValidationSchema';
|
||||
import { IntegratedAppsDatatable } from '@/react/kubernetes/components/IntegratedAppsDatatable/IntegratedAppsDatatable';
|
||||
|
||||
import { applicationsModule } from './applications';
|
||||
import { namespacesModule } from './namespaces';
|
||||
import { clusterManagementModule } from './clusterManagement';
|
||||
import { registriesModule } from './registries';
|
||||
|
||||
export const ngModule = angular
|
||||
.module('portainer.kubernetes.react.components', [
|
||||
applicationsModule,
|
||||
namespacesModule,
|
||||
clusterManagementModule,
|
||||
registriesModule,
|
||||
|
@ -208,15 +205,6 @@ export const ngModule = angular
|
|||
['formValues', 'oldFormValues']
|
||||
)
|
||||
)
|
||||
.component(
|
||||
'kubernetesApplicationsStacksDatatable',
|
||||
r2a(withUIRouter(withCurrentUser(ApplicationsStacksDatatable)), [
|
||||
'onRemove',
|
||||
'namespace',
|
||||
'namespaces',
|
||||
'onNamespaceChange',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'kubernetesIntegratedApplicationsDatatable',
|
||||
r2a(withUIRouter(withCurrentUser(IntegratedAppsDatatable)), [
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ServicesView } from '@/react/kubernetes/services/ServicesView';
|
|||
import { ConsoleView } from '@/react/kubernetes/applications/ConsoleView';
|
||||
import { ConfigmapsAndSecretsView } from '@/react/kubernetes/configs/ListView/ConfigmapsAndSecretsView';
|
||||
import { CreateNamespaceView } from '@/react/kubernetes/namespaces/CreateView/CreateNamespaceView';
|
||||
import { ApplicationsView } from '@/react/kubernetes/applications/ListView/ApplicationsView';
|
||||
import { ApplicationDetailsView } from '@/react/kubernetes/applications/DetailsView/ApplicationDetailsView';
|
||||
import { ConfigureView } from '@/react/kubernetes/cluster/ConfigureView';
|
||||
import { NamespacesView } from '@/react/kubernetes/namespaces/ListView/NamespacesView';
|
||||
|
@ -55,6 +56,10 @@ export const viewsModule = angular
|
|||
[]
|
||||
)
|
||||
)
|
||||
.component(
|
||||
'kubernetesApplicationsView',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(ApplicationsView))), [])
|
||||
)
|
||||
.component(
|
||||
'applicationDetailsView',
|
||||
r2a(
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
<page-header ng-if="ctrl.state.viewReady" title="'Application list'" breadcrumbs="['Applications']" reload="true"></page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row">
|
||||
<div class="col-sm-12" data-cy="k8sApp-appList">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<uib-tabset active="ctrl.state.activeTab" justified="true" type="pills" ng-if="!ctrl.deploymentOptions.hideStacksFunctionality">
|
||||
<uib-tab index="0" classes="btn-sm" select="ctrl.selectTab(0)">
|
||||
<uib-tab-heading class="vertical-center"> <pr-icon icon="'box'"></pr-icon> Applications </uib-tab-heading>
|
||||
|
||||
<kubernetes-applications-datatable
|
||||
dataset="ctrl.state.applications"
|
||||
on-refresh="(ctrl.getApplications)"
|
||||
namespaces="ctrl.state.namespaces"
|
||||
namespace="ctrl.state.namespaceName"
|
||||
on-namespace-change="(ctrl.onChangeNamespaceDropdown)"
|
||||
is-loading="ctrl.state.isAppsLoading"
|
||||
on-remove="(ctrl.removeAction)"
|
||||
hide-stacks="ctrl.deploymentOptions.hideStacksFunctionality"
|
||||
>
|
||||
</kubernetes-applications-datatable>
|
||||
</uib-tab>
|
||||
<uib-tab index="2" classes="btn-sm" select="ctrl.selectTab(2)">
|
||||
<uib-tab-heading class="vertical-center"> <pr-icon icon="'list'"></pr-icon> Stacks </uib-tab-heading>
|
||||
|
||||
<kubernetes-applications-stacks-datatable
|
||||
dataset="ctrl.state.stacks"
|
||||
on-refresh="(ctrl.getApplications)"
|
||||
on-remove="(ctrl.removeStacksAction)"
|
||||
namespaces="ctrl.state.namespaces"
|
||||
namespace="ctrl.state.namespaceName"
|
||||
is-loading="ctrl.state.isAppsLoading"
|
||||
on-namespace-change="(ctrl.onChangeNamespaceDropdown)"
|
||||
show-system="ctrl.state.isSystemResources"
|
||||
set-system-resources="(ctrl.setSystemResources)"
|
||||
>
|
||||
</kubernetes-applications-stacks-datatable>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
<kubernetes-applications-datatable
|
||||
ng-if="ctrl.deploymentOptions.hideStacksFunctionality"
|
||||
dataset="ctrl.state.applications"
|
||||
on-refresh="(ctrl.getApplications)"
|
||||
namespaces="ctrl.state.namespaces"
|
||||
namespace="ctrl.state.namespaceName"
|
||||
on-namespace-change="(ctrl.onChangeNamespaceDropdown)"
|
||||
is-loading="ctrl.state.isAppsLoading"
|
||||
on-remove="(ctrl.removeAction)"
|
||||
hide-stacks="ctrl.deploymentOptions.hideStacksFunctionality"
|
||||
>
|
||||
</kubernetes-applications-datatable>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesApplicationsView', {
|
||||
templateUrl: './applications.html',
|
||||
controller: 'KubernetesApplicationsController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
endpoint: '<',
|
||||
},
|
||||
});
|
|
@ -1,181 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import { KubernetesApplicationTypes } from 'Kubernetes/models/application/models/appConstants';
|
||||
import { KubernetesPortainerApplicationStackNameLabel } from 'Kubernetes/models/application/models';
|
||||
import { getDeploymentOptions } from '@/react/portainer/environments/environment.service';
|
||||
import { getStacksFromApplications } from '@/react/kubernetes/applications/ListView/ApplicationsStacksDatatable/getStacksFromApplications';
|
||||
import { getApplications } from '@/react/kubernetes/applications/application.queries.ts';
|
||||
import { getNamespaces } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
||||
class KubernetesApplicationsController {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async,
|
||||
$state,
|
||||
$scope,
|
||||
Authentication,
|
||||
Notifications,
|
||||
KubernetesApplicationService,
|
||||
EndpointService,
|
||||
HelmService,
|
||||
KubernetesConfigurationService,
|
||||
LocalStorage,
|
||||
StackService,
|
||||
KubernetesNamespaceService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$scope = $scope;
|
||||
this.Authentication = Authentication;
|
||||
this.Notifications = Notifications;
|
||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
this.HelmService = HelmService;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.Authentication = Authentication;
|
||||
this.LocalStorage = LocalStorage;
|
||||
this.StackService = StackService;
|
||||
this.KubernetesNamespaceService = KubernetesNamespaceService;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.removeAction = this.removeAction.bind(this);
|
||||
this.removeActionAsync = this.removeActionAsync.bind(this);
|
||||
this.removeStacksAction = this.removeStacksAction.bind(this);
|
||||
this.removeStacksActionAsync = this.removeStacksActionAsync.bind(this);
|
||||
this.onPublishingModeClick = this.onPublishingModeClick.bind(this);
|
||||
this.onChangeNamespaceDropdown = this.onChangeNamespaceDropdown.bind(this);
|
||||
}
|
||||
|
||||
selectTab(index) {
|
||||
this.LocalStorage.storeActiveTab('applications', index);
|
||||
}
|
||||
|
||||
async removeStacksActionAsync(selectedItems) {
|
||||
let actionCount = selectedItems.length;
|
||||
for (const stack of selectedItems) {
|
||||
try {
|
||||
const isAppFormCreated = stack.Applications.some((x) => !x.ApplicationKind);
|
||||
|
||||
if (isAppFormCreated) {
|
||||
const promises = _.map(stack.Applications, (app) => this.KubernetesApplicationService.delete(app));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
await this.StackService.removeKubernetesStacksByName(stack.Name, stack.ResourcePool, false, this.endpoint.Id);
|
||||
|
||||
this.Notifications.success('Stack successfully removed', stack.Name);
|
||||
_.remove(this.state.stacks, { Name: stack.Name });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to remove stack');
|
||||
} finally {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
this.$state.reload(this.$state.current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeStacksAction(selectedItems) {
|
||||
return this.$async(this.removeStacksActionAsync, selectedItems);
|
||||
}
|
||||
|
||||
async removeActionAsync(selectedItems) {
|
||||
let actionCount = selectedItems.length;
|
||||
for (const application of selectedItems) {
|
||||
try {
|
||||
if (application.ApplicationType === KubernetesApplicationTypes.Helm) {
|
||||
await this.HelmService.uninstall(this.endpoint.Id, application);
|
||||
} else {
|
||||
if (application.Metadata.labels && application.Metadata.labels[KubernetesPortainerApplicationStackNameLabel]) {
|
||||
// remove stack if no app left in the stack
|
||||
const appsInNamespace = await getApplications(this.endpoint.Id, { namespace: application.ResourcePool, withDependencies: false });
|
||||
const stacksInNamespace = getStacksFromApplications(appsInNamespace);
|
||||
const stack = stacksInNamespace.find((x) => x.Name === application.StackName);
|
||||
if (stack.Applications.length === 0 && application.StackId) {
|
||||
await this.StackService.remove({ Id: application.StackId }, false, this.endpoint.Id);
|
||||
}
|
||||
}
|
||||
await this.KubernetesApplicationService.delete(application);
|
||||
}
|
||||
this.Notifications.success('Application successfully removed', application.Name);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to remove application');
|
||||
} finally {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
this.$state.reload(this.$state.current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeAction(selectedItems) {
|
||||
this.$async(() => this.removeActionAsync(selectedItems));
|
||||
}
|
||||
|
||||
onPublishingModeClick(application) {
|
||||
this.state.activeTab = 1;
|
||||
_.forEach(this.state.ports, (item) => {
|
||||
item.Expanded = false;
|
||||
item.Highlighted = false;
|
||||
if (item.Name === application.Name && item.Ports.length > 1) {
|
||||
item.Expanded = true;
|
||||
item.Highlighted = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onChangeNamespaceDropdown(namespaceName) {
|
||||
return this.$async(async () => {
|
||||
this.state.namespaceName = namespaceName;
|
||||
// save the selected namespaceName in local storage with the key 'kubernetes_namespace_filter_${environmentId}_${userID}'
|
||||
this.LocalStorage.storeNamespaceFilter(this.endpoint.Id, this.user.ID, namespaceName);
|
||||
});
|
||||
}
|
||||
|
||||
async onInit() {
|
||||
this.state = {
|
||||
activeTab: this.LocalStorage.getActiveTab('applications'),
|
||||
currentName: this.$state.$current.name,
|
||||
isAdmin: this.Authentication.isAdmin(),
|
||||
viewReady: false,
|
||||
applications: [],
|
||||
stacks: [],
|
||||
ports: [],
|
||||
namespaces: [],
|
||||
namespaceName: '',
|
||||
};
|
||||
|
||||
this.deploymentOptions = await getDeploymentOptions();
|
||||
|
||||
this.user = this.Authentication.getUserDetails();
|
||||
this.state.namespaces = await getNamespaces(this.endpoint.Id);
|
||||
|
||||
const savedNamespace = this.LocalStorage.getNamespaceFilter(this.endpoint.Id, this.user.ID); // could be null if not found, and '' if all namepsaces is selected
|
||||
const preferredNamespace = savedNamespace === null ? 'default' : savedNamespace;
|
||||
|
||||
this.state.namespaces = this.state.namespaces.filter((n) => n.Status.phase === 'Active');
|
||||
this.state.namespaces = _.sortBy(this.state.namespaces, 'Name');
|
||||
// set all namespaces ('') if there are no namespaces, or if all namespaces is selected
|
||||
if (!this.state.namespaces.length || preferredNamespace === '') {
|
||||
this.state.namespaceName = '';
|
||||
} else {
|
||||
// otherwise, set the preferred namespaceName if it exists, otherwise set the first namespaceName
|
||||
this.state.namespaceName = this.state.namespaces.find((n) => n.Name === preferredNamespace) ? preferredNamespace : this.state.namespaces[0].Name;
|
||||
}
|
||||
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(this.onInit);
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
if (this.state.currentName !== this.$state.$current.name) {
|
||||
this.LocalStorage.storeActiveTab('applications', 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesApplicationsController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesApplicationsController', KubernetesApplicationsController);
|
Loading…
Add table
Add a link
Reference in a new issue