@spinnaker/azure 2026.2.2 → 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,782 @@
1
+ import React from 'react';
2
+ import { Modal } from 'react-bootstrap';
3
+
4
+ import type { Application, IModalComponentProps, IRouterInjectedProps, ISecurityGroup } from '@spinnaker/core';
5
+ import {
6
+ AccountService,
7
+ ModalClose,
8
+ NetworkReader,
9
+ noop,
10
+ ReactModal,
11
+ SubmitButton,
12
+ TaskMonitor,
13
+ TaskMonitorWrapper,
14
+ withRouter,
15
+ } from '@spinnaker/core';
16
+
17
+ import { AzureSecurityGroupWriter } from '../securityGroup.write.service';
18
+
19
+ export type AzureSecurityGroupModalMode = 'create' | 'edit' | 'clone';
20
+
21
+ interface IAzureSecurityGroupModalProps extends IModalComponentProps {
22
+ app?: Application;
23
+ application?: Application;
24
+ credentials?: string;
25
+ mode: AzureSecurityGroupModalMode;
26
+ region?: string;
27
+ securityGroup?: any;
28
+ }
29
+
30
+ interface IAzureSecurityGroupModalState {
31
+ accounts: any[];
32
+ regions: any[];
33
+ securityGroup: any;
34
+ selectedSubnets: IAzureSubnet[];
35
+ selectedVnets: IAzureVnet[];
36
+ taskMonitor: TaskMonitor;
37
+ }
38
+
39
+ interface IAzureVnet {
40
+ account: string;
41
+ name: string;
42
+ region: string;
43
+ resourceGroup?: string;
44
+ subnets?: IAzureSubnet[];
45
+ }
46
+
47
+ interface IAzureSubnet {
48
+ name: string;
49
+ }
50
+
51
+ const defaultRule = {
52
+ access: 'Allow',
53
+ destinationAddressPrefix: '*',
54
+ destinationPortRange: '*',
55
+ destinationPortRanges: [] as string[],
56
+ destinationPorts: ['*'],
57
+ destPortRanges: '*',
58
+ direction: 'Inbound',
59
+ protocol: 'Tcp',
60
+ sourceAddressPrefix: '*',
61
+ sourceAddressPrefixes: [] as string[],
62
+ sourceCidrs: ['*'],
63
+ sourceIPCIDRRanges: '*',
64
+ sourcePortRange: '*',
65
+ };
66
+
67
+ function parseDelimitedList(value: any): string[] {
68
+ if (Array.isArray(value)) {
69
+ return value.map((item) => String(item).trim()).filter(Boolean);
70
+ }
71
+ if (value === null || value === undefined || value === '') {
72
+ return [];
73
+ }
74
+ return String(value)
75
+ .split(/[\n,]+/)
76
+ .map((item) => item.trim())
77
+ .filter(Boolean);
78
+ }
79
+
80
+ function firstPopulatedList(values: any[], fallback: string[] = []): string[] {
81
+ for (const value of values) {
82
+ const parsed = parseDelimitedList(value);
83
+ if (parsed.length) {
84
+ return parsed;
85
+ }
86
+ }
87
+ return fallback;
88
+ }
89
+
90
+ function hasOwnProperty(value: any, property: string): boolean {
91
+ return Object.prototype.hasOwnProperty.call(value, property);
92
+ }
93
+
94
+ function destinationPortsForRule(rule: any, fallback: string[] = []): string[] {
95
+ if (hasOwnProperty(rule, 'destinationPorts')) {
96
+ return parseDelimitedList(rule.destinationPorts);
97
+ }
98
+
99
+ return firstPopulatedList(
100
+ [rule.destPortRanges, rule.destinationPortRanges, rule.destinationPortRange, rule.destinationPortRangeModel],
101
+ fallback,
102
+ );
103
+ }
104
+
105
+ function sourceCidrsForRule(rule: any, fallback: string[] = []): string[] {
106
+ if (hasOwnProperty(rule, 'sourceCidrs')) {
107
+ return parseDelimitedList(rule.sourceCidrs);
108
+ }
109
+
110
+ return firstPopulatedList(
111
+ [rule.sourceIPCIDRRanges, rule.sourceAddressPrefixes, rule.sourceAddressPrefix, rule.sourceAddressPrefixModel],
112
+ fallback,
113
+ );
114
+ }
115
+
116
+ function isValidPortRange(value: string): boolean {
117
+ if (value === '*') {
118
+ return true;
119
+ }
120
+
121
+ const bounds = value.split('-');
122
+ if (bounds.length > 2) {
123
+ return false;
124
+ }
125
+
126
+ const ports = bounds.map((bound) => {
127
+ if (!/^\d+$/.test(bound)) {
128
+ return null;
129
+ }
130
+ const port = Number(bound);
131
+ return port >= 0 && port <= 65535 ? port : null;
132
+ });
133
+ if (ports.some((port) => port === null)) {
134
+ return false;
135
+ }
136
+
137
+ return bounds.length === 1 || (ports[0] as number) <= (ports[1] as number);
138
+ }
139
+
140
+ function isValidSourceCidr(value: string): boolean {
141
+ if (value === '*') {
142
+ return true;
143
+ }
144
+
145
+ const match = value.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,2}))?$/);
146
+ if (!match) {
147
+ return false;
148
+ }
149
+
150
+ const octets = match.slice(1, 5).map(Number);
151
+ const prefix = match[5] ? Number(match[5]) : null;
152
+ return octets.every((octet) => octet >= 0 && octet <= 255) && (prefix === null || (prefix >= 0 && prefix <= 32));
153
+ }
154
+
155
+ function buildAzureSecurityGroupName(applicationName: string, detail: any): string {
156
+ const detailText = String(detail || '').trim();
157
+ return detailText ? `${applicationName}-${detailText}` : applicationName;
158
+ }
159
+
160
+ function detailFromSecurityGroupName(name: string, applicationName: string): string {
161
+ const prefix = `${applicationName}-`;
162
+ return name?.startsWith(prefix) ? name.slice(prefix.length) : '';
163
+ }
164
+
165
+ function securityGroupAccount(securityGroup: any): string {
166
+ return String(
167
+ securityGroup.accountId || securityGroup.account || securityGroup.accountName || securityGroup.credentials || '',
168
+ );
169
+ }
170
+
171
+ function matchesDefinedCoordinate(left: any, right: any): boolean {
172
+ if (!left || !right) {
173
+ return true;
174
+ }
175
+ return String(left).toLowerCase() === String(right).toLowerCase();
176
+ }
177
+
178
+ function getExistingSecurityGroups(application?: Application): any[] {
179
+ const securityGroups = application?.securityGroups?.data || application?.getDataSource?.('securityGroups')?.data;
180
+ return Array.isArray(securityGroups) ? securityGroups : [];
181
+ }
182
+
183
+ function isDuplicateSecurityGroupName(securityGroup: any, application?: Application): boolean {
184
+ const name = String(securityGroup.name || '').toLowerCase();
185
+ const account = securityGroupAccount(securityGroup);
186
+ const region = securityGroup.region;
187
+
188
+ if (!name || !account) {
189
+ return false;
190
+ }
191
+
192
+ return getExistingSecurityGroups(application).some((existingSecurityGroup) => {
193
+ return (
194
+ String(existingSecurityGroup.name || '').toLowerCase() === name &&
195
+ matchesDefinedCoordinate(securityGroupAccount(existingSecurityGroup), account) &&
196
+ matchesDefinedCoordinate(existingSecurityGroup.region, region)
197
+ );
198
+ });
199
+ }
200
+
201
+ export function isAzureSecurityGroupValid(
202
+ securityGroup: any,
203
+ mode: AzureSecurityGroupModalMode = 'edit',
204
+ application?: Application,
205
+ ): boolean {
206
+ if (!securityGroup.name || !securityGroup.accountId || !securityGroup.region) {
207
+ return false;
208
+ }
209
+
210
+ if (mode !== 'edit') {
211
+ if (!String(securityGroup.detail || '').trim()) {
212
+ return false;
213
+ }
214
+ if (isDuplicateSecurityGroupName(securityGroup, application)) {
215
+ return false;
216
+ }
217
+ }
218
+
219
+ return (securityGroup.securityRules || []).every((rule: any) => {
220
+ const destinationPorts = destinationPortsForRule(rule);
221
+ const sourceCidrs = sourceCidrsForRule(rule);
222
+
223
+ return (
224
+ destinationPorts.length > 0 &&
225
+ sourceCidrs.length > 0 &&
226
+ destinationPorts.every(isValidPortRange) &&
227
+ sourceCidrs.every(isValidSourceCidr)
228
+ );
229
+ });
230
+ }
231
+
232
+ function cloneRule(rule: any): any {
233
+ const destinationPorts = destinationPortsForRule(rule, defaultRule.destinationPorts);
234
+ const sourceCidrs = sourceCidrsForRule(rule, defaultRule.sourceCidrs);
235
+
236
+ return {
237
+ ...defaultRule,
238
+ ...rule,
239
+ destinationPortRangeModel: rule.destinationPortRangeModel || destinationPorts.join(','),
240
+ destinationPorts,
241
+ destPortRanges: rule.destPortRanges || destinationPorts.join(','),
242
+ sourceAddressPrefixModel: rule.sourceAddressPrefixModel || sourceCidrs.join(','),
243
+ sourceCidrs,
244
+ sourceIPCIDRRanges: rule.sourceIPCIDRRanges || sourceCidrs.join(','),
245
+ };
246
+ }
247
+
248
+ function normalizePriorities(rules: any[]): any[] {
249
+ return rules.map((rule, index) => ({ ...rule, priority: 100 + index }));
250
+ }
251
+
252
+ export function addSecurityRule(rules: any[]): any[] {
253
+ return normalizePriorities([...rules, { ...defaultRule, name: `rule-${rules.length + 1}` }]);
254
+ }
255
+
256
+ export function removeSecurityRule(rules: any[], index: number): any[] {
257
+ return normalizePriorities(rules.filter((_rule, ruleIndex) => ruleIndex !== index));
258
+ }
259
+
260
+ export function moveSecurityRule(rules: any[], fromIndex: number, toIndex: number): any[] {
261
+ const nextRules = [...rules];
262
+ const [rule] = nextRules.splice(fromIndex, 1);
263
+ nextRules.splice(toIndex, 0, rule);
264
+ return normalizePriorities(nextRules);
265
+ }
266
+
267
+ export function normalizeAzureSecurityGroupForSubmit(securityGroup: ISecurityGroup & Record<string, any>): any {
268
+ return {
269
+ ...securityGroup,
270
+ type: 'upsertSecurityGroup',
271
+ securityRules: (securityGroup.securityRules || []).map((rule: any, index: number) => {
272
+ const normalized = {
273
+ ...rule,
274
+ priority: rule.priority ?? 100 + index,
275
+ };
276
+ const destinationPorts = destinationPortsForRule(rule);
277
+ const sourceCidrs = sourceCidrsForRule(rule);
278
+
279
+ delete normalized.destinationPorts;
280
+ delete normalized.destPortRanges;
281
+ delete normalized.sourceCidrs;
282
+ delete normalized.sourceIPCIDRRanges;
283
+ delete normalized.destinationPortRange;
284
+ delete normalized.destinationPortRanges;
285
+ delete normalized.destinationPortRangeModel;
286
+ delete normalized.sourceAddressPrefix;
287
+ delete normalized.sourceAddressPrefixes;
288
+ delete normalized.sourceAddressPrefixModel;
289
+
290
+ if (destinationPorts.length > 1) {
291
+ normalized.destinationPortRanges = destinationPorts;
292
+ } else if (destinationPorts.length === 1) {
293
+ normalized.destinationPortRange = destinationPorts[0];
294
+ }
295
+
296
+ if (sourceCidrs.length > 1) {
297
+ normalized.sourceAddressPrefixes = sourceCidrs;
298
+ } else if (sourceCidrs.length === 1) {
299
+ normalized.sourceAddressPrefix = sourceCidrs[0];
300
+ }
301
+
302
+ return normalized;
303
+ }),
304
+ };
305
+ }
306
+
307
+ export function getAzureVnetSelectValue(vnet: IAzureVnet): string {
308
+ return `${vnet.name}|${vnet.resourceGroup || ''}`;
309
+ }
310
+
311
+ function getAzureVnetSelectLabel(vnet: IAzureVnet): string {
312
+ return vnet.resourceGroup ? `${vnet.name} (${vnet.resourceGroup})` : vnet.name;
313
+ }
314
+
315
+ function findAzureVnetBySelectValue(vnets: IAzureVnet[], value: string): IAzureVnet | null {
316
+ return vnets.find((vnet) => getAzureVnetSelectValue(vnet) === value) || null;
317
+ }
318
+
319
+ export function initializeAzureSecurityGroupForModal(
320
+ props: IAzureSecurityGroupModalProps,
321
+ applicationName: string,
322
+ ): any {
323
+ const source = props.securityGroup || {};
324
+ const selectedVnet = source.selectedVnet || null;
325
+ const selectedSubnet = source.selectedSubnet || null;
326
+ const accountId =
327
+ source.accountId || source.account || source.accountName || source.credentials || props.credentials || '';
328
+ const region = source.region || props.region || source.regions?.[0] || '';
329
+ const detail = source.detail || detailFromSecurityGroupName(source.name, applicationName);
330
+ const securityGroup = {
331
+ ...source,
332
+ accountId,
333
+ credentials: source.credentials || accountId,
334
+ detail,
335
+ name:
336
+ props.mode === 'edit'
337
+ ? source.name || buildAzureSecurityGroupName(applicationName, detail)
338
+ : buildAzureSecurityGroupName(applicationName, detail),
339
+ region,
340
+ securityRules: (source.securityRules || source.inboundRules || []).map(cloneRule),
341
+ selectedSubnet,
342
+ selectedVnet,
343
+ subnet: source.subnet || selectedSubnet?.name || '',
344
+ vnet: source.vnet || selectedVnet?.name || '',
345
+ vnetResourceGroup: source.vnetResourceGroup || selectedVnet?.resourceGroup || '',
346
+ vpcId: source.vpcId || '',
347
+ };
348
+
349
+ if (props.mode === 'clone') {
350
+ delete securityGroup.id;
351
+ }
352
+
353
+ return securityGroup;
354
+ }
355
+
356
+ export class AzureSecurityGroupModalComponent extends React.Component<
357
+ IAzureSecurityGroupModalProps & IRouterInjectedProps,
358
+ IAzureSecurityGroupModalState
359
+ > {
360
+ public static defaultProps: Partial<IAzureSecurityGroupModalProps> = {
361
+ closeModal: noop,
362
+ dismissModal: noop,
363
+ mode: 'create',
364
+ };
365
+
366
+ public static show(props: IAzureSecurityGroupModalProps): Promise<any> {
367
+ return ReactModal.show(AzureSecurityGroupModal, props, { dialogClassName: 'modal-lg' });
368
+ }
369
+
370
+ private mounted = false;
371
+ private applicationRefreshUnsubscribe?: () => void;
372
+
373
+ constructor(props: IAzureSecurityGroupModalProps & IRouterInjectedProps) {
374
+ super(props);
375
+ const application = this.getApplication(props);
376
+ this.state = {
377
+ accounts: [],
378
+ regions: [],
379
+ securityGroup: initializeAzureSecurityGroupForModal(props, application.name),
380
+ selectedSubnets: props.securityGroup?.selectedVnet?.subnets || [],
381
+ selectedVnets: props.securityGroup?.selectedVnet ? [props.securityGroup.selectedVnet] : [],
382
+ taskMonitor: new TaskMonitor({
383
+ application,
384
+ title: `${props.mode === 'edit' ? 'Updating' : 'Creating'} your security group`,
385
+ onDismiss: () => props.dismissModal(),
386
+ onTaskComplete: this.onTaskComplete,
387
+ }),
388
+ };
389
+ }
390
+
391
+ public componentDidMount(): void {
392
+ this.mounted = true;
393
+ AccountService.listAccounts('azure').then((accounts) => this.setState({ accounts }));
394
+ if (this.state.securityGroup.accountId || this.state.securityGroup.credentials) {
395
+ this.accountUpdated(false);
396
+ } else if (this.state.securityGroup.region) {
397
+ this.vnetUpdated();
398
+ }
399
+ }
400
+
401
+ public componentWillUnmount(): void {
402
+ this.mounted = false;
403
+ this.clearApplicationRefreshSubscription();
404
+ }
405
+
406
+ private getApplication(props = this.props): Application {
407
+ return (props.app || props.application) as Application;
408
+ }
409
+
410
+ public onTaskComplete = (): void => {
411
+ const application = this.getApplication();
412
+
413
+ if (this.props.mode === 'edit') {
414
+ application.securityGroups?.refresh?.();
415
+ this.props.closeModal();
416
+ return;
417
+ }
418
+
419
+ const showNewSecurityGroup = (): void => {
420
+ this.clearApplicationRefreshSubscription();
421
+ if (!this.mounted) {
422
+ return;
423
+ }
424
+ const { securityGroup } = this.state;
425
+ this.props.closeModal();
426
+ this.props.stateService.go(
427
+ this.props.stateService.includes('**.firewallDetails') ? '^.firewallDetails' : '.firewallDetails',
428
+ {
429
+ accountId: securityGroup.credentials || securityGroup.accountId || securityGroup.accountName,
430
+ name: securityGroup.name,
431
+ provider: 'azure',
432
+ region: securityGroup.region,
433
+ vpcId: securityGroup.vpcId,
434
+ },
435
+ );
436
+ };
437
+
438
+ if (application.securityGroups?.onNextRefresh) {
439
+ this.clearApplicationRefreshSubscription();
440
+ this.applicationRefreshUnsubscribe = application.securityGroups.onNextRefresh(showNewSecurityGroup);
441
+ application.securityGroups.refresh?.();
442
+ } else {
443
+ application.securityGroups?.refresh?.();
444
+ showNewSecurityGroup();
445
+ }
446
+ };
447
+
448
+ private clearApplicationRefreshSubscription = (): void => {
449
+ this.applicationRefreshUnsubscribe?.();
450
+ this.applicationRefreshUnsubscribe = undefined;
451
+ };
452
+
453
+ private updateSecurityGroup(values: any, callback?: () => void): void {
454
+ this.setState((state) => ({ securityGroup: { ...state.securityGroup, ...values } }), callback);
455
+ }
456
+
457
+ private updateField = (field: string, value: string): void => {
458
+ if (field === 'detail') {
459
+ this.updateSecurityGroup({ detail: value, name: buildAzureSecurityGroupName(this.getApplication().name, value) });
460
+ return;
461
+ }
462
+
463
+ this.updateSecurityGroup(
464
+ { [field]: value },
465
+ field === 'accountId' ? this.accountUpdated : field === 'region' ? this.vnetUpdated : undefined,
466
+ );
467
+ };
468
+
469
+ public accountUpdated = (clearNetworkSelections = true): void => {
470
+ const account = this.state.securityGroup.accountId || this.state.securityGroup.credentials;
471
+ if (!account) {
472
+ return;
473
+ }
474
+
475
+ AccountService.getRegionsForAccount(account).then((regions) => {
476
+ const region = this.state.securityGroup.region || regions[0]?.name || regions[0] || '';
477
+ this.setState(
478
+ (state) => ({
479
+ regions,
480
+ securityGroup: { ...state.securityGroup, accountId: account, credentials: account, region },
481
+ }),
482
+ () => this.vnetUpdated(clearNetworkSelections),
483
+ );
484
+ });
485
+ };
486
+
487
+ public vnetUpdated = (clearNetworkSelections = true): void => {
488
+ const account = this.state.securityGroup.accountId || this.state.securityGroup.credentials;
489
+ const region = this.state.securityGroup.region;
490
+ if (clearNetworkSelections) {
491
+ this.setState((state) => ({
492
+ securityGroup: {
493
+ ...state.securityGroup,
494
+ selectedSubnet: null,
495
+ selectedVnet: null,
496
+ subnet: null,
497
+ vnet: null,
498
+ vnetResourceGroup: null,
499
+ vpcId: null,
500
+ },
501
+ selectedSubnets: [],
502
+ selectedVnets: [],
503
+ }));
504
+ }
505
+
506
+ if (!account || !region) {
507
+ return;
508
+ }
509
+
510
+ NetworkReader.listNetworks().then((networks: any) => {
511
+ const azureNetworks = Array.isArray(networks) ? networks : networks.azure || [];
512
+ this.setState({
513
+ selectedVnets: azureNetworks.filter((vnet: IAzureVnet) => vnet.account === account && vnet.region === region),
514
+ });
515
+ });
516
+ };
517
+
518
+ private selectedVnetChanged = (vnetValue: string): void => {
519
+ const selectedVnet = findAzureVnetBySelectValue(this.state.selectedVnets, vnetValue);
520
+ this.setState((state) => ({
521
+ securityGroup: {
522
+ ...state.securityGroup,
523
+ selectedSubnet: null,
524
+ selectedVnet,
525
+ subnet: '',
526
+ vnet: selectedVnet?.name || '',
527
+ vnetResourceGroup: selectedVnet?.resourceGroup || '',
528
+ vpcId: selectedVnet?.name || '',
529
+ },
530
+ selectedSubnets: selectedVnet?.subnets || [],
531
+ }));
532
+ };
533
+
534
+ private selectedSubnetChanged = (subnetName: string): void => {
535
+ const selectedSubnet = this.state.selectedSubnets.find((subnet) => subnet.name === subnetName) || null;
536
+ this.updateSecurityGroup({ selectedSubnet, subnet: selectedSubnet?.name || '' });
537
+ };
538
+
539
+ private updateRule = (index: number, field: string, value: any): void => {
540
+ const securityRules = [...(this.state.securityGroup.securityRules || [])];
541
+ securityRules[index] = { ...securityRules[index], [field]: value };
542
+ this.updateSecurityGroup({ securityRules });
543
+ };
544
+
545
+ private addRule = (): void => {
546
+ this.updateSecurityGroup({ securityRules: addSecurityRule(this.state.securityGroup.securityRules || []) });
547
+ };
548
+
549
+ private removeRule = (index: number): void => {
550
+ this.updateSecurityGroup({
551
+ securityRules: removeSecurityRule(this.state.securityGroup.securityRules || [], index),
552
+ });
553
+ };
554
+
555
+ private moveRule = (index: number, offset: number): void => {
556
+ this.updateSecurityGroup({
557
+ securityRules: moveSecurityRule(this.state.securityGroup.securityRules || [], index, index + offset),
558
+ });
559
+ };
560
+
561
+ public submit(): void {
562
+ const application = this.getApplication();
563
+ const securityGroup = normalizeAzureSecurityGroupForSubmit(this.state.securityGroup);
564
+ const descriptor = this.props.mode === 'edit' ? 'Update' : this.props.mode === 'clone' ? 'Clone' : 'Create';
565
+ const params = {
566
+ appName: application.name,
567
+ cloudProvider: 'azure',
568
+ credentials: securityGroup.accountId,
569
+ region: securityGroup.region,
570
+ regions: [securityGroup.region],
571
+ securityGroupName: securityGroup.name,
572
+ subnet: securityGroup.subnet || null,
573
+ vpcId: securityGroup.vpcId || 'null',
574
+ };
575
+
576
+ this.state.taskMonitor.submit(() =>
577
+ AzureSecurityGroupWriter.upsertSecurityGroup(securityGroup, application, descriptor, params),
578
+ );
579
+ }
580
+
581
+ private renderInput(label: string, field: string) {
582
+ return (
583
+ <div className="form-group">
584
+ <label className="col-md-3 control-label">{label}</label>
585
+ <div className="col-md-7">
586
+ <input
587
+ className="form-control input-sm"
588
+ value={this.state.securityGroup[field] || ''}
589
+ onChange={(e) => this.updateField(field, e.target.value)}
590
+ />
591
+ </div>
592
+ </div>
593
+ );
594
+ }
595
+
596
+ private renderSelect(
597
+ label: string,
598
+ value: string,
599
+ values: any[],
600
+ onChange: (value: string) => void,
601
+ getLabel = (item: any) => item.name || item,
602
+ getValue = getLabel,
603
+ ) {
604
+ return (
605
+ <div className="form-group">
606
+ <label className="col-md-3 control-label">{label}</label>
607
+ <div className="col-md-7">
608
+ <select className="form-control input-sm" value={value || ''} onChange={(e) => onChange(e.target.value)}>
609
+ <option value="">Select...</option>
610
+ {values.map((item) => (
611
+ <option key={getValue(item)} value={getValue(item)}>
612
+ {getLabel(item)}
613
+ </option>
614
+ ))}
615
+ </select>
616
+ </div>
617
+ </div>
618
+ );
619
+ }
620
+
621
+ private renderNamePreview(duplicateName: boolean) {
622
+ return (
623
+ <div className="form-group">
624
+ <div className={`col-md-12 well ${duplicateName ? 'alert-danger' : 'alert-info'}`}>
625
+ <strong>Your security group will be named:</strong> <span>{this.state.securityGroup.name}</span>
626
+ {duplicateName && <div>There is already a security group with that name in this account and region.</div>}
627
+ </div>
628
+ </div>
629
+ );
630
+ }
631
+
632
+ public render() {
633
+ const { dismissModal, mode } = this.props;
634
+ const { accounts, regions, securityGroup, selectedSubnets, selectedVnets, taskMonitor } = this.state;
635
+ const submitting = taskMonitor.submitting;
636
+ const title =
637
+ mode === 'edit'
638
+ ? `Edit ${securityGroup.name}`
639
+ : mode === 'clone'
640
+ ? `Clone ${securityGroup.name}`
641
+ : 'Create Security Group';
642
+ const showLocationFields = mode !== 'edit';
643
+ const duplicateName = showLocationFields && isDuplicateSecurityGroupName(securityGroup, this.getApplication());
644
+
645
+ return (
646
+ <>
647
+ <TaskMonitorWrapper monitor={taskMonitor} />
648
+ <ModalClose dismiss={dismissModal} />
649
+ <Modal.Header>
650
+ <Modal.Title>{title}</Modal.Title>
651
+ </Modal.Header>
652
+ <Modal.Body>
653
+ <form className="form-horizontal" onSubmit={(e) => e.preventDefault()}>
654
+ {showLocationFields && (
655
+ <>
656
+ {this.renderNamePreview(duplicateName)}
657
+ {this.renderInput('Detail', 'detail')}
658
+ {this.renderInput('Description', 'description')}
659
+ {this.renderSelect(
660
+ 'Account',
661
+ securityGroup.accountId,
662
+ accounts,
663
+ (value) => this.updateField('accountId', value),
664
+ (item) => item.name || item,
665
+ )}
666
+ {this.renderSelect(
667
+ 'Region',
668
+ securityGroup.region,
669
+ regions,
670
+ (value) => this.updateField('region', value),
671
+ (item) => item.name || item,
672
+ )}
673
+ {this.renderSelect(
674
+ 'VNet',
675
+ securityGroup.selectedVnet ? getAzureVnetSelectValue(securityGroup.selectedVnet) : '',
676
+ selectedVnets,
677
+ this.selectedVnetChanged,
678
+ getAzureVnetSelectLabel,
679
+ getAzureVnetSelectValue,
680
+ )}
681
+ {this.renderSelect(
682
+ 'Subnet',
683
+ securityGroup.selectedSubnet?.name || securityGroup.subnet,
684
+ selectedSubnets,
685
+ this.selectedSubnetChanged,
686
+ )}
687
+ </>
688
+ )}
689
+ <h4>Inbound Rules</h4>
690
+ {(securityGroup.securityRules || []).map((rule: any, index: number) => (
691
+ <div className="well" key={index}>
692
+ <div className="form-group">
693
+ <label className="col-md-3 control-label">Name</label>
694
+ <div className="col-md-7">
695
+ <input
696
+ className="form-control input-sm"
697
+ value={rule.name || ''}
698
+ onChange={(e) => this.updateRule(index, 'name', e.target.value)}
699
+ />
700
+ </div>
701
+ </div>
702
+ <div className="form-group">
703
+ <label className="col-md-3 control-label">Source CIDRs</label>
704
+ <div className="col-md-7">
705
+ <textarea
706
+ className="form-control input-sm"
707
+ value={(rule.sourceCidrs || []).join(', ')}
708
+ onChange={(e) => this.updateRule(index, 'sourceCidrs', parseDelimitedList(e.target.value))}
709
+ />
710
+ </div>
711
+ </div>
712
+ <div className="form-group">
713
+ <label className="col-md-3 control-label">Destination Ports</label>
714
+ <div className="col-md-7">
715
+ <input
716
+ className="form-control input-sm"
717
+ value={(rule.destinationPorts || []).join(', ')}
718
+ onChange={(e) => this.updateRule(index, 'destinationPorts', parseDelimitedList(e.target.value))}
719
+ />
720
+ </div>
721
+ </div>
722
+ <div className="form-group">
723
+ <label className="col-md-3 control-label">Protocol</label>
724
+ <div className="col-md-3">
725
+ <select
726
+ className="form-control input-sm"
727
+ value={rule.protocol || 'Tcp'}
728
+ onChange={(e) => this.updateRule(index, 'protocol', e.target.value)}
729
+ >
730
+ <option value="Tcp">Tcp</option>
731
+ <option value="Udp">Udp</option>
732
+ <option value="*">Any</option>
733
+ </select>
734
+ </div>
735
+ </div>
736
+ <button
737
+ className="btn btn-link"
738
+ disabled={index === 0}
739
+ type="button"
740
+ onClick={() => this.moveRule(index, -1)}
741
+ >
742
+ Move Up
743
+ </button>
744
+ <button
745
+ className="btn btn-link"
746
+ disabled={index === securityGroup.securityRules.length - 1}
747
+ type="button"
748
+ onClick={() => this.moveRule(index, 1)}
749
+ >
750
+ Move Down
751
+ </button>
752
+ <button className="btn btn-link" type="button" onClick={() => this.removeRule(index)}>
753
+ Remove Rule
754
+ </button>
755
+ </div>
756
+ ))}
757
+ <button className="btn btn-default" type="button" onClick={this.addRule}>
758
+ Add Rule
759
+ </button>
760
+ </form>
761
+ </Modal.Body>
762
+ <Modal.Footer>
763
+ <button className="btn btn-default" disabled={submitting} onClick={dismissModal} type="button">
764
+ Cancel
765
+ </button>
766
+ <SubmitButton
767
+ isDisabled={submitting || !isAzureSecurityGroupValid(securityGroup, mode, this.getApplication())}
768
+ isNew={mode !== 'edit'}
769
+ label={mode === 'edit' ? 'Update' : mode === 'clone' ? 'Clone' : 'Create'}
770
+ onClick={() => this.submit()}
771
+ submitting={submitting}
772
+ />
773
+ </Modal.Footer>
774
+ </>
775
+ );
776
+ }
777
+ }
778
+
779
+ export const AzureSecurityGroupModal = Object.assign(
780
+ withRouter<IAzureSecurityGroupModalProps & IRouterInjectedProps>(AzureSecurityGroupModalComponent),
781
+ { show: AzureSecurityGroupModalComponent.show },
782
+ );