@spinnaker/azure 2026.2.3 → 2026.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/azure.module.d.ts +3 -1
- package/dist/image/image.reader.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/instance/azureInstanceType.service.d.ts +6 -2
- package/dist/instance/details/AzureInstanceDetails.d.ts +67 -0
- package/dist/loadBalancer/configure/AzureLoadBalancerChoiceModal.d.ts +10 -5
- package/dist/loadBalancer/configure/AzureLoadBalancerModal.d.ts +102 -0
- package/dist/loadBalancer/details/azureLoadBalancerDetails.d.ts +26 -0
- package/dist/loadBalancer/loadBalancer.transformer.d.ts +45 -2
- package/dist/pipeline/stages/AzureAccountRegionClusterSelector.d.ts +32 -0
- package/dist/pipeline/stages/bake/azureBakeStage.d.ts +46 -2
- package/dist/pipeline/stages/destroyAsg/azureDestroyAsgStage.d.ts +14 -2
- package/dist/pipeline/stages/disableAsg/azureDisableAsgStage.d.ts +4 -2
- package/dist/pipeline/stages/enableAsg/azureEnableAsgStage.d.ts +4 -2
- package/dist/securityGroup/configure/AzureSecurityGroupModal.d.ts +70 -0
- package/dist/securityGroup/details/AzureSecurityGroupDetails.d.ts +51 -0
- package/dist/securityGroup/securityGroup.reader.d.ts +3 -2
- package/dist/securityGroup/securityGroup.transformer.d.ts +3 -2
- package/dist/securityGroup/securityGroup.write.service.d.ts +9 -2
- package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +88 -2
- package/dist/serverGroup/configure/serverGroupConfiguration.service.d.ts +30 -2
- package/dist/serverGroup/configure/wizard/AzureCloneServerGroupModal.d.ts +44 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupAdvancedSettings.d.ts +8 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +12 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacityAndZones.d.ts +11 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupHealthSettings.d.ts +10 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +12 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +9 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupLoadBalancers.d.ts +14 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupNetworkSettings.d.ts +10 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupSecurityGroups.d.ts +6 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupTags.d.ts +9 -0
- package/dist/serverGroup/configure/wizard/pages/common.d.ts +14 -0
- package/dist/serverGroup/configure/wizard/pages/index.d.ts +10 -0
- package/dist/serverGroup/details/azureServerGroupDetails.d.ts +13 -0
- package/dist/serverGroup/details/rollback/RollbackServerGroupModal.d.ts +6 -4
- package/dist/serverGroup/serverGroup.transformer.d.ts +61 -2
- package/dist/validation/applicationName.validator.d.ts +6 -2
- package/package.json +5 -8
- package/src/azure.module.spec.ts +921 -0
- package/src/azure.module.ts +56 -68
- package/src/image/image.reader.js +4 -15
- package/src/image/image.reader.spec.js +4 -7
- package/src/instance/azureInstanceType.service.js +8 -16
- package/src/instance/azureInstanceType.service.spec.js +5 -13
- package/src/instance/details/AzureInstanceDetails.spec.tsx +364 -0
- package/src/instance/details/AzureInstanceDetails.tsx +533 -0
- package/src/loadBalancer/configure/AzureLoadBalancerChoiceModal.spec.tsx +125 -0
- package/src/loadBalancer/configure/AzureLoadBalancerChoiceModal.tsx +46 -26
- package/src/loadBalancer/configure/AzureLoadBalancerModal.spec.tsx +658 -0
- package/src/loadBalancer/configure/AzureLoadBalancerModal.tsx +960 -0
- package/src/loadBalancer/details/azureLoadBalancerDetails.spec.tsx +470 -0
- package/src/loadBalancer/details/azureLoadBalancerDetails.tsx +412 -0
- package/src/loadBalancer/loadBalancer.transformer.js +83 -93
- package/src/pipeline/stages/AzureAccountRegionClusterSelector.tsx +238 -0
- package/src/pipeline/stages/bake/azureBakeStage.tsx +723 -0
- package/src/pipeline/stages/destroyAsg/azureDestroyAsgStage.tsx +128 -0
- package/src/pipeline/stages/disableAsg/azureDisableAsgStage.tsx +92 -0
- package/src/pipeline/stages/enableAsg/azureEnableAsgStage.tsx +87 -0
- package/src/routerConsumers.spec.tsx +69 -0
- package/src/securityGroup/configure/AzureSecurityGroupModal.spec.tsx +558 -0
- package/src/securityGroup/configure/AzureSecurityGroupModal.tsx +782 -0
- package/src/securityGroup/details/AzureSecurityGroupDetails.spec.tsx +291 -0
- package/src/securityGroup/details/AzureSecurityGroupDetails.tsx +354 -0
- package/src/securityGroup/securityGroup.reader.js +3 -13
- package/src/securityGroup/securityGroup.transformer.js +3 -13
- package/src/securityGroup/securityGroup.write.service.spec.ts +105 -0
- package/src/securityGroup/securityGroup.write.service.ts +73 -0
- package/src/serverGroup/configure/serverGroupCommandBuilder.service.js +167 -166
- package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +63 -0
- package/src/serverGroup/configure/serverGroupConfiguration.service.js +325 -309
- package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +57 -53
- package/src/serverGroup/configure/wizard/AzureCloneServerGroupModal.spec.tsx +758 -0
- package/src/serverGroup/configure/wizard/AzureCloneServerGroupModal.tsx +349 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupAdvancedSettings.tsx +208 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +122 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacityAndZones.tsx +80 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupHealthSettings.tsx +97 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +151 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +41 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupLoadBalancers.tsx +214 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupNetworkSettings.tsx +119 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupSecurityGroups.tsx +54 -0
- package/src/serverGroup/configure/wizard/pages/ServerGroupTags.tsx +40 -0
- package/src/serverGroup/configure/wizard/pages/common.tsx +72 -0
- package/src/serverGroup/configure/wizard/pages/index.ts +10 -0
- package/src/serverGroup/details/azureServerGroupDetails.spec.tsx +263 -0
- package/src/serverGroup/details/azureServerGroupDetails.tsx +318 -0
- package/src/serverGroup/details/rollback/RollbackServerGroupModal.tsx +24 -30
- package/src/serverGroup/serverGroup.transformer.js +11 -19
- package/src/serverGroup/serverGroup.transformer.spec.js +2 -6
- package/src/validation/applicationName.validator.js +14 -29
- package/dist/instance/details/instance.details.controller.d.ts +0 -2
- package/dist/loadBalancer/configure/createLoadBalancer.controller.d.ts +0 -2
- package/dist/loadBalancer/details/loadBalancerDetail.controller.d.ts +0 -2
- package/dist/pipeline/stages/bake/bakeExecutionDetails.controller.d.ts +0 -2
- package/dist/securityGroup/clone/cloneSecurityGroup.controller.d.ts +0 -2
- package/dist/securityGroup/configure/CreateSecurityGroupCtrl.d.ts +0 -2
- package/dist/securityGroup/configure/EditSecurityGroupCtrl.d.ts +0 -2
- package/dist/securityGroup/details/securityGroupDetail.controller.d.ts +0 -2
- package/dist/serverGroup/configure/serverGroup.configure.azure.module.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/CloneServerGroup.azure.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/ServerGroupInstanceArchetype.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/ServerGroupInstanceType.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/advancedSettings/ServerGroupAdvancedSettings.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/basicSettings/ServerGroupBasicSettings.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/basicSettings/image.regional.filter.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/capacity/capacitySelector.directive.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/healthSettings/ServerGroupHealthSettings.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/image/ServerGroupImageSettings.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/image/imageSettingsSelector.directive.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/loadBalancers/ServerGroupLoadBalancers.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettings.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/securityGroup/ServerGroupSecurityGroups.controller.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/tags/tagsSelector.directive.d.ts +0 -2
- package/dist/serverGroup/configure/wizard/zones/zoneSelector.directive.d.ts +0 -2
- package/dist/serverGroup/details/serverGroup.details.module.d.ts +0 -2
- package/dist/serverGroup/details/serverGroupDetails.azure.controller.d.ts +0 -2
- package/src/instance/details/instance.details.controller.js +0 -371
- package/src/instance/details/instance.details.controller.spec.js +0 -129
- package/src/instance/details/instanceDetails.html +0 -43
- package/src/loadBalancer/configure/advancedSettings.html +0 -95
- package/src/loadBalancer/configure/createLoadBalancer.controller.js +0 -275
- package/src/loadBalancer/configure/createLoadBalancer.controller.spec.js +0 -39
- package/src/loadBalancer/configure/createLoadBalancer.html +0 -25
- package/src/loadBalancer/configure/createLoadBalancerProperties.html +0 -128
- package/src/loadBalancer/configure/editLoadBalancer.html +0 -26
- package/src/loadBalancer/configure/healthCheck.html +0 -74
- package/src/loadBalancer/configure/listeners.html +0 -50
- package/src/loadBalancer/details/loadBalancerDetail.controller.js +0 -181
- package/src/loadBalancer/details/loadBalancerDetail.controller.spec.js +0 -39
- package/src/loadBalancer/details/loadBalancerDetail.html +0 -128
- package/src/pipeline/stages/bake/azureBakeStage.js +0 -276
- package/src/pipeline/stages/bake/bakeExecutionDetails.controller.js +0 -40
- package/src/pipeline/stages/bake/bakeExecutionDetails.html +0 -50
- package/src/pipeline/stages/bake/bakeStage.html +0 -187
- package/src/pipeline/stages/destroyAsg/azureDestroyAsgStage.js +0 -73
- package/src/pipeline/stages/destroyAsg/destroyAsgStage.html +0 -9
- package/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html +0 -1
- package/src/pipeline/stages/disableAsg/azureDisableAsgStage.js +0 -65
- package/src/pipeline/stages/disableAsg/disableAsgStage.html +0 -11
- package/src/pipeline/stages/disableAsg/disableAsgStepLabel.html +0 -1
- package/src/pipeline/stages/enableAsg/azureEnableAsgStage.js +0 -70
- package/src/pipeline/stages/enableAsg/enableAsgStage.html +0 -11
- package/src/pipeline/stages/enableAsg/enableAsgStepLabel.html +0 -1
- package/src/securityGroup/clone/cloneSecurityGroup.controller.js +0 -162
- package/src/securityGroup/clone/cloneSecurityGroup.html +0 -8
- package/src/securityGroup/configure/CreateSecurityGroup.controller.spec.js +0 -78
- package/src/securityGroup/configure/CreateSecurityGroupCtrl.js +0 -265
- package/src/securityGroup/configure/EditSecurityGroupCtrl.js +0 -222
- package/src/securityGroup/configure/configSecurityGroup.mixin.controller.js +0 -262
- package/src/securityGroup/configure/createSecurityGroup.html +0 -20
- package/src/securityGroup/configure/createSecurityGroupIngress.html +0 -75
- package/src/securityGroup/configure/createSecurityGroupProperties.html +0 -88
- package/src/securityGroup/configure/editSecurityGroup.html +0 -19
- package/src/securityGroup/details/securityGroupDetail.controller.js +0 -142
- package/src/securityGroup/details/securityGroupDetail.html +0 -78
- package/src/securityGroup/securityGroup.write.service.js +0 -57
- package/src/serverGroup/configure/serverGroup.configure.azure.module.js +0 -47
- package/src/serverGroup/configure/wizard/CloneServerGroup.azure.controller.js +0 -247
- package/src/serverGroup/configure/wizard/ServerGroupInstanceArchetype.controller.js +0 -35
- package/src/serverGroup/configure/wizard/ServerGroupInstanceType.controller.js +0 -15
- package/src/serverGroup/configure/wizard/advancedSettings/ServerGroupAdvancedSettings.controller.js +0 -25
- package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.html +0 -5
- package/src/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.html +0 -138
- package/src/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.js +0 -43
- package/src/serverGroup/configure/wizard/basicSettings/ServerGroupBasicSettings.controller.js +0 -71
- package/src/serverGroup/configure/wizard/basicSettings/basicSettings.html +0 -131
- package/src/serverGroup/configure/wizard/basicSettings/image.regional.filter.js +0 -15
- package/src/serverGroup/configure/wizard/basicSettings/image.regional.filter.spec.js +0 -42
- package/src/serverGroup/configure/wizard/capacity/capacity.html +0 -5
- package/src/serverGroup/configure/wizard/capacity/capacitySelector.directive.html +0 -20
- package/src/serverGroup/configure/wizard/capacity/capacitySelector.directive.js +0 -21
- package/src/serverGroup/configure/wizard/capacity/zones.html +0 -5
- package/src/serverGroup/configure/wizard/healthSettings/ServerGroupHealthSettings.controller.js +0 -38
- package/src/serverGroup/configure/wizard/healthSettings/healthSettings.html +0 -5
- package/src/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.html +0 -32
- package/src/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.js +0 -22
- package/src/serverGroup/configure/wizard/image/ServerGroupImageSettings.controller.js +0 -34
- package/src/serverGroup/configure/wizard/image/imageSettings.html +0 -5
- package/src/serverGroup/configure/wizard/image/imageSettingsSelector.directive.html +0 -33
- package/src/serverGroup/configure/wizard/image/imageSettingsSelector.directive.js +0 -22
- package/src/serverGroup/configure/wizard/instanceArchetype.html +0 -33
- package/src/serverGroup/configure/wizard/instanceType/instanceType.html +0 -10
- package/src/serverGroup/configure/wizard/instanceType.html +0 -33
- package/src/serverGroup/configure/wizard/loadBalancers/ServerGroupLoadBalancers.controller.js +0 -137
- package/src/serverGroup/configure/wizard/loadBalancers/loadBalancers.html +0 -3
- package/src/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.html +0 -41
- package/src/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.js +0 -22
- package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettings.controller.js +0 -44
- package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.html +0 -54
- package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.js +0 -19
- package/src/serverGroup/configure/wizard/networkSettings/networkSettings.html +0 -3
- package/src/serverGroup/configure/wizard/securityGroup/ServerGroupSecurityGroups.controller.js +0 -28
- package/src/serverGroup/configure/wizard/securityGroup/securityGroups.html +0 -3
- package/src/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.html +0 -40
- package/src/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.js +0 -38
- package/src/serverGroup/configure/wizard/serverGroupWizard.html +0 -61
- package/src/serverGroup/configure/wizard/tags/tags.html +0 -5
- package/src/serverGroup/configure/wizard/tags/tagsSelector.directive.html +0 -16
- package/src/serverGroup/configure/wizard/tags/tagsSelector.directive.js +0 -30
- package/src/serverGroup/configure/wizard/templateSelection.html +0 -9
- package/src/serverGroup/configure/wizard/zones/zoneSelector.directive.html +0 -21
- package/src/serverGroup/configure/wizard/zones/zoneSelector.directive.js +0 -28
- package/src/serverGroup/details/serverGroup.details.module.js +0 -11
- package/src/serverGroup/details/serverGroupDetails.azure.controller.js +0 -260
- package/src/serverGroup/details/serverGroupDetails.html +0 -98
- package/src/subnet/subnetSelectField.directive.html +0 -28
- package/src/subnet/subnetSelectField.directive.js +0 -35
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Modal } from 'react-bootstrap';
|
|
3
|
+
|
|
4
|
+
import type { Application, IModalComponentProps, IRouterInjectedProps } from '@spinnaker/core';
|
|
5
|
+
import {
|
|
6
|
+
AccountService,
|
|
7
|
+
LoadBalancerWriter,
|
|
8
|
+
ModalClose,
|
|
9
|
+
NameUtils,
|
|
10
|
+
nativePromiseService,
|
|
11
|
+
NetworkReader,
|
|
12
|
+
noop,
|
|
13
|
+
ReactModal,
|
|
14
|
+
SubmitButton,
|
|
15
|
+
TaskMonitor,
|
|
16
|
+
TaskMonitorWrapper,
|
|
17
|
+
withRouter,
|
|
18
|
+
} from '@spinnaker/core';
|
|
19
|
+
|
|
20
|
+
import { AzureLoadBalancerTransformer } from '../loadBalancer.transformer';
|
|
21
|
+
import type { IAzureLoadBalancer } from '../../utility';
|
|
22
|
+
import { AzureLoadBalancerTypes } from '../../utility';
|
|
23
|
+
|
|
24
|
+
interface IAzureLoadBalancerModalProps extends IModalComponentProps {
|
|
25
|
+
app?: Application;
|
|
26
|
+
application?: Application;
|
|
27
|
+
forPipelineConfig?: boolean;
|
|
28
|
+
isNew?: boolean;
|
|
29
|
+
loadBalancer?: any;
|
|
30
|
+
loadBalancerType?: IAzureLoadBalancer | string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface IAzureVnet {
|
|
34
|
+
account: string;
|
|
35
|
+
name: string;
|
|
36
|
+
region: string;
|
|
37
|
+
resourceGroup?: string;
|
|
38
|
+
subnets?: IAzureSubnet[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface IAzureSubnet {
|
|
42
|
+
devices?: Array<{ type: string }>;
|
|
43
|
+
name: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface IAzureLoadBalancerModalState {
|
|
47
|
+
accounts: any[];
|
|
48
|
+
accountsLoaded: boolean;
|
|
49
|
+
existingLoadBalancerNames: string[];
|
|
50
|
+
loadBalancer: any;
|
|
51
|
+
regions: any[];
|
|
52
|
+
selectedSubnets: IAzureSubnet[];
|
|
53
|
+
selectedVnets: IAzureVnet[];
|
|
54
|
+
taskMonitor: TaskMonitor;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface IValidationOptions {
|
|
58
|
+
existingLoadBalancerNames?: string[];
|
|
59
|
+
isNew?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const validSkus = ['Standard_v2', 'Standard_Small'];
|
|
63
|
+
|
|
64
|
+
function normalizeLoadBalancerTypeName(loadBalancerType: any): string {
|
|
65
|
+
const normalized = String(loadBalancerType || '')
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.split('_')
|
|
68
|
+
.join(' ');
|
|
69
|
+
if (normalized === 'application gateway') {
|
|
70
|
+
return 'azure application gateway';
|
|
71
|
+
}
|
|
72
|
+
if (normalized === 'load balancer') {
|
|
73
|
+
return 'azure load balancer';
|
|
74
|
+
}
|
|
75
|
+
return normalized;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getAzureLoadBalancerTypeChoice(
|
|
79
|
+
loadBalancer?: any,
|
|
80
|
+
loadBalancerType?: IAzureLoadBalancer | string,
|
|
81
|
+
): IAzureLoadBalancer {
|
|
82
|
+
const explicitType = typeof loadBalancerType === 'string' ? loadBalancerType : loadBalancerType?.type;
|
|
83
|
+
const normalizedType = normalizeLoadBalancerTypeName(
|
|
84
|
+
explicitType || loadBalancer?.loadBalancerType || loadBalancer?.elb?.loadBalancerType,
|
|
85
|
+
);
|
|
86
|
+
return (
|
|
87
|
+
AzureLoadBalancerTypes.find((candidate) => candidate.type.toLowerCase() === normalizedType) ||
|
|
88
|
+
AzureLoadBalancerTypes[0]
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function formatInputValue(value: any): any {
|
|
93
|
+
return value ?? '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getAzureVnetSelectValue(vnet: IAzureVnet): string {
|
|
97
|
+
return `${vnet.name}|${vnet.resourceGroup || ''}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getAzureVnetSelectLabel(vnet: IAzureVnet): string {
|
|
101
|
+
return vnet.resourceGroup ? `${vnet.name} (${vnet.resourceGroup})` : vnet.name;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function findAzureVnetBySelectValue(vnets: IAzureVnet[], value: string): IAzureVnet | null {
|
|
105
|
+
return vnets.find((vnet) => getAzureVnetSelectValue(vnet) === value) || null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function liftAzureLoadBalancerSessionPersistence(
|
|
109
|
+
loadBalancer: any,
|
|
110
|
+
loadBalancerType: string,
|
|
111
|
+
sourceLoadBalancer = loadBalancer,
|
|
112
|
+
): any {
|
|
113
|
+
if (!isAzureLoadBalancerType(loadBalancerType)) {
|
|
114
|
+
return loadBalancer;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const sourceRule =
|
|
118
|
+
sourceLoadBalancer.elb?.loadBalancingRules?.[0] || sourceLoadBalancer.loadBalancingRules?.[0] || {};
|
|
119
|
+
const convertedRule = loadBalancer.loadBalancingRules?.[0] || {};
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
...loadBalancer,
|
|
123
|
+
sessionPersistence:
|
|
124
|
+
loadBalancer.sessionPersistence ||
|
|
125
|
+
sourceLoadBalancer.elb?.sessionPersistence ||
|
|
126
|
+
sourceRule.persistence ||
|
|
127
|
+
sourceRule.sessionPersistence ||
|
|
128
|
+
convertedRule.persistence ||
|
|
129
|
+
convertedRule.sessionPersistence,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function isAzureLoadBalancerType(loadBalancerType: string): boolean {
|
|
134
|
+
return loadBalancerType === 'Azure Load Balancer';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function shouldRenderCreateFields(isNew: boolean): boolean {
|
|
138
|
+
return isNew;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function shouldRenderNetworkFields(isNew: boolean, loadBalancerType: string): boolean {
|
|
142
|
+
return isNew && !isAzureLoadBalancerType(loadBalancerType);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function shouldRenderSkuField(isNew: boolean, loadBalancerType: string): boolean {
|
|
146
|
+
return isNew && !isAzureLoadBalancerType(loadBalancerType);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function listenerProtocolOptions(loadBalancerType: string): string[] {
|
|
150
|
+
return isAzureLoadBalancerType(loadBalancerType) ? ['TCP', 'UDP'] : ['HTTP'];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function probeProtocolOptions(loadBalancerType: string): string[] {
|
|
154
|
+
return isAzureLoadBalancerType(loadBalancerType) ? ['TCP', 'HTTP'] : ['HTTP'];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function parseOptionalNumber(value: any): number {
|
|
158
|
+
if (value === '' || value === null || value === undefined) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
const parsed = Number(value);
|
|
162
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function applyAzureLoadBalancerTypeDefaults(loadBalancer: any, loadBalancerType: string): any {
|
|
166
|
+
const listenerOptions = listenerProtocolOptions(loadBalancerType);
|
|
167
|
+
const probeOptions = probeProtocolOptions(loadBalancerType);
|
|
168
|
+
return {
|
|
169
|
+
...loadBalancer,
|
|
170
|
+
sessionPersistence: isAzureLoadBalancerType(loadBalancerType)
|
|
171
|
+
? loadBalancer.sessionPersistence || 'None'
|
|
172
|
+
: loadBalancer.sessionPersistence,
|
|
173
|
+
probes: (loadBalancer.probes || [{}]).map((probe: any) => ({
|
|
174
|
+
...probe,
|
|
175
|
+
probeProtocol: probeOptions.includes(probe.probeProtocol) ? probe.probeProtocol : probeOptions[0],
|
|
176
|
+
})),
|
|
177
|
+
loadBalancingRules: (loadBalancer.loadBalancingRules || [{}]).map((rule: any) => ({
|
|
178
|
+
...rule,
|
|
179
|
+
protocol: listenerOptions.includes(rule.protocol) ? rule.protocol : listenerOptions[0],
|
|
180
|
+
})),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isPresent(value: any): boolean {
|
|
185
|
+
return value !== '' && value !== null && value !== undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isNegativeNumber(value: any): boolean {
|
|
189
|
+
if (!isPresent(value)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
const parsed = Number(value);
|
|
193
|
+
return Number.isFinite(parsed) && parsed < 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function validateNonNegative(errors: string[], value: any, message: string): void {
|
|
197
|
+
if (isNegativeNumber(value)) {
|
|
198
|
+
errors.push(message);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function isValidStack(value: string): boolean {
|
|
203
|
+
return /^[a-zA-Z0-9]+$/.test(value || '');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isValidDetail(value: string): boolean {
|
|
207
|
+
return /^[a-zA-Z0-9-]+$/.test(value || '');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function validateAzureLoadBalancerForSubmit(
|
|
211
|
+
loadBalancer: any,
|
|
212
|
+
loadBalancerType: string,
|
|
213
|
+
options: IValidationOptions = {},
|
|
214
|
+
): string[] {
|
|
215
|
+
const errors: string[] = [];
|
|
216
|
+
const probe = loadBalancer.probes?.[0] || {};
|
|
217
|
+
const rules = loadBalancer.loadBalancingRules || [];
|
|
218
|
+
|
|
219
|
+
if (!loadBalancer.name) {
|
|
220
|
+
errors.push('Name is required.');
|
|
221
|
+
} else if (loadBalancer.name.length > 32) {
|
|
222
|
+
errors.push('Name must be 32 characters or fewer.');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (options.isNew) {
|
|
226
|
+
if (!loadBalancer.credentials) {
|
|
227
|
+
errors.push('Account is required.');
|
|
228
|
+
}
|
|
229
|
+
if (!loadBalancer.region) {
|
|
230
|
+
errors.push('Region is required.');
|
|
231
|
+
}
|
|
232
|
+
if (!loadBalancer.stack) {
|
|
233
|
+
errors.push('Stack is required.');
|
|
234
|
+
} else if (!isValidStack(loadBalancer.stack)) {
|
|
235
|
+
errors.push('Stack may only contain letters and numbers.');
|
|
236
|
+
}
|
|
237
|
+
if (!loadBalancer.detail) {
|
|
238
|
+
errors.push('Detail is required.');
|
|
239
|
+
} else if (!isValidDetail(loadBalancer.detail)) {
|
|
240
|
+
errors.push('Detail may only contain letters, numbers, and hyphens.');
|
|
241
|
+
}
|
|
242
|
+
if (
|
|
243
|
+
(options.existingLoadBalancerNames || []).some((name) => name.toLowerCase() === loadBalancer.name.toLowerCase())
|
|
244
|
+
) {
|
|
245
|
+
errors.push(`A load balancer named ${loadBalancer.name} already exists in this account and region.`);
|
|
246
|
+
}
|
|
247
|
+
if (!isAzureLoadBalancerType(loadBalancerType)) {
|
|
248
|
+
if (!loadBalancer.selectedVnet && !loadBalancer.vnet) {
|
|
249
|
+
errors.push('VNet is required.');
|
|
250
|
+
}
|
|
251
|
+
if (!loadBalancer.selectedSubnet && !loadBalancer.subnet) {
|
|
252
|
+
errors.push('Subnet is required.');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!rules.length) {
|
|
258
|
+
errors.push('At least one listener is required.');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
rules.forEach((rule: any, index: number) => {
|
|
262
|
+
if (!listenerProtocolOptions(loadBalancerType).includes(rule.protocol)) {
|
|
263
|
+
errors.push(`Listener ${index + 1} protocol is invalid.`);
|
|
264
|
+
}
|
|
265
|
+
if (!isPresent(rule.externalPort)) {
|
|
266
|
+
errors.push(`Listener ${index + 1} external port is required.`);
|
|
267
|
+
} else {
|
|
268
|
+
validateNonNegative(errors, rule.externalPort, `Listener ${index + 1} external port must be 0 or greater.`);
|
|
269
|
+
}
|
|
270
|
+
if (!isPresent(rule.backendPort)) {
|
|
271
|
+
errors.push(`Listener ${index + 1} backend port is required.`);
|
|
272
|
+
} else {
|
|
273
|
+
validateNonNegative(errors, rule.backendPort, `Listener ${index + 1} backend port must be 0 or greater.`);
|
|
274
|
+
}
|
|
275
|
+
validateNonNegative(errors, rule.idleTimeout, `Listener ${index + 1} idle timeout must be 0 or greater.`);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (!probeProtocolOptions(loadBalancerType).includes(probe.probeProtocol)) {
|
|
279
|
+
errors.push('Health check protocol is invalid.');
|
|
280
|
+
}
|
|
281
|
+
if (!isPresent(probe.probePort)) {
|
|
282
|
+
errors.push('Health check port is required.');
|
|
283
|
+
} else {
|
|
284
|
+
validateNonNegative(errors, probe.probePort, 'Health check port must be 0 or greater.');
|
|
285
|
+
}
|
|
286
|
+
if (probe.probeProtocol?.indexOf('HTTP') === 0 && !probe.probePath) {
|
|
287
|
+
errors.push('Health check path is required.');
|
|
288
|
+
}
|
|
289
|
+
validateNonNegative(errors, probe.probeInterval, 'Health check interval must be 0 or greater.');
|
|
290
|
+
validateNonNegative(errors, probe.unhealthyThreshold, 'Unhealthy threshold must be 0 or greater.');
|
|
291
|
+
validateNonNegative(errors, probe.timeout, 'Health check timeout must be 0 or greater.');
|
|
292
|
+
|
|
293
|
+
return errors;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function normalizeAzureLoadBalancerForSubmit(loadBalancer: any, loadBalancerType: string): any {
|
|
297
|
+
const normalized = {
|
|
298
|
+
...loadBalancer,
|
|
299
|
+
probes: (loadBalancer.probes || []).map((probe: any) => ({ ...probe })),
|
|
300
|
+
loadBalancingRules: (loadBalancer.loadBalancingRules || []).map((rule: any) => ({ ...rule })),
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (!isAzureLoadBalancerType(loadBalancerType) && normalized.selectedVnet) {
|
|
304
|
+
normalized.vnet = normalized.selectedVnet.name;
|
|
305
|
+
normalized.vnetResourceGroup = normalized.selectedVnet.resourceGroup;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!isAzureLoadBalancerType(loadBalancerType) && normalized.selectedSubnet) {
|
|
309
|
+
normalized.subnet = normalized.selectedSubnet.name;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const name = normalized.clusterName || normalized.name;
|
|
313
|
+
const probeName = `${name}-probe`;
|
|
314
|
+
const ruleNameBase = `${name}-rule`;
|
|
315
|
+
normalized.type = 'upsertLoadBalancer';
|
|
316
|
+
normalized.loadBalancerType = loadBalancerType;
|
|
317
|
+
|
|
318
|
+
if (!normalized.vnet && !normalized.subnetType) {
|
|
319
|
+
normalized.securityGroups = null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (normalized.probes[0]) {
|
|
323
|
+
normalized.probes[0].probeName = probeName;
|
|
324
|
+
if (normalized.probes[0].probeProtocol === 'TCP') {
|
|
325
|
+
normalized.probes[0].probePath = undefined;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
normalized.loadBalancingRules.forEach((rule: any, index: number) => {
|
|
330
|
+
delete rule.persistence;
|
|
331
|
+
delete rule.sessionPersistence;
|
|
332
|
+
rule.ruleName = `${ruleNameBase}${index}`;
|
|
333
|
+
rule.probeName = probeName;
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return normalized;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export class AzureLoadBalancerModalComponent extends React.Component<
|
|
340
|
+
IAzureLoadBalancerModalProps & IRouterInjectedProps,
|
|
341
|
+
IAzureLoadBalancerModalState
|
|
342
|
+
> {
|
|
343
|
+
public static defaultProps: Partial<IAzureLoadBalancerModalProps> = {
|
|
344
|
+
closeModal: noop,
|
|
345
|
+
dismissModal: noop,
|
|
346
|
+
isNew: true,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
public static show(props: IAzureLoadBalancerModalProps): Promise<any> {
|
|
350
|
+
return ReactModal.show(AzureLoadBalancerModal, props, { dialogClassName: 'modal-lg' });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private mounted = false;
|
|
354
|
+
private applicationRefreshUnsubscribe?: () => void;
|
|
355
|
+
private transformer = new AzureLoadBalancerTransformer(nativePromiseService);
|
|
356
|
+
|
|
357
|
+
constructor(props: IAzureLoadBalancerModalProps & IRouterInjectedProps) {
|
|
358
|
+
super(props);
|
|
359
|
+
const application = this.getApplication();
|
|
360
|
+
const taskMonitor = new TaskMonitor({
|
|
361
|
+
application,
|
|
362
|
+
title: `${props.isNew ? 'Creating' : 'Updating'} your load balancer`,
|
|
363
|
+
onDismiss: () => this.props.dismissModal(),
|
|
364
|
+
onTaskComplete: this.onTaskComplete,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
this.state = {
|
|
368
|
+
accounts: [],
|
|
369
|
+
accountsLoaded: !props.isNew,
|
|
370
|
+
existingLoadBalancerNames: [],
|
|
371
|
+
loadBalancer: this.initializeLoadBalancer(),
|
|
372
|
+
regions: [],
|
|
373
|
+
selectedSubnets: [],
|
|
374
|
+
selectedVnets: [],
|
|
375
|
+
taskMonitor,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
public componentDidMount(): void {
|
|
380
|
+
this.mounted = true;
|
|
381
|
+
if (this.props.isNew) {
|
|
382
|
+
this.updateName();
|
|
383
|
+
this.initializeCreateMode();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
public componentWillUnmount(): void {
|
|
388
|
+
this.mounted = false;
|
|
389
|
+
this.applicationRefreshUnsubscribe?.();
|
|
390
|
+
this.applicationRefreshUnsubscribe = undefined;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private getApplication(): Application {
|
|
394
|
+
return (this.props.app || this.props.application) as Application;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private getLoadBalancerType(): string {
|
|
398
|
+
return getAzureLoadBalancerTypeChoice(this.props.loadBalancer, this.props.loadBalancerType).type;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private initializeLoadBalancer(): any {
|
|
402
|
+
if (this.props.loadBalancer) {
|
|
403
|
+
const sourceLoadBalancer = this.props.loadBalancer;
|
|
404
|
+
let loadBalancer =
|
|
405
|
+
this.props.forPipelineConfig && !sourceLoadBalancer.elb
|
|
406
|
+
? { ...sourceLoadBalancer }
|
|
407
|
+
: this.transformer.convertLoadBalancerForEditing(sourceLoadBalancer);
|
|
408
|
+
loadBalancer = liftAzureLoadBalancerSessionPersistence(
|
|
409
|
+
loadBalancer,
|
|
410
|
+
this.getLoadBalancerType(),
|
|
411
|
+
sourceLoadBalancer,
|
|
412
|
+
);
|
|
413
|
+
if (this.props.isNew) {
|
|
414
|
+
const nameParts = NameUtils.parseLoadBalancerName(loadBalancer.name);
|
|
415
|
+
loadBalancer.stack = nameParts.stack;
|
|
416
|
+
loadBalancer.detail = nameParts.freeFormDetails;
|
|
417
|
+
delete loadBalancer.name;
|
|
418
|
+
}
|
|
419
|
+
return applyAzureLoadBalancerTypeDefaults(loadBalancer, this.getLoadBalancerType());
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return applyAzureLoadBalancerTypeDefaults(
|
|
423
|
+
this.transformer.constructNewLoadBalancerTemplate(this.getApplication()),
|
|
424
|
+
this.getLoadBalancerType(),
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private initializeCreateMode(): void {
|
|
429
|
+
AccountService.listAccounts('azure').then((accounts) => {
|
|
430
|
+
if (!this.mounted) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
this.setState({ accounts, accountsLoaded: true }, this.accountUpdated);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private onTaskComplete = (): void => {
|
|
438
|
+
const application = this.getApplication();
|
|
439
|
+
this.applicationRefreshUnsubscribe?.();
|
|
440
|
+
this.applicationRefreshUnsubscribe = undefined;
|
|
441
|
+
this.applicationRefreshUnsubscribe = application.loadBalancers.onNextRefresh(this.onApplicationRefresh);
|
|
442
|
+
application.loadBalancers.refresh();
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
private onApplicationRefresh = (): void => {
|
|
446
|
+
this.applicationRefreshUnsubscribe?.();
|
|
447
|
+
this.applicationRefreshUnsubscribe = undefined;
|
|
448
|
+
if (!this.mounted) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const { loadBalancer } = this.state;
|
|
453
|
+
this.props.closeModal();
|
|
454
|
+
const newStateParams = {
|
|
455
|
+
name: loadBalancer.name,
|
|
456
|
+
accountId: loadBalancer.credentials,
|
|
457
|
+
region: loadBalancer.region,
|
|
458
|
+
provider: 'azure',
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
if (!this.props.stateService.includes('**.loadBalancerDetails')) {
|
|
462
|
+
this.props.stateService.go('.loadBalancerDetails', newStateParams);
|
|
463
|
+
} else {
|
|
464
|
+
this.props.stateService.go('^.loadBalancerDetails', newStateParams);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
private getName(): string {
|
|
469
|
+
const { loadBalancer } = this.state;
|
|
470
|
+
return [this.getApplication().name, loadBalancer.stack || '', loadBalancer.detail || '']
|
|
471
|
+
.join('-')
|
|
472
|
+
.replace(/-+$/, '');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private updateName = (): void => {
|
|
476
|
+
if (!this.props.isNew) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
this.updateLoadBalancer({ name: this.getName() });
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
private accountUpdated = (): void => {
|
|
483
|
+
AccountService.getRegionsForAccount(this.state.loadBalancer.credentials).then((regions) => {
|
|
484
|
+
if (!this.mounted) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
this.setState({ regions }, this.regionUpdated);
|
|
488
|
+
});
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
private regionUpdated = (): void => {
|
|
492
|
+
this.updateName();
|
|
493
|
+
this.updateLoadBalancerNames();
|
|
494
|
+
this.vnetUpdated();
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
private updateLoadBalancerNames = (): void => {
|
|
498
|
+
const loadBalancers = this.getApplication().getDataSource?.('loadBalancers');
|
|
499
|
+
if (!loadBalancers || !this.props.isNew) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const { credentials, region } = this.state.loadBalancer;
|
|
504
|
+
loadBalancers.refresh(true).then(() => {
|
|
505
|
+
if (!this.mounted) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const existingLoadBalancerNames = (loadBalancers.data || [])
|
|
509
|
+
.filter((loadBalancer: any) => loadBalancer.account === credentials && loadBalancer.region === region)
|
|
510
|
+
.map((loadBalancer: any) => loadBalancer.name);
|
|
511
|
+
this.setState({ existingLoadBalancerNames });
|
|
512
|
+
});
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
private vnetUpdated = (): void => {
|
|
516
|
+
const { credentials, region } = this.state.loadBalancer;
|
|
517
|
+
this.setState((state) => ({
|
|
518
|
+
loadBalancer: {
|
|
519
|
+
...state.loadBalancer,
|
|
520
|
+
selectedVnet: null,
|
|
521
|
+
vnet: null,
|
|
522
|
+
vnetResourceGroup: null,
|
|
523
|
+
},
|
|
524
|
+
selectedVnets: [],
|
|
525
|
+
}));
|
|
526
|
+
|
|
527
|
+
NetworkReader.listNetworks().then((networks: any) => {
|
|
528
|
+
if (!this.mounted) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const azureNetworks = Array.isArray(networks) ? networks : networks.azure || [];
|
|
532
|
+
const selectedVnets = azureNetworks.filter(
|
|
533
|
+
(vnet: IAzureVnet) => vnet.account === credentials && vnet.region === region,
|
|
534
|
+
);
|
|
535
|
+
this.setState({ selectedVnets });
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
this.subnetUpdated();
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
private subnetUpdated = (): void => {
|
|
542
|
+
this.setState((state) => ({
|
|
543
|
+
loadBalancer: {
|
|
544
|
+
...state.loadBalancer,
|
|
545
|
+
selectedSubnet: null,
|
|
546
|
+
subnet: null,
|
|
547
|
+
},
|
|
548
|
+
selectedSubnets: [],
|
|
549
|
+
}));
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
private selectedVnetChanged = (vnetName: string): void => {
|
|
553
|
+
const selectedVnet = findAzureVnetBySelectValue(this.state.selectedVnets, vnetName);
|
|
554
|
+
const selectedSubnets = (selectedVnet?.subnets || []).filter((subnet) =>
|
|
555
|
+
(subnet.devices || []).every((device) => !device || device.type === 'applicationGateways'),
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
this.setState((state) => ({
|
|
559
|
+
loadBalancer: {
|
|
560
|
+
...state.loadBalancer,
|
|
561
|
+
selectedVnet,
|
|
562
|
+
vnet: selectedVnet?.name || null,
|
|
563
|
+
vnetResourceGroup: selectedVnet?.resourceGroup || null,
|
|
564
|
+
selectedSubnet: null,
|
|
565
|
+
subnet: null,
|
|
566
|
+
},
|
|
567
|
+
selectedSubnets,
|
|
568
|
+
}));
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
private selectedSubnetChanged = (subnetName: string): void => {
|
|
572
|
+
const selectedSubnet = this.state.selectedSubnets.find((subnet) => subnet.name === subnetName) || null;
|
|
573
|
+
this.updateLoadBalancer({ selectedSubnet, subnet: selectedSubnet?.name || null });
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
private updateLoadBalancer(values: any, callback?: () => void): void {
|
|
577
|
+
this.setState((state) => ({ loadBalancer: { ...state.loadBalancer, ...values } }), callback);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private updateField = (field: string, value: any): void => {
|
|
581
|
+
this.updateLoadBalancer(
|
|
582
|
+
{ [field]: value },
|
|
583
|
+
field === 'credentials' ? this.accountUpdated : field === 'region' ? this.regionUpdated : undefined,
|
|
584
|
+
);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
private updateNamePart = (field: string, value: string): void => {
|
|
588
|
+
this.updateLoadBalancer({ [field]: value }, this.updateName);
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
private updateProbe = (field: string, value: any): void => {
|
|
592
|
+
const probes = [{ ...(this.state.loadBalancer.probes?.[0] || {}), [field]: value }];
|
|
593
|
+
this.updateLoadBalancer({ probes });
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
private updateRule = (index: number, field: string, value: any): void => {
|
|
597
|
+
const loadBalancingRules = [...(this.state.loadBalancer.loadBalancingRules || [])];
|
|
598
|
+
loadBalancingRules[index] = { ...loadBalancingRules[index], [field]: value };
|
|
599
|
+
this.updateLoadBalancer({ loadBalancingRules });
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
private updateFirstRule = (field: string, value: any): void => {
|
|
603
|
+
this.updateRule(0, field, value);
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
private addListener = (): void => {
|
|
607
|
+
const loadBalancingRules = [
|
|
608
|
+
...(this.state.loadBalancer.loadBalancingRules || []),
|
|
609
|
+
{ protocol: listenerProtocolOptions(this.getLoadBalancerType())[0] },
|
|
610
|
+
];
|
|
611
|
+
this.updateLoadBalancer({ loadBalancingRules });
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
private removeListener = (index: number): void => {
|
|
615
|
+
const loadBalancingRules = [...(this.state.loadBalancer.loadBalancingRules || [])];
|
|
616
|
+
loadBalancingRules.splice(index, 1);
|
|
617
|
+
this.updateLoadBalancer({ loadBalancingRules });
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
private submit = (): void => {
|
|
621
|
+
const application = this.getApplication();
|
|
622
|
+
const descriptor = this.props.isNew ? 'Create' : 'Update';
|
|
623
|
+
const errors = validateAzureLoadBalancerForSubmit(this.state.loadBalancer, this.getLoadBalancerType(), {
|
|
624
|
+
existingLoadBalancerNames: this.state.existingLoadBalancerNames,
|
|
625
|
+
isNew: this.props.isNew,
|
|
626
|
+
});
|
|
627
|
+
if (errors.length) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const loadBalancer = normalizeAzureLoadBalancerForSubmit(this.state.loadBalancer, this.getLoadBalancerType());
|
|
631
|
+
|
|
632
|
+
if (this.props.forPipelineConfig) {
|
|
633
|
+
this.props.closeModal(loadBalancer);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const params = {
|
|
638
|
+
cloudProvider: 'azure',
|
|
639
|
+
appName: application.name,
|
|
640
|
+
clusterName: loadBalancer.clusterName,
|
|
641
|
+
resourceGroupName: loadBalancer.clusterName,
|
|
642
|
+
loadBalancerName: loadBalancer.name,
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
this.setState({ loadBalancer });
|
|
646
|
+
this.state.taskMonitor.submit(() =>
|
|
647
|
+
LoadBalancerWriter.upsertLoadBalancer(loadBalancer, application, descriptor, params),
|
|
648
|
+
);
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
private renderInput(label: string, field: string, type = 'text', onChange = this.updateField) {
|
|
652
|
+
const value = formatInputValue(this.state.loadBalancer[field]);
|
|
653
|
+
return (
|
|
654
|
+
<div className="form-group">
|
|
655
|
+
<label className="col-md-3 control-label">{label}</label>
|
|
656
|
+
<div className="col-md-7">
|
|
657
|
+
<input
|
|
658
|
+
className="form-control input-sm"
|
|
659
|
+
type={type}
|
|
660
|
+
value={value}
|
|
661
|
+
onChange={(event) => onChange(field, event.target.value)}
|
|
662
|
+
/>
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
private renderSelect(
|
|
669
|
+
label: string,
|
|
670
|
+
value: string,
|
|
671
|
+
values: any[],
|
|
672
|
+
onChange: (value: string) => void,
|
|
673
|
+
getLabel = (item: any) => item.name || item,
|
|
674
|
+
getValue = getLabel,
|
|
675
|
+
) {
|
|
676
|
+
return (
|
|
677
|
+
<div className="form-group">
|
|
678
|
+
<label className="col-md-3 control-label">{label}</label>
|
|
679
|
+
<div className="col-md-7">
|
|
680
|
+
<select
|
|
681
|
+
className="form-control input-sm"
|
|
682
|
+
value={value || ''}
|
|
683
|
+
onChange={(event) => onChange(event.target.value)}
|
|
684
|
+
>
|
|
685
|
+
<option value="">Select...</option>
|
|
686
|
+
{values.map((item) => (
|
|
687
|
+
<option key={getValue(item)} value={getValue(item)}>
|
|
688
|
+
{getLabel(item)}
|
|
689
|
+
</option>
|
|
690
|
+
))}
|
|
691
|
+
</select>
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
private renderSkuSelect() {
|
|
698
|
+
return this.renderSelect(
|
|
699
|
+
'SKU',
|
|
700
|
+
this.state.loadBalancer.sku,
|
|
701
|
+
validSkus,
|
|
702
|
+
(value) => this.updateField('sku', value),
|
|
703
|
+
(item) => item,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private renderProbeInput(label: string, field: string, type = 'text') {
|
|
708
|
+
const probe = this.state.loadBalancer.probes?.[0] || {};
|
|
709
|
+
const value = formatInputValue(probe[field]);
|
|
710
|
+
return (
|
|
711
|
+
<div className="form-group">
|
|
712
|
+
<label className="col-md-3 control-label">{label}</label>
|
|
713
|
+
<div className="col-md-7">
|
|
714
|
+
<input
|
|
715
|
+
className="form-control input-sm"
|
|
716
|
+
min={type === 'number' ? 0 : undefined}
|
|
717
|
+
type={type}
|
|
718
|
+
value={value}
|
|
719
|
+
onChange={(event) =>
|
|
720
|
+
this.updateProbe(field, type === 'number' ? parseOptionalNumber(event.target.value) : event.target.value)
|
|
721
|
+
}
|
|
722
|
+
/>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private renderFirstRuleInput(label: string, field: string, type = 'text') {
|
|
729
|
+
const rule = this.state.loadBalancer.loadBalancingRules?.[0] || {};
|
|
730
|
+
const value = formatInputValue(rule[field]);
|
|
731
|
+
return (
|
|
732
|
+
<div className="form-group">
|
|
733
|
+
<label className="col-md-3 control-label">{label}</label>
|
|
734
|
+
<div className="col-md-7">
|
|
735
|
+
<input
|
|
736
|
+
className="form-control input-sm"
|
|
737
|
+
min={type === 'number' ? 0 : undefined}
|
|
738
|
+
type={type}
|
|
739
|
+
value={value}
|
|
740
|
+
onChange={(event) =>
|
|
741
|
+
this.updateFirstRule(
|
|
742
|
+
field,
|
|
743
|
+
type === 'number' ? parseOptionalNumber(event.target.value) : event.target.value,
|
|
744
|
+
)
|
|
745
|
+
}
|
|
746
|
+
/>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
public render() {
|
|
753
|
+
const { dismissModal, isNew } = this.props;
|
|
754
|
+
const {
|
|
755
|
+
accounts,
|
|
756
|
+
accountsLoaded,
|
|
757
|
+
existingLoadBalancerNames,
|
|
758
|
+
loadBalancer,
|
|
759
|
+
regions,
|
|
760
|
+
selectedSubnets,
|
|
761
|
+
selectedVnets,
|
|
762
|
+
taskMonitor,
|
|
763
|
+
} = this.state;
|
|
764
|
+
const probe = loadBalancer.probes?.[0] || {};
|
|
765
|
+
const submitting = taskMonitor.submitting;
|
|
766
|
+
const loadBalancerType = this.getLoadBalancerType();
|
|
767
|
+
const validationErrors = validateAzureLoadBalancerForSubmit(loadBalancer, loadBalancerType, {
|
|
768
|
+
existingLoadBalancerNames,
|
|
769
|
+
isNew,
|
|
770
|
+
});
|
|
771
|
+
const listenerOptions = listenerProtocolOptions(loadBalancerType);
|
|
772
|
+
const probeOptions = probeProtocolOptions(loadBalancerType);
|
|
773
|
+
const showCreateFields = shouldRenderCreateFields(isNew);
|
|
774
|
+
const showNetworkFields = shouldRenderNetworkFields(isNew, loadBalancerType);
|
|
775
|
+
const showSkuField = shouldRenderSkuField(isNew, loadBalancerType);
|
|
776
|
+
|
|
777
|
+
return (
|
|
778
|
+
<>
|
|
779
|
+
<TaskMonitorWrapper monitor={taskMonitor} />
|
|
780
|
+
<ModalClose dismiss={dismissModal} />
|
|
781
|
+
<Modal.Header>
|
|
782
|
+
<Modal.Title>
|
|
783
|
+
{isNew ? `Create New ${loadBalancerType}` : `Edit ${loadBalancerType} ${loadBalancer.name}`}
|
|
784
|
+
</Modal.Title>
|
|
785
|
+
</Modal.Header>
|
|
786
|
+
<Modal.Body>
|
|
787
|
+
<form className="form-horizontal" onSubmit={(event) => event.preventDefault()}>
|
|
788
|
+
{showCreateFields && this.renderInput('Stack', 'stack', 'text', this.updateNamePart)}
|
|
789
|
+
{showCreateFields && this.renderInput('Detail', 'detail', 'text', this.updateNamePart)}
|
|
790
|
+
{showCreateFields && this.renderInput('Name', 'name')}
|
|
791
|
+
{showCreateFields &&
|
|
792
|
+
this.renderSelect('Account', loadBalancer.credentials, accounts, (value) =>
|
|
793
|
+
this.updateField('credentials', value),
|
|
794
|
+
)}
|
|
795
|
+
{showCreateFields &&
|
|
796
|
+
this.renderSelect('Region', loadBalancer.region, regions, (value) => this.updateField('region', value))}
|
|
797
|
+
{showNetworkFields &&
|
|
798
|
+
this.renderSelect(
|
|
799
|
+
'VNet',
|
|
800
|
+
loadBalancer.selectedVnet ? getAzureVnetSelectValue(loadBalancer.selectedVnet) : '',
|
|
801
|
+
selectedVnets,
|
|
802
|
+
this.selectedVnetChanged,
|
|
803
|
+
getAzureVnetSelectLabel,
|
|
804
|
+
getAzureVnetSelectValue,
|
|
805
|
+
)}
|
|
806
|
+
{showNetworkFields &&
|
|
807
|
+
this.renderSelect(
|
|
808
|
+
'Subnet',
|
|
809
|
+
loadBalancer.selectedSubnet?.name || loadBalancer.subnet,
|
|
810
|
+
selectedSubnets,
|
|
811
|
+
this.selectedSubnetChanged,
|
|
812
|
+
)}
|
|
813
|
+
{this.renderInput('DNS Name', 'dnsName')}
|
|
814
|
+
{showSkuField && this.renderSkuSelect()}
|
|
815
|
+
<h4>Listeners</h4>
|
|
816
|
+
{(loadBalancer.loadBalancingRules || []).map((rule: any, index: number) => (
|
|
817
|
+
<div className="well" key={index}>
|
|
818
|
+
<div className="form-group">
|
|
819
|
+
<label className="col-md-3 control-label">Protocol</label>
|
|
820
|
+
<div className="col-md-7">
|
|
821
|
+
<select
|
|
822
|
+
className="form-control input-sm"
|
|
823
|
+
value={rule.protocol || listenerOptions[0]}
|
|
824
|
+
onChange={(event) => this.updateRule(index, 'protocol', event.target.value)}
|
|
825
|
+
>
|
|
826
|
+
{listenerOptions.map((protocol) => (
|
|
827
|
+
<option key={protocol} value={protocol}>
|
|
828
|
+
{protocol}
|
|
829
|
+
</option>
|
|
830
|
+
))}
|
|
831
|
+
</select>
|
|
832
|
+
</div>
|
|
833
|
+
</div>
|
|
834
|
+
<div className="form-group">
|
|
835
|
+
<label className="col-md-3 control-label">External Port</label>
|
|
836
|
+
<div className="col-md-3">
|
|
837
|
+
<input
|
|
838
|
+
className="form-control input-sm"
|
|
839
|
+
min={0}
|
|
840
|
+
type="number"
|
|
841
|
+
value={formatInputValue(rule.externalPort)}
|
|
842
|
+
onChange={(event) =>
|
|
843
|
+
this.updateRule(index, 'externalPort', parseOptionalNumber(event.target.value))
|
|
844
|
+
}
|
|
845
|
+
/>
|
|
846
|
+
</div>
|
|
847
|
+
<label className="col-md-2 control-label">Backend Port</label>
|
|
848
|
+
<div className="col-md-3">
|
|
849
|
+
<input
|
|
850
|
+
className="form-control input-sm"
|
|
851
|
+
min={0}
|
|
852
|
+
type="number"
|
|
853
|
+
value={formatInputValue(rule.backendPort)}
|
|
854
|
+
onChange={(event) =>
|
|
855
|
+
this.updateRule(index, 'backendPort', parseOptionalNumber(event.target.value))
|
|
856
|
+
}
|
|
857
|
+
/>
|
|
858
|
+
</div>
|
|
859
|
+
</div>
|
|
860
|
+
<button className="btn btn-link" type="button" onClick={() => this.removeListener(index)}>
|
|
861
|
+
Remove Listener
|
|
862
|
+
</button>
|
|
863
|
+
</div>
|
|
864
|
+
))}
|
|
865
|
+
<button className="btn btn-default" type="button" onClick={this.addListener}>
|
|
866
|
+
Add Listener
|
|
867
|
+
</button>
|
|
868
|
+
<h4>Health Check</h4>
|
|
869
|
+
<div className="form-group">
|
|
870
|
+
<label className="col-md-3 control-label">Probe Protocol</label>
|
|
871
|
+
<div className="col-md-7">
|
|
872
|
+
<select
|
|
873
|
+
className="form-control input-sm"
|
|
874
|
+
value={probe.probeProtocol || probeOptions[0]}
|
|
875
|
+
onChange={(event) => this.updateProbe('probeProtocol', event.target.value)}
|
|
876
|
+
>
|
|
877
|
+
{probeOptions.map((protocol) => (
|
|
878
|
+
<option key={protocol} value={protocol}>
|
|
879
|
+
{protocol}
|
|
880
|
+
</option>
|
|
881
|
+
))}
|
|
882
|
+
</select>
|
|
883
|
+
</div>
|
|
884
|
+
</div>
|
|
885
|
+
<div className="form-group">
|
|
886
|
+
<label className="col-md-3 control-label">Probe Port</label>
|
|
887
|
+
<div className="col-md-7">
|
|
888
|
+
<input
|
|
889
|
+
className="form-control input-sm"
|
|
890
|
+
min={0}
|
|
891
|
+
type="number"
|
|
892
|
+
value={formatInputValue(probe.probePort)}
|
|
893
|
+
onChange={(event) => this.updateProbe('probePort', parseOptionalNumber(event.target.value))}
|
|
894
|
+
/>
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
{probe.probeProtocol !== 'TCP' && (
|
|
898
|
+
<div className="form-group">
|
|
899
|
+
<label className="col-md-3 control-label">Probe Path</label>
|
|
900
|
+
<div className="col-md-7">
|
|
901
|
+
<input
|
|
902
|
+
className="form-control input-sm"
|
|
903
|
+
value={probe.probePath || ''}
|
|
904
|
+
onChange={(event) => this.updateProbe('probePath', event.target.value)}
|
|
905
|
+
/>
|
|
906
|
+
</div>
|
|
907
|
+
</div>
|
|
908
|
+
)}
|
|
909
|
+
<h4>Advanced Settings</h4>
|
|
910
|
+
{this.renderProbeInput('Probe Interval', 'probeInterval', 'number')}
|
|
911
|
+
{this.renderProbeInput('Unhealthy Threshold', 'unhealthyThreshold', 'number')}
|
|
912
|
+
{isAzureLoadBalancerType(loadBalancerType) &&
|
|
913
|
+
this.renderFirstRuleInput('Idle Timeout', 'idleTimeout', 'number')}
|
|
914
|
+
{isAzureLoadBalancerType(loadBalancerType) && (
|
|
915
|
+
<div className="form-group">
|
|
916
|
+
<label className="col-md-3 control-label">Session Persistence</label>
|
|
917
|
+
<div className="col-md-7">
|
|
918
|
+
<select
|
|
919
|
+
className="form-control input-sm"
|
|
920
|
+
value={loadBalancer.sessionPersistence || 'None'}
|
|
921
|
+
onChange={(event) => this.updateField('sessionPersistence', event.target.value)}
|
|
922
|
+
>
|
|
923
|
+
<option value="None">None</option>
|
|
924
|
+
<option value="Client IP">Client IP</option>
|
|
925
|
+
<option value="Client IP and protocol">Client IP and protocol</option>
|
|
926
|
+
</select>
|
|
927
|
+
</div>
|
|
928
|
+
</div>
|
|
929
|
+
)}
|
|
930
|
+
{!isAzureLoadBalancerType(loadBalancerType) && this.renderProbeInput('Probe Timeout', 'timeout', 'number')}
|
|
931
|
+
</form>
|
|
932
|
+
{!!validationErrors.length && (
|
|
933
|
+
<div className="alert alert-danger">
|
|
934
|
+
{validationErrors.map((error) => (
|
|
935
|
+
<div key={error}>{error}</div>
|
|
936
|
+
))}
|
|
937
|
+
</div>
|
|
938
|
+
)}
|
|
939
|
+
</Modal.Body>
|
|
940
|
+
<Modal.Footer>
|
|
941
|
+
<button className="btn btn-default" disabled={submitting} onClick={dismissModal} type="button">
|
|
942
|
+
Cancel
|
|
943
|
+
</button>
|
|
944
|
+
<SubmitButton
|
|
945
|
+
isDisabled={(isNew && !accountsLoaded) || submitting || !!validationErrors.length}
|
|
946
|
+
isNew={isNew}
|
|
947
|
+
label={isNew ? 'Create' : 'Update'}
|
|
948
|
+
onClick={this.submit}
|
|
949
|
+
submitting={submitting}
|
|
950
|
+
/>
|
|
951
|
+
</Modal.Footer>
|
|
952
|
+
</>
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
export const AzureLoadBalancerModal = Object.assign(
|
|
958
|
+
withRouter<IAzureLoadBalancerModalProps & IRouterInjectedProps>(AzureLoadBalancerModalComponent),
|
|
959
|
+
{ show: AzureLoadBalancerModalComponent.show },
|
|
960
|
+
);
|