@spinnaker/ecs 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 (209) hide show
  1. package/dist/ecs.module.d.ts +3 -1
  2. package/dist/ecsCluster/ecsCluster.read.service.d.ts +2 -3
  3. package/dist/iamRoles/iamRole.read.service.d.ts +1 -2
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/instance/details/EcsInstanceDetails.d.ts +50 -0
  8. package/dist/loadBalancer/loadBalancer.transformer.d.ts +1 -5
  9. package/dist/loadBalancer/targetGroup.states.d.ts +2 -1
  10. package/dist/metricAlarm/metricAlarm.read.service.d.ts +1 -2
  11. package/dist/pipeline/stages/cloneServerGroup/ecsCloneServerGroupStage.d.ts +1 -0
  12. package/dist/pipeline/stages/common/EcsServerGroupStepLabel.d.ts +7 -0
  13. package/dist/pipeline/stages/common/EcsStageConfigs.d.ts +11 -0
  14. package/dist/pipeline/stages/destroyAsg/ecsDestroyAsgStage.d.ts +1 -2
  15. package/dist/pipeline/stages/disableAsg/ecsDisableAsgStage.d.ts +1 -2
  16. package/dist/pipeline/stages/disableCluster/ecsDisableClusterStage.d.ts +1 -2
  17. package/dist/pipeline/stages/enableAsg/ecsEnableAsgStage.d.ts +1 -2
  18. package/dist/pipeline/stages/findImageFromTags/EcsFindImageFromTagsExecutionDetails.d.ts +5 -0
  19. package/dist/pipeline/stages/findImageFromTags/ecsFindImageFromTagStage.d.ts +1 -2
  20. package/dist/pipeline/stages/resizeAsg/ecsResizeAsgStage.d.ts +1 -2
  21. package/dist/pipeline/stages/scaleDownCluster/ecsScaleDownClusterStage.d.ts +1 -2
  22. package/dist/pipeline/stages/shrinkCluster/ecsShrinkClusterStage.d.ts +1 -2
  23. package/dist/placementStrategy/placementStrategy.service.d.ts +0 -1
  24. package/dist/secrets/secret.read.service.d.ts +1 -2
  25. package/dist/securityGroup/details/EcsSecurityGroupDetails.d.ts +45 -0
  26. package/dist/securityGroup/securityGroup.reader.d.ts +0 -1
  27. package/dist/securityGroup/securityGroup.transformer.d.ts +3 -2
  28. package/dist/serverGroup/configure/dockerImage.util.d.ts +3 -0
  29. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +111 -2
  30. package/dist/serverGroup/configure/serverGroupConfiguration.service.d.ts +13 -63
  31. package/dist/serverGroup/configure/wizard/EcsCloneServerGroupModal.d.ts +64 -0
  32. package/dist/serverGroup/configure/wizard/capacityProvider/CapacityProvider.d.ts +29 -4
  33. package/dist/serverGroup/configure/wizard/container/Container.d.ts +3 -3
  34. package/dist/serverGroup/configure/wizard/networking/Networking.d.ts +3 -3
  35. package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
  36. package/dist/serverGroup/configure/wizard/pages/BasicSettings.d.ts +3 -0
  37. package/dist/serverGroup/configure/wizard/pages/ContainerSettings.d.ts +3 -0
  38. package/dist/serverGroup/configure/wizard/pages/HorizontalScalingSettings.d.ts +3 -0
  39. package/dist/serverGroup/configure/wizard/pages/LoggingSettings.d.ts +3 -0
  40. package/dist/serverGroup/configure/wizard/pages/NetworkingSettings.d.ts +3 -0
  41. package/dist/serverGroup/configure/wizard/pages/ServiceDiscoverySettings.d.ts +3 -0
  42. package/dist/serverGroup/configure/wizard/pages/TaskDefinitionSettings.d.ts +3 -0
  43. package/dist/serverGroup/configure/wizard/pages/common.d.ts +8 -0
  44. package/dist/serverGroup/configure/wizard/pages/validation.d.ts +22 -0
  45. package/dist/serverGroup/configure/wizard/serviceDiscovery/ServiceDiscovery.d.ts +5 -3
  46. package/dist/serverGroup/configure/wizard/taskDefinition/TaskDefinition.d.ts +3 -3
  47. package/dist/serverGroup/details/EcsServerGroupActions.d.ts +6 -0
  48. package/dist/serverGroup/details/ecsServerGroupDetailsGetter.d.ts +3 -0
  49. package/dist/serverGroup/details/resize/EcsResizeServerGroupModal.d.ts +43 -0
  50. package/dist/serverGroup/details/resize/index.d.ts +1 -0
  51. package/dist/serverGroup/details/rollback/EcsRollbackServerGroupModal.d.ts +46 -0
  52. package/dist/serverGroup/details/rollback/index.d.ts +1 -0
  53. package/dist/serverGroup/details/sections/EcsServerGroupDetailsSections.d.ts +9 -0
  54. package/dist/serverGroup/details/sections/EcsServerGroupEventsSection.d.ts +3 -0
  55. package/dist/serverGroup/details/sections/EcsServerGroupInformationSection.d.ts +3 -0
  56. package/dist/serverGroup/events/EcsServerGroupEventsModal.d.ts +22 -0
  57. package/dist/serverGroup/events/EventsLink.d.ts +2 -0
  58. package/dist/serverGroup/events/serverGroupEventsReader.service.d.ts +1 -1
  59. package/dist/serverGroup/serverGroup.transformer.d.ts +1 -2
  60. package/dist/serviceDiscovery/serviceDiscovery.read.service.d.ts +1 -1
  61. package/package.json +3 -9
  62. package/src/ecs.module.spec.ts +86 -0
  63. package/src/ecs.module.ts +68 -84
  64. package/src/ecsCluster/ecsCluster.read.service.ts +3 -9
  65. package/src/iamRoles/iamRole.read.service.ts +1 -7
  66. package/src/index.ts +6 -0
  67. package/src/instance/details/EcsInstanceDetails.spec.tsx +426 -0
  68. package/src/instance/details/EcsInstanceDetails.tsx +474 -0
  69. package/src/loadBalancer/details/loadBalancerDetails.tsx +1 -1
  70. package/src/loadBalancer/details/targetGroupDetails.tsx +1 -1
  71. package/src/loadBalancer/loadBalancer.transformer.ts +2 -7
  72. package/src/loadBalancer/targetGroup.states.ts +55 -53
  73. package/src/metricAlarm/metricAlarm.read.service.ts +1 -7
  74. package/src/pipeline/stages/cloneServerGroup/ecsCloneServerGroupStage.js +22 -94
  75. package/src/pipeline/stages/common/EcsServerGroupStepLabel.tsx +16 -0
  76. package/src/pipeline/stages/common/EcsStageConfigs.tsx +587 -0
  77. package/src/pipeline/stages/destroyAsg/ecsDestroyAsgStage.js +29 -65
  78. package/src/pipeline/stages/disableAsg/ecsDisableAsgStage.js +27 -70
  79. package/src/pipeline/stages/disableCluster/ecsDisableClusterStage.js +21 -84
  80. package/src/pipeline/stages/enableAsg/ecsEnableAsgStage.js +22 -73
  81. package/src/pipeline/stages/findImageFromTags/EcsFindImageFromTagsExecutionDetails.tsx +40 -0
  82. package/src/pipeline/stages/findImageFromTags/ecsFindImageFromTagStage.js +12 -24
  83. package/src/pipeline/stages/resizeAsg/ecsResizeAsgStage.js +30 -128
  84. package/src/pipeline/stages/scaleDownCluster/ecsScaleDownClusterStage.js +24 -78
  85. package/src/pipeline/stages/shrinkCluster/ecsShrinkClusterStage.js +19 -73
  86. package/src/placementStrategy/placementStrategy.service.ts +0 -6
  87. package/src/secrets/secret.read.service.ts +1 -7
  88. package/src/securityGroup/details/EcsSecurityGroupDetails.spec.tsx +245 -0
  89. package/src/securityGroup/details/EcsSecurityGroupDetails.tsx +285 -0
  90. package/src/securityGroup/securityGroup.reader.ts +0 -5
  91. package/src/securityGroup/securityGroup.transformer.js +5 -13
  92. package/src/serverGroup/configure/dockerImage.util.ts +29 -0
  93. package/src/serverGroup/configure/serverGroupCommandBuilder.service.js +215 -278
  94. package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.ts +83 -0
  95. package/src/serverGroup/configure/serverGroupConfiguration.service.ts +16 -633
  96. package/src/serverGroup/configure/wizard/EcsCloneServerGroupModal.spec.tsx +2078 -0
  97. package/src/serverGroup/configure/wizard/EcsCloneServerGroupModal.tsx +712 -0
  98. package/src/serverGroup/configure/wizard/capacityProvider/CapacityProvider.tsx +98 -49
  99. package/src/serverGroup/configure/wizard/container/Container.spec.tsx +20 -8
  100. package/src/serverGroup/configure/wizard/container/Container.tsx +53 -24
  101. package/src/serverGroup/configure/wizard/networking/Networking.tsx +60 -38
  102. package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +290 -0
  103. package/src/serverGroup/configure/wizard/pages/BasicSettings.tsx +101 -0
  104. package/src/serverGroup/configure/wizard/pages/ContainerSettings.tsx +10 -0
  105. package/src/serverGroup/configure/wizard/pages/HorizontalScalingSettings.tsx +137 -0
  106. package/src/serverGroup/configure/wizard/pages/LoggingSettings.tsx +60 -0
  107. package/src/serverGroup/configure/wizard/pages/NetworkingSettings.tsx +10 -0
  108. package/src/serverGroup/configure/wizard/pages/ServiceDiscoverySettings.tsx +10 -0
  109. package/src/serverGroup/configure/wizard/pages/TaskDefinitionSettings.tsx +59 -0
  110. package/src/serverGroup/configure/wizard/pages/common.ts +10 -0
  111. package/src/serverGroup/configure/wizard/pages/validation.tsx +190 -0
  112. package/src/serverGroup/configure/wizard/serviceDiscovery/ServiceDiscovery.tsx +83 -34
  113. package/src/serverGroup/configure/wizard/taskDefinition/TaskDefinition.spec.tsx +21 -8
  114. package/src/serverGroup/configure/wizard/taskDefinition/TaskDefinition.tsx +63 -33
  115. package/src/serverGroup/details/EcsServerGroupActions.spec.tsx +244 -0
  116. package/src/serverGroup/details/EcsServerGroupActions.tsx +152 -0
  117. package/src/serverGroup/details/ecsServerGroupDetailsGetter.spec.ts +110 -0
  118. package/src/serverGroup/details/ecsServerGroupDetailsGetter.ts +73 -0
  119. package/src/serverGroup/details/resize/EcsResizeServerGroupModal.spec.tsx +130 -0
  120. package/src/serverGroup/details/resize/EcsResizeServerGroupModal.tsx +263 -0
  121. package/src/serverGroup/details/resize/index.ts +1 -0
  122. package/src/serverGroup/details/rollback/EcsRollbackServerGroupModal.spec.tsx +173 -0
  123. package/src/serverGroup/details/rollback/EcsRollbackServerGroupModal.tsx +285 -0
  124. package/src/serverGroup/details/rollback/index.ts +1 -0
  125. package/src/serverGroup/details/sections/EcsServerGroupDetailsSections.spec.tsx +130 -0
  126. package/src/serverGroup/details/sections/EcsServerGroupDetailsSections.tsx +172 -0
  127. package/src/serverGroup/details/sections/EcsServerGroupEventsSection.tsx +14 -0
  128. package/src/serverGroup/details/sections/EcsServerGroupInformationSection.tsx +25 -0
  129. package/src/serverGroup/events/EcsServerGroupEventsModal.spec.tsx +109 -0
  130. package/src/serverGroup/events/EcsServerGroupEventsModal.tsx +112 -0
  131. package/src/serverGroup/events/EventsLink.tsx +18 -0
  132. package/src/serverGroup/events/serverGroupEventsReader.service.spec.ts +41 -0
  133. package/src/serverGroup/events/serverGroupEventsReader.service.ts +2 -8
  134. package/src/serverGroup/serverGroup.transformer.spec.ts +10 -24
  135. package/src/serverGroup/serverGroup.transformer.ts +1 -5
  136. package/src/serviceDiscovery/serviceDiscovery.read.service.ts +1 -1
  137. package/dist/common/common.module.d.ts +0 -1
  138. package/dist/common/footer.component.d.ts +0 -1
  139. package/dist/instance/details/instance.details.controller.d.ts +0 -2
  140. package/dist/securityGroup/details/securityGroupDetail.controller.d.ts +0 -2
  141. package/dist/securityGroup/securityGroup.module.d.ts +0 -1
  142. package/dist/serverGroup/configure/wizard/CloneServerGroup.ecs.controller.d.ts +0 -2
  143. package/dist/serverGroup/configure/wizard/advancedSettings/advancedSettings.component.d.ts +0 -2
  144. package/dist/serverGroup/configure/wizard/horizontalScaling/horizontalScaling.component.d.ts +0 -2
  145. package/dist/serverGroup/configure/wizard/location/ServerGroupBasicSettings.controller.d.ts +0 -2
  146. package/dist/serverGroup/configure/wizard/logging/logging.component.d.ts +0 -1
  147. package/dist/serverGroup/details/resize/resizeCapacity.component.d.ts +0 -1
  148. package/dist/serverGroup/details/resize/resizeServerGroup.controller.d.ts +0 -2
  149. package/dist/serverGroup/details/rollback/rollbackServerGroup.controller.d.ts +0 -2
  150. package/dist/serverGroup/details/serverGroupDetails.ecs.controller.d.ts +0 -2
  151. package/dist/serverGroup/details/serverGroupDetails.module.d.ts +0 -1
  152. package/dist/serverGroup/events/events.component.d.ts +0 -3
  153. package/dist/serverGroup/events/events.controller.d.ts +0 -19
  154. package/dist/serverGroup/serverGroup.module.d.ts +0 -1
  155. package/src/common/common.module.ts +0 -6
  156. package/src/common/footer.component.ts +0 -29
  157. package/src/instance/details/instance.details.controller.js +0 -356
  158. package/src/instance/details/instanceDetails.html +0 -204
  159. package/src/pipeline/stages/cloneServerGroup/cloneServerGroupStage.html +0 -101
  160. package/src/pipeline/stages/cloneServerGroup/cloneServerGroupStepLabel.html +0 -1
  161. package/src/pipeline/stages/destroyAsg/destroyAsgStage.html +0 -9
  162. package/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html +0 -1
  163. package/src/pipeline/stages/disableAsg/disableAsgStage.html +0 -11
  164. package/src/pipeline/stages/disableAsg/disableAsgStepLabel.html +0 -1
  165. package/src/pipeline/stages/disableCluster/disableClusterStage.html +0 -26
  166. package/src/pipeline/stages/enableAsg/enableAsgStage.html +0 -11
  167. package/src/pipeline/stages/enableAsg/enableAsgStepLabel.html +0 -1
  168. package/src/pipeline/stages/findImageFromTags/findImageFromTagsExecutionDetails.html +0 -36
  169. package/src/pipeline/stages/findImageFromTags/findImageFromTagsStage.html +0 -9
  170. package/src/pipeline/stages/resizeAsg/resizeAsgStage.html +0 -87
  171. package/src/pipeline/stages/resizeAsg/resizeAsgStepLabel.html +0 -1
  172. package/src/pipeline/stages/scaleDownCluster/scaleDownClusterStage.html +0 -35
  173. package/src/pipeline/stages/shrinkCluster/shrinkClusterStage.html +0 -34
  174. package/src/securityGroup/details/securityGroupDetail.controller.js +0 -147
  175. package/src/securityGroup/details/securityGroupDetail.html +0 -104
  176. package/src/securityGroup/securityGroup.module.ts +0 -12
  177. package/src/serverGroup/configure/wizard/CloneServerGroup.ecs.controller.js +0 -259
  178. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.component.html +0 -194
  179. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.component.js +0 -17
  180. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.html +0 -10
  181. package/src/serverGroup/configure/wizard/capacityProvider/capacityProvider.html +0 -11
  182. package/src/serverGroup/configure/wizard/container/container.html +0 -9
  183. package/src/serverGroup/configure/wizard/horizontalScaling/horizontalScaling.component.html +0 -118
  184. package/src/serverGroup/configure/wizard/horizontalScaling/horizontalScaling.component.js +0 -20
  185. package/src/serverGroup/configure/wizard/horizontalScaling/horizontalScaling.html +0 -13
  186. package/src/serverGroup/configure/wizard/location/ServerGroupBasicSettings.controller.js +0 -41
  187. package/src/serverGroup/configure/wizard/location/basicSettings.html +0 -137
  188. package/src/serverGroup/configure/wizard/logging/logging.component.html +0 -29
  189. package/src/serverGroup/configure/wizard/logging/logging.component.ts +0 -12
  190. package/src/serverGroup/configure/wizard/logging/logging.html +0 -7
  191. package/src/serverGroup/configure/wizard/networking/networking.html +0 -9
  192. package/src/serverGroup/configure/wizard/serverGroupWizard.html +0 -55
  193. package/src/serverGroup/configure/wizard/serviceDiscovery/serviceDiscovery.html +0 -11
  194. package/src/serverGroup/configure/wizard/taskDefinition/taskDefinition.html +0 -51
  195. package/src/serverGroup/configure/wizard/templateSelection/templateSelection.html +0 -9
  196. package/src/serverGroup/configure/wizard/verticalScaling/verticalScaling.html +0 -10
  197. package/src/serverGroup/details/resize/resizeCapacity.component.html +0 -94
  198. package/src/serverGroup/details/resize/resizeCapacity.component.ts +0 -14
  199. package/src/serverGroup/details/resize/resizeServerGroup.controller.js +0 -85
  200. package/src/serverGroup/details/resize/resizeServerGroup.html +0 -28
  201. package/src/serverGroup/details/rollback/rollbackServerGroup.controller.js +0 -97
  202. package/src/serverGroup/details/rollback/rollbackServerGroup.html +0 -67
  203. package/src/serverGroup/details/serverGroupDetails.ecs.controller.js +0 -366
  204. package/src/serverGroup/details/serverGroupDetails.html +0 -216
  205. package/src/serverGroup/details/serverGroupDetails.module.ts +0 -5
  206. package/src/serverGroup/events/events.component.ts +0 -35
  207. package/src/serverGroup/events/events.controller.ts +0 -45
  208. package/src/serverGroup/events/events.html +0 -36
  209. package/src/serverGroup/serverGroup.module.ts +0 -6
@@ -1,14 +1,8 @@
1
- import { module } from 'angular';
2
-
3
1
  import { REST } from '@spinnaker/core';
4
2
  import type { IRoleDescriptor } from './IRole';
5
3
 
6
4
  export class IamRoleReader {
7
- public listRoles(provider: string): PromiseLike<IRoleDescriptor[]> {
5
+ public listRoles(provider: string): Promise<IRoleDescriptor[]> {
8
6
  return REST('/roles').path(provider).get();
9
7
  }
10
8
  }
11
-
12
- export const IAM_ROLE_READ_SERVICE = 'spinnaker.ecs.iamRole.read.service';
13
-
14
- module(IAM_ROLE_READ_SERVICE, []).service('iamRoleReader', IamRoleReader);
package/src/index.ts CHANGED
@@ -1 +1,7 @@
1
+ import './ecs.module';
2
+
1
3
  export * from './ecs.module';
4
+ export * from './serverGroup/configure/serverGroupCommandBuilder.service';
5
+ export * from './serverGroup/serverGroup.transformer';
6
+ export * from './securityGroup/securityGroup.reader';
7
+ export * from './securityGroup/securityGroup.transformer';
@@ -0,0 +1,426 @@
1
+ import { shallow } from 'enzyme';
2
+ import React from 'react';
3
+
4
+ import { InstanceInformation, InstanceStatus, VpcTag } from '@spinnaker/amazon';
5
+ import {
6
+ CollapsibleSection,
7
+ ConsoleOutputLink,
8
+ InstanceDetailsHeader,
9
+ InstanceLinks,
10
+ InstanceReader,
11
+ LabeledValue,
12
+ RecentHistoryService,
13
+ } from '@spinnaker/core';
14
+
15
+ import { EcsInstanceDetailsComponent as EcsInstanceDetails } from './EcsInstanceDetails';
16
+
17
+ describe('EcsInstanceDetails', () => {
18
+ let stateService: { go: jasmine.Spy };
19
+
20
+ beforeEach(() => {
21
+ stateService = { go: jasmine.createSpy('go') };
22
+ spyOn(RecentHistoryService, 'addExtraDataToLatest');
23
+ spyOn(RecentHistoryService, 'removeLastItem');
24
+ });
25
+
26
+ it('loads routed details, merges the application summary, and renders all ECS detail sections', async () => {
27
+ const app = application();
28
+ const details = instanceDetails();
29
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(Promise.resolve(details) as any);
30
+
31
+ const wrapper = shallow(
32
+ <EcsInstanceDetails
33
+ app={app}
34
+ environment="test"
35
+ moniker={{ app: 'fnord', cluster: 'fnord-main' }}
36
+ $stateParams={{ provider: 'ecs', instanceId: 'task-1' }}
37
+ />,
38
+ );
39
+
40
+ await settle();
41
+ wrapper.update();
42
+
43
+ expect(InstanceReader.getInstanceDetails).toHaveBeenCalledWith('test-account', 'eu-west-1', 'task-1');
44
+ expect(RecentHistoryService.addExtraDataToLatest).toHaveBeenCalledWith('instances', {
45
+ account: 'test-account',
46
+ region: 'eu-west-1',
47
+ serverGroup: 'fnord-main-v001',
48
+ vpcId: 'vpc-1',
49
+ });
50
+ expect(wrapper.find(InstanceDetailsHeader).props()).toEqual(
51
+ jasmine.objectContaining({ healthState: 'Up', instanceId: 'task-1', loading: false, standalone: false }),
52
+ );
53
+
54
+ const information = wrapper.find(InstanceInformation);
55
+ expect(information.props()).toEqual(
56
+ jasmine.objectContaining({
57
+ account: 'test-account',
58
+ launchTime: 200,
59
+ provider: 'ecs',
60
+ region: 'eu-west-1',
61
+ serverGroup: 'fnord-main-v001',
62
+ }),
63
+ );
64
+ expect(
65
+ wrapper
66
+ .find(LabeledValue)
67
+ .filterWhere((value) => value.prop('label') === 'Image ID')
68
+ .prop('value'),
69
+ ).toBe('ami-complete');
70
+ expect(wrapper.find(InstanceStatus).props()).toEqual(
71
+ jasmine.objectContaining({
72
+ healthMetrics: [jasmine.objectContaining({ description: 'healthy in ECS', state: 'Up', type: 'Ecs' })],
73
+ healthState: 'Up',
74
+ }),
75
+ );
76
+ const networking = wrapper
77
+ .find(CollapsibleSection)
78
+ .filterWhere((section) => section.prop('heading') === 'Networking');
79
+ const vpc = shallow(<div>{networking.prop('children')}</div>)
80
+ .find(LabeledValue)
81
+ .filterWhere((value) => value.prop('label') === 'VPC');
82
+ expect(vpc.prop('value')).toEqual(<VpcTag vpcId="vpc-1" />);
83
+ const networkAddresses = shallow(<div>{networking.prop('children')}</div>)
84
+ .find('NetworkAddress')
85
+ .map((address) => address.prop('address'));
86
+ expect(networkAddresses).toEqual(['10.0.0.10', '2001:db8::10', '172.17.0.2']);
87
+ expect(wrapper.find(ConsoleOutputLink).prop('instance')).toEqual(
88
+ jasmine.objectContaining({ imageId: 'ami-complete' }),
89
+ );
90
+
91
+ const links = wrapper.find(InstanceLinks);
92
+ expect(links.props()).toEqual(
93
+ jasmine.objectContaining({ address: 'task.example.test', application: app, environment: 'test' }),
94
+ );
95
+ const renderedLinks = shallow(<InstanceLinks {...(links.props() as any)} />);
96
+ const linkSection = shallow(<div>{renderedLinks.find(CollapsibleSection).prop('children')}</div>);
97
+ expect(linkSection.text()).toContain('Health endpoint');
98
+ expect(linkSection.find('a').prop('href')).toBe('http://task.example.test:8080/health');
99
+ });
100
+
101
+ it('loads complete details from a standalone instance prop', async () => {
102
+ const app = application(true);
103
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(
104
+ Promise.resolve(
105
+ instanceDetails({ loadBalancers: ['standalone-lb'], targetGroups: ['standalone-target'] }),
106
+ ) as any,
107
+ );
108
+
109
+ const wrapper = shallow(
110
+ <EcsInstanceDetails
111
+ app={app}
112
+ environment="test"
113
+ instance={{ account: 'standalone-account', instanceId: 'standalone-task', region: 'us-east-1' }}
114
+ moniker={{ app: 'fnord' }}
115
+ />,
116
+ );
117
+
118
+ await settle();
119
+ wrapper.update();
120
+
121
+ expect(InstanceReader.getInstanceDetails).toHaveBeenCalledWith(
122
+ 'standalone-account',
123
+ 'us-east-1',
124
+ 'standalone-task',
125
+ );
126
+ expect(wrapper.find(InstanceDetailsHeader).props()).toEqual(
127
+ jasmine.objectContaining({ instanceId: 'standalone-task', standalone: true }),
128
+ );
129
+ expect(wrapper.find(ConsoleOutputLink).prop('instance')).toEqual(
130
+ jasmine.objectContaining({ loadBalancers: ['standalone-lb'], targetGroups: ['standalone-target'] }),
131
+ );
132
+ });
133
+
134
+ it('finds string instance IDs in ECS target groups scoped to the routed account', async () => {
135
+ const app = application(false, []);
136
+ app.loadBalancers.data = [
137
+ loadBalancer('wrong-account', 'wrong-target', ['target-task']),
138
+ loadBalancer('test-account', 'correct-target', ['target-task']),
139
+ ];
140
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(
141
+ Promise.resolve(instanceDetails({ instanceId: 'target-task' })) as any,
142
+ );
143
+
144
+ const wrapper = shallow(
145
+ <EcsInstanceDetails
146
+ app={app}
147
+ accountId="test-account"
148
+ $stateParams={{ provider: 'ecs', instanceId: 'target-task' }}
149
+ />,
150
+ );
151
+
152
+ await settle();
153
+ wrapper.update();
154
+
155
+ expect(InstanceReader.getInstanceDetails).toHaveBeenCalledWith('test-account', 'eu-west-1', 'target-task');
156
+ expect(wrapper.find(ConsoleOutputLink).prop('instance')).toEqual(
157
+ jasmine.objectContaining({ targetGroups: ['correct-target'] }),
158
+ );
159
+ });
160
+
161
+ it('adds health check details only from the matching account and region target group', async () => {
162
+ const app = application();
163
+ app.serverGroups.data[0].instances[0].health = [
164
+ {
165
+ state: 'Up',
166
+ targetGroups: [
167
+ { state: 'healthy', targetGroupName: 'fnord-target' },
168
+ { state: 'healthy', targetGroupName: 'missing-target' },
169
+ ],
170
+ type: 'TargetGroup',
171
+ },
172
+ ];
173
+ app.loadBalancers.data = [
174
+ loadBalancer('test-account', 'fnord-target', [], 'us-east-1', {
175
+ healthCheckPath: '/wrong-region',
176
+ healthCheckPort: 80,
177
+ healthCheckProtocol: 'HTTP',
178
+ }),
179
+ loadBalancer('test-account', 'unrelated-target', [], 'eu-west-1', {
180
+ healthCheckPath: '/wrong-target',
181
+ healthCheckPort: 81,
182
+ healthCheckProtocol: 'HTTP',
183
+ }),
184
+ loadBalancer('test-account', 'fnord-target', [], 'eu-west-1', {
185
+ healthCheckPath: '/health',
186
+ healthCheckPort: 8443,
187
+ healthCheckProtocol: 'HTTPS',
188
+ }),
189
+ ];
190
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(
191
+ Promise.resolve(
192
+ instanceDetails({
193
+ health: [
194
+ {
195
+ state: 'Up',
196
+ targetGroups: [
197
+ { state: 'healthy', targetGroupName: 'fnord-target' },
198
+ { state: 'healthy', targetGroupName: 'missing-target' },
199
+ ],
200
+ type: 'TargetGroup',
201
+ },
202
+ ],
203
+ }),
204
+ ) as any,
205
+ );
206
+
207
+ const wrapper = shallow(<EcsInstanceDetails app={app} $stateParams={{ provider: 'ecs', instanceId: 'task-1' }} />);
208
+
209
+ await settle();
210
+ wrapper.update();
211
+
212
+ const [matching, missing] = (wrapper.find(InstanceStatus).prop('healthMetrics') as any[])[0].targetGroups;
213
+ expect(matching).toEqual(
214
+ jasmine.objectContaining({ healthCheckPath: ':8443/health', healthCheckProtocol: 'https' }),
215
+ );
216
+ expect(missing.healthCheckPath).toBeUndefined();
217
+ expect(missing.healthCheckProtocol).toBeUndefined();
218
+ });
219
+
220
+ it('renders the ECS zone before availability zone fallbacks', async () => {
221
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(
222
+ Promise.resolve(instanceDetails({ availabilityZone: 'fallback-zone', zone: 'ecs-zone' })) as any,
223
+ );
224
+ const wrapper = shallow(
225
+ <EcsInstanceDetails app={application()} $stateParams={{ provider: 'ecs', instanceId: 'task-1' }} />,
226
+ );
227
+
228
+ await settle();
229
+ wrapper.update();
230
+
231
+ expect(wrapper.find(InstanceInformation).prop('availabilityZone')).toBe('ecs-zone');
232
+ });
233
+
234
+ it('shows an inline not-found state when a standalone load fails', async () => {
235
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(Promise.reject(new Error('not found')) as any);
236
+ const wrapper = shallow(
237
+ <EcsInstanceDetails
238
+ app={application(true)}
239
+ environment="test"
240
+ instance={{ account: 'standalone-account', instanceId: 'missing-task', region: 'us-east-1' }}
241
+ moniker={{ app: 'fnord' }}
242
+ />,
243
+ );
244
+
245
+ await settle();
246
+ wrapper.update();
247
+
248
+ expect(wrapper.text()).toContain('Instance not found.');
249
+ expect(wrapper.text()).toContain('missing-task');
250
+ expect(RecentHistoryService.removeLastItem).toHaveBeenCalledWith('instances');
251
+ expect(stateService.go).not.toHaveBeenCalled();
252
+ });
253
+
254
+ it('closes routed details when loading fails', async () => {
255
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(Promise.reject(new Error('not found')) as any);
256
+ shallow(
257
+ <EcsInstanceDetails
258
+ app={application()}
259
+ environment="test"
260
+ moniker={{ app: 'fnord' }}
261
+ router={{} as any}
262
+ stateParams={{}}
263
+ stateService={stateService as any}
264
+ $stateParams={{ provider: 'ecs', instanceId: 'task-1' }}
265
+ />,
266
+ );
267
+
268
+ await settle();
269
+
270
+ expect(stateService.go).toHaveBeenCalledWith('^', { allowModalToStayOpen: true }, { location: 'replace' });
271
+ });
272
+
273
+ it('keeps newer instance details when an older request resolves last', async () => {
274
+ const oldRequest = deferred<any>();
275
+ const newRequest = deferred<any>();
276
+ const app = application(false, [
277
+ serverGroup('fnord-main-v001', 'task-1'),
278
+ serverGroup('fnord-main-v002', 'task-2'),
279
+ ]);
280
+ spyOn(InstanceReader, 'getInstanceDetails').and.callFake((_account: string, _region: string, instanceId: string) =>
281
+ instanceId === 'task-1' ? oldRequest.promise : newRequest.promise,
282
+ );
283
+ const wrapper = shallow(
284
+ <EcsInstanceDetails
285
+ app={app}
286
+ environment="test"
287
+ moniker={{ app: 'fnord' }}
288
+ $stateParams={{ provider: 'ecs', instanceId: 'task-1' }}
289
+ />,
290
+ );
291
+
292
+ await settle();
293
+ wrapper.setProps({ $stateParams: { provider: 'ecs', instanceId: 'task-2' } });
294
+ newRequest.resolve(instanceDetails({ instanceId: 'task-2', name: 'task-2' }));
295
+ await settle();
296
+ wrapper.update();
297
+ expect(wrapper.find(InstanceDetailsHeader).prop('instanceId')).toBe('task-2');
298
+
299
+ oldRequest.resolve(instanceDetails({ instanceId: 'task-1', name: 'task-1' }));
300
+ await settle();
301
+ wrapper.update();
302
+
303
+ expect(wrapper.find(InstanceDetailsHeader).prop('instanceId')).toBe('task-2');
304
+ });
305
+
306
+ it('does not update state when a request resolves after unmount', async () => {
307
+ const request = deferred<any>();
308
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(request.promise as any);
309
+ const wrapper = shallow(
310
+ <EcsInstanceDetails
311
+ app={application(true)}
312
+ environment="test"
313
+ instance={{ account: 'standalone-account', instanceId: 'standalone-task', region: 'us-east-1' }}
314
+ moniker={{ app: 'fnord' }}
315
+ />,
316
+ );
317
+ const component = wrapper.instance() as React.Component;
318
+ spyOn(component, 'setState');
319
+
320
+ wrapper.unmount();
321
+ request.resolve(instanceDetails());
322
+ await settle();
323
+
324
+ expect(component.setState).not.toHaveBeenCalled();
325
+ });
326
+ });
327
+
328
+ function application(isStandalone = false, serverGroups = [serverGroup()]): any {
329
+ return {
330
+ attributes: {
331
+ instanceLinks: [{ title: 'Application', links: [{ title: 'Health endpoint', path: '/health' }] }],
332
+ instancePort: 8080,
333
+ },
334
+ isStandalone,
335
+ loadBalancers: {
336
+ data: [],
337
+ ready: () => Promise.resolve(),
338
+ },
339
+ serverGroups: isStandalone
340
+ ? undefined
341
+ : {
342
+ data: serverGroups,
343
+ onRefresh: () => jasmine.createSpy('unsubscribe'),
344
+ ready: () => Promise.resolve(),
345
+ },
346
+ };
347
+ }
348
+
349
+ function serverGroup(name = 'fnord-main-v001', instanceId = 'task-1'): any {
350
+ return {
351
+ account: 'test-account',
352
+ instances: [
353
+ {
354
+ health: [{ state: 'Up', type: 'Ecs' }],
355
+ healthState: 'Up',
356
+ id: instanceId,
357
+ launchTime: 100,
358
+ name: instanceId,
359
+ },
360
+ ],
361
+ loadBalancers: ['fnord-lb'],
362
+ name,
363
+ region: 'eu-west-1',
364
+ targetGroup: ['fnord-target'],
365
+ vpcId: 'vpc-1',
366
+ };
367
+ }
368
+
369
+ function instanceDetails(overrides: any = {}): any {
370
+ return {
371
+ health: [{ description: 'healthy in ECS', state: 'Up', type: 'Ecs' }],
372
+ imageId: 'ami-complete',
373
+ instanceId: 'task-1',
374
+ launchTime: 200,
375
+ name: 'task-1',
376
+ networkInterface: {
377
+ ipv6Address: '2001:db8::10',
378
+ privateIpv4Address: '10.0.0.10',
379
+ },
380
+ privateAddress: '172.17.0.2',
381
+ publicDnsName: 'task.example.test',
382
+ subnetId: 'subnet-1',
383
+ ...overrides,
384
+ };
385
+ }
386
+
387
+ function loadBalancer(
388
+ account: string,
389
+ targetGroupName: string,
390
+ instances: Array<string | { id: string }>,
391
+ region = 'eu-west-1',
392
+ targetGroupOverrides: any = {},
393
+ ): any {
394
+ return {
395
+ account,
396
+ instances: [],
397
+ name: `${targetGroupName}-load-balancer`,
398
+ region,
399
+ targetGroups: [
400
+ {
401
+ account,
402
+ healthCheckPath: '/default',
403
+ healthCheckPort: 'traffic-port',
404
+ healthCheckProtocol: 'HTTP',
405
+ instances,
406
+ port: 8080,
407
+ region,
408
+ targetGroupName,
409
+ ...targetGroupOverrides,
410
+ },
411
+ ],
412
+ vpcId: 'vpc-1',
413
+ };
414
+ }
415
+
416
+ const settle = () => new Promise((resolve) => setTimeout(resolve));
417
+
418
+ function deferred<T>() {
419
+ let resolve!: (value: T | PromiseLike<T>) => void;
420
+ let reject!: (reason?: any) => void;
421
+ const promise = new Promise<T>((resolvePromise, rejectPromise) => {
422
+ resolve = resolvePromise;
423
+ reject = rejectPromise;
424
+ });
425
+ return { promise, reject, resolve };
426
+ }