@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.
Files changed (215) hide show
  1. package/dist/azure.module.d.ts +3 -1
  2. package/dist/image/image.reader.d.ts +4 -2
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/instance/azureInstanceType.service.d.ts +6 -2
  6. package/dist/instance/details/AzureInstanceDetails.d.ts +67 -0
  7. package/dist/loadBalancer/configure/AzureLoadBalancerChoiceModal.d.ts +10 -5
  8. package/dist/loadBalancer/configure/AzureLoadBalancerModal.d.ts +102 -0
  9. package/dist/loadBalancer/details/azureLoadBalancerDetails.d.ts +26 -0
  10. package/dist/loadBalancer/loadBalancer.transformer.d.ts +45 -2
  11. package/dist/pipeline/stages/AzureAccountRegionClusterSelector.d.ts +32 -0
  12. package/dist/pipeline/stages/bake/azureBakeStage.d.ts +46 -2
  13. package/dist/pipeline/stages/destroyAsg/azureDestroyAsgStage.d.ts +14 -2
  14. package/dist/pipeline/stages/disableAsg/azureDisableAsgStage.d.ts +4 -2
  15. package/dist/pipeline/stages/enableAsg/azureEnableAsgStage.d.ts +4 -2
  16. package/dist/securityGroup/configure/AzureSecurityGroupModal.d.ts +70 -0
  17. package/dist/securityGroup/details/AzureSecurityGroupDetails.d.ts +51 -0
  18. package/dist/securityGroup/securityGroup.reader.d.ts +3 -2
  19. package/dist/securityGroup/securityGroup.transformer.d.ts +3 -2
  20. package/dist/securityGroup/securityGroup.write.service.d.ts +9 -2
  21. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +88 -2
  22. package/dist/serverGroup/configure/serverGroupConfiguration.service.d.ts +30 -2
  23. package/dist/serverGroup/configure/wizard/AzureCloneServerGroupModal.d.ts +44 -0
  24. package/dist/serverGroup/configure/wizard/pages/ServerGroupAdvancedSettings.d.ts +8 -0
  25. package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +12 -0
  26. package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacityAndZones.d.ts +11 -0
  27. package/dist/serverGroup/configure/wizard/pages/ServerGroupHealthSettings.d.ts +10 -0
  28. package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +12 -0
  29. package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +9 -0
  30. package/dist/serverGroup/configure/wizard/pages/ServerGroupLoadBalancers.d.ts +14 -0
  31. package/dist/serverGroup/configure/wizard/pages/ServerGroupNetworkSettings.d.ts +10 -0
  32. package/dist/serverGroup/configure/wizard/pages/ServerGroupSecurityGroups.d.ts +6 -0
  33. package/dist/serverGroup/configure/wizard/pages/ServerGroupTags.d.ts +9 -0
  34. package/dist/serverGroup/configure/wizard/pages/common.d.ts +14 -0
  35. package/dist/serverGroup/configure/wizard/pages/index.d.ts +10 -0
  36. package/dist/serverGroup/details/azureServerGroupDetails.d.ts +13 -0
  37. package/dist/serverGroup/details/rollback/RollbackServerGroupModal.d.ts +6 -4
  38. package/dist/serverGroup/serverGroup.transformer.d.ts +61 -2
  39. package/dist/validation/applicationName.validator.d.ts +6 -2
  40. package/package.json +5 -8
  41. package/src/azure.module.spec.ts +921 -0
  42. package/src/azure.module.ts +56 -68
  43. package/src/image/image.reader.js +4 -15
  44. package/src/image/image.reader.spec.js +4 -7
  45. package/src/instance/azureInstanceType.service.js +8 -16
  46. package/src/instance/azureInstanceType.service.spec.js +5 -13
  47. package/src/instance/details/AzureInstanceDetails.spec.tsx +364 -0
  48. package/src/instance/details/AzureInstanceDetails.tsx +533 -0
  49. package/src/loadBalancer/configure/AzureLoadBalancerChoiceModal.spec.tsx +125 -0
  50. package/src/loadBalancer/configure/AzureLoadBalancerChoiceModal.tsx +46 -26
  51. package/src/loadBalancer/configure/AzureLoadBalancerModal.spec.tsx +658 -0
  52. package/src/loadBalancer/configure/AzureLoadBalancerModal.tsx +960 -0
  53. package/src/loadBalancer/details/azureLoadBalancerDetails.spec.tsx +470 -0
  54. package/src/loadBalancer/details/azureLoadBalancerDetails.tsx +412 -0
  55. package/src/loadBalancer/loadBalancer.transformer.js +83 -93
  56. package/src/pipeline/stages/AzureAccountRegionClusterSelector.tsx +238 -0
  57. package/src/pipeline/stages/bake/azureBakeStage.tsx +723 -0
  58. package/src/pipeline/stages/destroyAsg/azureDestroyAsgStage.tsx +128 -0
  59. package/src/pipeline/stages/disableAsg/azureDisableAsgStage.tsx +92 -0
  60. package/src/pipeline/stages/enableAsg/azureEnableAsgStage.tsx +87 -0
  61. package/src/routerConsumers.spec.tsx +69 -0
  62. package/src/securityGroup/configure/AzureSecurityGroupModal.spec.tsx +558 -0
  63. package/src/securityGroup/configure/AzureSecurityGroupModal.tsx +782 -0
  64. package/src/securityGroup/details/AzureSecurityGroupDetails.spec.tsx +291 -0
  65. package/src/securityGroup/details/AzureSecurityGroupDetails.tsx +354 -0
  66. package/src/securityGroup/securityGroup.reader.js +3 -13
  67. package/src/securityGroup/securityGroup.transformer.js +3 -13
  68. package/src/securityGroup/securityGroup.write.service.spec.ts +105 -0
  69. package/src/securityGroup/securityGroup.write.service.ts +73 -0
  70. package/src/serverGroup/configure/serverGroupCommandBuilder.service.js +167 -166
  71. package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +63 -0
  72. package/src/serverGroup/configure/serverGroupConfiguration.service.js +325 -309
  73. package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +57 -53
  74. package/src/serverGroup/configure/wizard/AzureCloneServerGroupModal.spec.tsx +758 -0
  75. package/src/serverGroup/configure/wizard/AzureCloneServerGroupModal.tsx +349 -0
  76. package/src/serverGroup/configure/wizard/pages/ServerGroupAdvancedSettings.tsx +208 -0
  77. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +122 -0
  78. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacityAndZones.tsx +80 -0
  79. package/src/serverGroup/configure/wizard/pages/ServerGroupHealthSettings.tsx +97 -0
  80. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +151 -0
  81. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +41 -0
  82. package/src/serverGroup/configure/wizard/pages/ServerGroupLoadBalancers.tsx +214 -0
  83. package/src/serverGroup/configure/wizard/pages/ServerGroupNetworkSettings.tsx +119 -0
  84. package/src/serverGroup/configure/wizard/pages/ServerGroupSecurityGroups.tsx +54 -0
  85. package/src/serverGroup/configure/wizard/pages/ServerGroupTags.tsx +40 -0
  86. package/src/serverGroup/configure/wizard/pages/common.tsx +72 -0
  87. package/src/serverGroup/configure/wizard/pages/index.ts +10 -0
  88. package/src/serverGroup/details/azureServerGroupDetails.spec.tsx +263 -0
  89. package/src/serverGroup/details/azureServerGroupDetails.tsx +318 -0
  90. package/src/serverGroup/details/rollback/RollbackServerGroupModal.tsx +24 -30
  91. package/src/serverGroup/serverGroup.transformer.js +11 -19
  92. package/src/serverGroup/serverGroup.transformer.spec.js +2 -6
  93. package/src/validation/applicationName.validator.js +14 -29
  94. package/dist/instance/details/instance.details.controller.d.ts +0 -2
  95. package/dist/loadBalancer/configure/createLoadBalancer.controller.d.ts +0 -2
  96. package/dist/loadBalancer/details/loadBalancerDetail.controller.d.ts +0 -2
  97. package/dist/pipeline/stages/bake/bakeExecutionDetails.controller.d.ts +0 -2
  98. package/dist/securityGroup/clone/cloneSecurityGroup.controller.d.ts +0 -2
  99. package/dist/securityGroup/configure/CreateSecurityGroupCtrl.d.ts +0 -2
  100. package/dist/securityGroup/configure/EditSecurityGroupCtrl.d.ts +0 -2
  101. package/dist/securityGroup/details/securityGroupDetail.controller.d.ts +0 -2
  102. package/dist/serverGroup/configure/serverGroup.configure.azure.module.d.ts +0 -2
  103. package/dist/serverGroup/configure/wizard/CloneServerGroup.azure.controller.d.ts +0 -2
  104. package/dist/serverGroup/configure/wizard/ServerGroupInstanceArchetype.controller.d.ts +0 -2
  105. package/dist/serverGroup/configure/wizard/ServerGroupInstanceType.controller.d.ts +0 -2
  106. package/dist/serverGroup/configure/wizard/advancedSettings/ServerGroupAdvancedSettings.controller.d.ts +0 -2
  107. package/dist/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.d.ts +0 -2
  108. package/dist/serverGroup/configure/wizard/basicSettings/ServerGroupBasicSettings.controller.d.ts +0 -2
  109. package/dist/serverGroup/configure/wizard/basicSettings/image.regional.filter.d.ts +0 -2
  110. package/dist/serverGroup/configure/wizard/capacity/capacitySelector.directive.d.ts +0 -2
  111. package/dist/serverGroup/configure/wizard/healthSettings/ServerGroupHealthSettings.controller.d.ts +0 -2
  112. package/dist/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.d.ts +0 -2
  113. package/dist/serverGroup/configure/wizard/image/ServerGroupImageSettings.controller.d.ts +0 -2
  114. package/dist/serverGroup/configure/wizard/image/imageSettingsSelector.directive.d.ts +0 -2
  115. package/dist/serverGroup/configure/wizard/loadBalancers/ServerGroupLoadBalancers.controller.d.ts +0 -2
  116. package/dist/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.d.ts +0 -2
  117. package/dist/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettings.controller.d.ts +0 -2
  118. package/dist/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.d.ts +0 -2
  119. package/dist/serverGroup/configure/wizard/securityGroup/ServerGroupSecurityGroups.controller.d.ts +0 -2
  120. package/dist/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.d.ts +0 -2
  121. package/dist/serverGroup/configure/wizard/tags/tagsSelector.directive.d.ts +0 -2
  122. package/dist/serverGroup/configure/wizard/zones/zoneSelector.directive.d.ts +0 -2
  123. package/dist/serverGroup/details/serverGroup.details.module.d.ts +0 -2
  124. package/dist/serverGroup/details/serverGroupDetails.azure.controller.d.ts +0 -2
  125. package/src/instance/details/instance.details.controller.js +0 -371
  126. package/src/instance/details/instance.details.controller.spec.js +0 -129
  127. package/src/instance/details/instanceDetails.html +0 -43
  128. package/src/loadBalancer/configure/advancedSettings.html +0 -95
  129. package/src/loadBalancer/configure/createLoadBalancer.controller.js +0 -275
  130. package/src/loadBalancer/configure/createLoadBalancer.controller.spec.js +0 -39
  131. package/src/loadBalancer/configure/createLoadBalancer.html +0 -25
  132. package/src/loadBalancer/configure/createLoadBalancerProperties.html +0 -128
  133. package/src/loadBalancer/configure/editLoadBalancer.html +0 -26
  134. package/src/loadBalancer/configure/healthCheck.html +0 -74
  135. package/src/loadBalancer/configure/listeners.html +0 -50
  136. package/src/loadBalancer/details/loadBalancerDetail.controller.js +0 -181
  137. package/src/loadBalancer/details/loadBalancerDetail.controller.spec.js +0 -39
  138. package/src/loadBalancer/details/loadBalancerDetail.html +0 -128
  139. package/src/pipeline/stages/bake/azureBakeStage.js +0 -276
  140. package/src/pipeline/stages/bake/bakeExecutionDetails.controller.js +0 -40
  141. package/src/pipeline/stages/bake/bakeExecutionDetails.html +0 -50
  142. package/src/pipeline/stages/bake/bakeStage.html +0 -187
  143. package/src/pipeline/stages/destroyAsg/azureDestroyAsgStage.js +0 -73
  144. package/src/pipeline/stages/destroyAsg/destroyAsgStage.html +0 -9
  145. package/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html +0 -1
  146. package/src/pipeline/stages/disableAsg/azureDisableAsgStage.js +0 -65
  147. package/src/pipeline/stages/disableAsg/disableAsgStage.html +0 -11
  148. package/src/pipeline/stages/disableAsg/disableAsgStepLabel.html +0 -1
  149. package/src/pipeline/stages/enableAsg/azureEnableAsgStage.js +0 -70
  150. package/src/pipeline/stages/enableAsg/enableAsgStage.html +0 -11
  151. package/src/pipeline/stages/enableAsg/enableAsgStepLabel.html +0 -1
  152. package/src/securityGroup/clone/cloneSecurityGroup.controller.js +0 -162
  153. package/src/securityGroup/clone/cloneSecurityGroup.html +0 -8
  154. package/src/securityGroup/configure/CreateSecurityGroup.controller.spec.js +0 -78
  155. package/src/securityGroup/configure/CreateSecurityGroupCtrl.js +0 -265
  156. package/src/securityGroup/configure/EditSecurityGroupCtrl.js +0 -222
  157. package/src/securityGroup/configure/configSecurityGroup.mixin.controller.js +0 -262
  158. package/src/securityGroup/configure/createSecurityGroup.html +0 -20
  159. package/src/securityGroup/configure/createSecurityGroupIngress.html +0 -75
  160. package/src/securityGroup/configure/createSecurityGroupProperties.html +0 -88
  161. package/src/securityGroup/configure/editSecurityGroup.html +0 -19
  162. package/src/securityGroup/details/securityGroupDetail.controller.js +0 -142
  163. package/src/securityGroup/details/securityGroupDetail.html +0 -78
  164. package/src/securityGroup/securityGroup.write.service.js +0 -57
  165. package/src/serverGroup/configure/serverGroup.configure.azure.module.js +0 -47
  166. package/src/serverGroup/configure/wizard/CloneServerGroup.azure.controller.js +0 -247
  167. package/src/serverGroup/configure/wizard/ServerGroupInstanceArchetype.controller.js +0 -35
  168. package/src/serverGroup/configure/wizard/ServerGroupInstanceType.controller.js +0 -15
  169. package/src/serverGroup/configure/wizard/advancedSettings/ServerGroupAdvancedSettings.controller.js +0 -25
  170. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.html +0 -5
  171. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.html +0 -138
  172. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.js +0 -43
  173. package/src/serverGroup/configure/wizard/basicSettings/ServerGroupBasicSettings.controller.js +0 -71
  174. package/src/serverGroup/configure/wizard/basicSettings/basicSettings.html +0 -131
  175. package/src/serverGroup/configure/wizard/basicSettings/image.regional.filter.js +0 -15
  176. package/src/serverGroup/configure/wizard/basicSettings/image.regional.filter.spec.js +0 -42
  177. package/src/serverGroup/configure/wizard/capacity/capacity.html +0 -5
  178. package/src/serverGroup/configure/wizard/capacity/capacitySelector.directive.html +0 -20
  179. package/src/serverGroup/configure/wizard/capacity/capacitySelector.directive.js +0 -21
  180. package/src/serverGroup/configure/wizard/capacity/zones.html +0 -5
  181. package/src/serverGroup/configure/wizard/healthSettings/ServerGroupHealthSettings.controller.js +0 -38
  182. package/src/serverGroup/configure/wizard/healthSettings/healthSettings.html +0 -5
  183. package/src/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.html +0 -32
  184. package/src/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.js +0 -22
  185. package/src/serverGroup/configure/wizard/image/ServerGroupImageSettings.controller.js +0 -34
  186. package/src/serverGroup/configure/wizard/image/imageSettings.html +0 -5
  187. package/src/serverGroup/configure/wizard/image/imageSettingsSelector.directive.html +0 -33
  188. package/src/serverGroup/configure/wizard/image/imageSettingsSelector.directive.js +0 -22
  189. package/src/serverGroup/configure/wizard/instanceArchetype.html +0 -33
  190. package/src/serverGroup/configure/wizard/instanceType/instanceType.html +0 -10
  191. package/src/serverGroup/configure/wizard/instanceType.html +0 -33
  192. package/src/serverGroup/configure/wizard/loadBalancers/ServerGroupLoadBalancers.controller.js +0 -137
  193. package/src/serverGroup/configure/wizard/loadBalancers/loadBalancers.html +0 -3
  194. package/src/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.html +0 -41
  195. package/src/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.js +0 -22
  196. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettings.controller.js +0 -44
  197. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.html +0 -54
  198. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.js +0 -19
  199. package/src/serverGroup/configure/wizard/networkSettings/networkSettings.html +0 -3
  200. package/src/serverGroup/configure/wizard/securityGroup/ServerGroupSecurityGroups.controller.js +0 -28
  201. package/src/serverGroup/configure/wizard/securityGroup/securityGroups.html +0 -3
  202. package/src/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.html +0 -40
  203. package/src/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.js +0 -38
  204. package/src/serverGroup/configure/wizard/serverGroupWizard.html +0 -61
  205. package/src/serverGroup/configure/wizard/tags/tags.html +0 -5
  206. package/src/serverGroup/configure/wizard/tags/tagsSelector.directive.html +0 -16
  207. package/src/serverGroup/configure/wizard/tags/tagsSelector.directive.js +0 -30
  208. package/src/serverGroup/configure/wizard/templateSelection.html +0 -9
  209. package/src/serverGroup/configure/wizard/zones/zoneSelector.directive.html +0 -21
  210. package/src/serverGroup/configure/wizard/zones/zoneSelector.directive.js +0 -28
  211. package/src/serverGroup/details/serverGroup.details.module.js +0 -11
  212. package/src/serverGroup/details/serverGroupDetails.azure.controller.js +0 -260
  213. package/src/serverGroup/details/serverGroupDetails.html +0 -98
  214. package/src/subnet/subnetSelectField.directive.html +0 -28
  215. 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
+ );