@spinnaker/appengine 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 (131) hide show
  1. package/dist/appengine.module.d.ts +7 -1
  2. package/dist/domain/index.d.ts +0 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/instance/details/AppengineInstanceDetails.d.ts +11 -0
  7. package/dist/loadBalancer/configure/AppengineCreateLoadBalancerModal.d.ts +29 -0
  8. package/dist/loadBalancer/details/AppengineLoadBalancerActions.d.ts +5 -0
  9. package/dist/loadBalancer/details/AppengineLoadBalancerDetailsSection.d.ts +3 -0
  10. package/dist/loadBalancer/details/index.d.ts +3 -0
  11. package/dist/loadBalancer/details/useAppengineLoadBalancerDetails.d.ts +3 -0
  12. package/dist/loadBalancer/transformer.d.ts +2 -6
  13. package/dist/pipeline/stages/AppengineExecutionDetails.d.ts +15 -0
  14. package/dist/pipeline/stages/AppengineServerGroupStageConfig.d.ts +12 -0
  15. package/dist/pipeline/stages/destroyAsg/appengineDestroyAsgStage.d.ts +1 -1
  16. package/dist/pipeline/stages/disableAsg/appengineDisableAsgStage.d.ts +1 -1
  17. package/dist/pipeline/stages/editLoadBalancer/AppengineEditLoadBalancerStageConfig.d.ts +3 -0
  18. package/dist/pipeline/stages/editLoadBalancer/appengineEditLoadBalancerStage.d.ts +1 -1
  19. package/dist/pipeline/stages/enableAsg/appengineEnableAsgStage.d.ts +1 -1
  20. package/dist/pipeline/stages/shrinkCluster/appengineShrinkClusterStage.d.ts +1 -1
  21. package/dist/pipeline/stages/startServerGroup/appengineStartServerGroupStage.d.ts +1 -1
  22. package/dist/pipeline/stages/stopServerGroup/appengineStopServerGroupStage.d.ts +1 -1
  23. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +5 -9
  24. package/dist/serverGroup/configure/wizard/AppengineCloneServerGroupModal.d.ts +22 -0
  25. package/dist/serverGroup/details/AppengineServerGroupActions.d.ts +12 -0
  26. package/dist/serverGroup/details/AppengineServerGroupDetailsSection.d.ts +3 -0
  27. package/dist/serverGroup/details/appengineServerGroupDetailsGetter.d.ts +4 -0
  28. package/dist/serverGroup/details/index.d.ts +3 -0
  29. package/dist/serverGroup/transformer.d.ts +1 -5
  30. package/dist/serverGroup/writer/serverGroup.write.service.d.ts +15 -4
  31. package/package.json +4 -8
  32. package/src/appengine.module.spec.ts +194 -0
  33. package/src/appengine.module.ts +47 -58
  34. package/src/domain/index.ts +0 -1
  35. package/src/index.ts +1 -1
  36. package/src/instance/details/AppengineInstanceDetails.tsx +33 -0
  37. package/src/loadBalancer/configure/AppengineCreateLoadBalancerModal.tsx +342 -0
  38. package/src/loadBalancer/details/AppengineLoadBalancerActions.tsx +66 -0
  39. package/src/loadBalancer/details/AppengineLoadBalancerDetailsSection.tsx +42 -0
  40. package/src/loadBalancer/details/index.ts +3 -0
  41. package/src/loadBalancer/details/useAppengineLoadBalancerDetails.ts +37 -0
  42. package/src/loadBalancer/transformer.ts +3 -14
  43. package/src/pipeline/stages/AppengineExecutionDetails.tsx +90 -0
  44. package/src/pipeline/stages/AppengineServerGroupStageConfig.tsx +190 -0
  45. package/src/pipeline/stages/destroyAsg/appengineDestroyAsgStage.ts +17 -43
  46. package/src/pipeline/stages/disableAsg/appengineDisableAsgStage.ts +18 -53
  47. package/src/pipeline/stages/editLoadBalancer/AppengineEditLoadBalancerStageConfig.tsx +94 -0
  48. package/src/pipeline/stages/editLoadBalancer/appengineEditLoadBalancerStage.ts +14 -74
  49. package/src/pipeline/stages/enableAsg/appengineEnableAsgStage.ts +13 -48
  50. package/src/pipeline/stages/shrinkCluster/appengineShrinkClusterStage.ts +13 -61
  51. package/src/pipeline/stages/startServerGroup/appengineStartServerGroupStage.ts +17 -54
  52. package/src/pipeline/stages/stopServerGroup/appengineStopServerGroupStage.ts +17 -54
  53. package/src/serverGroup/configure/serverGroupCommandBuilder.service.ts +20 -19
  54. package/src/serverGroup/configure/wizard/AppengineCloneServerGroupModal.tsx +119 -0
  55. package/src/serverGroup/configure/wizard/serverGroupWizard.less +0 -8
  56. package/src/serverGroup/details/AppengineServerGroupActions.tsx +245 -0
  57. package/src/serverGroup/details/AppengineServerGroupDetailsSection.tsx +27 -0
  58. package/src/serverGroup/details/appengineServerGroupDetailsGetter.ts +62 -0
  59. package/src/serverGroup/details/index.ts +3 -0
  60. package/src/serverGroup/transformer.ts +3 -15
  61. package/src/serverGroup/writer/serverGroup.write.service.ts +57 -8
  62. package/dist/common/componentUrlDetails.component.d.ts +0 -1
  63. package/dist/common/conditionalDescriptionListItem.component.d.ts +0 -1
  64. package/dist/common/loadBalancerMessage.component.d.ts +0 -1
  65. package/dist/domain/IAppengineStageScope.d.ts +0 -18
  66. package/dist/instance/details/details.controller.d.ts +0 -1
  67. package/dist/loadBalancer/configure/wizard/advancedSettings.component.d.ts +0 -1
  68. package/dist/loadBalancer/configure/wizard/allocationConfigurationRow.component.d.ts +0 -1
  69. package/dist/loadBalancer/configure/wizard/basicSettings.component.d.ts +0 -1
  70. package/dist/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.d.ts +0 -1
  71. package/dist/loadBalancer/configure/wizard/wizard.controller.d.ts +0 -2
  72. package/dist/loadBalancer/details/details.controller.d.ts +0 -1
  73. package/dist/loadBalancer/loadBalancer.module.d.ts +0 -1
  74. package/dist/pipeline/pipeline.module.d.ts +0 -1
  75. package/dist/pipeline/stages/appengineStage.controller.d.ts +0 -12
  76. package/dist/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.d.ts +0 -1
  77. package/dist/serverGroup/configure/wizard/basicSettings.controller.d.ts +0 -1
  78. package/dist/serverGroup/configure/wizard/cloneServerGroup.controller.d.ts +0 -2
  79. package/dist/serverGroup/configure/wizard/configFileArtifactList.module.d.ts +0 -1
  80. package/dist/serverGroup/configure/wizard/configFiles.component.d.ts +0 -20
  81. package/dist/serverGroup/configure/wizard/dynamicBranchLabel.component.d.ts +0 -1
  82. package/dist/serverGroup/details/details.controller.d.ts +0 -1
  83. package/src/common/componentUrlDetails.component.ts +0 -29
  84. package/src/common/conditionalDescriptionListItem.component.ts +0 -37
  85. package/src/common/loadBalancerMessage.component.html +0 -33
  86. package/src/common/loadBalancerMessage.component.ts +0 -13
  87. package/src/domain/IAppengineStageScope.ts +0 -22
  88. package/src/instance/details/details.controller.ts +0 -116
  89. package/src/instance/details/details.html +0 -99
  90. package/src/loadBalancer/configure/wizard/advancedSettings.component.ts +0 -56
  91. package/src/loadBalancer/configure/wizard/allocationConfigurationRow.component.ts +0 -70
  92. package/src/loadBalancer/configure/wizard/basicSettings.component.html +0 -65
  93. package/src/loadBalancer/configure/wizard/basicSettings.component.ts +0 -98
  94. package/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html +0 -117
  95. package/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.ts +0 -108
  96. package/src/loadBalancer/configure/wizard/wizard.controller.ts +0 -163
  97. package/src/loadBalancer/configure/wizard/wizard.html +0 -44
  98. package/src/loadBalancer/configure/wizard/wizard.less +0 -17
  99. package/src/loadBalancer/details/details.controller.ts +0 -151
  100. package/src/loadBalancer/details/details.html +0 -102
  101. package/src/loadBalancer/loadBalancer.module.ts +0 -21
  102. package/src/pipeline/pipeline.module.ts +0 -20
  103. package/src/pipeline/stages/appengineStage.controller.ts +0 -45
  104. package/src/pipeline/stages/destroyAsg/destroyAsgStage.html +0 -16
  105. package/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html +0 -1
  106. package/src/pipeline/stages/disableAsg/disableAsgStage.html +0 -18
  107. package/src/pipeline/stages/disableAsg/disableAsgStepLabel.html +0 -1
  108. package/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html +0 -33
  109. package/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html +0 -51
  110. package/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.ts +0 -65
  111. package/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html +0 -45
  112. package/src/pipeline/stages/enableAsg/enableAsgStage.html +0 -18
  113. package/src/pipeline/stages/enableAsg/enableAsgStepLabel.html +0 -1
  114. package/src/pipeline/stages/shrinkCluster/shrinkClusterStage.html +0 -41
  115. package/src/pipeline/stages/startServerGroup/startServerGroupExecutionDetails.html +0 -26
  116. package/src/pipeline/stages/startServerGroup/startServerGroupStage.html +0 -18
  117. package/src/pipeline/stages/startServerGroup/startServerGroupStepLabel.html +0 -1
  118. package/src/pipeline/stages/stopServerGroup/stopServerGroupExecutionDetails.html +0 -26
  119. package/src/pipeline/stages/stopServerGroup/stopServerGroupStage.html +0 -18
  120. package/src/pipeline/stages/stopServerGroup/stopServerGroupStepLabel.html +0 -1
  121. package/src/serverGroup/configure/wizard/advancedSettings.html +0 -33
  122. package/src/serverGroup/configure/wizard/basicSettings.controller.ts +0 -151
  123. package/src/serverGroup/configure/wizard/basicSettings.html +0 -287
  124. package/src/serverGroup/configure/wizard/cloneServerGroup.controller.ts +0 -103
  125. package/src/serverGroup/configure/wizard/configFileArtifactList.module.ts +0 -18
  126. package/src/serverGroup/configure/wizard/configFiles.component.html +0 -81
  127. package/src/serverGroup/configure/wizard/configFiles.component.ts +0 -121
  128. package/src/serverGroup/configure/wizard/dynamicBranchLabel.component.ts +0 -19
  129. package/src/serverGroup/configure/wizard/serverGroupWizard.html +0 -39
  130. package/src/serverGroup/details/details.controller.ts +0 -463
  131. package/src/serverGroup/details/details.html +0 -229
@@ -2,5 +2,4 @@ export * from './IAppengineAccount';
2
2
  export * from './IAppengineInstance';
3
3
  export * from './IAppengineLoadBalancer';
4
4
  export * from './IAppengineServerGroup';
5
- export * from './IAppengineStageScope';
6
5
  export * from './IAppengineTriggers';
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { APPENGINE_MODULE } from './appengine.module';
1
+ import './appengine.module';
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+
3
+ interface IAppengineInstanceDetailsProps {
4
+ instance: {
5
+ account: string;
6
+ instanceId: string;
7
+ provider: string;
8
+ region: string;
9
+ };
10
+ }
11
+
12
+ export function AppengineInstanceDetails({ instance }: IAppengineInstanceDetailsProps) {
13
+ return (
14
+ <div className="details-panel">
15
+ <div className="header">
16
+ <div className="header-text horizontal middle">
17
+ <i className="fa icon-instance" />
18
+ <h3 className="horizontal middle space-between flex-1">{instance.instanceId}</h3>
19
+ </div>
20
+ </div>
21
+ <div className="content">
22
+ <dl className="dl-horizontal dl-narrow">
23
+ <dt>Account</dt>
24
+ <dd>{instance.account}</dd>
25
+ <dt>Region</dt>
26
+ <dd>{instance.region}</dd>
27
+ <dt>Provider</dt>
28
+ <dd>{instance.provider}</dd>
29
+ </dl>
30
+ </div>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,342 @@
1
+ import { cloneDeep } from 'lodash';
2
+ import React from 'react';
3
+ import { Modal } from 'react-bootstrap';
4
+
5
+ import type { ILoadBalancerModalProps } from '@spinnaker/core';
6
+ import { LoadBalancerWriter, ModalClose, noop, ReactModal, TaskMonitor, TaskMonitorWrapper } from '@spinnaker/core';
7
+
8
+ import type { IAppengineLoadBalancer } from '../../domain';
9
+ import type { IAppengineAllocationDescription } from '../transformer';
10
+ import { AppengineLoadBalancerUpsertDescription } from '../transformer';
11
+
12
+ const TARGET_OPTIONS = [
13
+ { label: 'Current Server Group', value: 'current_asg_dynamic' },
14
+ { label: 'Previous Server Group', value: 'ancestor_asg_dynamic' },
15
+ { label: 'Oldest Server Group', value: 'oldest_asg_dynamic' },
16
+ ];
17
+
18
+ export function updateAllocationLocatorType(
19
+ allocation: IAppengineAllocationDescription,
20
+ locatorType: IAppengineAllocationDescription['locatorType'],
21
+ ): IAppengineAllocationDescription {
22
+ const updated = { ...allocation, locatorType };
23
+ if (locatorType === 'targetCoordinate') {
24
+ delete updated.serverGroupName;
25
+ updated.target = updated.target || TARGET_OPTIONS[0].value;
26
+ } else {
27
+ delete updated.cluster;
28
+ delete updated.target;
29
+ }
30
+ return updated;
31
+ }
32
+
33
+ interface IAppengineCreateLoadBalancerModalState {
34
+ taskMonitor: TaskMonitor;
35
+ }
36
+
37
+ export class AppengineCreateLoadBalancerModal extends React.Component<
38
+ ILoadBalancerModalProps,
39
+ IAppengineCreateLoadBalancerModalState
40
+ > {
41
+ public static defaultProps: Partial<ILoadBalancerModalProps> = {
42
+ closeModal: noop,
43
+ dismissModal: noop,
44
+ };
45
+
46
+ public static show(props: ILoadBalancerModalProps & { isNew?: boolean }): Promise<IAppengineLoadBalancer> {
47
+ return ReactModal.show(AppengineCreateLoadBalancerModal, props, { dialogClassName: 'modal-lg' });
48
+ }
49
+
50
+ public state: IAppengineCreateLoadBalancerModalState = {
51
+ taskMonitor: new TaskMonitor({
52
+ application: this.props.app,
53
+ title: 'Updating your load balancer',
54
+ onDismiss: () => this.props.dismissModal(),
55
+ onTaskComplete: () => this.props.app.getDataSource('loadBalancers').refresh(),
56
+ }),
57
+ };
58
+
59
+ private command: AppengineLoadBalancerUpsertDescription = this.buildInitialCommand();
60
+
61
+ private buildInitialCommand(): AppengineLoadBalancerUpsertDescription {
62
+ const loadBalancer = (this.props.loadBalancer || {}) as IAppengineLoadBalancer;
63
+ const command = new AppengineLoadBalancerUpsertDescription({
64
+ ...loadBalancer,
65
+ account: loadBalancer.account || loadBalancer.credentials,
66
+ credentials: loadBalancer.credentials || loadBalancer.account,
67
+ name: loadBalancer.name,
68
+ region: loadBalancer.region,
69
+ serverGroups: loadBalancer.serverGroups || [],
70
+ cloudProvider: 'appengine',
71
+ migrateTraffic: loadBalancer.migrateTraffic || false,
72
+ } as IAppengineLoadBalancer);
73
+
74
+ command.splitDescription = loadBalancer.splitDescription ||
75
+ (loadBalancer.split &&
76
+ AppengineLoadBalancerUpsertDescription.convertTrafficSplitToTrafficSplitDescription(loadBalancer.split)) || {
77
+ shardBy: 'UNSPECIFIED',
78
+ allocationDescriptions: (loadBalancer.serverGroups || []).slice(0, 1).map((serverGroup) => ({
79
+ allocation: 100,
80
+ locatorType: 'fromExisting',
81
+ serverGroupName: serverGroup.name,
82
+ })),
83
+ };
84
+ command.mapAllocationsToPercentages();
85
+
86
+ return command;
87
+ }
88
+
89
+ private update = (field: keyof IAppengineLoadBalancer, value: any) => {
90
+ (this.command as any)[field] = value;
91
+ this.forceUpdate();
92
+ };
93
+
94
+ private updateSplit = (field: string, value: any) => {
95
+ (this.command.splitDescription as any)[field] = value;
96
+ this.forceUpdate();
97
+ };
98
+
99
+ private updateAllocation = (index: number, field: string, value: any) => {
100
+ (this.command.splitDescription.allocationDescriptions[index] as any)[field] = value;
101
+ this.forceUpdate();
102
+ };
103
+
104
+ private updateAllocationLocatorType = (
105
+ index: number,
106
+ locatorType: IAppengineAllocationDescription['locatorType'],
107
+ ) => {
108
+ this.command.splitDescription.allocationDescriptions[index] = updateAllocationLocatorType(
109
+ this.command.splitDescription.allocationDescriptions[index],
110
+ locatorType,
111
+ );
112
+ this.forceUpdate();
113
+ };
114
+
115
+ private addAllocation = () => {
116
+ this.command.splitDescription.allocationDescriptions.push({
117
+ allocation: 0,
118
+ locatorType: this.props.forPipelineConfig ? 'text' : 'fromExisting',
119
+ serverGroupName: '',
120
+ });
121
+ if (this.command.splitDescription.allocationDescriptions.length > 1 && !this.command.splitDescription.shardBy) {
122
+ this.command.splitDescription.shardBy = 'IP';
123
+ }
124
+ this.forceUpdate();
125
+ };
126
+
127
+ private removeAllocation = (index: number) => {
128
+ this.command.splitDescription.allocationDescriptions.splice(index, 1);
129
+ this.forceUpdate();
130
+ };
131
+
132
+ private allocationTotal = () => {
133
+ return this.command.splitDescription.allocationDescriptions.reduce((sum, description) => {
134
+ return sum + Number(description.allocation || 0);
135
+ }, 0);
136
+ };
137
+
138
+ private submit = () => {
139
+ const description = cloneDeep(this.command);
140
+ description.mapAllocationsToDecimals();
141
+ delete description.serverGroups;
142
+
143
+ if (this.props.forPipelineConfig) {
144
+ this.props.closeModal(description as any);
145
+ return;
146
+ }
147
+
148
+ this.state.taskMonitor.submit(() => LoadBalancerWriter.upsertLoadBalancer(description, this.props.app, 'Update'));
149
+ };
150
+
151
+ private renderAllocationTarget(allocation: IAppengineAllocationDescription, index: number) {
152
+ if (!this.props.forPipelineConfig) {
153
+ return (
154
+ <input
155
+ className="form-control input-sm"
156
+ onChange={(event) => this.updateAllocation(index, 'serverGroupName', event.target.value)}
157
+ value={allocation.serverGroupName || ''}
158
+ />
159
+ );
160
+ }
161
+
162
+ return (
163
+ <div>
164
+ <div className="radio">
165
+ <label>
166
+ <input
167
+ checked={allocation.locatorType === 'text'}
168
+ onChange={() => this.updateAllocationLocatorType(index, 'text')}
169
+ type="radio"
170
+ />{' '}
171
+ Text input
172
+ </label>
173
+ </div>
174
+ <div className="radio">
175
+ <label>
176
+ <input
177
+ checked={allocation.locatorType === 'targetCoordinate'}
178
+ onChange={() => this.updateAllocationLocatorType(index, 'targetCoordinate')}
179
+ type="radio"
180
+ />{' '}
181
+ Target coordinate
182
+ </label>
183
+ </div>
184
+ <div className="radio">
185
+ <label>
186
+ <input
187
+ checked={allocation.locatorType === 'fromExisting'}
188
+ onChange={() => this.updateAllocationLocatorType(index, 'fromExisting')}
189
+ type="radio"
190
+ />{' '}
191
+ Existing server group
192
+ </label>
193
+ </div>
194
+ {allocation.locatorType === 'targetCoordinate' ? (
195
+ <div>
196
+ <input
197
+ className="form-control input-sm"
198
+ onChange={(event) => this.updateAllocation(index, 'cluster', event.target.value)}
199
+ placeholder="Cluster"
200
+ value={allocation.cluster || ''}
201
+ />
202
+ <select
203
+ className="form-control input-sm"
204
+ onChange={(event) => this.updateAllocation(index, 'target', event.target.value)}
205
+ value={allocation.target || TARGET_OPTIONS[0].value}
206
+ >
207
+ {TARGET_OPTIONS.map((target) => (
208
+ <option key={target.value} value={target.value}>
209
+ {target.label}
210
+ </option>
211
+ ))}
212
+ </select>
213
+ </div>
214
+ ) : (
215
+ <input
216
+ className="form-control input-sm"
217
+ onChange={(event) => this.updateAllocation(index, 'serverGroupName', event.target.value)}
218
+ placeholder={allocation.locatorType === 'text' ? 'Server group expression or name' : 'Server group'}
219
+ value={allocation.serverGroupName || ''}
220
+ />
221
+ )}
222
+ </div>
223
+ );
224
+ }
225
+
226
+ public render() {
227
+ const { dismissModal, isNew } = this.props as ILoadBalancerModalProps & { isNew?: boolean };
228
+
229
+ return (
230
+ <div>
231
+ <ModalClose dismiss={dismissModal} />
232
+ <Modal.Header>
233
+ <Modal.Title>{isNew ? 'Create' : 'Edit'} App Engine Load Balancer</Modal.Title>
234
+ </Modal.Header>
235
+ <Modal.Body>
236
+ <div className="form-horizontal">
237
+ <label>Name</label>
238
+ <input
239
+ className="form-control input-sm"
240
+ onChange={(event) => this.update('name', event.target.value)}
241
+ value={this.command.name || ''}
242
+ />
243
+ <label>Account</label>
244
+ <input
245
+ className="form-control input-sm"
246
+ onChange={(event) => {
247
+ this.update('credentials', event.target.value);
248
+ this.update('account', event.target.value);
249
+ }}
250
+ value={this.command.credentials || this.command.account || ''}
251
+ />
252
+ <label>Region</label>
253
+ <input
254
+ className="form-control input-sm"
255
+ onChange={(event) => this.update('region', event.target.value)}
256
+ value={this.command.region || ''}
257
+ />
258
+ <div className="checkbox">
259
+ <label>
260
+ <input
261
+ checked={!!this.command.migrateTraffic}
262
+ onChange={(event) => this.update('migrateTraffic', event.target.checked)}
263
+ type="checkbox"
264
+ />{' '}
265
+ Migrate traffic
266
+ </label>
267
+ </div>
268
+ {(this.command.splitDescription.allocationDescriptions.length > 1 || this.command.migrateTraffic) && (
269
+ <div>
270
+ <label>Shard By</label>
271
+ <select
272
+ className="form-control input-sm"
273
+ onChange={(event) => this.updateSplit('shardBy', event.target.value)}
274
+ value={this.command.splitDescription.shardBy || 'UNSPECIFIED'}
275
+ >
276
+ <option value="UNSPECIFIED">Unspecified</option>
277
+ <option value="IP">IP</option>
278
+ <option value="COOKIE">Cookie</option>
279
+ </select>
280
+ </div>
281
+ )}
282
+ <label>Allocations</label>
283
+ <table className="table table-condensed">
284
+ <thead>
285
+ <tr>
286
+ <th>Server Group</th>
287
+ <th>Allocation %</th>
288
+ <th />
289
+ </tr>
290
+ </thead>
291
+ <tbody>
292
+ {this.command.splitDescription.allocationDescriptions.map((allocation, index) => (
293
+ <tr key={index}>
294
+ <td>{this.renderAllocationTarget(allocation, index)}</td>
295
+ <td>
296
+ <input
297
+ className="form-control input-sm"
298
+ min={0}
299
+ onChange={(event) => this.updateAllocation(index, 'allocation', Number(event.target.value))}
300
+ type="number"
301
+ value={allocation.allocation || 0}
302
+ />
303
+ </td>
304
+ <td>
305
+ <button
306
+ className="btn btn-sm btn-link"
307
+ onClick={() => this.removeAllocation(index)}
308
+ type="button"
309
+ >
310
+ <span className="glyphicon glyphicon-trash" />
311
+ </button>
312
+ </td>
313
+ </tr>
314
+ ))}
315
+ </tbody>
316
+ </table>
317
+ <button className="btn btn-block btn-sm add-new" onClick={this.addAllocation} type="button">
318
+ <span className="glyphicon glyphicon-plus-sign" /> Add allocation
319
+ </button>
320
+ {this.allocationTotal() !== 100 && (
321
+ <div className="alert alert-warning">Traffic allocations must add up to 100%.</div>
322
+ )}
323
+ </div>
324
+ </Modal.Body>
325
+ <TaskMonitorWrapper monitor={this.state.taskMonitor} />
326
+ <Modal.Footer>
327
+ <button className="btn btn-default" onClick={() => dismissModal()} type="button">
328
+ Cancel
329
+ </button>
330
+ <button
331
+ className="btn btn-primary"
332
+ disabled={this.allocationTotal() !== 100}
333
+ onClick={this.submit}
334
+ type="button"
335
+ >
336
+ {isNew ? 'Create' : 'Done'}
337
+ </button>
338
+ </Modal.Footer>
339
+ </div>
340
+ );
341
+ }
342
+ }
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+
3
+ import type { ILoadBalancerActionsProps, ILoadBalancerDeleteCommand } from '@spinnaker/core';
4
+ import { ConfirmationModalService, LoadBalancerWriter } from '@spinnaker/core';
5
+
6
+ import { AppengineCreateLoadBalancerModal } from '../configure/AppengineCreateLoadBalancerModal';
7
+
8
+ export function canDeleteAppengineLoadBalancer(loadBalancer: any): boolean {
9
+ return loadBalancer.name !== 'default';
10
+ }
11
+
12
+ export function getDeleteLoadBalancerWarning(loadBalancer: any): string {
13
+ const serverGroupNames = (loadBalancer.serverGroups || []).map((serverGroup: any) => serverGroup.name);
14
+ if (!serverGroupNames.length) {
15
+ return null;
16
+ }
17
+
18
+ if (serverGroupNames.length === 1) {
19
+ return `<div class="alert alert-warning"><p>Deleting <b>${loadBalancer.name}</b> will destroy <b>${serverGroupNames[0]}</b>.</p></div>`;
20
+ }
21
+
22
+ return `<div class="alert alert-warning"><p>Deleting <b>${
23
+ loadBalancer.name
24
+ }</b> will destroy the following server groups:<ul>${serverGroupNames
25
+ .map((name: string) => `<li>${name}</li>`)
26
+ .join('')}</ul></p></div>`;
27
+ }
28
+
29
+ export function AppengineLoadBalancerActions({ app, loadBalancer }: ILoadBalancerActionsProps) {
30
+ const deleteLoadBalancer = () => {
31
+ const command: ILoadBalancerDeleteCommand = {
32
+ cloudProvider: loadBalancer.cloudProvider,
33
+ loadBalancerName: loadBalancer.name,
34
+ credentials: loadBalancer.account,
35
+ };
36
+
37
+ ConfirmationModalService.confirm({
38
+ header: `Really delete ${loadBalancer.name}?`,
39
+ buttonText: `Delete ${loadBalancer.name}`,
40
+ body: getDeleteLoadBalancerWarning(loadBalancer),
41
+ account: loadBalancer.account,
42
+ taskMonitorConfig: { application: app, title: `Deleting ${loadBalancer.name}` },
43
+ submitMethod: () => LoadBalancerWriter.deleteLoadBalancer(command, app),
44
+ });
45
+ };
46
+
47
+ return (
48
+ <div className="actions">
49
+ <button
50
+ className="btn btn-sm btn-default"
51
+ onClick={() => AppengineCreateLoadBalancerModal.show({ app, loadBalancer, isNew: false })}
52
+ type="button"
53
+ >
54
+ Edit
55
+ </button>
56
+ <button
57
+ className="btn btn-sm btn-default"
58
+ disabled={!canDeleteAppengineLoadBalancer(loadBalancer)}
59
+ onClick={deleteLoadBalancer}
60
+ type="button"
61
+ >
62
+ Delete
63
+ </button>
64
+ </div>
65
+ );
66
+ }
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+
3
+ import type { ILoadBalancerDetailsSectionProps } from '@spinnaker/core';
4
+
5
+ import type { IAppengineLoadBalancer } from '../../domain';
6
+
7
+ export function AppengineLoadBalancerDetailsSection({ loadBalancer }: ILoadBalancerDetailsSectionProps) {
8
+ const appengineLoadBalancer = loadBalancer as IAppengineLoadBalancer;
9
+ const allocations = appengineLoadBalancer.split?.allocations || {};
10
+
11
+ return (
12
+ <div className="content-section">
13
+ <div className="content-section-heading">Traffic Split</div>
14
+ <dl className="dl-horizontal dl-narrow">
15
+ <dt>Account</dt>
16
+ <dd>{appengineLoadBalancer.account || appengineLoadBalancer.credentials}</dd>
17
+ <dt>Region</dt>
18
+ <dd>{appengineLoadBalancer.region}</dd>
19
+ <dt>Shard By</dt>
20
+ <dd>{appengineLoadBalancer.split?.shardBy || 'Unspecified'}</dd>
21
+ </dl>
22
+ {Object.keys(allocations).length > 0 && (
23
+ <table className="table table-condensed">
24
+ <thead>
25
+ <tr>
26
+ <th>Server Group</th>
27
+ <th>Allocation</th>
28
+ </tr>
29
+ </thead>
30
+ <tbody>
31
+ {Object.entries(allocations).map(([serverGroup, allocation]) => (
32
+ <tr key={serverGroup}>
33
+ <td>{serverGroup}</td>
34
+ <td>{Math.round(allocation * 1000) / 10}%</td>
35
+ </tr>
36
+ ))}
37
+ </tbody>
38
+ </table>
39
+ )}
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,3 @@
1
+ export * from './AppengineLoadBalancerActions';
2
+ export * from './AppengineLoadBalancerDetailsSection';
3
+ export * from './useAppengineLoadBalancerDetails';
@@ -0,0 +1,37 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import type { IUseDetailsHookProps, UseDetailsResult } from '@spinnaker/core';
4
+
5
+ import type { IAppengineLoadBalancer } from '../../domain';
6
+
7
+ export function useAppengineLoadBalancerDetails({
8
+ app,
9
+ loadBalancerParams,
10
+ autoClose,
11
+ }: IUseDetailsHookProps): UseDetailsResult<IAppengineLoadBalancer> {
12
+ const [data, setData] = useState<IAppengineLoadBalancer>();
13
+ const [loading, setLoading] = useState(true);
14
+
15
+ const refetch = async () => {
16
+ setLoading(true);
17
+ await app.getDataSource('loadBalancers').ready();
18
+ const loadBalancer = app.getDataSource('loadBalancers').data.find((candidate: IAppengineLoadBalancer) => {
19
+ return candidate.name === loadBalancerParams.name && candidate.account === loadBalancerParams.accountId;
20
+ });
21
+
22
+ setData(loadBalancer);
23
+ setLoading(false);
24
+
25
+ if (!loadBalancer) {
26
+ autoClose();
27
+ }
28
+ };
29
+
30
+ useEffect(() => {
31
+ refetch();
32
+ const unsubscribe = app.getDataSource('loadBalancers').onRefresh(refetch);
33
+ return () => unsubscribe();
34
+ }, [loadBalancerParams.name, loadBalancerParams.accountId, loadBalancerParams.region]);
35
+
36
+ return { data, loading, error: null, refetch };
37
+ }
@@ -1,4 +1,3 @@
1
- import { module } from 'angular';
2
1
  import { camelCase, chain, cloneDeep, filter, get, has, reduce } from 'lodash';
3
2
 
4
3
  import type {
@@ -76,10 +75,7 @@ export class AppengineLoadBalancerUpsertDescription implements ILoadBalancerUpse
76
75
  }
77
76
 
78
77
  export class AppengineLoadBalancerTransformer {
79
- public static $inject = ['$q'];
80
- constructor(private $q: ng.IQService) {}
81
-
82
- public normalizeLoadBalancer(loadBalancer: ILoadBalancer): PromiseLike<ILoadBalancer> {
78
+ public normalizeLoadBalancer(loadBalancer: ILoadBalancer): Promise<ILoadBalancer> {
83
79
  loadBalancer.provider = loadBalancer.type;
84
80
  loadBalancer.instanceCounts = this.buildInstanceCounts(loadBalancer.serverGroups);
85
81
  loadBalancer.instances = [];
@@ -97,13 +93,13 @@ export class AppengineLoadBalancerTransformer {
97
93
 
98
94
  const activeServerGroups = filter(loadBalancer.serverGroups, { isDisabled: false });
99
95
  loadBalancer.instances = chain(activeServerGroups).map('instances').flatten().value() as IInstance[];
100
- return this.$q.resolve(loadBalancer);
96
+ return Promise.resolve(loadBalancer);
101
97
  }
102
98
 
103
99
  public convertLoadBalancerForEditing(
104
100
  loadBalancer: IAppengineLoadBalancer,
105
101
  application: Application,
106
- ): PromiseLike<IAppengineLoadBalancer> {
102
+ ): Promise<IAppengineLoadBalancer> {
107
103
  return application
108
104
  .getDataSource('loadBalancers')
109
105
  .ready()
@@ -161,10 +157,3 @@ export class AppengineLoadBalancerTransformer {
161
157
  return instance as IInstance;
162
158
  }
163
159
  }
164
-
165
- export const APPENGINE_LOAD_BALANCER_TRANSFORMER = 'spinnaker.appengine.loadBalancer.transformer.service';
166
-
167
- module(APPENGINE_LOAD_BALANCER_TRANSFORMER, []).service(
168
- 'appengineLoadBalancerTransformer',
169
- AppengineLoadBalancerTransformer,
170
- );
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+
3
+ import type { IExecutionDetailsSectionProps } from '@spinnaker/core';
4
+ import { ExecutionDetailsSection } from '@spinnaker/core';
5
+
6
+ function FailureMessages({ stage }: Pick<IExecutionDetailsSectionProps, 'stage'>) {
7
+ const failures = (stage.context?.exception?.details?.errors || stage.context?.failureMessages || []) as string[];
8
+ if (!failures.length) {
9
+ return null;
10
+ }
11
+
12
+ return (
13
+ <div className="alert alert-danger">
14
+ {failures.map((message, index) => (
15
+ <p key={index}>{message}</p>
16
+ ))}
17
+ </div>
18
+ );
19
+ }
20
+
21
+ export function getServerGroupDisplayName(context: any): string {
22
+ return context.cluster || context.serverGroupName;
23
+ }
24
+
25
+ function ServerGroupConfigDetails(props: IExecutionDetailsSectionProps) {
26
+ const { stage } = props;
27
+ return (
28
+ <div>
29
+ <dl className="dl-horizontal dl-narrow">
30
+ <dt>Account</dt>
31
+ <dd>{stage.context.credentials || stage.context.account}</dd>
32
+ <dt>Region</dt>
33
+ <dd>{stage.context.region}</dd>
34
+ <dt>{stage.context.cluster ? 'Cluster' : 'Server Group'}</dt>
35
+ <dd>{getServerGroupDisplayName(stage.context)}</dd>
36
+ {stage.context.target && [
37
+ <dt key="target-label">Target</dt>,
38
+ <dd key="target-value">{stage.context.target}</dd>,
39
+ ]}
40
+ </dl>
41
+ <FailureMessages stage={stage} />
42
+ </div>
43
+ );
44
+ }
45
+
46
+ export function AppengineStartServerGroupExecutionDetails(props: IExecutionDetailsSectionProps) {
47
+ return (
48
+ <ExecutionDetailsSection name={AppengineStartServerGroupExecutionDetails.title} current={props.current}>
49
+ <ServerGroupConfigDetails {...props} />
50
+ </ExecutionDetailsSection>
51
+ );
52
+ }
53
+ AppengineStartServerGroupExecutionDetails.title = 'startServerGroupConfig';
54
+
55
+ export function AppengineStopServerGroupExecutionDetails(props: IExecutionDetailsSectionProps) {
56
+ return (
57
+ <ExecutionDetailsSection name={AppengineStopServerGroupExecutionDetails.title} current={props.current}>
58
+ <ServerGroupConfigDetails {...props} />
59
+ </ExecutionDetailsSection>
60
+ );
61
+ }
62
+ AppengineStopServerGroupExecutionDetails.title = 'stopServerGroupConfig';
63
+
64
+ export function AppengineEditLoadBalancerExecutionDetails(props: IExecutionDetailsSectionProps) {
65
+ const loadBalancers = props.stage.context.loadBalancers || [];
66
+ return (
67
+ <ExecutionDetailsSection name={AppengineEditLoadBalancerExecutionDetails.title} current={props.current}>
68
+ <table className="table table-condensed">
69
+ <thead>
70
+ <tr>
71
+ <th>Account</th>
72
+ <th>Name</th>
73
+ <th>Region</th>
74
+ </tr>
75
+ </thead>
76
+ <tbody>
77
+ {loadBalancers.map((loadBalancer: any, index: number) => (
78
+ <tr key={index}>
79
+ <td>{loadBalancer.credentials || loadBalancer.account}</td>
80
+ <td>{loadBalancer.name || loadBalancer.loadBalancerName}</td>
81
+ <td>{loadBalancer.region}</td>
82
+ </tr>
83
+ ))}
84
+ </tbody>
85
+ </table>
86
+ <FailureMessages stage={props.stage} />
87
+ </ExecutionDetailsSection>
88
+ );
89
+ }
90
+ AppengineEditLoadBalancerExecutionDetails.title = 'editLoadBalancerConfig';