mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(app): add ingress to app service form [EE-5569] (#9106)
This commit is contained in:
parent
8c16fbb8aa
commit
89c1d0e337
47 changed files with 1929 additions and 1181 deletions
|
@ -1,10 +0,0 @@
|
|||
.published-url-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 3fr;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.publish-url-link {
|
||||
width: min-content;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<div class="published-url-container">
|
||||
<div class="text-muted"> Published URL </div>
|
||||
<a ng-href="{{ $ctrl.publishedUrl }}" target="_blank" class="publish-url-link vertical-center">
|
||||
<pr-icon icon="'external-link'"></pr-icon>
|
||||
{{ $ctrl.publishedUrl }}
|
||||
</a>
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import './applications-datatable-url.css';
|
||||
|
||||
angular.module('portainer.kubernetes').component('kubernetesApplicationsDatatableUrl', {
|
||||
templateUrl: './applications-datatable-url.html',
|
||||
bindings: {
|
||||
publishedUrl: '@',
|
||||
},
|
||||
});
|
|
@ -9,3 +9,14 @@
|
|||
.datatable-wide {
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.published-url-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 3fr;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.publish-url-link {
|
||||
width: min-content;
|
||||
}
|
||||
|
|
|
@ -328,7 +328,19 @@
|
|||
</kubernetes-applications-datatable>
|
||||
</span>
|
||||
<span ng-if="!item.KubernetesApplications">
|
||||
<kubernetes-applications-datatable-url ng-if="$ctrl.getPublishedUrl(item)" published-url="{{ $ctrl.getPublishedUrl(item) }}"></kubernetes-applications-datatable-url>
|
||||
<div class="published-url-container">
|
||||
<div>
|
||||
<div class="text-muted"> Published URL(s) </div>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-repeat="url in $ctrl.getPublishedUrls(item)">
|
||||
<a ng-href="{{ url }}" target="_blank" class="publish-url-link vertical-center">
|
||||
<pr-icon icon="'external-link'"></pr-icon>
|
||||
{{ url }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<kubernetes-applications-datatable-details
|
||||
ng-if="$ctrl.hasConfigurationSecrets(item)"
|
||||
configurations="item.Configurations"
|
||||
|
|
|
@ -50,7 +50,7 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
|
|||
};
|
||||
|
||||
this.isExpandable = function (item) {
|
||||
return item.KubernetesApplications || this.hasConfigurationSecrets(item) || !!this.getPublishedUrl(item).length;
|
||||
return item.KubernetesApplications || this.hasConfigurationSecrets(item) || !!this.getPublishedUrls(item).length;
|
||||
};
|
||||
|
||||
this.expandItem = function (item, expanded) {
|
||||
|
@ -100,7 +100,7 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
|
|||
return !ctrl.isSystemNamespace(item) || ctrl.settings.showSystem;
|
||||
};
|
||||
|
||||
this.getPublishedUrl = function (item) {
|
||||
this.getPublishedUrls = function (item) {
|
||||
// Map all ingress rules in published ports to their respective URLs
|
||||
const ingressUrls = item.PublishedPorts.flatMap((pp) => pp.IngressRules)
|
||||
.filter(({ Host, IP }) => Host || IP)
|
||||
|
@ -119,7 +119,7 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
|
|||
const publishedUrls = [...ingressUrls, ...loadBalancerURLs];
|
||||
|
||||
// Return the first URL - priority given to ingress urls, then services (load balancers)
|
||||
return publishedUrls.length > 0 ? publishedUrls[0] : '';
|
||||
return publishedUrls.length > 0 ? publishedUrls : '';
|
||||
};
|
||||
|
||||
this.hasConfigurationSecrets = function (item) {
|
||||
|
|
|
@ -307,22 +307,18 @@ class KubernetesApplicationHelper {
|
|||
svcport.protocol = port.protocol;
|
||||
svcport.targetPort = port.targetPort;
|
||||
svcport.serviceName = service.metadata.name;
|
||||
svcport.ingress = {};
|
||||
svcport.ingressPaths = [];
|
||||
|
||||
app.Ingresses.value.forEach((ingress) => {
|
||||
const ingressNameMatched = ingress.Paths.find((ingPath) => ingPath.ServiceName === service.metadata.name);
|
||||
const ingressPortMatched = ingress.Paths.find((ingPath) => ingPath.Port === port.port);
|
||||
// only add ingress info to the port if the ingress serviceport matches the port in the service
|
||||
if (ingressPortMatched) {
|
||||
svcport.ingress = {
|
||||
IngressName: ingressPortMatched.IngressName,
|
||||
Host: ingressPortMatched.Host,
|
||||
Path: ingressPortMatched.Path,
|
||||
};
|
||||
}
|
||||
if (ingressNameMatched) {
|
||||
svc.Ingress = true;
|
||||
}
|
||||
const matchingIngressPaths = ingress.Paths.filter((ingPath) => ingPath.ServiceName === service.metadata.name && ingPath.Port === port.port);
|
||||
// only add ingress info to the port if the ingress serviceport and name matches
|
||||
const newPaths = matchingIngressPaths.map((ingPath) => ({
|
||||
IngressName: ingPath.IngressName,
|
||||
Host: ingPath.Host,
|
||||
Path: ingPath.Path,
|
||||
}));
|
||||
svcport.ingressPaths = [...svcport.ingressPaths, ...newPaths];
|
||||
svc.Ingress = matchingIngressPaths.length > 0;
|
||||
});
|
||||
|
||||
ports.push(svcport);
|
||||
|
|
|
@ -82,10 +82,8 @@ export class KubernetesIngressConverter {
|
|||
const ingresses = angular.copy(formValues.OriginalIngresses);
|
||||
application.Services.forEach((service) => {
|
||||
ingresses.forEach((ingress) => {
|
||||
const path = _.find(ingress.Paths, { ServiceName: service.metadata.name });
|
||||
if (path) {
|
||||
_.remove(ingress.Paths, path);
|
||||
}
|
||||
const paths = _.filter(ingress.Paths, { ServiceName: service.metadata.name });
|
||||
paths.forEach((path) => _.remove(ingress.Paths, path));
|
||||
});
|
||||
});
|
||||
return ingresses;
|
||||
|
|
|
@ -28,7 +28,6 @@ export function KubernetesApplicationFormValues() {
|
|||
this.PlacementType = KubernetesApplicationPlacementTypes.PREFERRED;
|
||||
this.Placements = []; // KubernetesApplicationPlacementFormValue lis;
|
||||
this.OriginalIngresses = undefined;
|
||||
this.IsPublishingService = false;
|
||||
}
|
||||
|
||||
export const KubernetesApplicationConfigurationFormValueOverridenKeyTypes = Object.freeze({
|
||||
|
|
|
@ -54,20 +54,6 @@ export class KubernetesIngressService {
|
|||
}
|
||||
}
|
||||
|
||||
const _KubernetesIngressServiceRoute = Object.freeze({
|
||||
Host: '',
|
||||
IngressName: '',
|
||||
Path: '',
|
||||
ServiceName: '',
|
||||
TLSCert: '',
|
||||
});
|
||||
|
||||
export class KubernetesIngressServiceRoute {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesIngressServiceRoute)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesServicePort Model
|
||||
*/
|
||||
|
|
|
@ -7,10 +7,8 @@ import { StorageAccessModeSelector } from '@/react/kubernetes/cluster/ConfigureV
|
|||
import { NamespaceAccessUsersSelector } from '@/react/kubernetes/namespaces/AccessView/NamespaceAccessUsersSelector';
|
||||
import { CreateNamespaceRegistriesSelector } from '@/react/kubernetes/namespaces/CreateView/CreateNamespaceRegistriesSelector';
|
||||
import { KubeApplicationAccessPolicySelector } from '@/react/kubernetes/applications/CreateView/KubeApplicationAccessPolicySelector';
|
||||
import {
|
||||
KubeServicesForm,
|
||||
kubeServicesValidation,
|
||||
} 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 { KubeApplicationDeploymentTypeSelector } from '@/react/kubernetes/applications/CreateView/KubeApplicationDeploymentTypeSelector';
|
||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
|
@ -117,6 +115,6 @@ withFormValidation(
|
|||
ngModule,
|
||||
withUIRouter(withCurrentUser(withReactQuery(KubeServicesForm))),
|
||||
'kubeServicesForm',
|
||||
['values', 'onChange', 'appName', 'selector', 'isEditMode'],
|
||||
['values', 'onChange', 'appName', 'selector', 'isEditMode', 'namespace'],
|
||||
kubeServicesValidation
|
||||
);
|
||||
|
|
|
@ -13,6 +13,8 @@ import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-p
|
|||
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
|
||||
import KubernetesPodConverter from 'Kubernetes/pod/converter';
|
||||
import { notifyError } from '@/portainer/services/notifications';
|
||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
||||
import { generateNewIngressesFromFormPaths } from '@/react/kubernetes/applications/CreateView/application-services/utils';
|
||||
|
||||
class KubernetesApplicationService {
|
||||
/* #region CONSTRUCTOR */
|
||||
|
@ -70,6 +72,12 @@ class KubernetesApplicationService {
|
|||
return apiService;
|
||||
}
|
||||
|
||||
_generateIngressPatchPromises(oldIngresses, newIngresses) {
|
||||
return _.map(newIngresses, (newIng) => {
|
||||
const oldIng = _.find(oldIngresses, { Name: newIng.Name });
|
||||
return this.KubernetesIngressService.patch(oldIng, newIng);
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region GET */
|
||||
|
@ -214,6 +222,18 @@ class KubernetesApplicationService {
|
|||
notifyError('Unable to create service', error);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
//Generate all ingresses from current form by passing services object
|
||||
const newServicePorts = formValues.Services.flatMap((service) => service.Ports);
|
||||
const newIngresses = generateNewIngressesFromFormPaths(formValues.OriginalIngresses, newServicePorts);
|
||||
if (newIngresses) {
|
||||
//Update original ingress with current ingress
|
||||
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, newIngresses));
|
||||
}
|
||||
} catch (error) {
|
||||
notifyError('Unable to update service', error);
|
||||
}
|
||||
}
|
||||
|
||||
const apiService = this._getApplicationApiService(app);
|
||||
|
@ -256,7 +276,7 @@ class KubernetesApplicationService {
|
|||
* To synchronise with kubernetes resource creation, update and delete summary output, any new resources created
|
||||
* in this method should also be displayed in the summary output (getUpdatedApplicationResources)
|
||||
*/
|
||||
async patchAsync(oldFormValues, newFormValues) {
|
||||
async patchAsync(oldFormValues, newFormValues, originalServicePorts) {
|
||||
const [oldApp, oldHeadlessService, oldServices, , oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
|
||||
const [newApp, newHeadlessService, newServices, , newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
|
||||
const oldApiService = this._getApplicationApiService(oldApp);
|
||||
|
@ -341,6 +361,21 @@ class KubernetesApplicationService {
|
|||
});
|
||||
}
|
||||
|
||||
// Update ingresses
|
||||
if (newServices) {
|
||||
try {
|
||||
//Generate all ingresses from current form by passing services object
|
||||
const newServicePorts = newFormValues.Services.flatMap((service) => service.Ports);
|
||||
const newIngresses = generateNewIngressesFromFormPaths(newFormValues.OriginalIngresses, newServicePorts, originalServicePorts);
|
||||
if (newIngresses) {
|
||||
//Update original ingress with current ingress
|
||||
await Promise.all(this._generateIngressPatchPromises(newFormValues.OriginalIngresses, newIngresses));
|
||||
}
|
||||
} catch (error) {
|
||||
notifyError('Unable to update service', error);
|
||||
}
|
||||
}
|
||||
|
||||
const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
|
||||
const newAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(newFormValues, newKind);
|
||||
if (!oldFormValues.AutoScaler.IsUsed) {
|
||||
|
@ -384,11 +419,11 @@ class KubernetesApplicationService {
|
|||
//
|
||||
// patch(oldValues: KubernetesApplication, newValues: KubernetesApplication, partial: (undefined | false)): Promise<unknown>
|
||||
// patch(oldValues: KubernetesApplicationFormValues, newValues: KubernetesApplicationFormValues, partial: true): Promise<unknown>
|
||||
patch(oldValues, newValues, partial = false) {
|
||||
patch(oldValues, newValues, partial = false, originalServicePorts) {
|
||||
if (partial) {
|
||||
return this.$async(this.patchPartialAsync, oldValues, newValues);
|
||||
}
|
||||
return this.$async(this.patchAsync, oldValues, newValues);
|
||||
return this.$async(this.patchAsync, oldValues, newValues, originalServicePorts);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
|
@ -412,6 +447,16 @@ class KubernetesApplicationService {
|
|||
if (application.ServiceType) {
|
||||
// delete headless service && non-headless service
|
||||
await this.KubernetesServiceService.delete(application.Services);
|
||||
if (application.Ingresses.length) {
|
||||
const originalIngresses = await this.KubernetesIngressService.get(payload.Namespace);
|
||||
const formValues = {
|
||||
OriginalIngresses: originalIngresses,
|
||||
PublishedPorts: KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(application.ServiceType, application.PublishedPorts),
|
||||
};
|
||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToDeleteIngresses(formValues, application);
|
||||
|
||||
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
}
|
||||
if (!_.isEmpty(application.AutoScaler)) {
|
||||
await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
|
||||
|
|
|
@ -1306,8 +1306,9 @@
|
|||
load-balancer-enabled="ctrl.publishViaLoadBalancerEnabled()"
|
||||
app-name="ctrl.formValues.Name"
|
||||
selector="ctrl.formValues.Selector"
|
||||
validation-data="{nodePortServices: ctrl.state.nodePortServices, formServices: ctrl.formValues.Services}"
|
||||
validation-data="{nodePortServices: ctrl.state.nodePortServices, formServices: ctrl.formValues.Services, ingressPaths: ctrl.ingressPaths, originalIngressPaths: ctrl.originalIngressPaths}"
|
||||
is-edit-mode="ctrl.state.isEdit"
|
||||
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
|
||||
></kube-services-form>
|
||||
<!-- kubernetes services options -->
|
||||
|
||||
|
@ -1355,8 +1356,9 @@
|
|||
values="ctrl.formValues.Services"
|
||||
app-name="ctrl.formValues.Name"
|
||||
selector="ctrl.formValues.Selector"
|
||||
validation-data="{nodePortServices: ctrl.state.nodePortServices, formServices: ctrl.formValues.Services}"
|
||||
validation-data="{nodePortServices: ctrl.state.nodePortServices, formServices: ctrl.formValues.Services, ingressPaths: ctrl.ingressPaths, originalIngressPaths: ctrl.originalIngressPaths}"
|
||||
is-edit-mode="ctrl.state.isEdit"
|
||||
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
|
||||
></kube-services-form>
|
||||
<!-- kubernetes services options -->
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,6 @@ import {
|
|||
KubernetesApplicationEnvironmentVariableFormValue,
|
||||
KubernetesApplicationFormValues,
|
||||
KubernetesApplicationPersistedFolderFormValue,
|
||||
KubernetesApplicationPublishedPortFormValue,
|
||||
KubernetesApplicationPlacementFormValue,
|
||||
KubernetesFormValidationReferences,
|
||||
} from 'Kubernetes/models/application/formValues';
|
||||
|
@ -125,12 +124,6 @@ class KubernetesCreateApplicationController {
|
|||
configMapPaths: new KubernetesFormValidationReferences(),
|
||||
secretPaths: new KubernetesFormValidationReferences(),
|
||||
existingVolumes: new KubernetesFormValidationReferences(),
|
||||
publishedPorts: {
|
||||
containerPorts: new KubernetesFormValidationReferences(),
|
||||
nodePorts: new KubernetesFormValidationReferences(),
|
||||
ingressRoutes: new KubernetesFormValidationReferences(),
|
||||
loadBalancerPorts: new KubernetesFormValidationReferences(),
|
||||
},
|
||||
placements: new KubernetesFormValidationReferences(),
|
||||
},
|
||||
isEdit: this.$state.params.namespace && this.$state.params.name,
|
||||
|
@ -153,7 +146,6 @@ class KubernetesCreateApplicationController {
|
|||
this.deployApplicationAsync = this.deployApplicationAsync.bind(this);
|
||||
this.setPullImageValidity = this.setPullImageValidity.bind(this);
|
||||
this.onChangeFileContent = this.onChangeFileContent.bind(this);
|
||||
this.onServicePublishChange = this.onServicePublishChange.bind(this);
|
||||
this.checkIngressesToUpdate = this.checkIngressesToUpdate.bind(this);
|
||||
this.confirmUpdateApplicationAsync = this.confirmUpdateApplicationAsync.bind(this);
|
||||
this.onDataAccessPolicyChange = this.onDataAccessPolicyChange.bind(this);
|
||||
|
@ -517,154 +509,12 @@ class KubernetesCreateApplicationController {
|
|||
|
||||
/* #endregion */
|
||||
|
||||
/* #region PUBLISHED PORTS UI MANAGEMENT */
|
||||
/* #region SERVICES UI MANAGEMENT */
|
||||
onServicesChange(services) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.Services = services;
|
||||
});
|
||||
}
|
||||
|
||||
onServicePublishChange() {
|
||||
// enable publishing with no previous ports exposed
|
||||
if (this.formValues.IsPublishingService && !this.formValues.PublishedPorts.length) {
|
||||
this.addPublishedPort();
|
||||
return;
|
||||
}
|
||||
|
||||
// service update
|
||||
if (this.formValues.IsPublishingService) {
|
||||
this.formValues.PublishedPorts.forEach((port) => (port.NeedsDeletion = false));
|
||||
} else {
|
||||
// delete new ports, mark old ports to be deleted
|
||||
this.formValues.PublishedPorts = this.formValues.PublishedPorts.filter((port) => !port.IsNew).map((port) => ({ ...port, NeedsDeletion: true }));
|
||||
}
|
||||
}
|
||||
|
||||
addPublishedPort() {
|
||||
const p = new KubernetesApplicationPublishedPortFormValue();
|
||||
const ingresses = this.ingresses;
|
||||
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
p.IngressName = ingresses && ingresses.length ? ingresses[0].Name : undefined;
|
||||
p.IngressHost = ingresses && ingresses.length ? ingresses[0].Hosts[0] : undefined;
|
||||
p.IngressHosts = ingresses && ingresses.length ? ingresses[0].Hosts : undefined;
|
||||
}
|
||||
if (this.formValues.PublishedPorts.length) {
|
||||
p.Protocol = this.formValues.PublishedPorts[0].Protocol;
|
||||
}
|
||||
this.formValues.PublishedPorts.push(p);
|
||||
}
|
||||
|
||||
resetPublishedPorts() {
|
||||
const ingresses = this.ingresses;
|
||||
_.forEach(this.formValues.PublishedPorts, (p) => {
|
||||
p.IngressName = ingresses && ingresses.length ? ingresses[0].Name : undefined;
|
||||
p.IngressHost = ingresses && ingresses.length ? ingresses[0].Hosts[0] : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
restorePublishedPort(index) {
|
||||
this.formValues.PublishedPorts[index].NeedsDeletion = false;
|
||||
this.onChangePublishedPorts();
|
||||
}
|
||||
|
||||
removePublishedPort(index) {
|
||||
if (this.state.isEdit && !this.formValues.PublishedPorts[index].IsNew) {
|
||||
this.formValues.PublishedPorts[index].NeedsDeletion = true;
|
||||
} else {
|
||||
this.formValues.PublishedPorts.splice(index, 1);
|
||||
}
|
||||
this.onChangePublishedPorts();
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region PUBLISHED PORTS ON CHANGE VALIDATION */
|
||||
onChangePublishedPorts() {
|
||||
this.onChangePortMappingContainerPort();
|
||||
this.onChangePortMappingNodePort();
|
||||
this.onChangePortMappingIngressRoute();
|
||||
this.onChangePortMappingLoadBalancer();
|
||||
this.onChangePortProtocol();
|
||||
}
|
||||
|
||||
onChangePortMappingContainerPort() {
|
||||
const state = this.state.duplicates.publishedPorts.containerPorts;
|
||||
if (this.formValues.PublishingType !== KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.ContainerPort + p.Protocol));
|
||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||
state.refs = duplicates;
|
||||
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||
} else {
|
||||
state.refs = {};
|
||||
state.hasRefs = false;
|
||||
}
|
||||
}
|
||||
|
||||
onChangePortMappingNodePort() {
|
||||
const state = this.state.duplicates.publishedPorts.nodePorts;
|
||||
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.NODE_PORT) {
|
||||
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.NodePort));
|
||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||
state.refs = duplicates;
|
||||
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||
} else {
|
||||
state.refs = {};
|
||||
state.hasRefs = false;
|
||||
}
|
||||
}
|
||||
|
||||
onChangePortMappingIngress(index) {
|
||||
const publishedPort = this.formValues.PublishedPorts[index];
|
||||
const ingress = _.find(this.ingresses, { Name: publishedPort.IngressName });
|
||||
publishedPort.IngressHosts = ingress.Hosts;
|
||||
this.ingressHostnames = ingress.Hosts;
|
||||
publishedPort.IngressHost = this.ingressHostnames.length ? this.ingressHostnames[0] : [];
|
||||
this.onChangePublishedPorts();
|
||||
}
|
||||
|
||||
onChangePortMappingIngressRoute() {
|
||||
const state = this.state.duplicates.publishedPorts.ingressRoutes;
|
||||
|
||||
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
const newRoutes = _.map(this.formValues.PublishedPorts, (p) => (p.IsNew && p.IngressRoute ? `${p.IngressHost || p.IngressName}${p.IngressRoute}` : undefined));
|
||||
const toDelRoutes = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion && p.IngressRoute ? `${p.IngressHost || p.IngressName}${p.IngressRoute}` : undefined));
|
||||
const allRoutes = _.flatMap(this.ingresses, (i) => _.map(i.Paths, (p) => `${p.Host || i.Name}${p.Path}`));
|
||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(newRoutes);
|
||||
_.forEach(newRoutes, (route, idx) => {
|
||||
if (_.includes(allRoutes, route) && !_.includes(toDelRoutes, route)) {
|
||||
duplicates[idx] = route;
|
||||
}
|
||||
});
|
||||
state.refs = duplicates;
|
||||
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||
} else {
|
||||
state.refs = {};
|
||||
state.hasRefs = false;
|
||||
}
|
||||
}
|
||||
|
||||
onChangePortMappingLoadBalancer() {
|
||||
const state = this.state.duplicates.publishedPorts.loadBalancerPorts;
|
||||
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.LOAD_BALANCER) {
|
||||
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.LoadBalancerPort));
|
||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||
state.refs = duplicates;
|
||||
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||
} else {
|
||||
state.refs = {};
|
||||
state.hasRefs = false;
|
||||
}
|
||||
}
|
||||
|
||||
onChangePortProtocol(index) {
|
||||
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.LOAD_BALANCER) {
|
||||
const newPorts = _.filter(this.formValues.PublishedPorts, { IsNew: true });
|
||||
_.forEach(newPorts, (port) => {
|
||||
port.Protocol = index ? this.formValues.PublishedPorts[index].Protocol : newPorts[0].Protocol;
|
||||
});
|
||||
this.onChangePortMappingLoadBalancer();
|
||||
}
|
||||
this.onChangePortMappingContainerPort();
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region STATE VALIDATION FUNCTIONS */
|
||||
|
@ -675,11 +525,7 @@ class KubernetesCreateApplicationController {
|
|||
!this.state.duplicates.persistedFolders.hasRefs &&
|
||||
!this.state.duplicates.configMapPaths.hasRefs &&
|
||||
!this.state.duplicates.secretPaths.hasRefs &&
|
||||
!this.state.duplicates.existingVolumes.hasRefs &&
|
||||
!this.state.duplicates.publishedPorts.containerPorts.hasRefs &&
|
||||
!this.state.duplicates.publishedPorts.nodePorts.hasRefs &&
|
||||
!this.state.duplicates.publishedPorts.ingressRoutes.hasRefs &&
|
||||
!this.state.duplicates.publishedPorts.loadBalancerPorts.hasRefs
|
||||
!this.state.duplicates.existingVolumes.hasRefs
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -860,22 +706,10 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
/* #endregion */
|
||||
|
||||
isEditAndNotNewPublishedPort(index) {
|
||||
return this.state.isEdit && !this.formValues.PublishedPorts[index].IsNew;
|
||||
}
|
||||
|
||||
hasNoPublishedPorts() {
|
||||
return this.formValues.PublishedPorts.filter((port) => !port.NeedsDeletion).length === 0;
|
||||
}
|
||||
|
||||
isEditAndNotNewPlacement(index) {
|
||||
return this.state.isEdit && !this.formValues.Placements[index].IsNew;
|
||||
}
|
||||
|
||||
isNewAndNotFirst(index) {
|
||||
return !this.state.isEdit && index !== 0;
|
||||
}
|
||||
|
||||
showPlacementPolicySection() {
|
||||
const placements = _.filter(this.formValues.Placements, { NeedsDeletion: false });
|
||||
return placements.length !== 0;
|
||||
|
@ -897,8 +731,7 @@ class KubernetesCreateApplicationController {
|
|||
const invalid = !this.isValid();
|
||||
const hasNoChanges = this.isEditAndNoChangesMade();
|
||||
const nonScalable = this.isNonScalable();
|
||||
const isPublishingWithoutPorts = this.formValues.IsPublishingService && this.hasNoPublishedPorts();
|
||||
return overflow || autoScalerOverflow || inProgress || invalid || hasNoChanges || nonScalable || isPublishingWithoutPorts;
|
||||
return overflow || autoScalerOverflow || inProgress || invalid || hasNoChanges || nonScalable;
|
||||
}
|
||||
|
||||
isExternalApplication() {
|
||||
|
@ -908,33 +741,6 @@ class KubernetesCreateApplicationController {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
disableLoadBalancerEdit() {
|
||||
return (
|
||||
this.state.isEdit &&
|
||||
this.application.ServiceType === this.ServiceTypes.LOAD_BALANCER &&
|
||||
!this.application.LoadBalancerIPAddress &&
|
||||
this.formValues.PublishingType === this.ApplicationPublishingTypes.LOAD_BALANCER
|
||||
);
|
||||
}
|
||||
|
||||
isPublishingTypeEditDisabled() {
|
||||
const ports = _.filter(this.formValues.PublishedPorts, { IsNew: false, NeedsDeletion: false });
|
||||
return this.state.isEdit && this.formValues.PublishedPorts.length > 0 && ports.length > 0;
|
||||
}
|
||||
|
||||
isEditLBWithPorts() {
|
||||
return this.formValues.PublishingType === KubernetesApplicationPublishingTypes.LOAD_BALANCER && _.filter(this.formValues.PublishedPorts, { IsNew: false }).length === 0;
|
||||
}
|
||||
|
||||
isProtocolOptionDisabled(index, protocol) {
|
||||
return (
|
||||
this.disableLoadBalancerEdit() ||
|
||||
(this.isEditAndNotNewPublishedPort(index) && this.formValues.PublishedPorts[index].Protocol !== protocol) ||
|
||||
(this.isEditLBWithPorts() && this.formValues.PublishedPorts[index].Protocol !== protocol && this.isNewAndNotFirst(index))
|
||||
);
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
||||
/* #region DATA AUTO REFRESH */
|
||||
|
@ -1061,6 +867,7 @@ class KubernetesCreateApplicationController {
|
|||
return this.$async(async () => {
|
||||
try {
|
||||
this.ingresses = await this.KubernetesIngressService.get(namespace);
|
||||
this.ingressPaths = this.ingresses.flatMap((ingress) => ingress.Paths);
|
||||
this.ingressHostnames = this.ingresses.length ? this.ingresses[0].Hosts : [];
|
||||
if (!this.publishViaIngressEnabled()) {
|
||||
if (this.savedFormValues) {
|
||||
|
@ -1093,7 +900,6 @@ class KubernetesCreateApplicationController {
|
|||
this.clearConfigMaps();
|
||||
this.clearSecrets();
|
||||
this.resetPersistedFolders();
|
||||
this.resetPublishedPorts();
|
||||
}
|
||||
|
||||
onResourcePoolSelectionChange() {
|
||||
|
@ -1115,7 +921,7 @@ class KubernetesCreateApplicationController {
|
|||
this.formValues.ApplicationOwner = this.Authentication.getUserDetails().username;
|
||||
// combine the secrets and configmap form values when submitting the form
|
||||
_.remove(this.formValues.Configurations, (item) => item.SelectedConfiguration === undefined);
|
||||
await this.KubernetesApplicationService.create(this.formValues);
|
||||
await this.KubernetesApplicationService.create(this.formValues, this.originalServicePorts);
|
||||
this.Notifications.success('Request to deploy application successfully submitted', this.formValues.Name);
|
||||
this.$state.go('kubernetes.applications');
|
||||
} catch (err) {
|
||||
|
@ -1137,7 +943,7 @@ class KubernetesCreateApplicationController {
|
|||
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
await this.KubernetesApplicationService.patch(this.savedFormValues, this.formValues);
|
||||
await this.KubernetesApplicationService.patch(this.savedFormValues, this.formValues, false, this.originalServicePorts);
|
||||
this.Notifications.success('Success', 'Request to update application successfully submitted');
|
||||
this.$state.go('kubernetes.applications.application', { name: this.application.Name, namespace: this.application.ResourcePool });
|
||||
} catch (err) {
|
||||
|
@ -1191,7 +997,7 @@ class KubernetesCreateApplicationController {
|
|||
});
|
||||
ingressesForService.forEach((ingressForService) => {
|
||||
updatedOldPorts.forEach((servicePort, pIndex) => {
|
||||
if (servicePort.ingress) {
|
||||
if (servicePort.ingressPaths) {
|
||||
// if there isn't a ingress path that has a matching service name and port
|
||||
const doesIngressPathMatchServicePort = ingressForService.Paths.find((ingPath) => ingPath.ServiceName === updatedService.Name && ingPath.Port === servicePort.port);
|
||||
if (!doesIngressPathMatchServicePort) {
|
||||
|
@ -1322,6 +1128,9 @@ class KubernetesCreateApplicationController {
|
|||
this.ingresses
|
||||
);
|
||||
|
||||
this.originalServicePorts = structuredClone(this.formValues.Services.flatMap((service) => service.Ports));
|
||||
this.originalIngressPaths = structuredClone(this.originalServicePorts.flatMap((port) => port.ingressPaths).filter((ingressPath) => ingressPath.Host));
|
||||
|
||||
if (this.application.ApplicationKind) {
|
||||
this.state.appType = KubernetesDeploymentTypes[this.application.ApplicationKind.toUpperCase()];
|
||||
if (this.application.ApplicationKind === KubernetesDeploymentTypes.URL) {
|
||||
|
@ -1359,8 +1168,6 @@ class KubernetesCreateApplicationController {
|
|||
this.nodesLimits.excludesPods(this.application.Pods, this.formValues.CpuLimit, KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit));
|
||||
}
|
||||
|
||||
this.formValues.IsPublishingService = this.formValues.PublishedPorts.length > 0;
|
||||
|
||||
this.oldFormValues = angular.copy(this.formValues);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||
|
|
|
@ -5,18 +5,14 @@ import { KubernetesDeployment } from 'Kubernetes/models/deployment/models';
|
|||
import { KubernetesStatefulSet } from 'Kubernetes/models/stateful-set/models';
|
||||
import { KubernetesDaemonSet } from 'Kubernetes/models/daemon-set/models';
|
||||
import { KubernetesService, KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||
import {
|
||||
KubernetesApplication,
|
||||
KubernetesApplicationDeploymentTypes,
|
||||
KubernetesApplicationPublishingTypes,
|
||||
KubernetesApplicationTypes,
|
||||
} from 'Kubernetes/models/application/models';
|
||||
import { KubernetesApplication, KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
|
||||
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
|
||||
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
||||
import KubernetesServiceConverter from 'Kubernetes/converters/service';
|
||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
||||
import KubernetesPersistentVolumeClaimConverter from 'Kubernetes/converters/persistentVolumeClaim';
|
||||
import { generateNewIngressesFromFormPaths } from '@/react/kubernetes/applications/CreateView/application-services/utils';
|
||||
|
||||
const { CREATE, UPDATE, DELETE } = KubernetesResourceActions;
|
||||
|
||||
|
@ -45,21 +41,16 @@ function getCreatedApplicationResources(formValues) {
|
|||
if (services) {
|
||||
services.forEach((service) => {
|
||||
resources.push({ action: CREATE, kind: KubernetesResourceTypes.SERVICE, name: service.Name, type: service.Type || KubernetesServiceTypes.CLUSTER_IP });
|
||||
if (formValues.OriginalIngresses.length !== 0) {
|
||||
const ingresses = KubernetesIngressConverter.newApplicationFormValuesToIngresses(formValues, service.Name, service.Ports);
|
||||
resources.push(...getIngressUpdateSummary(formValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
// Ingress
|
||||
const newServicePorts = formValues.Services.flatMap((service) => service.Ports);
|
||||
const newIngresses = generateNewIngressesFromFormPaths(formValues.OriginalIngresses, newServicePorts);
|
||||
resources.push(...getIngressUpdateSummary(formValues.OriginalIngresses, newIngresses));
|
||||
});
|
||||
}
|
||||
|
||||
if (service) {
|
||||
// Service
|
||||
resources.push({ action: CREATE, kind: KubernetesResourceTypes.SERVICE, name: service.Name, type: service.Type || KubernetesServiceTypes.CLUSTER_IP });
|
||||
if (formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
// Ingress
|
||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, service.Name);
|
||||
resources.push(...getIngressUpdateSummary(formValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
}
|
||||
|
||||
if (app instanceof KubernetesStatefulSet) {
|
||||
|
@ -147,28 +138,18 @@ function getUpdatedApplicationResources(oldFormValues, newFormValues) {
|
|||
});
|
||||
}
|
||||
|
||||
if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS || oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
// Ingress
|
||||
const oldIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(oldFormValues, oldService.Name);
|
||||
const newIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
|
||||
resources.push(...getIngressUpdateSummary(oldIngresses, newIngresses));
|
||||
}
|
||||
// Ingress
|
||||
const oldIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(oldFormValues, oldService.Name);
|
||||
const newServicePorts = newFormValues.Services.flatMap((service) => service.Ports);
|
||||
const oldServicePorts = oldFormValues.Services.flatMap((service) => service.Ports);
|
||||
const newIngresses = generateNewIngressesFromFormPaths(newFormValues.OriginalIngresses, newServicePorts, oldServicePorts);
|
||||
resources.push(...getIngressUpdateSummary(oldIngresses, newIngresses));
|
||||
} else if (!oldService && newService) {
|
||||
// Service
|
||||
resources.push({ action: CREATE, kind: KubernetesResourceTypes.SERVICE, name: newService.Name, type: newService.Type || KubernetesServiceTypes.CLUSTER_IP });
|
||||
if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
// Ingress
|
||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
|
||||
resources.push(...getIngressUpdateSummary(newFormValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
} else if (oldService && !newService) {
|
||||
// Service
|
||||
resources.push({ action: DELETE, kind: KubernetesResourceTypes.SERVICE, name: oldService.Name, type: oldService.Type || KubernetesServiceTypes.CLUSTER_IP });
|
||||
if (oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||
// Ingress
|
||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, oldService.Name);
|
||||
resources.push(...getIngressUpdateSummary(oldFormValues.OriginalIngresses, ingresses));
|
||||
}
|
||||
}
|
||||
|
||||
const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue