@spinnaker/appengine 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 (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
@@ -1,54 +1,17 @@
1
- import { module } from 'angular';
2
-
3
- import { Registry } from '@spinnaker/core';
4
-
5
- import { AppengineStageCtrl } from '../appengineStage.controller';
6
- import { AppengineHealth } from '../../../common/appengineHealth';
7
- import type { IAppengineStageScope } from '../../../domain';
8
-
9
- class AppengineStartServerGroupStageCtrl extends AppengineStageCtrl {
10
- public static $inject = ['$scope'];
11
- constructor(public $scope: IAppengineStageScope) {
12
- super($scope);
13
-
14
- super.setAccounts().then(() => {
15
- super.setStageRegion();
16
- });
17
-
18
- super.setStageCloudProvider();
19
- super.setTargets();
20
- super.setStageCredentials();
21
-
22
- if (
23
- $scope.stage.isNew &&
24
- $scope.application.attributes.platformHealthOnlyShowOverride &&
25
- $scope.application.attributes.platformHealthOnly
26
- ) {
27
- $scope.stage.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
28
- }
29
- }
30
- }
31
-
32
- export const APPENGINE_START_SERVER_GROUP_STAGE = 'spinnaker.appengine.pipeline.stage.startServerGroupStage';
33
-
34
- module(APPENGINE_START_SERVER_GROUP_STAGE, [])
35
- .config(() => {
36
- Registry.pipeline.registerStage({
37
- label: 'Start Server Group',
38
- description: 'Starts a server group.',
39
- key: 'startAppEngineServerGroup',
40
- templateUrl: require('./startServerGroupStage.html'),
41
- executionDetailsUrl: require('./startServerGroupExecutionDetails.html'),
42
- executionConfigSections: ['startServerGroupConfig', 'taskStatus'],
43
- executionStepLabelUrl: require('./startServerGroupStepLabel.html'),
44
- controller: 'appengineStartServerGroupStageCtrl',
45
- controllerAs: 'startServerGroupStageCtrl',
46
- validators: [
47
- { type: 'requiredField', fieldName: 'cluster' },
48
- { type: 'requiredField', fieldName: 'target' },
49
- { type: 'requiredField', fieldName: 'credentials', fieldLabel: 'account' },
50
- ],
51
- cloudProvider: 'appengine',
52
- });
53
- })
54
- .controller('appengineStartServerGroupStageCtrl', AppengineStartServerGroupStageCtrl);
1
+ import { ExecutionDetailsTasks, Registry } from '@spinnaker/core';
2
+ import { AppengineStartServerGroupExecutionDetails } from '../AppengineExecutionDetails';
3
+ import { AppengineServerGroupStageConfigWithHealthOverride } from '../AppengineServerGroupStageConfig';
4
+
5
+ Registry.pipeline.registerStage({
6
+ label: 'Start Server Group',
7
+ description: 'Starts a server group.',
8
+ key: 'startAppEngineServerGroup',
9
+ component: AppengineServerGroupStageConfigWithHealthOverride,
10
+ executionDetailsSections: [AppengineStartServerGroupExecutionDetails, ExecutionDetailsTasks],
11
+ validators: [
12
+ { type: 'requiredField', fieldName: 'cluster' },
13
+ { type: 'requiredField', fieldName: 'target' },
14
+ { type: 'requiredField', fieldName: 'credentials', fieldLabel: 'account' },
15
+ ],
16
+ cloudProvider: 'appengine',
17
+ });
@@ -1,54 +1,17 @@
1
- import { module } from 'angular';
2
-
3
- import { Registry } from '@spinnaker/core';
4
-
5
- import { AppengineStageCtrl } from '../appengineStage.controller';
6
- import { AppengineHealth } from '../../../common/appengineHealth';
7
- import type { IAppengineStageScope } from '../../../domain/index';
8
-
9
- class AppengineStopServerGroupStageCtrl extends AppengineStageCtrl {
10
- public static $inject = ['$scope'];
11
- constructor(public $scope: IAppengineStageScope) {
12
- super($scope);
13
-
14
- super.setAccounts().then(() => {
15
- super.setStageRegion();
16
- });
17
-
18
- super.setStageCloudProvider();
19
- super.setTargets();
20
- super.setStageCredentials();
21
-
22
- if (
23
- $scope.stage.isNew &&
24
- $scope.application.attributes.platformHealthOnlyShowOverride &&
25
- $scope.application.attributes.platformHealthOnly
26
- ) {
27
- $scope.stage.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
28
- }
29
- }
30
- }
31
-
32
- export const APPENGINE_STOP_SERVER_GROUP_STAGE = 'spinnaker.appengine.pipeline.stage.stopServerGroupStage';
33
-
34
- module(APPENGINE_STOP_SERVER_GROUP_STAGE, [])
35
- .config(() => {
36
- Registry.pipeline.registerStage({
37
- label: 'Stop Server Group',
38
- description: 'Stops a server group.',
39
- key: 'stopAppEngineServerGroup',
40
- templateUrl: require('./stopServerGroupStage.html'),
41
- executionDetailsUrl: require('./stopServerGroupExecutionDetails.html'),
42
- executionConfigSections: ['stopServerGroupConfig', 'taskStatus'],
43
- executionStepLabelUrl: require('./stopServerGroupStepLabel.html'),
44
- controller: 'appengineStopServerGroupStageCtrl',
45
- controllerAs: 'stopServerGroupStageCtrl',
46
- validators: [
47
- { type: 'requiredField', fieldName: 'cluster' },
48
- { type: 'requiredField', fieldName: 'target' },
49
- { type: 'requiredField', fieldName: 'credentials', fieldLabel: 'account' },
50
- ],
51
- cloudProvider: 'appengine',
52
- });
53
- })
54
- .controller('appengineStopServerGroupStageCtrl', AppengineStopServerGroupStageCtrl);
1
+ import { ExecutionDetailsTasks, Registry } from '@spinnaker/core';
2
+ import { AppengineStopServerGroupExecutionDetails } from '../AppengineExecutionDetails';
3
+ import { AppengineServerGroupStageConfigWithHealthOverride } from '../AppengineServerGroupStageConfig';
4
+
5
+ Registry.pipeline.registerStage({
6
+ label: 'Stop Server Group',
7
+ description: 'Stops a server group.',
8
+ key: 'stopAppEngineServerGroup',
9
+ component: AppengineServerGroupStageConfigWithHealthOverride,
10
+ executionDetailsSections: [AppengineStopServerGroupExecutionDetails, ExecutionDetailsTasks],
11
+ validators: [
12
+ { type: 'requiredField', fieldName: 'cluster' },
13
+ { type: 'requiredField', fieldName: 'target' },
14
+ { type: 'requiredField', fieldName: 'credentials', fieldLabel: 'account' },
15
+ ],
16
+ cloudProvider: 'appengine',
17
+ });
@@ -1,6 +1,3 @@
1
- import type { IQService } from 'angular';
2
- import { module } from 'angular';
3
-
4
1
  import type {
5
2
  Application,
6
3
  IArtifact,
@@ -95,14 +92,11 @@ export class AppengineServerGroupCommandBuilder {
95
92
  return pipeline.expectedArtifacts || [];
96
93
  }
97
94
 
98
- public static $inject = ['$q'];
99
- constructor(private $q: IQService) {}
100
-
101
95
  public buildNewServerGroupCommand(
102
96
  app: Application,
103
97
  selectedProvider: string,
104
98
  mode = 'create',
105
- ): PromiseLike<IAppengineServerGroupCommand> {
99
+ ): Promise<IAppengineServerGroupCommand> {
106
100
  if (selectedProvider == null) {
107
101
  selectedProvider = 'appengine';
108
102
  }
@@ -117,7 +111,7 @@ export class AppengineServerGroupCommandBuilder {
117
111
  disableStrategySelection: mode === 'create',
118
112
  };
119
113
 
120
- return this.$q.all(dataToFetch).then((backingData: any) => {
114
+ return this.resolveAll(dataToFetch).then((backingData: any) => {
121
115
  const credentials = this.getCredentials(backingData.accounts);
122
116
  const region = this.getRegion(backingData.accounts, credentials);
123
117
 
@@ -129,6 +123,9 @@ export class AppengineServerGroupCommandBuilder {
129
123
  credentials,
130
124
  region,
131
125
  selectedProvider,
126
+ configArtifacts: [],
127
+ configFilepaths: [],
128
+ configFiles: [],
132
129
  interestingHealthProviderNames: [],
133
130
  sourceType: AppengineSourceType.GIT,
134
131
  } as IAppengineServerGroupCommand;
@@ -138,7 +135,7 @@ export class AppengineServerGroupCommandBuilder {
138
135
  public buildServerGroupCommandFromExisting(
139
136
  app: Application,
140
137
  serverGroup: IAppengineServerGroup,
141
- ): PromiseLike<IAppengineServerGroupCommand> {
138
+ ): Promise<IAppengineServerGroupCommand> {
142
139
  return this.buildNewServerGroupCommand(app, 'appengine', 'clone').then((command) => {
143
140
  command.stack = serverGroup.stack;
144
141
  command.freeFormDetails = serverGroup.detail;
@@ -149,7 +146,7 @@ export class AppengineServerGroupCommandBuilder {
149
146
  public buildNewServerGroupCommandForPipeline(
150
147
  _stage: IStage,
151
148
  pipeline: IPipeline,
152
- ): PromiseLike<{
149
+ ): Promise<{
153
150
  viewState: {
154
151
  stage: IStage;
155
152
  pipeline: IPipeline;
@@ -161,7 +158,7 @@ export class AppengineServerGroupCommandBuilder {
161
158
  }> {
162
159
  // We can't copy server group configuration for App Engine, and can't build the full command here because we don't have
163
160
  // access to the application.
164
- return this.$q.when({
161
+ return Promise.resolve({
165
162
  viewState: {
166
163
  pipeline,
167
164
  stage: _stage,
@@ -178,7 +175,7 @@ export class AppengineServerGroupCommandBuilder {
178
175
  cluster: AppengineDeployDescription,
179
176
  _stage: IStage,
180
177
  pipeline: IPipeline,
181
- ): PromiseLike<IAppengineServerGroupCommand> {
178
+ ): Promise<IAppengineServerGroupCommand> {
182
179
  return this.buildNewServerGroupCommand(app, 'appengine', 'editPipeline').then(
183
180
  (command: IAppengineServerGroupCommand) => {
184
181
  command = {
@@ -225,11 +222,15 @@ export class AppengineServerGroupCommandBuilder {
225
222
  return 'Create';
226
223
  }
227
224
  }
228
- }
229
-
230
- export const APPENGINE_SERVER_GROUP_COMMAND_BUILDER = 'spinnaker.appengine.serverGroupCommandBuilder.service';
231
225
 
232
- module(APPENGINE_SERVER_GROUP_COMMAND_BUILDER, []).service(
233
- 'appengineServerGroupCommandBuilder',
234
- AppengineServerGroupCommandBuilder,
235
- );
226
+ private resolveAll<T extends Record<string, PromiseLike<any>>>(
227
+ dataToFetch: T,
228
+ ): Promise<{ [K in keyof T]: Awaited<T[K]> }> {
229
+ const entries = Object.entries(dataToFetch);
230
+ return Promise.all(entries.map(([, promise]) => promise)).then((values) => {
231
+ return entries.reduce((result, [key], index) => ({ ...result, [key]: values[index] }), {}) as {
232
+ [K in keyof T]: Awaited<T[K]>;
233
+ };
234
+ });
235
+ }
236
+ }
@@ -0,0 +1,119 @@
1
+ import React from 'react';
2
+ import { Modal } from 'react-bootstrap';
3
+
4
+ import type { Application, IModalComponentProps } from '@spinnaker/core';
5
+ import { ModalClose, noop, ReactModal, TaskMonitor, TaskMonitorWrapper } from '@spinnaker/core';
6
+
7
+ import type { IAppengineServerGroupCommand } from '../serverGroupCommandBuilder.service';
8
+ import { AppengineSourceType } from '../serverGroupCommandBuilder.service';
9
+ import { AppengineServerGroupWriter } from '../../writer/serverGroup.write.service';
10
+
11
+ export interface IAppengineCloneServerGroupModalProps extends Partial<IModalComponentProps> {
12
+ application: Application;
13
+ command: IAppengineServerGroupCommand;
14
+ title: string;
15
+ }
16
+
17
+ interface IAppengineCloneServerGroupModalState {
18
+ taskMonitor: TaskMonitor;
19
+ }
20
+
21
+ const writer = new AppengineServerGroupWriter();
22
+
23
+ export function normalizeAppengineServerGroupCommandForSubmit(
24
+ command: IAppengineServerGroupCommand,
25
+ ): IAppengineServerGroupCommand {
26
+ return {
27
+ ...command,
28
+ configArtifacts: command.configArtifacts || [],
29
+ configFilepaths: command.configFilepaths || [],
30
+ configFiles: command.configFiles || [],
31
+ interestingHealthProviderNames: command.interestingHealthProviderNames || [],
32
+ fromArtifact: command.fromArtifact || false,
33
+ sourceType: command.sourceType || AppengineSourceType.GIT,
34
+ };
35
+ }
36
+
37
+ export class AppengineCloneServerGroupModal extends React.Component<
38
+ IAppengineCloneServerGroupModalProps,
39
+ IAppengineCloneServerGroupModalState
40
+ > {
41
+ public static defaultProps: Partial<IAppengineCloneServerGroupModalProps> = {
42
+ closeModal: noop,
43
+ dismissModal: noop,
44
+ };
45
+
46
+ public static show(props: IAppengineCloneServerGroupModalProps): Promise<IAppengineServerGroupCommand> {
47
+ return ReactModal.show(AppengineCloneServerGroupModal, props, { dialogClassName: 'modal-lg' });
48
+ }
49
+
50
+ public state: IAppengineCloneServerGroupModalState = {
51
+ taskMonitor: new TaskMonitor({
52
+ application: this.props.application,
53
+ title: 'Creating your server group',
54
+ onDismiss: () => this.props.dismissModal(),
55
+ onTaskComplete: () => this.props.application.serverGroups.refresh(),
56
+ }),
57
+ };
58
+
59
+ private update = (field: keyof IAppengineServerGroupCommand, value: any) => {
60
+ (this.props.command as any)[field] = value;
61
+ this.forceUpdate();
62
+ };
63
+
64
+ private submit = () => {
65
+ const { command } = this.props;
66
+ if (command.viewState.mode === 'editPipeline' || command.viewState.mode === 'createPipeline') {
67
+ this.props.closeModal(command);
68
+ return;
69
+ }
70
+
71
+ this.state.taskMonitor.submit(() =>
72
+ writer.cloneServerGroup(normalizeAppengineServerGroupCommandForSubmit(command), this.props.application),
73
+ );
74
+ };
75
+
76
+ public render() {
77
+ const { command, dismissModal, title } = this.props;
78
+
79
+ return (
80
+ <div>
81
+ <ModalClose dismiss={dismissModal} />
82
+ <Modal.Header>
83
+ <Modal.Title>{title}</Modal.Title>
84
+ </Modal.Header>
85
+ <Modal.Body>
86
+ <div className="form-horizontal">
87
+ <label>Account</label>
88
+ <input
89
+ className="form-control input-sm"
90
+ onChange={(event) => this.update('credentials', event.target.value)}
91
+ value={command.credentials || ''}
92
+ />
93
+ <label>Region</label>
94
+ <input
95
+ className="form-control input-sm"
96
+ onChange={(event) => this.update('region', event.target.value)}
97
+ value={command.region || ''}
98
+ />
99
+ <label>Branch</label>
100
+ <input
101
+ className="form-control input-sm"
102
+ onChange={(event) => this.update('branch', event.target.value)}
103
+ value={command.branch || ''}
104
+ />
105
+ </div>
106
+ </Modal.Body>
107
+ <TaskMonitorWrapper monitor={this.state.taskMonitor} />
108
+ <Modal.Footer>
109
+ <button className="btn btn-default" onClick={() => dismissModal()} type="button">
110
+ Cancel
111
+ </button>
112
+ <button className="btn btn-primary" onClick={this.submit} type="button">
113
+ {command.viewState?.submitButtonLabel || 'Clone'}
114
+ </button>
115
+ </Modal.Footer>
116
+ </div>
117
+ );
118
+ }
119
+ }
@@ -3,14 +3,6 @@
3
3
  margin-top: 0.75rem;
4
4
  }
5
5
 
6
- help-field.help-field-absolute {
7
- span {
8
- position: absolute;
9
- top: 7px;
10
- right: 2px;
11
- }
12
- }
13
-
14
6
  .artifact-configuration-section {
15
7
  border-bottom: 1px solid #eee;
16
8
  padding: 0 0 4px 0;
@@ -0,0 +1,245 @@
1
+ import React from 'react';
2
+
3
+ import type { Application, IConfirmationModalParams, IServerGroup, IServerGroupActionsProps } from '@spinnaker/core';
4
+ import { ConfirmationModalService, ServerGroupWarningMessageService } from '@spinnaker/core';
5
+
6
+ import { AppengineHealth } from '../../common/appengineHealth';
7
+ import { AppengineServerGroupCommandBuilder } from '../configure/serverGroupCommandBuilder.service';
8
+ import { AppengineCloneServerGroupModal } from '../configure/wizard/AppengineCloneServerGroupModal';
9
+ import type { IAppengineServerGroup } from '../../domain';
10
+ import { AppengineServerGroupWriter } from '../writer/serverGroup.write.service';
11
+
12
+ const writer = new AppengineServerGroupWriter();
13
+ const commandBuilder = new AppengineServerGroupCommandBuilder();
14
+
15
+ export function canStartOrStopServerGroup(serverGroup: IAppengineServerGroup): boolean {
16
+ return serverGroup.env === 'FLEXIBLE' || ['MANUAL', 'BASIC'].includes(serverGroup.scalingPolicy?.type);
17
+ }
18
+
19
+ export function canStartServerGroup(serverGroup: IAppengineServerGroup): boolean {
20
+ return canStartOrStopServerGroup(serverGroup) && serverGroup.servingStatus === 'STOPPED';
21
+ }
22
+
23
+ export function canStopServerGroup(serverGroup: IAppengineServerGroup): boolean {
24
+ return canStartOrStopServerGroup(serverGroup) && serverGroup.servingStatus === 'SERVING';
25
+ }
26
+
27
+ export function expectedAllocationsAfterDisableOperation(
28
+ serverGroup: IServerGroup,
29
+ loadBalancers: any[] = [],
30
+ ): { [key: string]: number } {
31
+ const loadBalancer = loadBalancers.find((toCheck) => {
32
+ return Object.keys(toCheck.split?.allocations || {}).includes(serverGroup.name);
33
+ });
34
+
35
+ if (!loadBalancer) {
36
+ return null;
37
+ }
38
+
39
+ const allocations: { [key: string]: number } = { ...loadBalancer.split.allocations };
40
+ delete allocations[serverGroup.name];
41
+ const denominator = Object.values(allocations).reduce((sum, allocation) => sum + allocation, 0);
42
+ if (!denominator) {
43
+ return {};
44
+ }
45
+
46
+ const precision = loadBalancer.split.shardBy === 'COOKIE' ? 1000 : 100;
47
+ Object.keys(allocations).forEach((serverGroupName) => {
48
+ allocations[serverGroupName] = Math.round((allocations[serverGroupName] / denominator) * precision) / precision;
49
+ });
50
+ return allocations;
51
+ }
52
+
53
+ export function canDisableServerGroup(serverGroup: IAppengineServerGroup, loadBalancers: any[] = []): boolean {
54
+ if (!serverGroup || serverGroup.disabled) {
55
+ return false;
56
+ }
57
+
58
+ return Object.keys(expectedAllocationsAfterDisableOperation(serverGroup, loadBalancers) || {}).length > 0;
59
+ }
60
+
61
+ export function canDestroyServerGroup(serverGroup: IAppengineServerGroup, loadBalancers: any[] = []): boolean {
62
+ if (!serverGroup) {
63
+ return false;
64
+ }
65
+
66
+ return (
67
+ serverGroup.disabled ||
68
+ Object.keys(expectedAllocationsAfterDisableOperation(serverGroup, loadBalancers) || {}).length > 0
69
+ );
70
+ }
71
+
72
+ function buildExpectedAllocationsTable(expectedAllocations: { [key: string]: number }): string {
73
+ const rows = Object.keys(expectedAllocations || {})
74
+ .map(
75
+ (serverGroupName) =>
76
+ `<tr><td>${serverGroupName}</td><td>${expectedAllocations[serverGroupName] * 100}%</td></tr>`,
77
+ )
78
+ .join('');
79
+
80
+ return `<table class="table table-condensed"><thead><tr><th>Server Group</th><th>Allocation</th></tr></thead><tbody>${rows}</tbody></table>`;
81
+ }
82
+
83
+ function getLoadBalancers(app: Application): any[] {
84
+ return app.getDataSource?.('loadBalancers')?.data || [];
85
+ }
86
+
87
+ function platformHealthParams(app: Application): Partial<IConfirmationModalParams> {
88
+ const params: Partial<IConfirmationModalParams> = {
89
+ platformHealthOnlyShowOverride: app.attributes?.platformHealthOnlyShowOverride,
90
+ platformHealthType: AppengineHealth.PLATFORM,
91
+ interestingHealthProviderNames: [],
92
+ };
93
+ if (app.attributes?.platformHealthOnlyShowOverride && app.attributes?.platformHealthOnly) {
94
+ params.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
95
+ }
96
+ return params;
97
+ }
98
+
99
+ function getDisableBody(serverGroup: IAppengineServerGroup, loadBalancers: any[]): string {
100
+ const expectedAllocations = expectedAllocationsAfterDisableOperation(serverGroup, loadBalancers);
101
+ return `<div class="well well-sm">
102
+ <p>For App Engine, a disable operation sets this server group's allocation to 0% and redistributes traffic to the other enabled server groups.</p>
103
+ <p>If you would like more fine-grained control, edit <b>${
104
+ serverGroup.loadBalancers?.[0]
105
+ }</b> under the <b>Load Balancers</b> tab.</p>
106
+ <div class="row"><div class="col-md-12">${buildExpectedAllocationsTable(expectedAllocations)}</div></div>
107
+ </div>`;
108
+ }
109
+
110
+ function getDestroyBody(app: Application, serverGroup: IAppengineServerGroup, loadBalancers: any[]): string {
111
+ let body = '';
112
+ const params: IConfirmationModalParams = {} as IConfirmationModalParams;
113
+ ServerGroupWarningMessageService.addDestroyWarningMessage(app, serverGroup, params);
114
+ if (params.body) {
115
+ body += params.body;
116
+ }
117
+ if (!serverGroup.disabled) {
118
+ body += `<div class="well well-sm">
119
+ <p>A destroy operation will first disable this server group.</p>
120
+ <p>For App Engine, a disable operation sets this server group's allocation to 0% and redistributes traffic to the other enabled server groups.</p>
121
+ <p>If you would like more fine-grained control, edit <b>${
122
+ serverGroup.loadBalancers?.[0]
123
+ }</b> under the <b>Load Balancers</b> tab.</p>
124
+ <div class="row"><div class="col-md-12">${buildExpectedAllocationsTable(
125
+ expectedAllocationsAfterDisableOperation(serverGroup, loadBalancers),
126
+ )}</div></div>
127
+ </div>`;
128
+ }
129
+ return body;
130
+ }
131
+
132
+ export function AppengineServerGroupActions({ app, serverGroup }: IServerGroupActionsProps) {
133
+ const appengineServerGroup = serverGroup as IAppengineServerGroup;
134
+ const loadBalancers = getLoadBalancers(app);
135
+
136
+ const clone = () => {
137
+ commandBuilder.buildServerGroupCommandFromExisting(app, appengineServerGroup).then((command) => {
138
+ AppengineCloneServerGroupModal.show({ application: app, command, title: `Clone ${serverGroup.name}` });
139
+ });
140
+ };
141
+
142
+ const start = () =>
143
+ ConfirmationModalService.confirm({
144
+ header: `Really start ${serverGroup.name}?`,
145
+ buttonText: `Start ${serverGroup.name}`,
146
+ account: appengineServerGroup.account,
147
+ taskMonitorConfig: { application: app, title: `Starting ${serverGroup.name}` },
148
+ submitMethod: () => writer.startServerGroup(appengineServerGroup, app),
149
+ askForReason: true,
150
+ ...platformHealthParams(app),
151
+ });
152
+
153
+ const stop = () =>
154
+ ConfirmationModalService.confirm({
155
+ header: `Really stop ${serverGroup.name}?`,
156
+ buttonText: `Stop ${serverGroup.name}`,
157
+ account: appengineServerGroup.account,
158
+ body: !appengineServerGroup.disabled
159
+ ? `<div class="alert alert-danger"><p>Stopping this server group will scale it down to zero instances.</p><p>This server group is currently serving traffic from <b>${appengineServerGroup.loadBalancers?.[0]}</b>. Traffic directed to this server group after it has been stopped will not be handled.</p></div>`
160
+ : undefined,
161
+ taskMonitorConfig: { application: app, title: `Stopping ${serverGroup.name}` },
162
+ submitMethod: () => writer.stopServerGroup(appengineServerGroup, app),
163
+ askForReason: true,
164
+ ...platformHealthParams(app),
165
+ });
166
+
167
+ const enable = () =>
168
+ ConfirmationModalService.confirm({
169
+ header: `Really enable ${serverGroup.name}?`,
170
+ buttonText: `Enable ${serverGroup.name}`,
171
+ account: appengineServerGroup.account,
172
+ body: `<div class="well well-sm"><p>Enabling <b>${serverGroup.name}</b> will set its traffic allocation for <b>${appengineServerGroup.loadBalancers?.[0]}</b> to 100%.</p><p>If you would like more fine-grained control, edit <b>${appengineServerGroup.loadBalancers?.[0]}</b> under the <b>Load Balancers</b> tab.</p></div>`,
173
+ taskMonitorConfig: { application: app, title: `Enabling ${serverGroup.name}` },
174
+ submitMethod: (params: any) => writer.enableServerGroup(appengineServerGroup, app, params),
175
+ askForReason: true,
176
+ ...platformHealthParams(app),
177
+ });
178
+
179
+ const disable = () =>
180
+ ConfirmationModalService.confirm({
181
+ header: `Really disable ${serverGroup.name}?`,
182
+ buttonText: `Disable ${serverGroup.name}`,
183
+ account: appengineServerGroup.account,
184
+ body: getDisableBody(appengineServerGroup, loadBalancers),
185
+ taskMonitorConfig: { application: app, title: `Disabling ${serverGroup.name}` },
186
+ submitMethod: (params: any) => writer.disableServerGroup(appengineServerGroup, app, params),
187
+ askForReason: true,
188
+ ...platformHealthParams(app),
189
+ });
190
+
191
+ const destroy = () =>
192
+ ConfirmationModalService.confirm({
193
+ header: `Really destroy ${serverGroup.name}?`,
194
+ buttonText: `Destroy ${serverGroup.name}`,
195
+ account: appengineServerGroup.account,
196
+ body: getDestroyBody(app, appengineServerGroup, loadBalancers),
197
+ taskMonitorConfig: { application: app, title: `Destroying ${serverGroup.name}` },
198
+ submitMethod: (params: any) => writer.destroyServerGroup(appengineServerGroup, app, params),
199
+ askForReason: true,
200
+ ...platformHealthParams(app),
201
+ });
202
+
203
+ return (
204
+ <div className="actions">
205
+ <button className="btn btn-sm btn-default" onClick={clone} type="button">
206
+ Clone
207
+ </button>
208
+ <button
209
+ className="btn btn-sm btn-default"
210
+ disabled={!canStartServerGroup(appengineServerGroup)}
211
+ onClick={start}
212
+ type="button"
213
+ >
214
+ Start
215
+ </button>
216
+ <button
217
+ className="btn btn-sm btn-default"
218
+ disabled={!canStopServerGroup(appengineServerGroup)}
219
+ onClick={stop}
220
+ type="button"
221
+ >
222
+ Stop
223
+ </button>
224
+ <button className="btn btn-sm btn-default" onClick={enable} type="button">
225
+ Enable
226
+ </button>
227
+ <button
228
+ className="btn btn-sm btn-default"
229
+ disabled={!canDisableServerGroup(appengineServerGroup, loadBalancers)}
230
+ onClick={disable}
231
+ type="button"
232
+ >
233
+ Disable
234
+ </button>
235
+ <button
236
+ className="btn btn-sm btn-default"
237
+ disabled={!canDestroyServerGroup(appengineServerGroup, loadBalancers)}
238
+ onClick={destroy}
239
+ type="button"
240
+ >
241
+ Destroy
242
+ </button>
243
+ </div>
244
+ );
245
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+
3
+ import type { IServerGroupDetailsSectionProps } from '@spinnaker/core';
4
+
5
+ import type { IAppengineServerGroup } from '../../domain';
6
+
7
+ export function AppengineServerGroupDetailsSection({ serverGroup }: IServerGroupDetailsSectionProps) {
8
+ const appengineServerGroup = serverGroup as IAppengineServerGroup;
9
+
10
+ return (
11
+ <div className="content-section">
12
+ <div className="content-section-heading">App Engine Details</div>
13
+ <dl className="dl-horizontal dl-narrow">
14
+ <dt>Account</dt>
15
+ <dd>{appengineServerGroup.account}</dd>
16
+ <dt>Region</dt>
17
+ <dd>{appengineServerGroup.region}</dd>
18
+ <dt>Status</dt>
19
+ <dd>{appengineServerGroup.servingStatus || appengineServerGroup.status || 'Unknown'}</dd>
20
+ <dt>Environment</dt>
21
+ <dd>{appengineServerGroup.env || 'Unknown'}</dd>
22
+ <dt>Scaling</dt>
23
+ <dd>{appengineServerGroup.scalingPolicy?.type || 'Unknown'}</dd>
24
+ </dl>
25
+ </div>
26
+ );
27
+ }