@spinnaker/appengine 2025.4.0 → 2026.0.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.
package/dist/index.js CHANGED
@@ -1,4084 +1,2 @@
1
- import { module, extend, copy } from 'angular';
2
- import { HelpContentsRegistry, ConfirmationModalService, RecentHistoryService, InstanceReader, InstanceWriter, StageConstants, AppListExtractor, LoadBalancerWriter, TaskMonitor, AccountService, Registry, CloudProviderRegistry, FormikFormField, ReactSelectInput, StageArtifactSelectorDelegate, excludeAllTypesExcept, ArtifactTypePatterns, FormikStageConfig, FormValidator, ExecutionDetailsTasks, ExecutionArtifactTab, SETTINGS, StorageAccountReader, NgAppEngineDeployArtifactDelegate, ExpectedArtifactSelectorViewController, NgAppengineConfigArtifactDelegate, SERVER_GROUP_WRITER, StageArtifactSelector, withErrorBoundary, TaskExecutor, ServerGroupWarningMessageService, ServerGroupReader, ApplicationNameValidator, DeploymentStrategyRegistry } from '@spinnaker/core';
3
- import { cloneDeep, flattenDeep, uniq, difference, filter, chain, has, camelCase, get, reduce, set, merge, map, mapValues } from 'lodash';
4
- import React from 'react';
5
- import { Subject, from } from 'rxjs';
6
- import { takeUntil } from 'rxjs/operators';
7
- import { react2angular } from 'react2angular';
8
- import classNames from 'classnames';
9
-
10
- const appengineComponentUrlDetailsComponent = {
11
- bindings: { component: "<" },
12
- template: `
13
- <dt>HTTPS</dt>
14
- <dl class="small">
15
- <a href="{{$ctrl.component.httpsUrl}}" target="_blank">{{$ctrl.component.httpsUrl}}</a>
16
- <copy-to-clipboard class="copy-to-clipboard copy-to-clipboard-sm"
17
- tool-tip="'Copy URL to clipboard'"
18
- text="$ctrl.component.httpsUrl"></copy-to-clipboard>
19
- </dl>
20
- <dt>HTTP</dt>
21
- <dl class="small">
22
- <a href="{{$ctrl.component.httpUrl}}" target="_blank">{{$ctrl.component.httpUrl}}</a>
23
- <copy-to-clipboard class="copy-to-clipboard copy-to-clipboard-sm"
24
- tool-tip="'Copy URL to clipboard'"
25
- text="$ctrl.component.httpUrl"></copy-to-clipboard>
26
- </dl>
27
- `
28
- };
29
- const APPENGINE_COMPONENT_URL_DETAILS = "spinnaker.appengine.componentUrlDetails.component";
30
- module(APPENGINE_COMPONENT_URL_DETAILS, []).component("appengineComponentUrlDetails", appengineComponentUrlDetailsComponent);
31
-
32
- class AppengineConditionalDescriptionListItemCtrl {
33
- constructor($filter) {
34
- this.$filter = $filter;
35
- }
36
- $onInit() {
37
- if (!this.label) {
38
- this.label = this.$filter("robotToHuman")(this.key);
39
- }
40
- }
41
- }
42
- AppengineConditionalDescriptionListItemCtrl.$inject = ["$filter"];
43
- const appengineConditionalDescriptionListItem = {
44
- bindings: { label: "@", key: "@", component: "<" },
45
- transclude: {
46
- keyLabel: "?keyText",
47
- valueLabel: "?valueLabel"
48
- },
49
- template: `
50
- <dt ng-if="$ctrl.component[$ctrl.key]">{{$ctrl.label}}<span ng-transclude="keyLabel"></span></dt>
51
- <dd ng-if="$ctrl.component[$ctrl.key]">{{$ctrl.component[$ctrl.key]}}<span ng-transclude="valueLabel"></span></dd>
52
- `,
53
- controller: AppengineConditionalDescriptionListItemCtrl
54
- };
55
- const APPENGINE_CONDITIONAL_DESCRIPTION_LIST_ITEM = "spinnaker.appengine.conditionalDescriptionListItem";
56
- module(APPENGINE_CONDITIONAL_DESCRIPTION_LIST_ITEM, []).component("appengineConditionalDtDd", appengineConditionalDescriptionListItem);
57
-
58
- const appengineLoadBalancerMessageComponent = {
59
- bindings: { showCreateMessage: "<", columnOffset: "@", columns: "@" },
60
- templateUrl: "appengine/src/common/loadBalancerMessage.component.html"
61
- };
62
- const APPENGINE_LOAD_BALANCER_CREATE_MESSAGE = "spinnaker.appengine.loadBalancer.createMessage.component";
63
- module(APPENGINE_LOAD_BALANCER_CREATE_MESSAGE, []).component("appengineLoadBalancerMessage", appengineLoadBalancerMessageComponent);
64
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
65
- templateCache.put("appengine/src/common/loadBalancerMessage.component.html", `<div class="row">
66
- <div class="col-md-offset-{{ $ctrl.columnOffset || 0 }} col-md-{{ $ctrl.columns || 12 }}">
67
- <div class="well">
68
- <p>
69
- <span ng-if="$ctrl.showCreateMessage">Spinnaker cannot create a load balancer for App Engine.</span>
70
- A Spinnaker load balancer maps to an App Engine service, which is specified in a version's
71
- <code>app.yaml</code>.
72
- </p>
73
- <p>For example, the following <code>app.yaml</code></p>
74
- <pre class="text-left" style="white-space: pre-line">
75
- runtime: python27
76
- api_version: 1
77
- &hellip;
78
- service: mobile
79
- &hellip;
80
- </pre
81
- >
82
- <p>
83
- deploys to the <code>mobile</code> service. If you do not specify a service, your version will be deployed to
84
- the <code>default</code> service.
85
- </p>
86
- <p>
87
- If a service does not exist when a version is deployed, it will be created. It will then be editable as a load
88
- balancer within Spinnaker.
89
- </p>
90
- <p>
91
- <a href="https://cloud.google.com/appengine/docs/python/config/appref" target="_blank"
92
- >App Engine Documentation</a
93
- >
94
- </p>
95
- </div>
96
- </div>
97
- </div>
98
- `);
99
- }]);
100
-
101
- const helpContents = [
102
- {
103
- key: "appengine.serverGroup.gcs.repositoryUrl",
104
- value: `The full URL to the GCS bucket or TAR file containing the source files for this deployment,
105
- including 'gs://'. For example, <b>gs://my-bucket/my-app</b> or <b>gs://my-bucket/app.tar</b>.`
106
- },
107
- {
108
- key: "appengine.serverGroup.git.repositoryUrl",
109
- value: `The full URL to the git repository containing the source files for this deployment,
110
- including protocol. For example, <b>https://github.com/spinnaker/deck.git</b>`
111
- },
112
- {
113
- key: "appengine.serverGroup.gitCredentialType",
114
- value: `The credential type that will be used to access the git repository for this deployment.
115
- You can configure these credentials alongside your App Engine credentials.`
116
- },
117
- {
118
- key: "appengine.serverGroup.branch",
119
- value: "The name of the branch in the above git repository to be used for this deployment."
120
- },
121
- {
122
- key: "appengine.serverGroup.applicationDirectoryRoot",
123
- value: `(Optional) Path to the directory root of the application to be deployed,
124
- starting from the root of the git repository. This is the directory from which
125
- <code>gcloud app deploy</code> will be run.`
126
- },
127
- {
128
- key: "appengine.serverGroup.configFilepaths",
129
- value: `Paths to App Engine application configuration files, starting from the application directory root,
130
- specified above. In most cases, this will be <code>app.yaml</code> or <code>cron.yaml</code>,
131
- but could be <code>path/to/app.yaml</code> or <code>path/to/cron.yaml</code>.`
132
- },
133
- {
134
- key: "appengine.serverGroup.configFiles",
135
- value: `<p>(Optional) The contents of an App Engine config file (e.g., an <code>app.yaml</code> or
136
- <code>cron.yaml</code> file). These files should not conflict with the config filepaths above:
137
- if you include, for example, the contents of an <code>app.yaml</code>
138
- file here, you should <b>not</b> specify the file path to an <code>app.yaml</code> above.<br></p>
139
- <p>If this is a pipeline stage, you can use Spinnaker Pipeline Expressions here.</p>`
140
- },
141
- {
142
- key: "appengine.serverGroup.configFilesRequired",
143
- value: `<p>The contents of an App Engine config file (e.g., an <code>app.yaml</code> or
144
- <code>cron.yaml</code> file).</p>
145
- <p>An <code>app.yaml</code> file is required and must have <code>runtime: custom</code> in
146
- order to deploy successfully.</p>
147
- <p>If this is a pipeline stage, you can use Spinnaker Pipeline Expressions here.</p>`
148
- },
149
- {
150
- key: "appengine.serverGroup.matchBranchOnRegex",
151
- value: `(Optional) A Jenkins trigger may produce details from multiple repositories and branches.
152
- Spinnaker will use the regex specified here to help resolve a branch for the deployment.
153
- If Spinnaker cannot resolve exactly one branch from the trigger, this pipeline will fail.`
154
- },
155
- {
156
- key: "appengine.serverGroup.promote",
157
- value: "If selected, the newly deployed server group will receive all traffic."
158
- },
159
- {
160
- key: "appengine.serverGroup.stopPreviousVersion",
161
- value: `If selected, the previously running server group in this server group's <b>service</b>
162
- (Spinnaker load balancer) will be stopped. This option will be respected only if this server group will
163
- be receiving all traffic and the previous server group is using manual scaling.`
164
- },
165
- {
166
- key: "appengine.serverGroup.containerImageUrl",
167
- value: `The full URL to the container image to use for deployment. The URL must be one of the valid GCR hostnames,
168
- for example <b>gcr.io/my-project/image:tag</b>`
169
- },
170
- {
171
- key: "appengine.serverGroup.suppress-version-string",
172
- value: `Spinnaker automatically versions your server groups. This means deployments through Spinnaker receive a
173
- short version string at the end of their name, like "v001". In most cases you will want to keep this
174
- version as part of the name. Preventing this string from being added to your server group will stop
175
- it from being considered when rolling back, promoting new versions or executing deployment strategies.
176
- If you are certain that you do not want the version string applied to this server group then check
177
- this box.`
178
- },
179
- {
180
- key: "appengine.loadBalancer.shardBy.cookie",
181
- value: 'Diversion based on a specially named cookie, "GOOGAPPUID." The cookie must be set by the application itself or no diversion will occur.'
182
- },
183
- {
184
- key: "appengine.loadBalancer.shardBy.ip",
185
- value: "Diversion based on applying the modulus operation to a fingerprint of the IP address."
186
- },
187
- {
188
- key: "appengine.loadBalancer.migrateTraffic",
189
- value: `If selected, traffic will be gradually shifted to a single version. For gradual traffic migration,
190
- the target version must be located within instances that are configured for
191
- both warmup requests and automatic scaling.
192
- Gradual traffic migration is not supported in the App Engine flexible environment.`
193
- },
194
- {
195
- key: "appengine.loadBalancer.allocations",
196
- value: "An allocation is the percent of traffic directed to a server group."
197
- },
198
- {
199
- key: "appengine.loadBalancer.textLocator",
200
- value: `Either the name of a server group, or a Spinnaker Pipeline Expression
201
- that resolves to the name of a server group.`
202
- },
203
- {
204
- key: "appengine.instance.availability",
205
- value: `
206
- An instance's <b>availability</b> is determined by its version (Spinnaker server group).
207
- <ul>
208
- <li>Manual scaling versions use resident instances</li>
209
- <li>Basic scaling versions use dynamic instances</li>
210
- <li>Auto scaling versions use dynamic instances - but if you specify a number, N,
211
- of minimum idle instances, the first N instances will be resident,
212
- and additional dynamic instances will be created as necessary.
213
- </li>
214
- </ul>`
215
- },
216
- {
217
- key: "appengine.instance.averageLatency",
218
- value: "Average latency over the last minute in milliseconds."
219
- },
220
- {
221
- key: "appengine.instance.vmStatus",
222
- value: "Status of the virtual machine where this instance lives."
223
- },
224
- {
225
- key: "appengine.instance.qps",
226
- value: "Average queries per second over the last minute."
227
- },
228
- {
229
- key: "appengine.instance.errors",
230
- value: "Number of errors since this instance was started."
231
- },
232
- {
233
- key: "appengine.instance.requests",
234
- value: "Number of requests since this instance was started."
235
- }
236
- ];
237
- helpContents.forEach((entry) => HelpContentsRegistry.register(entry.key, entry.value));
238
-
239
- class AppengineInstanceDetailsController {
240
- constructor($q, app, instance) {
241
- this.$q = $q;
242
- this.app = app;
243
- this.state = { loading: true };
244
- this.upToolTip = "An App Engine instance is 'Up' if a load balancer is directing traffic to its server group.";
245
- this.outOfServiceToolTip = `
246
- An App Engine instance is 'Out Of Service' if no load balancers are directing traffic to its server group.`;
247
- this.app.ready().then(() => this.retrieveInstance(instance)).then((instanceDetails) => {
248
- this.instance = instanceDetails;
249
- this.state.loading = false;
250
- }).catch(() => {
251
- this.instanceIdNotFound = instance.instanceId;
252
- this.state.loading = false;
253
- });
254
- }
255
- terminateInstance() {
256
- const instance = cloneDeep(this.instance);
257
- const shortName = `${this.instance.name.substring(0, 10)}...`;
258
- instance.placement = {};
259
- instance.instanceId = instance.name;
260
- const taskMonitor = {
261
- application: this.app,
262
- title: "Terminating " + shortName,
263
- onTaskComplete() {
264
- if (this.$state.includes("**.instanceDetails", { instanceId: instance.name })) {
265
- this.$state.go("^");
266
- }
267
- }
268
- };
269
- const submitMethod = () => {
270
- return InstanceWriter.terminateInstance(instance, this.app, { cloudProvider: "appengine" });
271
- };
272
- ConfirmationModalService.confirm({
273
- header: "Really terminate " + shortName + "?",
274
- buttonText: "Terminate " + shortName,
275
- account: instance.account,
276
- taskMonitorConfig: taskMonitor,
277
- submitMethod
278
- });
279
- }
280
- retrieveInstance(instance) {
281
- const instanceLocatorPredicate = (dataSource) => {
282
- return dataSource.instances.some((possibleMatch) => possibleMatch.id === instance.instanceId);
283
- };
284
- const dataSources = flattenDeep([
285
- this.app.getDataSource("serverGroups").data,
286
- this.app.getDataSource("loadBalancers").data,
287
- this.app.getDataSource("loadBalancers").data.map((loadBalancer) => loadBalancer.serverGroups)
288
- ]);
289
- const instanceManager = dataSources.find(instanceLocatorPredicate);
290
- if (instanceManager) {
291
- const recentHistoryExtraData = {
292
- region: instanceManager.region,
293
- account: instanceManager.account
294
- };
295
- if (instanceManager.category === "serverGroup") {
296
- recentHistoryExtraData.serverGroup = instanceManager.name;
297
- }
298
- RecentHistoryService.addExtraDataToLatest("instances", recentHistoryExtraData);
299
- return InstanceReader.getInstanceDetails(instanceManager.account, instanceManager.region, instance.instanceId).then((instanceDetails) => {
300
- instanceDetails.account = instanceManager.account;
301
- instanceDetails.region = instanceManager.region;
302
- return instanceDetails;
303
- });
304
- } else {
305
- return this.$q.reject();
306
- }
307
- }
308
- }
309
- AppengineInstanceDetailsController.$inject = ["$q", "app", "instance"];
310
- const APPENGINE_INSTANCE_DETAILS_CTRL = "spinnaker.appengine.instanceDetails.controller";
311
- module(APPENGINE_INSTANCE_DETAILS_CTRL, []).controller("appengineInstanceDetailsCtrl", AppengineInstanceDetailsController);
312
-
313
- class AppengineLoadBalancerAdvancedSettingsCtrl {
314
- constructor() {
315
- this.state = { error: false };
316
- }
317
- disableMigrateTraffic() {
318
- if (this.loadBalancer.splitDescription.allocationDescriptions.length !== 1) {
319
- return true;
320
- } else {
321
- const targetServerGroupName = this.loadBalancer.splitDescription.allocationDescriptions[0].serverGroupName;
322
- const targetServerGroup = this.loadBalancer.serverGroups.find((candidate) => candidate.name === targetServerGroupName);
323
- if (targetServerGroup) {
324
- return !targetServerGroup.allowsGradualTrafficMigration;
325
- } else {
326
- return false;
327
- }
328
- }
329
- }
330
- }
331
- const appengineLoadBalancerAdvancedSettingsComponent = {
332
- bindings: { loadBalancer: "=", application: "<" },
333
- template: `
334
- <ng-form name="advancedSettingsForm">
335
- <div class="row">
336
- <div class="form-group">
337
- <div class="col-md-3 sm-label-right">
338
- Migrate Traffic <help-field key="appengine.loadBalancer.migrateTraffic"></help-field>
339
- </div>
340
- <div class="col-md-9">
341
- <div class="checkbox">
342
- <input type="checkbox" ng-disabled="$ctrl.disableMigrateTraffic() && !($ctrl.loadBalancer.migrateTraffic = false)" ng-model="$ctrl.loadBalancer.migrateTraffic">
343
- </div>
344
- </div>
345
- </div>
346
- </div>
347
- </ng-form>
348
- `,
349
- controller: AppengineLoadBalancerAdvancedSettingsCtrl
350
- };
351
- const APPENGINE_LOAD_BALANCER_ADVANCED_SETTINGS = "spinnaker.appengine.loadBalancer.advancedSettings.component";
352
- module(APPENGINE_LOAD_BALANCER_ADVANCED_SETTINGS, []).component("appengineLoadBalancerAdvancedSettings", appengineLoadBalancerAdvancedSettingsComponent);
353
-
354
- class AppengineAllocationConfigurationRowCtrl {
355
- getServerGroupOptions() {
356
- if (this.allocationDescription.serverGroupName) {
357
- return uniq(this.serverGroupOptions.concat(this.allocationDescription.serverGroupName));
358
- } else {
359
- return this.serverGroupOptions;
360
- }
361
- }
362
- }
363
- const appengineAllocationConfigurationRowComponent = {
364
- bindings: {
365
- allocationDescription: "<",
366
- removeAllocation: "&",
367
- serverGroupOptions: "<",
368
- onAllocationChange: "&"
369
- },
370
- template: `
371
- <div class="form-group">
372
- <div class="row">
373
- <div class="col-md-7">
374
- <ui-select ng-model="$ctrl.allocationDescription.serverGroupName"
375
- on-select="$ctrl.onAllocationChange()"
376
- class="form-control input-sm">
377
- <ui-select-match placeholder="Select...">
378
- {{$select.selected}}
379
- </ui-select-match>
380
- <ui-select-choices repeat="serverGroup as serverGroup in $ctrl.getServerGroupOptions() | filter: $select.search">
381
- <div ng-bind-html="serverGroup | highlight: $select.search"></div>
382
- </ui-select-choices>
383
- </ui-select>
384
- </div>
385
- <div class="col-md-3">
386
- <div class="input-group input-group-sm">
387
- <input type="number"
388
- ng-model="$ctrl.allocationDescription.allocation"
389
- required
390
- class="form-control input-sm"
391
- min="0"
392
- ng-change="$ctrl.onAllocationChange()"
393
- max="100"/>
394
- <span class="input-group-addon">%</span>
395
- </div>
396
- </div>
397
- <div class="col-md-2">
398
- <a class="btn btn-link sm-label" ng-click="$ctrl.removeAllocation()">
399
- <span class="glyphicon glyphicon-trash"></span>
400
- </a>
401
- </div>
402
- </div>
403
- </div>
404
- `,
405
- controller: AppengineAllocationConfigurationRowCtrl
406
- };
407
- const APPENGINE_ALLOCATION_CONFIGURATION_ROW = "spinnaker.appengine.allocationConfigurationRow.component";
408
- module(APPENGINE_ALLOCATION_CONFIGURATION_ROW, []).component("appengineAllocationConfigurationRow", appengineAllocationConfigurationRowComponent);
409
-
410
- class AppengineLoadBalancerSettingsController {
411
- $onInit() {
412
- this.updateServerGroupOptions();
413
- }
414
- addAllocation() {
415
- const remainingServerGroups = this.serverGroupsWithoutAllocation();
416
- if (remainingServerGroups.length) {
417
- this.loadBalancer.splitDescription.allocationDescriptions.push({
418
- serverGroupName: remainingServerGroups[0],
419
- allocation: 0,
420
- locatorType: "fromExisting"
421
- });
422
- if (this.loadBalancer.splitDescription.allocationDescriptions.length > 1 && !this.loadBalancer.splitDescription.shardBy) {
423
- this.loadBalancer.splitDescription.shardBy = "IP";
424
- }
425
- this.updateServerGroupOptions();
426
- } else if (this.forPipelineConfig) {
427
- this.loadBalancer.splitDescription.allocationDescriptions.push({
428
- allocation: 0,
429
- locatorType: "text",
430
- serverGroupName: ""
431
- });
432
- }
433
- }
434
- removeAllocation(index) {
435
- this.loadBalancer.splitDescription.allocationDescriptions.splice(index, 1);
436
- this.updateServerGroupOptions();
437
- }
438
- allocationIsInvalid() {
439
- return this.loadBalancer.splitDescription.allocationDescriptions.reduce((sum, allocationDescription) => sum + allocationDescription.allocation, 0) !== 100;
440
- }
441
- updateServerGroupOptions() {
442
- this.serverGroupOptions = this.serverGroupsWithoutAllocation();
443
- }
444
- showAddButton() {
445
- if (this.forPipelineConfig) {
446
- return true;
447
- } else {
448
- return this.serverGroupsWithoutAllocation().length > 0;
449
- }
450
- }
451
- showShardByOptions() {
452
- return this.loadBalancer.splitDescription.allocationDescriptions.length > 1 || this.loadBalancer.migrateTraffic;
453
- }
454
- initializeAsTextInput(serverGroupName) {
455
- if (this.forPipelineConfig) {
456
- return !this.loadBalancer.serverGroups.map((serverGroup) => serverGroup.name).includes(serverGroupName);
457
- } else {
458
- return false;
459
- }
460
- }
461
- serverGroupsWithoutAllocation() {
462
- const serverGroupsWithAllocation = this.loadBalancer.splitDescription.allocationDescriptions.map((description) => description.serverGroupName);
463
- const allServerGroups = this.loadBalancer.serverGroups.map((serverGroup) => serverGroup.name);
464
- return difference(allServerGroups, serverGroupsWithAllocation);
465
- }
466
- }
467
- const appengineLoadBalancerSettingsComponent = {
468
- bindings: { loadBalancer: "=", forPipelineConfig: "<", application: "<" },
469
- controller: AppengineLoadBalancerSettingsController,
470
- templateUrl: "appengine/src/loadBalancer/configure/wizard/basicSettings.component.html"
471
- };
472
- const APPENGINE_LOAD_BALANCER_BASIC_SETTINGS = "spinnaker.appengine.loadBalancerSettings.component";
473
- module(APPENGINE_LOAD_BALANCER_BASIC_SETTINGS, []).component("appengineLoadBalancerBasicSettings", appengineLoadBalancerSettingsComponent);
474
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
475
- templateCache.put("appengine/src/loadBalancer/configure/wizard/basicSettings.component.html", `<ng-form name="basicSettingsForm">
476
- <div class="row">
477
- <div class="form-group">
478
- <div class="col-md-3 sm-label-right">
479
- Allocations
480
- <help-field key="appengine.loadBalancer.allocations"></help-field>
481
- </div>
482
- <div class="col-md-9">
483
- <div ng-if="!$ctrl.forPipelineConfig">
484
- <appengine-allocation-configuration-row
485
- ng-repeat="description in $ctrl.loadBalancer.splitDescription.allocationDescriptions"
486
- allocation-description="description"
487
- server-group-options="$ctrl.serverGroupOptions"
488
- on-allocation-change="$ctrl.updateServerGroupOptions()"
489
- remove-allocation="$ctrl.removeAllocation($index)"
490
- >
491
- </appengine-allocation-configuration-row>
492
- </div>
493
- <div ng-if="$ctrl.forPipelineConfig">
494
- <appengine-stage-allocation-configuration-row
495
- ng-repeat="description in $ctrl.loadBalancer.splitDescription.allocationDescriptions"
496
- allocation-description="description"
497
- application="$ctrl.application"
498
- region="{{ $ctrl.loadBalancer.region }}"
499
- account="{{ $ctrl.loadBalancer.account || $ctrl.loadBalancer.credentials }}"
500
- server-group-options="$ctrl.serverGroupOptions"
501
- on-allocation-change="$ctrl.updateServerGroupOptions()"
502
- remove-allocation="$ctrl.removeAllocation($index)"
503
- >
504
- </appengine-stage-allocation-configuration-row>
505
- </div>
506
- <button class="add-new col-md-11" ng-if="$ctrl.showAddButton()" ng-click="$ctrl.addAllocation()">
507
- <span class="glyphicon glyphicon-plus-sign"></span> Add allocation
508
- </button>
509
- </div>
510
- </div>
511
- <div class="form-group" ng-if="$ctrl.allocationIsInvalid()">
512
- <div class="col-md-12 text-center">
513
- <p class="alert alert-warning">Allocations must sum to 100%.</p>
514
- </div>
515
- </div>
516
- </div>
517
-
518
- <div class="row" ng-if="$ctrl.showShardByOptions()">
519
- <div class="form-group">
520
- <div class="col-md-3 sm-label-right">Shard By</div>
521
- <div class="col-md-9">
522
- <form class="form-inline">
523
- <div class="form-group">
524
- <label class="radio-inline">
525
- <input type="radio" ng-model="$ctrl.loadBalancer.splitDescription.shardBy" value="IP" />
526
- IP
527
- <help-field key="appengine.loadBalancer.shardBy.ip"></help-field>
528
- </label>
529
- <label class="radio-inline">
530
- <input type="radio" ng-model="$ctrl.loadBalancer.splitDescription.shardBy" value="COOKIE" />
531
- Cookie
532
- <help-field key="appengine.loadBalancer.shardBy.cookie"></help-field>
533
- </label>
534
- </div>
535
- </form>
536
- </div>
537
- </div>
538
- </div>
539
- </ng-form>
540
- `);
541
- }]);
542
-
543
- class AppengineStageAllocationLabelCtrl {
544
- static mapTargetCoordinateToLabel(targetCoordinate) {
545
- const target = StageConstants.TARGET_LIST.find((t) => t.val === targetCoordinate);
546
- if (target) {
547
- return target.label;
548
- } else {
549
- return null;
550
- }
551
- }
552
- $doCheck() {
553
- this.setInputViewValue();
554
- }
555
- setInputViewValue() {
556
- switch (this.allocationDescription.locatorType) {
557
- case "text":
558
- this.inputViewValue = this.allocationDescription.serverGroupName;
559
- break;
560
- case "fromExisting":
561
- this.inputViewValue = this.allocationDescription.serverGroupName;
562
- break;
563
- case "targetCoordinate":
564
- if (this.allocationDescription.cluster && this.allocationDescription.target) {
565
- const targetLabel = AppengineStageAllocationLabelCtrl.mapTargetCoordinateToLabel(this.allocationDescription.target);
566
- this.inputViewValue = `${targetLabel} (${this.allocationDescription.cluster})`;
567
- } else {
568
- this.inputViewValue = null;
569
- }
570
- break;
571
- default:
572
- this.inputViewValue = null;
573
- break;
574
- }
575
- }
576
- }
577
- const appengineStageAllocationLabel = {
578
- bindings: { allocationDescription: "<" },
579
- controller: AppengineStageAllocationLabelCtrl,
580
- template: `<input ng-model="$ctrl.inputViewValue" type="text" class="form-control input-sm" readonly/>`
581
- };
582
- class AppengineStageAllocationConfigurationRowCtrl {
583
- constructor() {
584
- this.targets = StageConstants.TARGET_LIST;
585
- }
586
- $onInit() {
587
- const clusterFilter = AppListExtractor.clusterFilterForCredentialsAndRegion(this.account, this.region);
588
- this.clusterList = AppListExtractor.getClusters([this.application], clusterFilter);
589
- }
590
- getServerGroupOptions() {
591
- if (this.allocationDescription.serverGroupName) {
592
- return uniq(this.serverGroupOptions.concat(this.allocationDescription.serverGroupName));
593
- } else {
594
- return this.serverGroupOptions;
595
- }
596
- }
597
- onLocatorTypeChange() {
598
- if (!this.serverGroupOptions.includes(this.allocationDescription.serverGroupName)) {
599
- delete this.allocationDescription.serverGroupName;
600
- }
601
- this.onAllocationChange();
602
- }
603
- }
604
- const appengineStageAllocationConfigurationRow = {
605
- bindings: {
606
- application: "<",
607
- region: "@",
608
- account: "@",
609
- allocationDescription: "<",
610
- removeAllocation: "&",
611
- serverGroupOptions: "<",
612
- onAllocationChange: "&"
613
- },
614
- controller: AppengineStageAllocationConfigurationRowCtrl,
615
- templateUrl: "appengine/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html"
616
- };
617
- const APPENGINE_STAGE_ALLOCATION_CONFIGURATION_ROW = "spinnaker.appengine.stageAllocationConfigurationRow.component";
618
- module(APPENGINE_STAGE_ALLOCATION_CONFIGURATION_ROW, []).component("appengineStageAllocationConfigurationRow", appengineStageAllocationConfigurationRow).component("appengineStageAllocationLabel", appengineStageAllocationLabel);
619
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
620
- templateCache.put("appengine/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html", `<div class="form-group">
621
- <div class="row">
622
- <div class="col-md-7">
623
- <appengine-stage-allocation-label allocation-description="$ctrl.allocationDescription">
624
- </appengine-stage-allocation-label>
625
- </div>
626
- <div class="col-md-3">
627
- <div class="input-group input-group-sm">
628
- <input
629
- type="number"
630
- ng-model="$ctrl.allocationDescription.allocation"
631
- required
632
- class="form-control input-sm"
633
- min="0"
634
- ng-change="$ctrl.onAllocationChange()"
635
- max="100"
636
- />
637
- <span class="input-group-addon">%</span>
638
- </div>
639
- </div>
640
- <div class="col-md-2">
641
- <a class="btn btn-link sm-label" ng-click="$ctrl.removeAllocation()">
642
- <span class="glyphicon glyphicon-trash"></span>
643
- </a>
644
- </div>
645
- </div>
646
- </div>
647
- <div class="form-group">
648
- <div class="well col-md-11" style="padding-top: 5px; padding-bottom: 10px">
649
- <div class="row">
650
- <div class="form-group">
651
- <div class="col-md-3 sm-label-right">Locator</div>
652
- <div class="col-md-7">
653
- <form>
654
- <div class="form-group">
655
- <label class="small" style="font-weight: normal">
656
- <input
657
- type="radio"
658
- ng-change="$ctrl.onLocatorTypeChange()"
659
- ng-model="$ctrl.allocationDescription.locatorType"
660
- value="fromExisting"
661
- />
662
- Existing server group
663
- </label>
664
- <br />
665
- <label class="small" style="font-weight: normal">
666
- <input
667
- type="radio"
668
- ng-change="$ctrl.onLocatorTypeChange()"
669
- ng-model="$ctrl.allocationDescription.locatorType"
670
- value="targetCoordinate"
671
- />
672
- Coordinates
673
- </label>
674
- <br />
675
- <label class="small" style="font-weight: normal">
676
- <input
677
- type="radio"
678
- ng-change="$ctrl.onLocatorTypeChange()"
679
- ng-model="$ctrl.allocationDescription.locatorType"
680
- value="text"
681
- />
682
- Text input
683
- </label>
684
- </div>
685
- </form>
686
- </div>
687
- </div>
688
- </div>
689
- <div class="row">
690
- <div class="form-group" ng-switch on="$ctrl.allocationDescription.locatorType">
691
- <div ng-switch-when="fromExisting">
692
- <div class="col-md-3 sm-label-right" style="padding-left: 0px">Server group</div>
693
- <div class="col-md-7">
694
- <ui-select
695
- ng-model="$ctrl.allocationDescription.serverGroupName"
696
- on-select="$ctrl.onAllocationChange()"
697
- class="form-control input-sm"
698
- >
699
- <ui-select-match placeholder="Select...">
700
- {{ $select.selected }}
701
- </ui-select-match>
702
- <ui-select-choices
703
- repeat="serverGroup as serverGroup in $ctrl.getServerGroupOptions() | filter: $select.search"
704
- >
705
- <div ng-bind-html="serverGroup | highlight: $select.search"></div>
706
- </ui-select-choices>
707
- </ui-select>
708
- </div>
709
- </div>
710
- <div ng-switch-when="targetCoordinate">
711
- <div class="col-md-3 sm-label-right">Cluster</div>
712
- <div class="col-md-7">
713
- <cluster-selector
714
- class="small"
715
- clusters="$ctrl.clusterList"
716
- model="$ctrl.allocationDescription.cluster"
717
- ></cluster-selector>
718
- </div>
719
- <div class="col-md-3 sm-label-right">Target</div>
720
- <div class="col-md-7">
721
- <target-select model="$ctrl.allocationDescription" options="$ctrl.targets"></target-select>
722
- </div>
723
- </div>
724
- <div ng-switch-when="text">
725
- <div class="col-md-3 sm-label-right">
726
- Text
727
- <help-field key="appengine.loadBalancer.textLocator"></help-field>
728
- </div>
729
- <div class="col-md-7">
730
- <input class="form-control input-sm" type="text" ng-model="$ctrl.allocationDescription.serverGroupName" />
731
- </div>
732
- </div>
733
- </div>
734
- </div>
735
- </div>
736
- </div>
737
- `);
738
- }]);
739
-
740
- class AppengineLoadBalancerUpsertDescription {
741
- static convertTrafficSplitToTrafficSplitDescription(split) {
742
- const allocationDescriptions = reduce(split.allocations, (acc, allocation, serverGroupName) => {
743
- return acc.concat({ serverGroupName, allocation, locatorType: "fromExisting" });
744
- }, []);
745
- return { shardBy: split.shardBy, allocationDescriptions };
746
- }
747
- constructor(loadBalancer) {
748
- this.credentials = loadBalancer.account || loadBalancer.credentials;
749
- this.account = this.credentials;
750
- this.cloudProvider = loadBalancer.cloudProvider;
751
- this.loadBalancerName = loadBalancer.name;
752
- this.name = loadBalancer.name;
753
- this.region = loadBalancer.region;
754
- this.migrateTraffic = loadBalancer.migrateTraffic || false;
755
- this.serverGroups = loadBalancer.serverGroups;
756
- }
757
- mapAllocationsToDecimals() {
758
- this.splitDescription.allocationDescriptions.forEach((description) => {
759
- description.allocation = description.allocation / 100;
760
- });
761
- }
762
- mapAllocationsToPercentages() {
763
- this.splitDescription.allocationDescriptions.forEach((description) => {
764
- description.allocation = Math.round(description.allocation * 1e3) / 10;
765
- });
766
- }
767
- }
768
- class AppengineLoadBalancerTransformer {
769
- constructor($q) {
770
- this.$q = $q;
771
- }
772
- normalizeLoadBalancer(loadBalancer) {
773
- loadBalancer.provider = loadBalancer.type;
774
- loadBalancer.instanceCounts = this.buildInstanceCounts(loadBalancer.serverGroups);
775
- loadBalancer.instances = [];
776
- loadBalancer.serverGroups.forEach((serverGroup) => {
777
- serverGroup.account = loadBalancer.account;
778
- serverGroup.region = loadBalancer.region;
779
- if (serverGroup.detachedInstances) {
780
- serverGroup.detachedInstances = serverGroup.detachedInstances.map((id) => ({ id }));
781
- }
782
- serverGroup.instances = serverGroup.instances.concat(serverGroup.detachedInstances || []).map((instance) => this.transformInstance(instance, loadBalancer));
783
- });
784
- const activeServerGroups = filter(loadBalancer.serverGroups, { isDisabled: false });
785
- loadBalancer.instances = chain(activeServerGroups).map("instances").flatten().value();
786
- return this.$q.resolve(loadBalancer);
787
- }
788
- convertLoadBalancerForEditing(loadBalancer, application) {
789
- return application.getDataSource("loadBalancers").ready().then(() => {
790
- const upToDateLoadBalancer = application.getDataSource("loadBalancers").data.find((candidate) => {
791
- return candidate.name === loadBalancer.name && (candidate.account === loadBalancer.account || candidate.account === loadBalancer.credentials);
792
- });
793
- if (upToDateLoadBalancer) {
794
- loadBalancer.serverGroups = cloneDeep(upToDateLoadBalancer.serverGroups);
795
- }
796
- return loadBalancer;
797
- });
798
- }
799
- convertLoadBalancerToUpsertDescription(loadBalancer) {
800
- return new AppengineLoadBalancerUpsertDescription(loadBalancer);
801
- }
802
- buildInstanceCounts(serverGroups) {
803
- const instanceCounts = chain(serverGroups).map("instances").flatten().reduce((acc, instance) => {
804
- if (has(instance, "health.state")) {
805
- acc[camelCase(instance.health.state)]++;
806
- }
807
- return acc;
808
- }, { up: 0, down: 0, outOfService: 0, succeeded: 0, failed: 0, starting: 0, unknown: 0 }).value();
809
- instanceCounts.outOfService += chain(serverGroups).map("detachedInstances").flatten().value().length;
810
- return instanceCounts;
811
- }
812
- transformInstance(instance, loadBalancer) {
813
- instance.provider = loadBalancer.type;
814
- instance.account = loadBalancer.account;
815
- instance.region = loadBalancer.region;
816
- instance.loadBalancers = [loadBalancer.name];
817
- const health = instance.health || {};
818
- instance.healthState = get(instance, "health.state") || "OutOfService";
819
- instance.health = [health];
820
- return instance;
821
- }
822
- }
823
- AppengineLoadBalancerTransformer.$inject = ["$q"];
824
- const APPENGINE_LOAD_BALANCER_TRANSFORMER = "spinnaker.appengine.loadBalancer.transformer.service";
825
- module(APPENGINE_LOAD_BALANCER_TRANSFORMER, []).service("appengineLoadBalancerTransformer", AppengineLoadBalancerTransformer);
826
-
827
- function styleInject(css, ref) {
828
- if ( ref === void 0 ) ref = {};
829
- var insertAt = ref.insertAt;
830
-
831
- if (!css || typeof document === 'undefined') { return; }
832
-
833
- var head = document.head || document.getElementsByTagName('head')[0];
834
- var style = document.createElement('style');
835
- style.type = 'text/css';
836
-
837
- if (insertAt === 'top') {
838
- if (head.firstChild) {
839
- head.insertBefore(style, head.firstChild);
840
- } else {
841
- head.appendChild(style);
842
- }
843
- } else {
844
- head.appendChild(style);
845
- }
846
-
847
- if (style.styleSheet) {
848
- style.styleSheet.cssText = css;
849
- } else {
850
- style.appendChild(document.createTextNode(css));
851
- }
852
- }
853
-
854
- var css_248z$2 = "appengine-load-balancer-basic-settings a.btn.btn-link {\n padding: 0;\n}\nappengine-load-balancer-basic-settings .form-group {\n margin-top: 0.4rem;\n}\nappengine-load-balancer-advanced-settings .checkbox input[type='checkbox'] {\n margin: 0 0 0.1rem 0;\n}\n";
855
- styleInject(css_248z$2);
856
-
857
- class AppengineLoadBalancerWizardController {
858
- constructor($scope, $state, $uibModalInstance, application, loadBalancer, isNew, forPipelineConfig, appengineLoadBalancerTransformer, wizardSubFormValidation) {
859
- this.$scope = $scope;
860
- this.$state = $state;
861
- this.$uibModalInstance = $uibModalInstance;
862
- this.application = application;
863
- this.isNew = isNew;
864
- this.forPipelineConfig = forPipelineConfig;
865
- this.appengineLoadBalancerTransformer = appengineLoadBalancerTransformer;
866
- this.wizardSubFormValidation = wizardSubFormValidation;
867
- this.state = { loading: true };
868
- this.submitButtonLabel = this.forPipelineConfig ? "Done" : "Update";
869
- if (this.isNew) {
870
- this.heading = "Create New Load Balancer";
871
- } else {
872
- this.heading = `Edit ${[
873
- loadBalancer.name,
874
- loadBalancer.region,
875
- loadBalancer.account || loadBalancer.credentials
876
- ].join(":")}`;
877
- this.appengineLoadBalancerTransformer.convertLoadBalancerForEditing(loadBalancer, application).then((convertedLoadBalancer) => {
878
- this.loadBalancer = this.appengineLoadBalancerTransformer.convertLoadBalancerToUpsertDescription(convertedLoadBalancer);
879
- if (loadBalancer.split && !this.loadBalancer.splitDescription) {
880
- this.loadBalancer.splitDescription = AppengineLoadBalancerUpsertDescription.convertTrafficSplitToTrafficSplitDescription(loadBalancer.split);
881
- } else {
882
- this.loadBalancer.splitDescription = loadBalancer.splitDescription;
883
- }
884
- this.loadBalancer.mapAllocationsToPercentages();
885
- this.setTaskMonitor();
886
- this.initializeFormValidation();
887
- this.state.loading = false;
888
- });
889
- }
890
- }
891
- submit() {
892
- const description = cloneDeep(this.loadBalancer);
893
- description.mapAllocationsToDecimals();
894
- delete description.serverGroups;
895
- if (this.forPipelineConfig) {
896
- return this.$uibModalInstance.close(description);
897
- } else {
898
- return this.taskMonitor.submit(() => {
899
- return LoadBalancerWriter.upsertLoadBalancer(description, this.application, "Update");
900
- });
901
- }
902
- }
903
- cancel() {
904
- this.$uibModalInstance.dismiss();
905
- }
906
- showSubmitButton() {
907
- return this.wizardSubFormValidation.subFormsAreValid();
908
- }
909
- setTaskMonitor() {
910
- this.taskMonitor = new TaskMonitor({
911
- application: this.application,
912
- title: "Updating your load balancer",
913
- modalInstance: this.$uibModalInstance,
914
- onTaskComplete: () => this.onTaskComplete()
915
- });
916
- }
917
- initializeFormValidation() {
918
- this.wizardSubFormValidation.config({ form: "form", scope: this.$scope }).register({
919
- page: "basic-settings",
920
- subForm: "basicSettingsForm",
921
- validators: [
922
- {
923
- watchString: "ctrl.loadBalancer.splitDescription",
924
- validator: (splitDescription) => {
925
- return splitDescription.allocationDescriptions.reduce((sum, description) => sum + description.allocation, 0) === 100;
926
- },
927
- watchDeep: true
928
- }
929
- ]
930
- }).register({ page: "advanced-settings", subForm: "advancedSettingsForm" });
931
- }
932
- onTaskComplete() {
933
- this.application.getDataSource("loadBalancers").refresh();
934
- this.application.getDataSource("loadBalancers").onNextRefresh(this.$scope, () => this.onApplicationRefresh());
935
- }
936
- onApplicationRefresh() {
937
- if (this.$scope.$$destroyed) {
938
- return;
939
- }
940
- this.$uibModalInstance.dismiss();
941
- const newStateParams = {
942
- name: this.loadBalancer.name,
943
- accountId: this.loadBalancer.credentials,
944
- region: this.loadBalancer.region,
945
- provider: "appengine"
946
- };
947
- if (!this.$state.includes("**.loadBalancerDetails")) {
948
- this.$state.go(".loadBalancerDetails", newStateParams);
949
- } else {
950
- this.$state.go("^.loadBalancerDetails", newStateParams);
951
- }
952
- }
953
- }
954
- AppengineLoadBalancerWizardController.$inject = [
955
- "$scope",
956
- "$state",
957
- "$uibModalInstance",
958
- "application",
959
- "loadBalancer",
960
- "isNew",
961
- "forPipelineConfig",
962
- "appengineLoadBalancerTransformer",
963
- "wizardSubFormValidation"
964
- ];
965
- const APPENGINE_LOAD_BALANCER_WIZARD_CTRL = "spinnaker.appengine.loadBalancer.wizard.controller";
966
- module(APPENGINE_LOAD_BALANCER_WIZARD_CTRL, []).controller("appengineLoadBalancerWizardCtrl", AppengineLoadBalancerWizardController);
967
-
968
- class AppengineLoadBalancerDetailsController {
969
- constructor($uibModal, $state, $scope, loadBalancer, app) {
970
- this.$uibModal = $uibModal;
971
- this.$state = $state;
972
- this.$scope = $scope;
973
- this.app = app;
974
- this.state = { loading: true };
975
- this.dispatchRules = [];
976
- this.loadBalancerFromParams = loadBalancer;
977
- this.app.getDataSource("loadBalancers").ready().then(() => this.extractLoadBalancer());
978
- }
979
- editLoadBalancer() {
980
- this.$uibModal.open({
981
- templateUrl: "appengine/src/loadBalancer/configure/wizard/wizard.html",
982
- controller: "appengineLoadBalancerWizardCtrl as ctrl",
983
- size: "lg",
984
- resolve: {
985
- application: () => this.app,
986
- loadBalancer: () => cloneDeep(this.loadBalancer),
987
- isNew: () => false,
988
- forPipelineConfig: () => false
989
- }
990
- });
991
- }
992
- deleteLoadBalancer() {
993
- const taskMonitor = {
994
- application: this.app,
995
- title: "Deleting " + this.loadBalancer.name
996
- };
997
- const submitMethod = () => {
998
- const loadBalancer = {
999
- cloudProvider: this.loadBalancer.cloudProvider,
1000
- loadBalancerName: this.loadBalancer.name,
1001
- credentials: this.loadBalancer.account
1002
- };
1003
- return LoadBalancerWriter.deleteLoadBalancer(loadBalancer, this.app);
1004
- };
1005
- ConfirmationModalService.confirm({
1006
- header: "Really delete " + this.loadBalancer.name + "?",
1007
- buttonText: "Delete " + this.loadBalancer.name,
1008
- body: this.getConfirmationModalBodyHtml(),
1009
- account: this.loadBalancer.account,
1010
- taskMonitorConfig: taskMonitor,
1011
- submitMethod
1012
- });
1013
- }
1014
- canDeleteLoadBalancer() {
1015
- return this.loadBalancer.name !== "default";
1016
- }
1017
- extractLoadBalancer() {
1018
- this.loadBalancer = this.app.getDataSource("loadBalancers").data.find((test) => {
1019
- return test.name === this.loadBalancerFromParams.name && test.account === this.loadBalancerFromParams.accountId;
1020
- });
1021
- if (this.loadBalancer) {
1022
- this.state.loading = false;
1023
- this.buildDispatchRules();
1024
- this.app.getDataSource("loadBalancers").onRefresh(this.$scope, () => this.extractLoadBalancer());
1025
- } else {
1026
- this.autoClose();
1027
- }
1028
- }
1029
- buildDispatchRules() {
1030
- this.dispatchRules = [];
1031
- if (this.loadBalancer && this.loadBalancer.dispatchRules) {
1032
- this.loadBalancer.dispatchRules.forEach((rule) => {
1033
- if (rule.service === this.loadBalancer.name) {
1034
- this.dispatchRules.push(rule.domain + rule.path);
1035
- }
1036
- });
1037
- }
1038
- }
1039
- getConfirmationModalBodyHtml() {
1040
- const serverGroupNames = this.loadBalancer.serverGroups.map((serverGroup) => serverGroup.name);
1041
- const hasAny = serverGroupNames ? serverGroupNames.length > 0 : false;
1042
- const hasMoreThanOne = serverGroupNames ? serverGroupNames.length > 1 : false;
1043
- if (hasAny) {
1044
- if (hasMoreThanOne) {
1045
- const listOfServerGroupNames = serverGroupNames.map((name) => `<li>${name}</li>`).join("");
1046
- return `<div class="alert alert-warning">
1047
- <p>
1048
- Deleting <b>${this.loadBalancer.name}</b> will destroy the following server groups:
1049
- <ul>
1050
- ${listOfServerGroupNames}
1051
- </ul>
1052
- </p>
1053
- </div>
1054
- `;
1055
- } else {
1056
- return `<div class="alert alert-warning">
1057
- <p>
1058
- Deleting <b>${this.loadBalancer.name}</b> will destroy <b>${serverGroupNames[0]}</b>.
1059
- </p>
1060
- </div>
1061
- `;
1062
- }
1063
- } else {
1064
- return null;
1065
- }
1066
- }
1067
- autoClose() {
1068
- if (this.$scope.$$destroyed) {
1069
- return;
1070
- } else {
1071
- this.$state.params.allowModalToStayOpen = true;
1072
- this.$state.go("^", null, { location: "replace" });
1073
- }
1074
- }
1075
- }
1076
- AppengineLoadBalancerDetailsController.$inject = ["$uibModal", "$state", "$scope", "loadBalancer", "app"];
1077
- const APPENGINE_LOAD_BALANCER_DETAILS_CTRL = "spinnaker.appengine.loadBalancerDetails.controller";
1078
- module(APPENGINE_LOAD_BALANCER_DETAILS_CTRL, []).controller("appengineLoadBalancerDetailsCtrl", AppengineLoadBalancerDetailsController);
1079
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1080
- templateCache.put("appengine/src/loadBalancer/configure/wizard/wizard.html", `<form name="form">
1081
- <div ng-if="ctrl.state.loading && !ctrl.isNew" style="height: 200px" class="horizontal center middle">
1082
- <loading-spinner size="'small'"></loading-spinner>
1083
- </div>
1084
- <v2-modal-wizard
1085
- heading="{{::ctrl.heading}}"
1086
- task-monitor="ctrl.taskMonitor"
1087
- dismiss="$dismiss()"
1088
- ng-if="!ctrl.state.loading || ctrl.isNew"
1089
- >
1090
- <div ng-if="!ctrl.isNew">
1091
- <v2-wizard-page key="basic-settings" label="Basic Settings" mark-complete-on-view="false">
1092
- <appengine-load-balancer-basic-settings
1093
- load-balancer="ctrl.loadBalancer"
1094
- application="ctrl.application"
1095
- for-pipeline-config="ctrl.forPipelineConfig"
1096
- ></appengine-load-balancer-basic-settings>
1097
- </v2-wizard-page>
1098
- <v2-wizard-page key="advanced-settings" label="Advanced Settings" mark-complete-on-view="false">
1099
- <appengine-load-balancer-advanced-settings
1100
- load-balancer="ctrl.loadBalancer"
1101
- ></appengine-load-balancer-advanced-settings>
1102
- </v2-wizard-page>
1103
- </div>
1104
- </v2-modal-wizard>
1105
- <appengine-load-balancer-message
1106
- ng-if="ctrl.isNew"
1107
- column-offset="1"
1108
- columns="10"
1109
- show-create-message="true"
1110
- ></appengine-load-balancer-message>
1111
- <div class="modal-footer">
1112
- <button class="btn btn-default" ng-click="ctrl.cancel()">Cancel</button>
1113
- <submit-button
1114
- ng-if="!ctrl.isNew && ctrl.showSubmitButton()"
1115
- label="ctrl.submitButtonLabel"
1116
- is-disabled="appengineLoadBalancerForm.$invalid || ctrl.taskMonitor.submitting || ctrl.state.loading"
1117
- submitting="ctrl.taskMonitor.submitting"
1118
- on-click="ctrl.submit()"
1119
- is-new="ctrl.isNew"
1120
- >
1121
- </submit-button>
1122
- </div>
1123
- </form>
1124
- `);
1125
- }]);
1126
-
1127
- const APPENGINE_LOAD_BALANCER_MODULE = "spinnaker.appengine.loadBalancer.module";
1128
- module(APPENGINE_LOAD_BALANCER_MODULE, [
1129
- APPENGINE_ALLOCATION_CONFIGURATION_ROW,
1130
- APPENGINE_LOAD_BALANCER_DETAILS_CTRL,
1131
- APPENGINE_LOAD_BALANCER_ADVANCED_SETTINGS,
1132
- APPENGINE_LOAD_BALANCER_BASIC_SETTINGS,
1133
- APPENGINE_LOAD_BALANCER_TRANSFORMER,
1134
- APPENGINE_LOAD_BALANCER_WIZARD_CTRL,
1135
- APPENGINE_STAGE_ALLOCATION_CONFIGURATION_ROW
1136
- ]);
1137
-
1138
- var logo = "appengine.logoc2c312af6aa99037.png";
1139
-
1140
- class AppengineHealth {
1141
- }
1142
- AppengineHealth.PLATFORM = "App Engine Service";
1143
-
1144
- class AppengineStageCtrl {
1145
- constructor($scope) {
1146
- this.$scope = $scope;
1147
- $scope.platformHealth = AppengineHealth.PLATFORM;
1148
- }
1149
- setStageRegion() {
1150
- const selected = this.$scope.accounts.find((account) => account.name === this.$scope.stage.credentials);
1151
- if (selected && selected.name) {
1152
- AccountService.getAccountDetails(selected.name).then((accountDetails) => {
1153
- this.$scope.stage.region = accountDetails.region;
1154
- });
1155
- }
1156
- }
1157
- setStageCloudProvider() {
1158
- this.$scope.stage.cloudProvider = "appengine";
1159
- }
1160
- setAccounts() {
1161
- return AccountService.listAccounts("appengine").then((accounts) => {
1162
- this.$scope.accounts = accounts;
1163
- });
1164
- }
1165
- setTargets() {
1166
- this.$scope.targets = StageConstants.TARGET_LIST;
1167
- if (!this.$scope.stage.target) {
1168
- this.$scope.stage.target = this.$scope.targets[0].val;
1169
- }
1170
- }
1171
- setStageCredentials() {
1172
- if (!this.$scope.stage.credentials && this.$scope.application.defaultCredentials.appengine) {
1173
- this.$scope.stage.credentials = this.$scope.application.defaultCredentials.appengine;
1174
- }
1175
- }
1176
- }
1177
- AppengineStageCtrl.$inject = ["$scope"];
1178
-
1179
- class AppengineDestroyAsgStageCtrl extends AppengineStageCtrl {
1180
- constructor($scope) {
1181
- super($scope);
1182
- this.$scope = $scope;
1183
- super.setAccounts().then(() => {
1184
- super.setStageRegion();
1185
- });
1186
- super.setStageCloudProvider();
1187
- super.setTargets();
1188
- super.setStageCredentials();
1189
- }
1190
- }
1191
- AppengineDestroyAsgStageCtrl.$inject = ["$scope"];
1192
- const APPENGINE_DESTROY_ASG_STAGE = "spinnaker.appengine.pipeline.stage.destroyAsgStage";
1193
- module(APPENGINE_DESTROY_ASG_STAGE, []).config(() => {
1194
- Registry.pipeline.registerStage({
1195
- provides: "destroyServerGroup",
1196
- key: "destroyServerGroup",
1197
- cloudProvider: "appengine",
1198
- templateUrl: "appengine/src/pipeline/stages/destroyAsg/destroyAsgStage.html",
1199
- executionStepLabelUrl: "appengine/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html",
1200
- validators: [
1201
- {
1202
- type: "targetImpedance",
1203
- message: "This pipeline will attempt to destroy a server group without deploying a new version into the same cluster."
1204
- },
1205
- { type: "requiredField", fieldName: "cluster" },
1206
- { type: "requiredField", fieldName: "target" },
1207
- { type: "requiredField", fieldName: "credentials", fieldLabel: "account" }
1208
- ]
1209
- });
1210
- }).controller("appengineDestroyAsgStageCtrl", AppengineDestroyAsgStageCtrl);
1211
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1212
- templateCache.put("appengine/src/pipeline/stages/destroyAsg/destroyAsgStage.html", `<div ng-controller="appengineDestroyAsgStageCtrl as destroyAsgStageCtrl" class="form-horizontal">
1213
- <div ng-if="!pipeline.strategy">
1214
- <account-region-cluster-selector
1215
- application="application"
1216
- single-region="true"
1217
- disable-region-select="true"
1218
- on-account-update="destroyAsgStageCtrl.setStageRegion()"
1219
- component="stage"
1220
- accounts="accounts"
1221
- >
1222
- </account-region-cluster-selector>
1223
- </div>
1224
- <stage-config-field label="Target">
1225
- <target-select model="stage" options="targets"></target-select>
1226
- </stage-config-field>
1227
- </div>
1228
- `);
1229
- }]);
1230
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1231
- templateCache.put("appengine/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html", `<span class="task-label"> Destroy Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>
1232
- `);
1233
- }]);
1234
-
1235
- class AppengineDisableAsgStageCtrl extends AppengineStageCtrl {
1236
- constructor($scope) {
1237
- super($scope);
1238
- this.$scope = $scope;
1239
- super.setAccounts().then(() => {
1240
- super.setStageRegion();
1241
- });
1242
- super.setStageCloudProvider();
1243
- super.setTargets();
1244
- super.setStageCredentials();
1245
- if ($scope.stage.isNew && $scope.application.attributes.platformHealthOnlyShowOverride && $scope.application.attributes.platformHealthOnly) {
1246
- $scope.stage.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
1247
- }
1248
- }
1249
- }
1250
- AppengineDisableAsgStageCtrl.$inject = ["$scope"];
1251
- const APPENGINE_DISABLE_ASG_STAGE = "spinnaker.appengine.pipeline.stage.disableAsgStage";
1252
- module(APPENGINE_DISABLE_ASG_STAGE, []).config(() => {
1253
- Registry.pipeline.registerStage({
1254
- provides: "disableServerGroup",
1255
- key: "disableServerGroup",
1256
- cloudProvider: "appengine",
1257
- templateUrl: "appengine/src/pipeline/stages/disableAsg/disableAsgStage.html",
1258
- executionStepLabelUrl: "appengine/src/pipeline/stages/disableAsg/disableAsgStepLabel.html",
1259
- validators: [
1260
- {
1261
- type: "targetImpedance",
1262
- message: "This pipeline will attempt to disable a server group without deploying a new version into the same cluster."
1263
- },
1264
- { type: "requiredField", fieldName: "cluster" },
1265
- { type: "requiredField", fieldName: "target" },
1266
- { type: "requiredField", fieldName: "credentials", fieldLabel: "account" }
1267
- ]
1268
- });
1269
- }).controller("appengineDisableAsgStageCtrl", AppengineDisableAsgStageCtrl);
1270
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1271
- templateCache.put("appengine/src/pipeline/stages/disableAsg/disableAsgStage.html", `<div ng-controller="appengineDisableAsgStageCtrl as disableAsgStageCtrl" class="form-horizontal">
1272
- <div ng-if="!pipeline.strategy">
1273
- <account-region-cluster-selector
1274
- application="application"
1275
- component="stage"
1276
- single-region="true"
1277
- disable-region-select="true"
1278
- on-account-update="disableAsgStageCtrl.setStageRegion()"
1279
- accounts="accounts"
1280
- >
1281
- </account-region-cluster-selector>
1282
- </div>
1283
- <stage-config-field label="Target">
1284
- <target-select model="stage" options="targets"></target-select>
1285
- </stage-config-field>
1286
- <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">
1287
- </stage-platform-health-override>
1288
- </div>
1289
- `);
1290
- }]);
1291
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1292
- templateCache.put("appengine/src/pipeline/stages/disableAsg/disableAsgStepLabel.html", `<span class="task-label"> Disable Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>
1293
- `);
1294
- }]);
1295
-
1296
- class AppengineLoadBalancerChoiceModalCtrl {
1297
- constructor($uibModal, $uibModalInstance, application) {
1298
- this.$uibModal = $uibModal;
1299
- this.$uibModalInstance = $uibModalInstance;
1300
- this.application = application;
1301
- this.state = { loading: true };
1302
- this.initialize();
1303
- }
1304
- submit() {
1305
- const config = CloudProviderRegistry.getValue("appengine", "loadBalancer");
1306
- const updatedLoadBalancerPromise = this.$uibModal.open({
1307
- templateUrl: config.createLoadBalancerTemplateUrl,
1308
- controller: `${config.createLoadBalancerController} as ctrl`,
1309
- size: "lg",
1310
- resolve: {
1311
- application: () => this.application,
1312
- loadBalancer: () => cloneDeep(this.selectedLoadBalancer),
1313
- isNew: () => false,
1314
- forPipelineConfig: () => true
1315
- }
1316
- }).result;
1317
- this.$uibModalInstance.close(updatedLoadBalancerPromise);
1318
- }
1319
- cancel() {
1320
- this.$uibModalInstance.dismiss();
1321
- }
1322
- initialize() {
1323
- this.application.getDataSource("loadBalancers").ready().then(() => {
1324
- this.loadBalancers = this.application.loadBalancers.data.filter((candidate) => candidate.cloudProvider === "appengine");
1325
- if (this.loadBalancers.length) {
1326
- this.selectedLoadBalancer = this.loadBalancers[0];
1327
- }
1328
- this.state.loading = false;
1329
- });
1330
- }
1331
- }
1332
- AppengineLoadBalancerChoiceModalCtrl.$inject = ["$uibModal", "$uibModalInstance", "application"];
1333
- const APPENGINE_LOAD_BALANCER_CHOICE_MODAL_CTRL = "spinnaker.appengine.loadBalancerChoiceModal.controller";
1334
- module(APPENGINE_LOAD_BALANCER_CHOICE_MODAL_CTRL, []).controller("appengineLoadBalancerChoiceModelCtrl", AppengineLoadBalancerChoiceModalCtrl);
1335
-
1336
- class AppengineEditLoadBalancerStageCtrl {
1337
- constructor($scope, $uibModal) {
1338
- this.$scope = $scope;
1339
- this.$uibModal = $uibModal;
1340
- $scope.stage.loadBalancers = $scope.stage.loadBalancers || [];
1341
- $scope.stage.cloudProvider = "appengine";
1342
- }
1343
- addLoadBalancer() {
1344
- this.$uibModal.open({
1345
- templateUrl: "appengine/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html",
1346
- controller: `appengineLoadBalancerChoiceModelCtrl as ctrl`,
1347
- resolve: {
1348
- application: () => this.$scope.application
1349
- }
1350
- }).result.then((newLoadBalancer) => {
1351
- this.$scope.stage.loadBalancers.push(newLoadBalancer);
1352
- }).catch(() => {
1353
- });
1354
- }
1355
- editLoadBalancer(index) {
1356
- const config = CloudProviderRegistry.getValue("appengine", "loadBalancer");
1357
- this.$uibModal.open({
1358
- templateUrl: config.createLoadBalancerTemplateUrl,
1359
- controller: `${config.createLoadBalancerController} as ctrl`,
1360
- size: "lg",
1361
- resolve: {
1362
- application: () => this.$scope.application,
1363
- loadBalancer: () => cloneDeep(this.$scope.stage.loadBalancers[index]),
1364
- isNew: () => false,
1365
- forPipelineConfig: () => true
1366
- }
1367
- }).result.then((updatedLoadBalancer) => {
1368
- this.$scope.stage.loadBalancers[index] = updatedLoadBalancer;
1369
- }).catch(() => {
1370
- });
1371
- }
1372
- removeLoadBalancer(index) {
1373
- this.$scope.stage.loadBalancers.splice(index, 1);
1374
- }
1375
- }
1376
- AppengineEditLoadBalancerStageCtrl.$inject = ["$scope", "$uibModal"];
1377
- const APPENGINE_EDIT_LOAD_BALANCER_STAGE = "spinnaker.appengine.pipeline.stage.editLoadBalancerStage";
1378
- module(APPENGINE_EDIT_LOAD_BALANCER_STAGE, [APPENGINE_LOAD_BALANCER_CHOICE_MODAL_CTRL]).config(() => {
1379
- Registry.pipeline.registerStage({
1380
- label: "Edit Load Balancer",
1381
- description: "Edits a load balancer",
1382
- key: "upsertAppEngineLoadBalancers",
1383
- cloudProvider: "appengine",
1384
- templateUrl: "appengine/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html",
1385
- executionDetailsUrl: "appengine/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html",
1386
- executionConfigSections: ["editLoadBalancerConfig", "taskStatus"],
1387
- controller: "appengineEditLoadBalancerStageCtrl",
1388
- controllerAs: "editLoadBalancerStageCtrl",
1389
- validators: []
1390
- });
1391
- }).controller("appengineEditLoadBalancerStageCtrl", AppengineEditLoadBalancerStageCtrl);
1392
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1393
- templateCache.put("appengine/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html", `<div modal-page>
1394
- <modal-close dismiss="$dismiss()"></modal-close>
1395
- <div class="modal-header">
1396
- <h4 class="modal-title">Select Load Balancer</h4>
1397
- </div>
1398
- <div class="modal-body" ng-if="ctrl.state.loading" style="height: 200px" class="horizontal center middle">
1399
- <loading-spinner size="'small'"></loading-spinner>
1400
- </div>
1401
- <div class="modal-body" ng-if="!ctrl.state.loading">
1402
- <div class="alert alert-warning" ng-if="ctrl.loadBalancers.length === 0">
1403
- <p>This application has no App Engine load balancers.</p>
1404
- </div>
1405
- <form
1406
- role="form"
1407
- name="form"
1408
- class="form-horizontal"
1409
- ng-submit="ctrl.submit()"
1410
- ng-if="ctrl.loadBalancers.length > 0"
1411
- >
1412
- <div class="form-group">
1413
- <div class="col-md-3 sm-label-right">
1414
- <b>Load Balancer</b>
1415
- </div>
1416
- <div class="col-md-7">
1417
- <ui-select class="form-control input-sm" ng-model="ctrl.selectedLoadBalancer">
1418
- <ui-select-match>
1419
- <account-tag account="$select.selected.account"></account-tag>
1420
- <span style="margin-left: 5px">{{$select.selected.name}}</span>
1421
- </ui-select-match>
1422
- <ui-select-choices repeat="loadBalancer in ctrl.loadBalancers | filter: $select.search">
1423
- <account-tag account="loadBalancer.account"></account-tag>
1424
- <span style="margin-left: 5px" ng-bind-html="loadBalancer.name"></span>
1425
- </ui-select-choices>
1426
- </ui-select>
1427
- </div>
1428
- </div>
1429
- </form>
1430
- </div>
1431
- <div class="modal-footer">
1432
- <button class="btn btn-default" ng-click="ctrl.cancel()">Cancel</button>
1433
- <button class="btn btn-primary" ng-if="ctrl.loadBalancers.length > 0" ng-click="ctrl.submit()">
1434
- <span class="far fa-check-circle"></span> Edit
1435
- </button>
1436
- </div>
1437
- </div>
1438
- `);
1439
- }]);
1440
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1441
- templateCache.put("appengine/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html", `<div class="well well-sm clearfix" ng-if="!pipeline.strategy">
1442
- <div class="row">
1443
- <div class="col-md-12">
1444
- <h4 class="text-left">Load Balancers</h4>
1445
- </div>
1446
- </div>
1447
- <div class="row">
1448
- <div class="col-md-12">
1449
- <table class="table table-condensed">
1450
- <thead>
1451
- <tr>
1452
- <th>Account</th>
1453
- <th>Name</th>
1454
- <th>Region</th>
1455
- <th>Actions</th>
1456
- </tr>
1457
- </thead>
1458
- <tbody>
1459
- <tr ng-repeat="loadBalancer in stage.loadBalancers">
1460
- <td>
1461
- <account-tag account="loadBalancer.credentials"></account-tag>
1462
- </td>
1463
- <td>{{ loadBalancer.name }}</td>
1464
- <td>{{ loadBalancer.region }}</td>
1465
- <td class="condensed-actions">
1466
- <a class="btn btn-sm btn-link" href ng-click="editLoadBalancerStageCtrl.editLoadBalancer($index)">
1467
- <span class="glyphicon glyphicon-edit" uib-tooltip="Edit"></span
1468
- ></a>
1469
- <a
1470
- class="btn btn-sm btn-link pad-left"
1471
- href
1472
- ng-click="editLoadBalancerStageCtrl.removeLoadBalancer($index)"
1473
- >
1474
- <span class="glyphicon glyphicon-trash" uib-tooltip="Remove"></span>
1475
- </a>
1476
- </td>
1477
- </tr>
1478
- </tbody>
1479
- <tfoot>
1480
- <tr>
1481
- <td colspan="8">
1482
- <button class="btn btn-block btn-sm add-new" ng-click="editLoadBalancerStageCtrl.addLoadBalancer()">
1483
- <span class="glyphicon glyphicon-plus-sign"></span> Add load balancer
1484
- </button>
1485
- </td>
1486
- </tr>
1487
- </tfoot>
1488
- </table>
1489
- </div>
1490
- </div>
1491
- </div>
1492
- `);
1493
- }]);
1494
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1495
- templateCache.put("appengine/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html", `<div ng-controller="BaseExecutionDetailsCtrl">
1496
- <execution-details-section-nav sections="configSections"></execution-details-section-nav>
1497
- <div class="step-section-details" ng-if="detailsSection === 'editLoadBalancerConfig'">
1498
- <div class="row">
1499
- <div class="col-md-12">
1500
- <table class="table table-condensed">
1501
- <thead>
1502
- <tr>
1503
- <th>Account</th>
1504
- <th>Name</th>
1505
- <th>Region</th>
1506
- </tr>
1507
- </thead>
1508
- <tbody>
1509
- <tr ng-repeat="loadBalancer in stage.context.loadBalancers">
1510
- <td>
1511
- <account-tag account="loadBalancer.credentials"></account-tag>
1512
- </td>
1513
- <td>{{ loadBalancer.name }}</td>
1514
- <td>{{ loadBalancer.region }}</td>
1515
- </tr>
1516
- </tbody>
1517
- </table>
1518
- </div>
1519
- </div>
1520
- <stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>
1521
- </div>
1522
- <div class="step-section-details" ng-if="detailsSection === 'taskStatus'">
1523
- <div class="row">
1524
- <execution-step-details item="stage"></execution-step-details>
1525
- </div>
1526
- </div>
1527
- </div>
1528
- `);
1529
- }]);
1530
-
1531
- class AppengineEnableAsgStageCtrl extends AppengineStageCtrl {
1532
- constructor($scope) {
1533
- super($scope);
1534
- this.$scope = $scope;
1535
- super.setAccounts().then(() => {
1536
- super.setStageRegion();
1537
- });
1538
- super.setStageCloudProvider();
1539
- super.setTargets();
1540
- super.setStageCredentials();
1541
- if ($scope.stage.isNew && $scope.application.attributes.platformHealthOnlyShowOverride && $scope.application.attributes.platformHealthOnly) {
1542
- $scope.stage.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
1543
- }
1544
- }
1545
- }
1546
- AppengineEnableAsgStageCtrl.$inject = ["$scope"];
1547
- const APPENGINE_ENABLE_ASG_STAGE = "spinnaker.appengine.pipeline.stage.enableAsgStage";
1548
- module(APPENGINE_ENABLE_ASG_STAGE, []).config(() => {
1549
- Registry.pipeline.registerStage({
1550
- provides: "enableServerGroup",
1551
- key: "enableServerGroup",
1552
- cloudProvider: "appengine",
1553
- templateUrl: "appengine/src/pipeline/stages/enableAsg/enableAsgStage.html",
1554
- executionStepLabelUrl: "appengine/src/pipeline/stages/enableAsg/enableAsgStepLabel.html",
1555
- validators: [
1556
- { type: "requiredField", fieldName: "cluster" },
1557
- { type: "requiredField", fieldName: "target" },
1558
- { type: "requiredField", fieldName: "credentials", fieldLabel: "account" }
1559
- ]
1560
- });
1561
- }).controller("appengineEnableAsgStageCtrl", AppengineEnableAsgStageCtrl);
1562
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1563
- templateCache.put("appengine/src/pipeline/stages/enableAsg/enableAsgStage.html", `<div ng-controller="appengineEnableAsgStageCtrl as enableAsgStageCtrl" class="form-horizontal">
1564
- <div ng-if="!pipeline.strategy">
1565
- <account-region-cluster-selector
1566
- application="application"
1567
- component="stage"
1568
- single-region="true"
1569
- disable-region-select="true"
1570
- on-account-update="enableAsgStageCtrl.setStageRegion()"
1571
- accounts="accounts"
1572
- >
1573
- </account-region-cluster-selector>
1574
- </div>
1575
- <stage-config-field label="Target">
1576
- <target-select model="stage" options="targets"></target-select>
1577
- </stage-config-field>
1578
- <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">
1579
- </stage-platform-health-override>
1580
- </div>
1581
- `);
1582
- }]);
1583
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1584
- templateCache.put("appengine/src/pipeline/stages/enableAsg/enableAsgStepLabel.html", `<span class="task-label"> Enable Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>
1585
- `);
1586
- }]);
1587
-
1588
- class AppengineShrinkClusterStageCtrl extends AppengineStageCtrl {
1589
- constructor($scope) {
1590
- super($scope);
1591
- this.$scope = $scope;
1592
- super.setAccounts().then(() => {
1593
- super.setStageRegion();
1594
- });
1595
- super.setStageCloudProvider();
1596
- super.setStageCredentials();
1597
- const stage = $scope.stage;
1598
- if (stage.shrinkToSize === void 0) {
1599
- stage.shrinkToSize = 1;
1600
- }
1601
- if (stage.allowDeleteActive === void 0) {
1602
- stage.allowDeleteActive = false;
1603
- }
1604
- if (stage.retainLargerOverNewer === void 0) {
1605
- stage.retainLargerOverNewer = "false";
1606
- }
1607
- stage.retainLargerOverNewer = stage.retainLargerOverNewer.toString();
1608
- }
1609
- pluralize(str, val) {
1610
- return val === 1 ? str : str + "s";
1611
- }
1612
- }
1613
- AppengineShrinkClusterStageCtrl.$inject = ["$scope"];
1614
- const APPENGINE_SHRINK_CLUSTER_STAGE = "spinnaker.appengine.pipeline.stage.shrinkClusterStage";
1615
- module(APPENGINE_SHRINK_CLUSTER_STAGE, []).config(function() {
1616
- Registry.pipeline.registerStage({
1617
- provides: "shrinkCluster",
1618
- key: "shrinkCluster",
1619
- cloudProvider: "appengine",
1620
- templateUrl: "appengine/src/pipeline/stages/shrinkCluster/shrinkClusterStage.html",
1621
- validators: [
1622
- { type: "requiredField", fieldName: "cluster" },
1623
- { type: "requiredField", fieldName: "shrinkToSize", fieldLabel: "shrink to [X] Server Groups" },
1624
- { type: "requiredField", fieldName: "credentials", fieldLabel: "account" }
1625
- ]
1626
- });
1627
- }).controller("appengineShrinkClusterStageCtrl", AppengineShrinkClusterStageCtrl);
1628
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1629
- templateCache.put("appengine/src/pipeline/stages/shrinkCluster/shrinkClusterStage.html", `<div ng-controller="appengineShrinkClusterStageCtrl as shrinkClusterStageCtrl" class="form-horizontal">
1630
- <div ng-if="!pipeline.strategy">
1631
- <account-region-cluster-selector
1632
- application="application"
1633
- component="stage"
1634
- single-region="true"
1635
- disable-region-select="true"
1636
- on-account-update="shrinkClusterStageCtrl.setStageRegion()"
1637
- accounts="accounts"
1638
- >
1639
- </account-region-cluster-selector>
1640
- </div>
1641
- <stage-config-field label="Shrink Options">
1642
- <div class="form-inline">
1643
- Shrink to
1644
- <input
1645
- type="number"
1646
- min="0"
1647
- required
1648
- ng-model="stage.shrinkToSize"
1649
- class="form-control input-sm"
1650
- style="width: 50px"
1651
- />
1652
- {{shrinkClusterStageCtrl.pluralize('server group', stage.shrinkToSize)}}, keeping the
1653
- <select class="form-control input-sm" ng-model="stage.retainLargerOverNewer" style="width: 100px">
1654
- <option value="true">largest</option>
1655
- <option value="false">newest</option>
1656
- </select>
1657
- </div>
1658
- </stage-config-field>
1659
- <div class="form-group">
1660
- <div class="col-md-offset-3 col-md-6 checkbox">
1661
- <label>
1662
- <input type="checkbox" ng-model="stage.allowDeleteActive" />
1663
- Allow deletion of active server groups
1664
- </label>
1665
- </div>
1666
- </div>
1667
- <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">
1668
- </stage-platform-health-override>
1669
- </div>
1670
- `);
1671
- }]);
1672
-
1673
- class AppengineStartServerGroupStageCtrl extends AppengineStageCtrl {
1674
- constructor($scope) {
1675
- super($scope);
1676
- this.$scope = $scope;
1677
- super.setAccounts().then(() => {
1678
- super.setStageRegion();
1679
- });
1680
- super.setStageCloudProvider();
1681
- super.setTargets();
1682
- super.setStageCredentials();
1683
- if ($scope.stage.isNew && $scope.application.attributes.platformHealthOnlyShowOverride && $scope.application.attributes.platformHealthOnly) {
1684
- $scope.stage.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
1685
- }
1686
- }
1687
- }
1688
- AppengineStartServerGroupStageCtrl.$inject = ["$scope"];
1689
- const APPENGINE_START_SERVER_GROUP_STAGE = "spinnaker.appengine.pipeline.stage.startServerGroupStage";
1690
- module(APPENGINE_START_SERVER_GROUP_STAGE, []).config(() => {
1691
- Registry.pipeline.registerStage({
1692
- label: "Start Server Group",
1693
- description: "Starts a server group.",
1694
- key: "startAppEngineServerGroup",
1695
- templateUrl: "appengine/src/pipeline/stages/startServerGroup/startServerGroupStage.html",
1696
- executionDetailsUrl: "appengine/src/pipeline/stages/startServerGroup/startServerGroupExecutionDetails.html",
1697
- executionConfigSections: ["startServerGroupConfig", "taskStatus"],
1698
- executionStepLabelUrl: "appengine/src/pipeline/stages/startServerGroup/startServerGroupStepLabel.html",
1699
- controller: "appengineStartServerGroupStageCtrl",
1700
- controllerAs: "startServerGroupStageCtrl",
1701
- validators: [
1702
- { type: "requiredField", fieldName: "cluster" },
1703
- { type: "requiredField", fieldName: "target" },
1704
- { type: "requiredField", fieldName: "credentials", fieldLabel: "account" }
1705
- ],
1706
- cloudProvider: "appengine"
1707
- });
1708
- }).controller("appengineStartServerGroupStageCtrl", AppengineStartServerGroupStageCtrl);
1709
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1710
- templateCache.put("appengine/src/pipeline/stages/startServerGroup/startServerGroupStage.html", `<div ng-controller="appengineStartServerGroupStageCtrl as startServerGroupStageCtrl" class="form-horizontal">
1711
- <div ng-if="!pipeline.strategy">
1712
- <account-region-cluster-selector
1713
- application="application"
1714
- component="stage"
1715
- single-region="true"
1716
- disable-region-select="true"
1717
- on-account-update="startServerGroupStageCtrl.setStageRegion()"
1718
- accounts="accounts"
1719
- >
1720
- </account-region-cluster-selector>
1721
- </div>
1722
- <stage-config-field label="Target">
1723
- <target-select model="stage" options="targets"></target-select>
1724
- </stage-config-field>
1725
- <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">
1726
- </stage-platform-health-override>
1727
- </div>
1728
- `);
1729
- }]);
1730
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1731
- templateCache.put("appengine/src/pipeline/stages/startServerGroup/startServerGroupExecutionDetails.html", `<div ng-controller="BaseExecutionDetailsCtrl">
1732
- <execution-details-section-nav sections="configSections"></execution-details-section-nav>
1733
- <div class="step-section-details" ng-if="detailsSection === 'startServerGroupConfig'">
1734
- <div class="row">
1735
- <div class="col-md-9">
1736
- <dl class="dl-narrow dl-horizontal">
1737
- <dt>Account</dt>
1738
- <dd>
1739
- <account-tag account="stage.context.credentials"></account-tag>
1740
- </dd>
1741
- <dt>Region</dt>
1742
- <dd>{{stage.context.region}}</dd>
1743
- <dt>Server Group</dt>
1744
- <dd>{{stage.context.serverGroupName}}</dd>
1745
- </dl>
1746
- </div>
1747
- </div>
1748
- <stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>
1749
- </div>
1750
-
1751
- <div class="step-section-details" ng-if="detailsSection === 'taskStatus'">
1752
- <div class="row">
1753
- <execution-step-details item="stage"></execution-step-details>
1754
- </div>
1755
- </div>
1756
- </div>
1757
- `);
1758
- }]);
1759
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1760
- templateCache.put("appengine/src/pipeline/stages/startServerGroup/startServerGroupStepLabel.html", `<span class="task-label"> Start Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>
1761
- `);
1762
- }]);
1763
-
1764
- class AppengineStopServerGroupStageCtrl extends AppengineStageCtrl {
1765
- constructor($scope) {
1766
- super($scope);
1767
- this.$scope = $scope;
1768
- super.setAccounts().then(() => {
1769
- super.setStageRegion();
1770
- });
1771
- super.setStageCloudProvider();
1772
- super.setTargets();
1773
- super.setStageCredentials();
1774
- if ($scope.stage.isNew && $scope.application.attributes.platformHealthOnlyShowOverride && $scope.application.attributes.platformHealthOnly) {
1775
- $scope.stage.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
1776
- }
1777
- }
1778
- }
1779
- AppengineStopServerGroupStageCtrl.$inject = ["$scope"];
1780
- const APPENGINE_STOP_SERVER_GROUP_STAGE = "spinnaker.appengine.pipeline.stage.stopServerGroupStage";
1781
- module(APPENGINE_STOP_SERVER_GROUP_STAGE, []).config(() => {
1782
- Registry.pipeline.registerStage({
1783
- label: "Stop Server Group",
1784
- description: "Stops a server group.",
1785
- key: "stopAppEngineServerGroup",
1786
- templateUrl: "appengine/src/pipeline/stages/stopServerGroup/stopServerGroupStage.html",
1787
- executionDetailsUrl: "appengine/src/pipeline/stages/stopServerGroup/stopServerGroupExecutionDetails.html",
1788
- executionConfigSections: ["stopServerGroupConfig", "taskStatus"],
1789
- executionStepLabelUrl: "appengine/src/pipeline/stages/stopServerGroup/stopServerGroupStepLabel.html",
1790
- controller: "appengineStopServerGroupStageCtrl",
1791
- controllerAs: "stopServerGroupStageCtrl",
1792
- validators: [
1793
- { type: "requiredField", fieldName: "cluster" },
1794
- { type: "requiredField", fieldName: "target" },
1795
- { type: "requiredField", fieldName: "credentials", fieldLabel: "account" }
1796
- ],
1797
- cloudProvider: "appengine"
1798
- });
1799
- }).controller("appengineStopServerGroupStageCtrl", AppengineStopServerGroupStageCtrl);
1800
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1801
- templateCache.put("appengine/src/pipeline/stages/stopServerGroup/stopServerGroupStage.html", `<div ng-controller="appengineStopServerGroupStageCtrl as stopServerGroupStageCtrl" class="form-horizontal">
1802
- <div ng-if="!pipeline.strategy">
1803
- <account-region-cluster-selector
1804
- application="application"
1805
- component="stage"
1806
- single-region="true"
1807
- disable-region-select="true"
1808
- on-account-update="stopServerGroupStageCtrl.setStageRegion()"
1809
- accounts="accounts"
1810
- >
1811
- </account-region-cluster-selector>
1812
- </div>
1813
- <stage-config-field label="Target">
1814
- <target-select model="stage" options="targets"></target-select>
1815
- </stage-config-field>
1816
- <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">
1817
- </stage-platform-health-override>
1818
- </div>
1819
- `);
1820
- }]);
1821
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1822
- templateCache.put("appengine/src/pipeline/stages/stopServerGroup/stopServerGroupExecutionDetails.html", `<div ng-controller="appengineStopServerGroupExecutionDetailsCtrl">
1823
- <execution-details-section-nav sections="configSections"></execution-details-section-nav>
1824
- <div class="step-section-details" ng-if="detailsSection === 'stopServerGroupConfig'">
1825
- <div class="row">
1826
- <div class="col-md-9">
1827
- <dl class="dl-narrow dl-horizontal">
1828
- <dt>Account</dt>
1829
- <dd>
1830
- <account-tag account="stage.context.credentials"></account-tag>
1831
- </dd>
1832
- <dt>Region</dt>
1833
- <dd>{{stage.context.region}}</dd>
1834
- <dt>Server Group</dt>
1835
- <dd>{{stage.context.serverGroupName}}</dd>
1836
- </dl>
1837
- </div>
1838
- </div>
1839
- <stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>
1840
- </div>
1841
-
1842
- <div class="step-section-details" ng-if="detailsSection === 'taskStatus'">
1843
- <div class="row">
1844
- <execution-step-details item="stage"></execution-step-details>
1845
- </div>
1846
- </div>
1847
- </div>
1848
- `);
1849
- }]);
1850
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
1851
- templateCache.put("appengine/src/pipeline/stages/stopServerGroup/stopServerGroupStepLabel.html", `<span class="task-label"> Stop Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>
1852
- `);
1853
- }]);
1854
-
1855
- const APPENGINE_PIPELINE_MODULE = "spinnaker.appengine.pipeline.module";
1856
- module(APPENGINE_PIPELINE_MODULE, [
1857
- APPENGINE_DESTROY_ASG_STAGE,
1858
- APPENGINE_DISABLE_ASG_STAGE,
1859
- APPENGINE_EDIT_LOAD_BALANCER_STAGE,
1860
- APPENGINE_ENABLE_ASG_STAGE,
1861
- APPENGINE_SHRINK_CLUSTER_STAGE,
1862
- APPENGINE_START_SERVER_GROUP_STAGE,
1863
- APPENGINE_STOP_SERVER_GROUP_STAGE
1864
- ]);
1865
-
1866
- class FormikAccountRegionSelector extends React.Component {
1867
- constructor(props) {
1868
- super(props);
1869
- this.destroy$ = new Subject();
1870
- this.setRegionList = (credentials) => {
1871
- const { application } = this.props;
1872
- const accountFilter = (serverGroup) => serverGroup ? serverGroup.account === credentials : true;
1873
- from(application.ready()).pipe(takeUntil(this.destroy$)).subscribe(() => {
1874
- const availableRegions = AppListExtractor.getRegions([application], accountFilter);
1875
- availableRegions.sort();
1876
- this.setState({ availableRegions });
1877
- });
1878
- };
1879
- this.accountChanged = (credentials) => {
1880
- this.setRegionList(credentials);
1881
- };
1882
- const credentialsField = props.credentialsField || "credentials";
1883
- this.state = {
1884
- availableRegions: [],
1885
- cloudProvider: props.cloudProvider,
1886
- componentName: props.componentName || "",
1887
- credentialsField
1888
- };
1889
- }
1890
- componentDidMount() {
1891
- const { componentName, formik } = this.props;
1892
- const { credentialsField } = this.state;
1893
- const credentials = get(formik.values, componentName ? `${componentName}.${credentialsField}` : `${credentialsField}`, void 0);
1894
- this.setRegionList(credentials);
1895
- }
1896
- componentWillUnmount() {
1897
- this.destroy$.next();
1898
- }
1899
- render() {
1900
- const { accounts } = this.props;
1901
- const { credentialsField, availableRegions, componentName } = this.state;
1902
- return /* @__PURE__ */ React.createElement("div", {
1903
- className: "col-md-9"
1904
- }, /* @__PURE__ */ React.createElement("div", {
1905
- className: "sp-margin-m-bottom"
1906
- }, /* @__PURE__ */ React.createElement(FormikFormField, {
1907
- name: componentName ? `${componentName}.${credentialsField}` : `${credentialsField}`,
1908
- label: "Account",
1909
- input: (props) => /* @__PURE__ */ React.createElement(ReactSelectInput, {
1910
- ...props,
1911
- stringOptions: accounts && accounts.map((acc) => acc.name),
1912
- clearable: false
1913
- }),
1914
- onChange: this.accountChanged,
1915
- required: true
1916
- })), /* @__PURE__ */ React.createElement("div", {
1917
- className: "sp-margin-m-bottom"
1918
- }, /* @__PURE__ */ React.createElement(FormikFormField, {
1919
- name: componentName ? `${componentName}.region` : "region",
1920
- label: "Region",
1921
- input: (props) => /* @__PURE__ */ React.createElement(ReactSelectInput, {
1922
- ...props,
1923
- stringOptions: availableRegions,
1924
- clearable: false
1925
- }),
1926
- required: true
1927
- })));
1928
- }
1929
- }
1930
-
1931
- const _DeployAppengineConfigForm = class extends React.Component {
1932
- constructor() {
1933
- super(...arguments);
1934
- this.destroy$ = new Subject();
1935
- this.state = {
1936
- accounts: []
1937
- };
1938
- this.onTemplateArtifactEdited = (artifact, name) => {
1939
- this.props.formik.setFieldValue(`${name}.id`, null);
1940
- this.props.formik.setFieldValue(`${name}.artifact`, artifact);
1941
- this.props.formik.setFieldValue(`${name}.account`, artifact.artifactAccount);
1942
- };
1943
- this.onTemplateArtifactSelected = (id, name) => {
1944
- this.props.formik.setFieldValue(`${name}.id`, id);
1945
- this.props.formik.setFieldValue(`${name}.artifact`, null);
1946
- };
1947
- this.removeInputArtifact = (name) => {
1948
- this.props.formik.setFieldValue(name, null);
1949
- };
1950
- this.getInputArtifact = (stage, name) => {
1951
- if (!stage[name]) {
1952
- return {
1953
- account: "",
1954
- id: ""
1955
- };
1956
- } else {
1957
- return stage[name];
1958
- }
1959
- };
1960
- }
1961
- componentDidMount() {
1962
- from(AccountService.listAccounts("appengine")).pipe(takeUntil(this.destroy$)).subscribe((accounts) => this.setState({ accounts }));
1963
- }
1964
- render() {
1965
- const stage = this.props.formik.values;
1966
- const accounts = this.state.accounts;
1967
- return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
1968
- className: "col-md-offset-0 col-md-9"
1969
- }, /* @__PURE__ */ React.createElement("h4", null, "Basic Settings")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(FormikAccountRegionSelector, {
1970
- componentName: "",
1971
- accounts,
1972
- application: this.props.application,
1973
- cloudProvider: "appengine",
1974
- credentialsField: "account",
1975
- formik: this.props.formik
1976
- })), /* @__PURE__ */ React.createElement("div", {
1977
- className: "col-md-offset-0 col-md-9"
1978
- }, /* @__PURE__ */ React.createElement("h4", null, "Configuration Settings")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
1979
- className: "col-md-offset-1 col-md-9"
1980
- }, /* @__PURE__ */ React.createElement(StageArtifactSelectorDelegate, {
1981
- artifact: this.getInputArtifact(stage, "cronArtifact").artifact,
1982
- excludedArtifactTypePatterns: _DeployAppengineConfigForm.excludedArtifactTypes,
1983
- expectedArtifactId: this.getInputArtifact(stage, "cronArtifact").id,
1984
- label: "Cron Artifact",
1985
- onArtifactEdited: (artifact) => {
1986
- this.onTemplateArtifactEdited(artifact, "cronArtifact");
1987
- },
1988
- helpKey: "",
1989
- onExpectedArtifactSelected: (artifact) => this.onTemplateArtifactSelected(artifact.id, "cronArtifact"),
1990
- pipeline: this.props.pipeline,
1991
- stage
1992
- })), /* @__PURE__ */ React.createElement("div", {
1993
- className: "col-md-1"
1994
- }, /* @__PURE__ */ React.createElement("div", {
1995
- className: "form-control-static"
1996
- }, /* @__PURE__ */ React.createElement("button", {
1997
- onClick: () => this.removeInputArtifact("cronArtifact")
1998
- }, /* @__PURE__ */ React.createElement("span", {
1999
- className: "glyphicon glyphicon-trash"
2000
- }), /* @__PURE__ */ React.createElement("span", {
2001
- className: "sr-only"
2002
- }, "Remove field"))))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
2003
- className: "col-md-offset-1 col-md-9"
2004
- }, /* @__PURE__ */ React.createElement(StageArtifactSelectorDelegate, {
2005
- artifact: this.getInputArtifact(stage, "dispatchArtifact").artifact,
2006
- excludedArtifactTypePatterns: _DeployAppengineConfigForm.excludedArtifactTypes,
2007
- expectedArtifactId: this.getInputArtifact(stage, "dispatchArtifact").id,
2008
- label: "Dispatch Artifact",
2009
- onArtifactEdited: (artifact) => {
2010
- this.onTemplateArtifactEdited(artifact, "dispatchArtifact");
2011
- },
2012
- helpKey: "",
2013
- onExpectedArtifactSelected: (artifact) => this.onTemplateArtifactSelected(artifact.id, "dispatchArtifact"),
2014
- pipeline: this.props.pipeline,
2015
- stage
2016
- })), /* @__PURE__ */ React.createElement("div", {
2017
- className: "col-md-1"
2018
- }, /* @__PURE__ */ React.createElement("div", {
2019
- className: "form-control-static"
2020
- }, /* @__PURE__ */ React.createElement("button", {
2021
- onClick: () => this.removeInputArtifact("dispatchArtifact")
2022
- }, /* @__PURE__ */ React.createElement("span", {
2023
- className: "glyphicon glyphicon-trash"
2024
- }), /* @__PURE__ */ React.createElement("span", {
2025
- className: "sr-only"
2026
- }, "Remove field"))))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
2027
- className: "col-md-offset-1 col-md-9"
2028
- }, /* @__PURE__ */ React.createElement(StageArtifactSelectorDelegate, {
2029
- artifact: this.getInputArtifact(stage, "indexArtifact").artifact,
2030
- excludedArtifactTypePatterns: _DeployAppengineConfigForm.excludedArtifactTypes,
2031
- expectedArtifactId: this.getInputArtifact(stage, "indexArtifact").id,
2032
- label: "Index Artifact",
2033
- onArtifactEdited: (artifact) => {
2034
- this.onTemplateArtifactEdited(artifact, "indexArtifact");
2035
- },
2036
- helpKey: "",
2037
- onExpectedArtifactSelected: (artifact) => this.onTemplateArtifactSelected(artifact.id, "indexArtifact"),
2038
- pipeline: this.props.pipeline,
2039
- stage
2040
- })), /* @__PURE__ */ React.createElement("div", {
2041
- className: "col-md-1"
2042
- }, /* @__PURE__ */ React.createElement("div", {
2043
- className: "form-control-static"
2044
- }, /* @__PURE__ */ React.createElement("button", {
2045
- onClick: () => this.removeInputArtifact("indexArtifact")
2046
- }, /* @__PURE__ */ React.createElement("span", {
2047
- className: "glyphicon glyphicon-trash"
2048
- }), /* @__PURE__ */ React.createElement("span", {
2049
- className: "sr-only"
2050
- }, "Remove field"))))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
2051
- className: "col-md-offset-1 col-md-9"
2052
- }, /* @__PURE__ */ React.createElement(StageArtifactSelectorDelegate, {
2053
- artifact: this.getInputArtifact(stage, "queueArtifact").artifact,
2054
- excludedArtifactTypePatterns: _DeployAppengineConfigForm.excludedArtifactTypes,
2055
- expectedArtifactId: this.getInputArtifact(stage, "queueArtifact").id,
2056
- label: "Queue Artifact",
2057
- onArtifactEdited: (artifact) => {
2058
- this.onTemplateArtifactEdited(artifact, "queueArtifact");
2059
- },
2060
- helpKey: "",
2061
- onExpectedArtifactSelected: (artifact) => this.onTemplateArtifactSelected(artifact.id, "queueArtifact"),
2062
- pipeline: this.props.pipeline,
2063
- stage
2064
- })), /* @__PURE__ */ React.createElement("div", {
2065
- className: "col-md-1"
2066
- }, /* @__PURE__ */ React.createElement("div", {
2067
- className: "form-control-static"
2068
- }, /* @__PURE__ */ React.createElement("button", {
2069
- onClick: () => this.removeInputArtifact("queueArtifact")
2070
- }, /* @__PURE__ */ React.createElement("span", {
2071
- className: "glyphicon glyphicon-trash"
2072
- }), /* @__PURE__ */ React.createElement("span", {
2073
- className: "sr-only"
2074
- }, "Remove field"))))));
2075
- }
2076
- };
2077
- let DeployAppengineConfigForm = _DeployAppengineConfigForm;
2078
- DeployAppengineConfigForm.excludedArtifactTypes = excludeAllTypesExcept(ArtifactTypePatterns.BITBUCKET_FILE, ArtifactTypePatterns.CUSTOM_OBJECT, ArtifactTypePatterns.EMBEDDED_BASE64, ArtifactTypePatterns.GCS_OBJECT, ArtifactTypePatterns.GITHUB_FILE, ArtifactTypePatterns.GITLAB_FILE, ArtifactTypePatterns.S3_OBJECT, ArtifactTypePatterns.HTTP_FILE, ArtifactTypePatterns.ORACLE_OBJECT);
2079
-
2080
- function DeployAppengineConfigurationConfig({ application, pipeline, stage, updateStage }) {
2081
- const stageWithDefaults = React.useMemo(() => {
2082
- return {
2083
- ...cloneDeep(stage)
2084
- };
2085
- }, []);
2086
- return /* @__PURE__ */ React.createElement(FormikStageConfig, {
2087
- application,
2088
- onChange: updateStage,
2089
- pipeline,
2090
- stage: stageWithDefaults,
2091
- validate: validateDeployAppengineConfigurationStage,
2092
- render: (props) => /* @__PURE__ */ React.createElement(DeployAppengineConfigForm, {
2093
- ...props
2094
- })
2095
- });
2096
- }
2097
- function validateDeployAppengineConfigurationStage(stage) {
2098
- const formValidator = new FormValidator(stage);
2099
- formValidator.field("account").required();
2100
- formValidator.field("region").required();
2101
- return formValidator.validateForm();
2102
- }
2103
-
2104
- const DEPLOY_APPENGINE_CONFIG_STAGE_KEY = "deployAppEngineConfiguration";
2105
- Registry.pipeline.registerStage({
2106
- label: "Deploy App Engine Configuration",
2107
- description: "Deploy index, dispatch, cron, and queue configuration to App Engine.",
2108
- key: DEPLOY_APPENGINE_CONFIG_STAGE_KEY,
2109
- component: DeployAppengineConfigurationConfig,
2110
- producesArtifacts: false,
2111
- cloudProvider: "appengine",
2112
- executionDetailsSections: [ExecutionDetailsTasks, ExecutionArtifactTab],
2113
- validateFn: validateDeployAppengineConfigurationStage
2114
- });
2115
-
2116
- const AppengineProviderSettings = SETTINGS.providers.appengine || { defaults: {} };
2117
- if (AppengineProviderSettings) {
2118
- AppengineProviderSettings.resetToOriginal = SETTINGS.resetProvider("appengine");
2119
- }
2120
-
2121
- var AppengineSourceType;
2122
- (function(AppengineSourceType2) {
2123
- AppengineSourceType2["GCS"] = "gcs";
2124
- AppengineSourceType2["GIT"] = "git";
2125
- AppengineSourceType2["ARTIFACT"] = "artifact";
2126
- AppengineSourceType2["CONTAINER_IMAGE"] = "containerImage";
2127
- })(AppengineSourceType || (AppengineSourceType = {}));
2128
- const _AppengineServerGroupCommandBuilder = class {
2129
- constructor($q) {
2130
- this.$q = $q;
2131
- }
2132
- static getTriggerOptions(pipeline) {
2133
- return (pipeline.triggers || []).filter((trigger) => trigger.type === "git" || trigger.type === "jenkins" || trigger.type === "travis").map((trigger) => {
2134
- if (trigger.type === "git") {
2135
- return {
2136
- source: trigger.source,
2137
- project: trigger.project,
2138
- slug: trigger.slug,
2139
- branch: trigger.branch,
2140
- type: "git"
2141
- };
2142
- } else {
2143
- return { master: trigger.master, job: trigger.job, type: trigger.type };
2144
- }
2145
- });
2146
- }
2147
- static getExpectedArtifacts(pipeline) {
2148
- return pipeline.expectedArtifacts || [];
2149
- }
2150
- buildNewServerGroupCommand(app, selectedProvider, mode = "create") {
2151
- if (selectedProvider == null) {
2152
- selectedProvider = "appengine";
2153
- }
2154
- const dataToFetch = {
2155
- accounts: AccountService.getAllAccountDetailsForProvider("appengine"),
2156
- storageAccounts: StorageAccountReader.getStorageAccounts()
2157
- };
2158
- const viewState = {
2159
- mode,
2160
- submitButtonLabel: this.getSubmitButtonLabel(mode),
2161
- disableStrategySelection: mode === "create"
2162
- };
2163
- return this.$q.all(dataToFetch).then((backingData) => {
2164
- const credentials = this.getCredentials(backingData.accounts);
2165
- const region = this.getRegion(backingData.accounts, credentials);
2166
- return {
2167
- application: app.name,
2168
- backingData,
2169
- viewState,
2170
- fromArtifact: false,
2171
- credentials,
2172
- region,
2173
- selectedProvider,
2174
- interestingHealthProviderNames: [],
2175
- sourceType: AppengineSourceType.GIT
2176
- };
2177
- });
2178
- }
2179
- buildServerGroupCommandFromExisting(app, serverGroup) {
2180
- return this.buildNewServerGroupCommand(app, "appengine", "clone").then((command) => {
2181
- command.stack = serverGroup.stack;
2182
- command.freeFormDetails = serverGroup.detail;
2183
- return command;
2184
- });
2185
- }
2186
- buildNewServerGroupCommandForPipeline(_stage, pipeline) {
2187
- return this.$q.when({
2188
- viewState: {
2189
- pipeline,
2190
- stage: _stage
2191
- },
2192
- backingData: {
2193
- triggerOptions: _AppengineServerGroupCommandBuilder.getTriggerOptions(pipeline),
2194
- expectedArtifacts: _AppengineServerGroupCommandBuilder.getExpectedArtifacts(pipeline)
2195
- }
2196
- });
2197
- }
2198
- buildServerGroupCommandFromPipeline(app, cluster, _stage, pipeline) {
2199
- return this.buildNewServerGroupCommand(app, "appengine", "editPipeline").then((command) => {
2200
- command = {
2201
- ...command,
2202
- ...cluster,
2203
- backingData: {
2204
- ...command.backingData,
2205
- triggerOptions: _AppengineServerGroupCommandBuilder.getTriggerOptions(pipeline),
2206
- expectedArtifacts: _AppengineServerGroupCommandBuilder.getExpectedArtifacts(pipeline)
2207
- },
2208
- credentials: cluster.account || command.credentials,
2209
- viewState: {
2210
- ...command.viewState,
2211
- stage: _stage,
2212
- pipeline
2213
- }
2214
- };
2215
- return command;
2216
- });
2217
- }
2218
- getCredentials(accounts) {
2219
- const accountNames = (accounts || []).map((account) => account.name);
2220
- const defaultCredentials = AppengineProviderSettings.defaults.account;
2221
- return accountNames.includes(defaultCredentials) ? defaultCredentials : accountNames[0];
2222
- }
2223
- getRegion(accounts, credentials) {
2224
- const account = accounts.find((_account) => _account.name === credentials);
2225
- return account ? account.region : null;
2226
- }
2227
- getSubmitButtonLabel(mode) {
2228
- switch (mode) {
2229
- case "createPipeline":
2230
- return "Add";
2231
- case "editPipeline":
2232
- return "Done";
2233
- case "clone":
2234
- return "Clone";
2235
- default:
2236
- return "Create";
2237
- }
2238
- }
2239
- };
2240
- let AppengineServerGroupCommandBuilder = _AppengineServerGroupCommandBuilder;
2241
- AppengineServerGroupCommandBuilder.$inject = ["$q"];
2242
- const APPENGINE_SERVER_GROUP_COMMAND_BUILDER = "spinnaker.appengine.serverGroupCommandBuilder.service";
2243
- module(APPENGINE_SERVER_GROUP_COMMAND_BUILDER, []).service("appengineServerGroupCommandBuilder", AppengineServerGroupCommandBuilder);
2244
-
2245
- class AppengineServerGroupBasicSettingsCtrl {
2246
- constructor($scope, $state, $controller, $uibModalStack) {
2247
- this.$scope = $scope;
2248
- this.excludedGcsArtifactTypes = excludeAllTypesExcept(ArtifactTypePatterns.GCS_OBJECT);
2249
- this.excludedContainerArtifactTypes = excludeAllTypesExcept(ArtifactTypePatterns.DOCKER_IMAGE);
2250
- this.onExpectedArtifactEdited = (artifact) => {
2251
- this.$scope.$applyAsync(() => {
2252
- this.$scope.command.expectedArtifactId = null;
2253
- this.$scope.command.expectedArtifact = artifact;
2254
- });
2255
- };
2256
- this.onExpectedArtifactSelected = (expectedArtifact) => {
2257
- this.onChangeExpectedArtifactId(expectedArtifact.id);
2258
- };
2259
- this.onChangeExpectedArtifactId = (artifactId) => {
2260
- this.$scope.$applyAsync(() => {
2261
- this.$scope.command.expectedArtifactId = artifactId;
2262
- this.$scope.command.expectedArtifact = null;
2263
- });
2264
- };
2265
- this.onExpectedArtifactAccountSelected = (accountName) => {
2266
- this.$scope.$applyAsync(() => {
2267
- this.$scope.command.storageAccountName = accountName;
2268
- });
2269
- };
2270
- extend(this, $controller("BasicSettingsMixin", {
2271
- $scope,
2272
- imageReader: null,
2273
- $uibModalStack,
2274
- $state
2275
- }));
2276
- if (!this.$scope.command.gitCredentialType) {
2277
- this.onAccountChange();
2278
- }
2279
- this.$scope.containerArtifactDelegate = new NgAppEngineDeployArtifactDelegate($scope, [
2280
- ArtifactTypePatterns.DOCKER_IMAGE
2281
- ]);
2282
- this.$scope.containerArtifactController = new ExpectedArtifactSelectorViewController(this.$scope.containerArtifactDelegate);
2283
- this.$scope.gcsArtifactDelegate = new NgAppEngineDeployArtifactDelegate($scope, [ArtifactTypePatterns.GCS_OBJECT]);
2284
- this.$scope.gcsArtifactController = new ExpectedArtifactSelectorViewController(this.$scope.gcsArtifactDelegate);
2285
- }
2286
- isGitSource() {
2287
- return this.$scope.command.sourceType === AppengineSourceType.GIT;
2288
- }
2289
- isGcsSource() {
2290
- return this.$scope.command.sourceType === AppengineSourceType.GCS;
2291
- }
2292
- isContainerImageSource() {
2293
- return this.$scope.command.sourceType === AppengineSourceType.CONTAINER_IMAGE;
2294
- }
2295
- toggleResolveViaTrigger() {
2296
- this.$scope.command.fromTrigger = !this.$scope.command.fromTrigger;
2297
- delete this.$scope.command.trigger;
2298
- delete this.$scope.command.branch;
2299
- }
2300
- onTriggerChange() {
2301
- set(this, "$scope.command.trigger.matchBranchOnRegex", void 0);
2302
- }
2303
- onAccountChange() {
2304
- const account = this.findAccountInBackingData();
2305
- if (account) {
2306
- this.$scope.command.gitCredentialType = this.getSupportedGitCredentialTypes()[0];
2307
- this.$scope.command.region = account.region;
2308
- } else {
2309
- this.$scope.command.gitCredentialType = "NONE";
2310
- delete this.$scope.command.region;
2311
- }
2312
- }
2313
- getSupportedGitCredentialTypes() {
2314
- const account = this.findAccountInBackingData();
2315
- if (account && account.supportedGitCredentialTypes) {
2316
- return account.supportedGitCredentialTypes;
2317
- } else {
2318
- return ["NONE"];
2319
- }
2320
- }
2321
- humanReadableGitCredentialType(type) {
2322
- switch (type) {
2323
- case "HTTPS_USERNAME_PASSWORD":
2324
- return "HTTPS with username and password";
2325
- case "HTTPS_GITHUB_OAUTH_TOKEN":
2326
- return "HTTPS with Github OAuth token";
2327
- case "SSH":
2328
- return "SSH";
2329
- case "NONE":
2330
- return "No credentials";
2331
- default:
2332
- return "No credentials";
2333
- }
2334
- }
2335
- findAccountInBackingData() {
2336
- return this.$scope.command.backingData.accounts.find((account) => {
2337
- return this.$scope.command.credentials === account.name;
2338
- });
2339
- }
2340
- }
2341
- AppengineServerGroupBasicSettingsCtrl.$inject = ["$scope", "$state", "$controller", "$uibModalStack"];
2342
- const APPENGINE_SERVER_GROUP_BASIC_SETTINGS_CTRL = "spinnaker.appengine.basicSettings.controller";
2343
- module(APPENGINE_SERVER_GROUP_BASIC_SETTINGS_CTRL, []).controller("appengineServerGroupBasicSettingsCtrl", AppengineServerGroupBasicSettingsCtrl);
2344
-
2345
- var css_248z$1 = ".appengine-server-group-wizard input[type='checkbox'] {\n margin-top: 0.75rem;\n}\n.appengine-server-group-wizard help-field.help-field-absolute span {\n position: absolute;\n top: 7px;\n right: 2px;\n}\n.appengine-server-group-wizard .artifact-configuration-section {\n border-bottom: 1px solid #eee;\n padding: 0 0 4px 0;\n margin: 0 0 5px 0;\n}\n.appengine-server-group-wizard .artifact-configuration-section.last-entry {\n border-bottom: none;\n}\n.appengine-server-group-wizard .artifact-configuration-section .Select,\n.appengine-server-group-wizard .artifact-configuration-section input {\n margin: 0 0 1px;\n}\n";
2346
- styleInject(css_248z$1);
2347
-
2348
- class ConfigArtifact {
2349
- constructor($scope, pair = { id: "", account: "" }) {
2350
- var _a;
2351
- const unserializable = { configurable: false, enumerable: false, writable: false };
2352
- this.id = pair == null ? void 0 : pair.id;
2353
- this.account = pair.account || ((_a = pair == null ? void 0 : pair.artifact) == null ? void 0 : _a.artifactAccount);
2354
- this.artifact = pair == null ? void 0 : pair.artifact;
2355
- Object.defineProperty(this, "$scope", { ...unserializable, value: $scope });
2356
- const delegate = new NgAppengineConfigArtifactDelegate(this);
2357
- const controller = new ExpectedArtifactSelectorViewController(delegate);
2358
- Object.defineProperty(this, "delegate", { ...unserializable, value: delegate });
2359
- Object.defineProperty(this, "controller", { ...unserializable, value: controller });
2360
- }
2361
- }
2362
- class AppengineConfigFileConfigurerCtrl {
2363
- constructor($scope) {
2364
- this.$scope = $scope;
2365
- this.artifactAccounts = [];
2366
- this.updateConfigArtifacts = (configArtifacts) => {
2367
- this.$scope.$applyAsync(() => {
2368
- this.command.configArtifacts = configArtifacts;
2369
- });
2370
- };
2371
- }
2372
- $onInit() {
2373
- if (!this.command.configFiles) {
2374
- this.command.configFiles = [];
2375
- }
2376
- if (!this.command.configArtifacts) {
2377
- this.command.configArtifacts = [];
2378
- }
2379
- if (!this.$scope.command) {
2380
- this.$scope.command = this.command;
2381
- }
2382
- this.command.configArtifacts = this.command.configArtifacts.map((artifactAccountPair) => {
2383
- return new ConfigArtifact(this.$scope, artifactAccountPair);
2384
- });
2385
- AccountService.getArtifactAccounts().then((accounts) => {
2386
- this.artifactAccounts = accounts;
2387
- this.command.configArtifacts.forEach((a) => {
2388
- a.delegate.setAccounts(accounts);
2389
- a.controller.updateAccounts(a.delegate.getSelectedExpectedArtifact());
2390
- });
2391
- });
2392
- }
2393
- addConfigFile() {
2394
- this.command.configFiles.push("");
2395
- }
2396
- addConfigArtifact() {
2397
- const artifact = new ConfigArtifact(this.$scope, { id: "", account: "" });
2398
- artifact.delegate.setAccounts(this.artifactAccounts);
2399
- artifact.controller.updateAccounts(artifact.delegate.getSelectedExpectedArtifact());
2400
- this.command.configArtifacts.push(artifact);
2401
- }
2402
- deleteConfigFile(index) {
2403
- this.command.configFiles.splice(index, 1);
2404
- }
2405
- deleteConfigArtifact(index) {
2406
- this.command.configArtifacts.splice(index, 1);
2407
- }
2408
- mapTabToSpaces(event) {
2409
- if (event.which === 9) {
2410
- event.preventDefault();
2411
- const cursorPosition = event.target.selectionStart;
2412
- const inputValue = event.target.value;
2413
- event.target.value = `${inputValue.substring(0, cursorPosition)} ${inputValue.substring(cursorPosition)}`;
2414
- event.target.selectionStart += 2;
2415
- }
2416
- }
2417
- isContainerImageSource() {
2418
- return this.command.sourceType === AppengineSourceType.CONTAINER_IMAGE;
2419
- }
2420
- }
2421
- AppengineConfigFileConfigurerCtrl.$inject = ["$scope"];
2422
- const appengineConfigFileConfigurerComponent = {
2423
- bindings: { command: "=" },
2424
- controller: AppengineConfigFileConfigurerCtrl,
2425
- templateUrl: "appengine/src/serverGroup/configure/wizard/configFiles.component.html"
2426
- };
2427
- const APPENGINE_CONFIG_FILE_CONFIGURER = "spinnaker.appengine.configFileConfigurer.component";
2428
- module(APPENGINE_CONFIG_FILE_CONFIGURER, []).component("appengineConfigFileConfigurer", appengineConfigFileConfigurerComponent);
2429
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
2430
- templateCache.put("appengine/src/serverGroup/configure/wizard/configFiles.component.html", `<div class="form-horizontal container-fluid">
2431
- <div class="form-group" ng-if="!$ctrl.isContainerImageSource()">
2432
- <div class="col-md-3 sm-label-right">
2433
- Application Root
2434
- <help-field class="help-field-absolute" key="appengine.serverGroup.applicationDirectoryRoot"></help-field>
2435
- </div>
2436
- <div class="col-md-7">
2437
- <input
2438
- type="text"
2439
- class="form-control input-sm"
2440
- name="applicationDirectoryRoot"
2441
- ng-model="$ctrl.command.applicationDirectoryRoot"
2442
- />
2443
- </div>
2444
- </div>
2445
-
2446
- <div class="form-group" ng-if="!$ctrl.isContainerImageSource()">
2447
- <div class="col-md-3 sm-label-right">
2448
- Config Filepaths <help-field key="appengine.serverGroup.configFilepaths"></help-field>
2449
- </div>
2450
- <div class="col-md-7">
2451
- <ui-select
2452
- multiple
2453
- tagging
2454
- tagging-label=""
2455
- style="width: 380px"
2456
- name="configFilepaths"
2457
- ng-model="$ctrl.command.configFilepaths"
2458
- class="form-control input-sm"
2459
- >
2460
- <ui-select-match>{{ $item }}</ui-select-match>
2461
- <ui-select-choices repeat="filepath in []">
2462
- <span ng-bind-html="filepath"></span>
2463
- </ui-select-choices>
2464
- </ui-select>
2465
- </div>
2466
- </div>
2467
-
2468
- <div class="form-group">
2469
- <div class="col-md-3 sm-label-right">
2470
- Config Files
2471
- <span ng-if="!$ctrl.isContainerImageSource()">
2472
- <help-field key="appengine.serverGroup.configFiles"></help-field>
2473
- </span>
2474
- <span ng-if="$ctrl.isContainerImageSource()">
2475
- <help-field key="appengine.serverGroup.configFilesRequired"></help-field>
2476
- </span>
2477
- </div>
2478
- <div ng-repeat="configFile in $ctrl.command.configFiles track by $index">
2479
- <div class="col-md-7" ng-class="{ 'col-md-offset-3': $index > 0 }" style="margin-top: 5px">
2480
- <textarea
2481
- cols="46"
2482
- rows="10"
2483
- class="small"
2484
- spellcheck="false"
2485
- ng-keydown="$ctrl.mapTabToSpaces($event)"
2486
- style="font-family: Menlo, Monaco, Consolas, 'Courier New', monospace"
2487
- ng-model="$ctrl.command.configFiles[$index]"
2488
- ></textarea>
2489
- </div>
2490
- <div class="col-md-1" style="margin-top: 5px">
2491
- <button type="button" class="btn btn-sm btn-default" ng-click="$ctrl.deleteConfigFile($index)">
2492
- <span class="glyphicon glyphicon-trash"></span> Delete
2493
- </button>
2494
- </div>
2495
- </div>
2496
- <div class="col-md-7" ng-class="{ 'col-md-offset-3': $ctrl.command.configFiles.length > 0 }">
2497
- <button class="btn btn-block btn-add-trigger add-new" ng-click="$ctrl.addConfigFile()">
2498
- <span class="glyphicon glyphicon-plus-sign"></span> Add Config File
2499
- </button>
2500
- </div>
2501
- <config-file-artifact-list
2502
- ng-if="$ctrl.command.viewState.pipeline"
2503
- config-artifacts="$ctrl.command.configArtifacts"
2504
- pipeline="$ctrl.command.viewState.pipeline"
2505
- stage="$ctrl.command.viewState.stage"
2506
- update-config-artifacts="$ctrl.updateConfigArtifacts"
2507
- >
2508
- </config-file-artifact-list>
2509
- </div>
2510
- </div>
2511
- `);
2512
- }]);
2513
-
2514
- const appengineDynamicBranchLabelComponent = {
2515
- bindings: { trigger: "<" },
2516
- template: `
2517
- <span ng-if="$ctrl.trigger.type === 'git'">
2518
- Resolved at runtime by <b>{{$ctrl.trigger.source}}</b> trigger: {{$ctrl.trigger.project}}/{{$ctrl.trigger.slug}}<span ng-if="$ctrl.trigger.branch">:{{$ctrl.trigger.branch}}</span>
2519
- </span>
2520
- <span ng-if="$ctrl.trigger.type === 'jenkins'">
2521
- Resolved at runtime by <b>Jenkins</b> trigger: {{$ctrl.trigger.master}}/{{$ctrl.trigger.job}}
2522
- </span>
2523
- `
2524
- };
2525
- const APPENGINE_DYNAMIC_BRANCH_LABEL = "spinnaker.appengine.dynamicBranchLabel.component";
2526
- module(APPENGINE_DYNAMIC_BRANCH_LABEL, []).component("appengineDynamicBranchLabel", appengineDynamicBranchLabelComponent);
2527
-
2528
- class AppengineCloneServerGroupCtrl {
2529
- constructor($scope, $uibModalInstance, serverGroupCommand, application, serverGroupWriter, appengineServerGroupCommandBuilder) {
2530
- this.$scope = $scope;
2531
- this.$uibModalInstance = $uibModalInstance;
2532
- this.serverGroupCommand = serverGroupCommand;
2533
- this.application = application;
2534
- this.serverGroupWriter = serverGroupWriter;
2535
- this.pages = {
2536
- basicSettings: "appengine/src/serverGroup/configure/wizard/basicSettings.html",
2537
- advancedSettings: "appengine/src/serverGroup/configure/wizard/advancedSettings.html"
2538
- };
2539
- this.state = {
2540
- loading: true
2541
- };
2542
- if (["create", "clone", "editPipeline"].includes(get(serverGroupCommand, "viewState.mode"))) {
2543
- this.$scope.command = serverGroupCommand;
2544
- this.state.loading = false;
2545
- this.initialize();
2546
- } else {
2547
- appengineServerGroupCommandBuilder.buildNewServerGroupCommand(application, "appengine", "createPipeline").then((constructedCommand) => {
2548
- this.$scope.command = merge(constructedCommand, serverGroupCommand);
2549
- this.$scope.command.viewState.pipeline = serverGroupCommand.viewState.pipeline;
2550
- this.$scope.command.viewState.stage = serverGroupCommand.viewState.stage;
2551
- this.state.loading = false;
2552
- this.initialize();
2553
- });
2554
- }
2555
- }
2556
- cancel() {
2557
- this.$uibModalInstance.dismiss();
2558
- }
2559
- submit() {
2560
- const mode = this.$scope.command.viewState.mode;
2561
- if (["editPipeline", "createPipeline"].includes(mode)) {
2562
- return this.$uibModalInstance.close(this.$scope.command);
2563
- } else {
2564
- const command = copy(this.$scope.command);
2565
- command.viewState.mode = "create";
2566
- const submitMethod = () => this.serverGroupWriter.cloneServerGroup(command, this.$scope.application);
2567
- this.taskMonitor.submit(submitMethod);
2568
- return null;
2569
- }
2570
- }
2571
- initialize() {
2572
- this.$scope.application = this.application;
2573
- this.taskMonitor = new TaskMonitor({
2574
- application: this.application,
2575
- title: "Creating your server group",
2576
- modalInstance: this.$uibModalInstance
2577
- });
2578
- this.$scope.showPlatformHealthOnlyOverride = this.application.attributes.platformHealthOnlyShowOverride;
2579
- this.$scope.platformHealth = AppengineHealth.PLATFORM;
2580
- if (this.application.attributes.platformHealthOnly) {
2581
- this.$scope.command.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
2582
- }
2583
- }
2584
- }
2585
- AppengineCloneServerGroupCtrl.$inject = [
2586
- "$scope",
2587
- "$uibModalInstance",
2588
- "serverGroupCommand",
2589
- "application",
2590
- "serverGroupWriter",
2591
- "appengineServerGroupCommandBuilder"
2592
- ];
2593
- const APPENGINE_CLONE_SERVER_GROUP_CTRL = "spinnaker.appengine.cloneServerGroup.controller";
2594
- module(APPENGINE_CLONE_SERVER_GROUP_CTRL, [
2595
- SERVER_GROUP_WRITER,
2596
- APPENGINE_DYNAMIC_BRANCH_LABEL,
2597
- APPENGINE_CONFIG_FILE_CONFIGURER
2598
- ]).controller("appengineCloneServerGroupCtrl", AppengineCloneServerGroupCtrl);
2599
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
2600
- templateCache.put("appengine/src/serverGroup/configure/wizard/basicSettings.html", `<div class="container-fluid form-horizontal" ng-controller="appengineServerGroupBasicSettingsCtrl as basicSettingsCtrl">
2601
- <ng-form name="basicSettings">
2602
- <div class="form-group">
2603
- <div class="col-md-3 sm-label-right">Account</div>
2604
- <div class="col-md-7">
2605
- <account-select-field
2606
- read-only="command.viewState.readOnlyFields.credentials"
2607
- component="command"
2608
- field="credentials"
2609
- on-change="basicSettingsCtrl.onAccountChange()"
2610
- accounts="command.backingData.accounts"
2611
- provider="'appengine'"
2612
- ></account-select-field>
2613
- </div>
2614
- </div>
2615
-
2616
- <div class="form-group">
2617
- <div class="col-md-3 sm-label-right">Region</div>
2618
- <div class="col-md-7">
2619
- <input type="text" readonly class="form-control input-sm" name="region" ng-model="command.region" />
2620
- </div>
2621
- </div>
2622
-
2623
- <div class="form-group">
2624
- <div class="col-md-3 sm-label-right">
2625
- Stack
2626
- <help-field key="aws.serverGroup.stack"></help-field>
2627
- </div>
2628
- <div class="col-md-7">
2629
- <input
2630
- type="text"
2631
- class="form-control input-sm no-spel"
2632
- ng-pattern="basicSettingsCtrl.stackPattern"
2633
- name="stack"
2634
- ng-model="command.stack"
2635
- />
2636
- </div>
2637
- </div>
2638
- <div class="form-group row slide-in" ng-if="basicSettings.stack.$error.pattern">
2639
- <div class="col-sm-9 col-sm-offset-2 error-message">
2640
- <span>Only dot(.) and underscore(_) special characters are allowed in the Stack field.</span>
2641
- </div>
2642
- </div>
2643
- <div class="form-group">
2644
- <div class="col-md-3 sm-label-right">
2645
- Detail
2646
- <help-field key="aws.serverGroup.detail"></help-field>
2647
- </div>
2648
- <div class="col-md-7">
2649
- <input
2650
- type="text"
2651
- class="form-control input-sm no-spel"
2652
- ng-pattern="basicSettingsCtrl.detailPattern"
2653
- name="details"
2654
- ng-model="command.freeFormDetails"
2655
- />
2656
- </div>
2657
- </div>
2658
-
2659
- <div class="form-group row slide-in" ng-if="basicSettings.details.$error.pattern">
2660
- <div class="col-sm-9 col-sm-offset-2 error-message">
2661
- <span>Only dot(.), underscore(_), and dash(-) special characters are allowed in the Detail field.</span>
2662
- </div>
2663
- </div>
2664
-
2665
- <div class="form-group row">
2666
- <label class="col-md-3 sm-label-right">Source Type</label>
2667
- <div class="col-md-7">
2668
- <div class="radio radio-inline">
2669
- <label> <input type="radio" ng-model="command.sourceType" value="git" /> Git </label>
2670
- </div>
2671
- <div class="radio radio-inline">
2672
- <label> <input type="radio" ng-model="command.sourceType" value="gcs" /> GCS </label>
2673
- </div>
2674
- <div class="radio radio-inline">
2675
- <label> <input type="radio" ng-model="command.sourceType" value="containerImage" /> Container Image </label>
2676
- </div>
2677
- </div>
2678
- </div>
2679
-
2680
- <div ng-if="basicSettingsCtrl.isGcsSource()">
2681
- <div class="form-group row">
2682
- <label class="col-md-3 sm-label-right">Resolve URL</label>
2683
- <div class="col-md-7">
2684
- <div class="radio radio-inline">
2685
- <label> <input type="radio" ng-model="command.fromArtifact" ng-value="false" /> via text input </label>
2686
- </div>
2687
- <div class="radio radio-inline" ng-if="command.viewState.pipeline">
2688
- <label>
2689
- <input type="radio" ng-model="command.fromArtifact" ng-value="true" /> via pipeline artifact
2690
- </label>
2691
- </div>
2692
- </div>
2693
- </div>
2694
- <stage-artifact-selector-delegate
2695
- ng-if="command.fromArtifact"
2696
- artifact="command.expectedArtifact"
2697
- excluded-artifact-type-patterns="basicSettingsCtrl.excludedGcsArtifactTypes"
2698
- expected-artifact-id="command.expectedArtifactId"
2699
- field-columns="7"
2700
- label="'Expected Artifact'"
2701
- on-artifact-edited="basicSettingsCtrl.onExpectedArtifactEdited"
2702
- on-expected-artifact-selected="basicSettingsCtrl.onExpectedArtifactSelected"
2703
- pipeline="command.viewState.pipeline"
2704
- stage="command.viewState.stage"
2705
- >
2706
- </stage-artifact-selector-delegate>
2707
- <div class="form-group" ng-if="!command.fromArtifact">
2708
- <div class="col-md-3 sm-label-right">
2709
- GCS URL
2710
- <help-field class="help-field-absolute" key="appengine.serverGroup.gcs.repositoryUrl"></help-field>
2711
- </div>
2712
- <div class="col-md-7">
2713
- <input type="text" required class="form-control input-sm" name="gcsUrl" ng-model="command.repositoryUrl" />
2714
- </div>
2715
- </div>
2716
- </div>
2717
-
2718
- <div ng-if="basicSettingsCtrl.isGitSource()">
2719
- <div class="form-group">
2720
- <div class="col-md-3 sm-label-right">
2721
- Git Repository URL
2722
- <help-field class="help-field-absolute" key="appengine.serverGroup.git.repositoryUrl"></help-field>
2723
- </div>
2724
- <div class="col-md-7">
2725
- <input type="text" required class="form-control input-sm" name="gitRepo" ng-model="command.repositoryUrl" />
2726
- </div>
2727
- </div>
2728
-
2729
- <div class="form-group">
2730
- <div class="col-md-3 sm-label-right">
2731
- Git Credential Type
2732
- <help-field class="help-field-absolute" key="appengine.serverGroup.gitCredentialType"></help-field>
2733
- </div>
2734
- <div class="col-md-7">
2735
- <select
2736
- class="form-control input-sm"
2737
- ng-options="basicSettingsCtrl.humanReadableGitCredentialType(type) for type in basicSettingsCtrl.getSupportedGitCredentialTypes()"
2738
- ng-model="command.gitCredentialType"
2739
- ></select>
2740
- </div>
2741
- </div>
2742
-
2743
- <div class="form-group">
2744
- <div class="col-md-3 sm-label-right">Branch <help-field key="appengine.serverGroup.branch"></help-field></div>
2745
- <div class="col-md-7">
2746
- <input
2747
- ng-if="!command.fromTrigger"
2748
- type="text"
2749
- required
2750
- class="form-control input-sm"
2751
- name="branch"
2752
- ng-model="command.branch"
2753
- />
2754
-
2755
- <ui-select
2756
- ng-if="command.fromTrigger"
2757
- ng-model="command.trigger"
2758
- class="form-control input-sm"
2759
- on-select="basicSettingsCtrl.onTriggerChange()"
2760
- required
2761
- >
2762
- <ui-select-match allow-clear>
2763
- <appengine-dynamic-branch-label trigger="command.trigger"></appengine-dynamic-branch-label>
2764
- </ui-select-match>
2765
- <ui-select-choices repeat="trigger in command.backingData.triggerOptions">
2766
- <appengine-dynamic-branch-label trigger="trigger"></appengine-dynamic-branch-label>
2767
- </ui-select-choices>
2768
- </ui-select>
2769
- </div>
2770
-
2771
- <div
2772
- class="col-md-7 col-md-offset-3"
2773
- ng-if="command.viewState.mode === 'createPipeline' || command.viewState.mode === 'editPipeline'"
2774
- >
2775
- <span class="pull-right small" ng-if="!command.fromTrigger">
2776
- <a href ng-click="basicSettingsCtrl.toggleResolveViaTrigger()">Resolve via trigger</a>
2777
- </span>
2778
- <span class="pull-right small" ng-if="command.fromTrigger">
2779
- <a href ng-click="basicSettingsCtrl.toggleResolveViaTrigger()">Click for text input</a>
2780
- </span>
2781
- </div>
2782
- </div>
2783
- </div>
2784
-
2785
- <div ng-if="basicSettingsCtrl.isContainerImageSource()">
2786
- <div class="form-group">
2787
- <label class="col-md-3 sm-label-right">Resolve URL</label>
2788
- <div class="col-md-7">
2789
- <div class="radio radio-inline">
2790
- <label> <input type="radio" ng-model="command.fromArtifact" ng-value="false" /> via text input </label>
2791
- </div>
2792
- <div class="radio radio-inline" ng-if="command.viewState.pipeline">
2793
- <label>
2794
- <input type="radio" ng-model="command.fromArtifact" ng-value="true" /> via pipeline artifact
2795
- </label>
2796
- </div>
2797
- </div>
2798
- </div>
2799
- <stage-artifact-selector-delegate
2800
- ng-if="command.fromArtifact"
2801
- artifact="command.expectedArtifact"
2802
- excluded-artifact-type-patterns="basicSettingsCtrl.excludedContainerArtifactTypes"
2803
- expected-artifact-id="command.expectedArtifactId"
2804
- field-columns="7"
2805
- label="'Expected Artifact'"
2806
- on-artifact-edited="basicSettingsCtrl.onExpectedArtifactEdited"
2807
- on-expected-artifact-selected="basicSettingsCtrl.onExpectedArtifactSelected"
2808
- pipeline="command.viewState.pipeline"
2809
- stage="command.viewState.stage"
2810
- >
2811
- </stage-artifact-selector-delegate>
2812
- <div class="form-group" ng-if="!command.fromArtifact">
2813
- <div class="col-md-3 sm-label-right">
2814
- Image URL
2815
- <help-field key="appengine.serverGroup.containerImageUrl"></help-field>
2816
- </div>
2817
- <div class="col-md-7">
2818
- <input
2819
- type="text"
2820
- required
2821
- class="form-control input-sm"
2822
- name="containerImageUrl"
2823
- ng-model="command.containerImageUrl"
2824
- />
2825
- </div>
2826
- </div>
2827
- </div>
2828
-
2829
- <div ng-if="command.trigger.type === 'jenkins'" class="form-group">
2830
- <div class="col-md-7 col-md-offset-3">
2831
- <div class="form-inline">
2832
- <small>Match branch from trigger on regex</small>
2833
- <help-field key="appengine.serverGroup.matchBranchOnRegex"></help-field>
2834
- <input
2835
- type="text"
2836
- style="width: 140px"
2837
- class="form-control input-sm pull-right"
2838
- name="matchOnRegex"
2839
- ng-model="command.trigger.matchBranchOnRegex"
2840
- />
2841
- </div>
2842
- </div>
2843
- </div>
2844
-
2845
- <deployment-strategy-selector
2846
- field-columns="7"
2847
- ng-if="!command.viewState.disableStrategySelection"
2848
- command="command"
2849
- ></deployment-strategy-selector>
2850
-
2851
- <div class="form-group" ng-if="!command.viewState.hideClusterNamePreview">
2852
- <div class="col-md-12">
2853
- <div class="well-compact" ng-class="basicSettingsCtrl.showPreviewAsWarning() ? 'alert alert-warning' : 'well'">
2854
- <h5 class="text-center">
2855
- <p>Your server group will be in the cluster:</p>
2856
- <p>
2857
- <strong>
2858
- {{basicSettingsCtrl.getNamePreview()}}
2859
- <span ng-if="basicSettingsCtrl.createsNewCluster()"> (new cluster)</span>
2860
- </strong>
2861
- </p>
2862
- <div
2863
- class="text-left"
2864
- ng-if="!basicSettingsCtrl.createsNewCluster() && command.viewState.mode === 'create' && latestServerGroup"
2865
- >
2866
- <p>There is already a server group in this cluster. Do you want to clone it?</p>
2867
- <p>
2868
- Cloning copies the entire configuration from the selected server group, allowing you to modify whichever
2869
- fields (e.g. image) you need to change in the new server group.
2870
- </p>
2871
- <p>
2872
- To clone a server group, select "Clone" from the "Server Group Actions" menu in the details view of the
2873
- server group.
2874
- </p>
2875
- <p>
2876
- <a href ng-click="basicSettingsCtrl.navigateToLatestServerGroup()">
2877
- Go to details for {{latestServerGroup.name}}
2878
- </a>
2879
- </p>
2880
- </div>
2881
- </h5>
2882
- </div>
2883
- </div>
2884
- </div>
2885
- </ng-form>
2886
- </div>
2887
- `);
2888
- }]);
2889
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
2890
- templateCache.put("appengine/src/serverGroup/configure/wizard/advancedSettings.html", `<div class="form-horizontal container-fluid">
2891
- <div class="form-group">
2892
- <div class="col-md-4 sm-label-right">Promote <help-field key="appengine.serverGroup.promote"></help-field></div>
2893
- <div class="col-md-7">
2894
- <input type="checkbox" ng-model="command.promote" />
2895
- </div>
2896
- </div>
2897
-
2898
- <div class="form-group">
2899
- <div class="col-md-4 sm-label-right">
2900
- Stop Previous Version <help-field key="appengine.serverGroup.stopPreviousVersion"></help-field>
2901
- </div>
2902
- <div class="col-md-7">
2903
- <input type="checkbox" ng-model="command.stopPreviousVersion" />
2904
- </div>
2905
- </div>
2906
- <div class="form-group" ng-if="showPlatformHealthOnlyOverride">
2907
- <div class="col-md-4 sm-label-right">Task Completion</div>
2908
- <div class="col-md-7">
2909
- <platform-health-override command="command" platform-health-type="platformHealth"> </platform-health-override>
2910
- </div>
2911
- </div>
2912
-
2913
- <div class="form-group">
2914
- <div class="col-md-4 sm-label-right">
2915
- Suppress Version String
2916
- <help-field key="appengine.serverGroup.suppress-version-string"></help-field>
2917
- </div>
2918
- <div class="col-md-7">
2919
- <input type="checkbox" ng-model="command.suppressVersionString" />
2920
- </div>
2921
- </div>
2922
- </div>
2923
- `);
2924
- }]);
2925
-
2926
- const ConfigFileArtifactList = (props) => {
2927
- const addConfigArtifact = () => {
2928
- props.updateConfigArtifacts(props.configArtifacts.concat([{ id: "", account: "" }]));
2929
- };
2930
- const deleteConfigArtifact = (index) => {
2931
- const newConfigArtifacts = [...props.configArtifacts];
2932
- newConfigArtifacts.splice(index, 1);
2933
- props.updateConfigArtifacts(newConfigArtifacts);
2934
- };
2935
- const onExpectedArtifactEdited = (artifact, index) => {
2936
- const newConfigArtifacts = [...props.configArtifacts];
2937
- newConfigArtifacts.splice(index, 1, { ...newConfigArtifacts[index], id: null, artifact });
2938
- props.updateConfigArtifacts(newConfigArtifacts);
2939
- };
2940
- const onExpectedArtifactSelected = (expectedArtifact, index) => {
2941
- onChangeExpectedArtifactId(expectedArtifact.id, index);
2942
- };
2943
- const onChangeExpectedArtifactId = (id, index) => {
2944
- const newConfigArtifacts = [...props.configArtifacts];
2945
- newConfigArtifacts.splice(index, 1, { ...newConfigArtifacts[index], id, artifact: null });
2946
- props.updateConfigArtifacts(newConfigArtifacts);
2947
- };
2948
- return /* @__PURE__ */ React.createElement(React.Fragment, null, props.configArtifacts.map((a, i) => {
2949
- return /* @__PURE__ */ React.createElement("div", {
2950
- key: a.id,
2951
- className: classNames("artifact-configuration-section col-md-12", {
2952
- "last-entry": props.configArtifacts.length - 1 === i
2953
- })
2954
- }, /* @__PURE__ */ React.createElement("div", {
2955
- className: "col-md-9"
2956
- }, /* @__PURE__ */ React.createElement(StageArtifactSelector, {
2957
- artifact: a.artifact,
2958
- excludedArtifactTypePatterns: [],
2959
- expectedArtifactId: a.artifact == null ? a.id : null,
2960
- onArtifactEdited: (artifact) => {
2961
- onExpectedArtifactEdited(artifact, i);
2962
- },
2963
- onExpectedArtifactSelected: (expectedArtifact) => {
2964
- onExpectedArtifactSelected(expectedArtifact, i);
2965
- },
2966
- pipeline: props.pipeline,
2967
- stage: props.stage
2968
- })), /* @__PURE__ */ React.createElement("div", {
2969
- className: "col-md-1"
2970
- }, /* @__PURE__ */ React.createElement("button", {
2971
- type: "button",
2972
- className: "btn btn-sm btn-default",
2973
- onClick: () => deleteConfigArtifact(i)
2974
- }, /* @__PURE__ */ React.createElement("span", {
2975
- className: "glyphicon glyphicon-trash"
2976
- }), " Delete")));
2977
- }), /* @__PURE__ */ React.createElement("div", {
2978
- className: "col-md-7 col-md-offset-3"
2979
- }, /* @__PURE__ */ React.createElement("button", {
2980
- className: "btn btn-block btn-add-trigger add-new",
2981
- onClick: () => addConfigArtifact()
2982
- }, /* @__PURE__ */ React.createElement("span", {
2983
- className: "glyphicon glyphicon-plus-sign"
2984
- }), " Add Config Artifact")));
2985
- };
2986
-
2987
- const CONFIG_FILE_ARTIFACT_LIST = "spinnaker.appengine.configFileArtifactList.component";
2988
- module(CONFIG_FILE_ARTIFACT_LIST, []).component("configFileArtifactList", react2angular(withErrorBoundary(ConfigFileArtifactList, "configFileArtifactList"), [
2989
- "configArtifacts",
2990
- "pipeline",
2991
- "stage",
2992
- "updateConfigArtifacts"
2993
- ]));
2994
-
2995
- class AppengineServerGroupWriter {
2996
- startServerGroup(serverGroup, application) {
2997
- const job = this.buildJob(serverGroup, application, "startAppEngineServerGroup");
2998
- const command = {
2999
- job: [job],
3000
- application,
3001
- description: `Start Server Group: ${serverGroup.name}`
3002
- };
3003
- return TaskExecutor.executeTask(command);
3004
- }
3005
- stopServerGroup(serverGroup, application) {
3006
- const job = this.buildJob(serverGroup, application, "stopAppEngineServerGroup");
3007
- const command = {
3008
- job: [job],
3009
- application,
3010
- description: `Stop Server Group: ${serverGroup.name}`
3011
- };
3012
- return TaskExecutor.executeTask(command);
3013
- }
3014
- buildJob(serverGroup, application, type) {
3015
- return {
3016
- type,
3017
- region: serverGroup.region,
3018
- serverGroupName: serverGroup.name,
3019
- credentials: serverGroup.account,
3020
- cloudProvider: "appengine",
3021
- application: application.name
3022
- };
3023
- }
3024
- }
3025
- const APPENGINE_SERVER_GROUP_WRITER = "spinnaker.appengine.serverGroup.write.service";
3026
- module(APPENGINE_SERVER_GROUP_WRITER, []).service("appengineServerGroupWriter", AppengineServerGroupWriter);
3027
-
3028
- const _AppengineServerGroupDetailsController = class {
3029
- constructor($state, $scope, $uibModal, serverGroup, app, serverGroupWriter, appengineServerGroupWriter, appengineServerGroupCommandBuilder) {
3030
- this.$state = $state;
3031
- this.$scope = $scope;
3032
- this.$uibModal = $uibModal;
3033
- this.app = app;
3034
- this.serverGroupWriter = serverGroupWriter;
3035
- this.appengineServerGroupWriter = appengineServerGroupWriter;
3036
- this.appengineServerGroupCommandBuilder = appengineServerGroupCommandBuilder;
3037
- this.state = { loading: true };
3038
- this.app.ready().then(() => this.extractServerGroup(serverGroup)).then(() => {
3039
- if (!this.$scope.$$destroyed) {
3040
- this.app.getDataSource("serverGroups").onRefresh(this.$scope, () => this.extractServerGroup(serverGroup));
3041
- }
3042
- }).catch(() => this.autoClose());
3043
- }
3044
- static buildExpectedAllocationsTable(expectedAllocations) {
3045
- const tableRows = map(expectedAllocations, (allocation, serverGroupName) => {
3046
- return `
3047
- <tr>
3048
- <td>${serverGroupName}</td>
3049
- <td>${allocation * 100}%</td>
3050
- </tr>`;
3051
- }).join("");
3052
- return `
3053
- <table class="table table-condensed">
3054
- <thead>
3055
- <tr>
3056
- <th>Server Group</th>
3057
- <th>Allocation</th>
3058
- </tr>
3059
- </thead>
3060
- <tbody>
3061
- ${tableRows}
3062
- </tbody>
3063
- </table>`;
3064
- }
3065
- canDisableServerGroup() {
3066
- if (this.serverGroup) {
3067
- if (this.serverGroup.disabled) {
3068
- return false;
3069
- }
3070
- const expectedAllocations = this.expectedAllocationsAfterDisableOperation(this.serverGroup, this.app);
3071
- if (expectedAllocations) {
3072
- return Object.keys(expectedAllocations).length > 0;
3073
- } else {
3074
- return false;
3075
- }
3076
- } else {
3077
- return false;
3078
- }
3079
- }
3080
- canDestroyServerGroup() {
3081
- if (this.serverGroup) {
3082
- if (this.serverGroup.disabled) {
3083
- return true;
3084
- }
3085
- const expectedAllocations = this.expectedAllocationsAfterDisableOperation(this.serverGroup, this.app);
3086
- if (expectedAllocations) {
3087
- return Object.keys(expectedAllocations).length > 0;
3088
- } else {
3089
- return false;
3090
- }
3091
- } else {
3092
- return false;
3093
- }
3094
- }
3095
- destroyServerGroup() {
3096
- const stateParams = {
3097
- name: this.serverGroup.name,
3098
- accountId: this.serverGroup.account,
3099
- region: this.serverGroup.region
3100
- };
3101
- const taskMonitor = {
3102
- application: this.app,
3103
- title: "Destroying " + this.serverGroup.name,
3104
- onTaskComplete: () => {
3105
- if (this.$state.includes("**.serverGroup", stateParams)) {
3106
- this.$state.go("^");
3107
- }
3108
- }
3109
- };
3110
- const submitMethod = (params) => this.serverGroupWriter.destroyServerGroup(this.serverGroup, this.app, params);
3111
- const confirmationModalParams = {
3112
- header: "Really destroy " + this.serverGroup.name + "?",
3113
- buttonText: "Destroy " + this.serverGroup.name,
3114
- account: this.serverGroup.account,
3115
- taskMonitorConfig: taskMonitor,
3116
- submitMethod,
3117
- askForReason: true,
3118
- platformHealthOnlyShowOverride: this.app.attributes.platformHealthOnlyShowOverride,
3119
- platformHealthType: AppengineHealth.PLATFORM,
3120
- body: this.getBodyTemplate(this.serverGroup, this.app),
3121
- interestingHealthProviderNames: []
3122
- };
3123
- if (this.app.attributes.platformHealthOnlyShowOverride && this.app.attributes.platformHealthOnly) {
3124
- confirmationModalParams.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
3125
- }
3126
- ConfirmationModalService.confirm(confirmationModalParams);
3127
- }
3128
- enableServerGroup() {
3129
- const taskMonitor = {
3130
- application: this.app,
3131
- title: "Enabling " + this.serverGroup.name
3132
- };
3133
- const submitMethod = (params) => this.serverGroupWriter.enableServerGroup(this.serverGroup, this.app, { ...params });
3134
- const modalBody = `<div class="well well-sm">
3135
- <p>
3136
- Enabling <b>${this.serverGroup.name}</b> will set its traffic allocation for
3137
- <b>${this.serverGroup.loadBalancers[0]}</b> to 100%.
3138
- </p>
3139
- <p>
3140
- If you would like more fine-grained control over your server groups' allocations,
3141
- edit <b>${this.serverGroup.loadBalancers[0]}</b> under the <b>Load Balancers</b> tab.
3142
- </p>
3143
- </div>
3144
- `;
3145
- const confirmationModalParams = {
3146
- header: "Really enable " + this.serverGroup.name + "?",
3147
- buttonText: "Enable " + this.serverGroup.name,
3148
- body: modalBody,
3149
- account: this.serverGroup.account,
3150
- taskMonitorConfig: taskMonitor,
3151
- platformHealthOnlyShowOverride: this.app.attributes.platformHealthOnlyShowOverride,
3152
- platformHealthType: AppengineHealth.PLATFORM,
3153
- submitMethod,
3154
- askForReason: true,
3155
- interestingHealthProviderNames: []
3156
- };
3157
- if (this.app.attributes.platformHealthOnlyShowOverride && this.app.attributes.platformHealthOnly) {
3158
- confirmationModalParams.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
3159
- }
3160
- ConfirmationModalService.confirm(confirmationModalParams);
3161
- }
3162
- disableServerGroup() {
3163
- const taskMonitor = {
3164
- application: this.app,
3165
- title: "Disabling " + this.serverGroup.name
3166
- };
3167
- const submitMethod = (params) => this.serverGroupWriter.disableServerGroup(this.serverGroup, this.app.name, params);
3168
- const expectedAllocations = this.expectedAllocationsAfterDisableOperation(this.serverGroup, this.app);
3169
- const modalBody = `<div class="well well-sm">
3170
- <p>
3171
- For App Engine, a disable operation sets this server group's allocation
3172
- to 0% and sets the other enabled server groups' allocations to their relative proportions
3173
- before the disable operation. The approximate allocations that will result from this operation are shown below.
3174
- </p>
3175
- <p>
3176
- If you would like more fine-grained control over your server groups' allocations,
3177
- edit <b>${this.serverGroup.loadBalancers[0]}</b> under the <b>Load Balancers</b> tab.
3178
- </p>
3179
- <div class="row">
3180
- <div class="col-md-12">
3181
- ${_AppengineServerGroupDetailsController.buildExpectedAllocationsTable(expectedAllocations)}
3182
- </div>
3183
- </div>
3184
- </div>
3185
- `;
3186
- const confirmationModalParams = {
3187
- header: "Really disable " + this.serverGroup.name + "?",
3188
- buttonText: "Disable " + this.serverGroup.name,
3189
- body: modalBody,
3190
- account: this.serverGroup.account,
3191
- taskMonitorConfig: taskMonitor,
3192
- platformHealthOnlyShowOverride: this.app.attributes.platformHealthOnlyShowOverride,
3193
- platformHealthType: AppengineHealth.PLATFORM,
3194
- submitMethod,
3195
- askForReason: true,
3196
- interestingHealthProviderNames: []
3197
- };
3198
- if (this.app.attributes.platformHealthOnlyShowOverride && this.app.attributes.platformHealthOnly) {
3199
- confirmationModalParams.interestingHealthProviderNames = [AppengineHealth.PLATFORM];
3200
- }
3201
- ConfirmationModalService.confirm(confirmationModalParams);
3202
- }
3203
- stopServerGroup() {
3204
- const taskMonitor = {
3205
- application: this.app,
3206
- title: "Stopping " + this.serverGroup.name
3207
- };
3208
- const submitMethod = () => this.appengineServerGroupWriter.stopServerGroup(this.serverGroup, this.app);
3209
- let modalBody;
3210
- if (!this.serverGroup.disabled) {
3211
- modalBody = `<div class="alert alert-danger">
3212
- <p>Stopping this server group will scale it down to zero instances.</p>
3213
- <p>
3214
- This server group is currently serving traffic from <b>${this.serverGroup.loadBalancers[0]}</b>.
3215
- Traffic directed to this server group after it has been stopped will not be handled.
3216
- </p>
3217
- </div>`;
3218
- }
3219
- const confirmationModalParams = {
3220
- header: "Really stop " + this.serverGroup.name + "?",
3221
- buttonText: "Stop " + this.serverGroup.name,
3222
- account: this.serverGroup.account,
3223
- body: modalBody,
3224
- platformHealthOnlyShowOverride: this.app.attributes.platformHealthOnlyShowOverride,
3225
- platformHealthType: AppengineHealth.PLATFORM,
3226
- taskMonitorConfig: taskMonitor,
3227
- submitMethod,
3228
- askForReason: true
3229
- };
3230
- ConfirmationModalService.confirm(confirmationModalParams);
3231
- }
3232
- startServerGroup() {
3233
- const taskMonitor = {
3234
- application: this.app,
3235
- title: "Starting " + this.serverGroup.name
3236
- };
3237
- const submitMethod = () => this.appengineServerGroupWriter.startServerGroup(this.serverGroup, this.app);
3238
- const confirmationModalParams = {
3239
- header: "Really start " + this.serverGroup.name + "?",
3240
- buttonText: "Start " + this.serverGroup.name,
3241
- account: this.serverGroup.account,
3242
- platformHealthOnlyShowOverride: this.app.attributes.platformHealthOnlyShowOverride,
3243
- platformHealthType: AppengineHealth.PLATFORM,
3244
- taskMonitorConfig: taskMonitor,
3245
- submitMethod,
3246
- askForReason: true
3247
- };
3248
- ConfirmationModalService.confirm(confirmationModalParams);
3249
- }
3250
- cloneServerGroup() {
3251
- this.$uibModal.open({
3252
- templateUrl: "appengine/src/serverGroup/configure/wizard/serverGroupWizard.html",
3253
- controller: "appengineCloneServerGroupCtrl as ctrl",
3254
- size: "lg",
3255
- resolve: {
3256
- title: () => "Clone " + this.serverGroup.name,
3257
- application: () => this.app,
3258
- serverGroup: () => this.serverGroup,
3259
- serverGroupCommand: () => this.appengineServerGroupCommandBuilder.buildServerGroupCommandFromExisting(this.app, this.serverGroup)
3260
- }
3261
- });
3262
- }
3263
- canStartServerGroup() {
3264
- if (this.canStartOrStopServerGroup()) {
3265
- return this.serverGroup.servingStatus === "STOPPED";
3266
- } else {
3267
- return false;
3268
- }
3269
- }
3270
- canStopServerGroup() {
3271
- if (this.canStartOrStopServerGroup()) {
3272
- return this.serverGroup.servingStatus === "SERVING";
3273
- } else {
3274
- return false;
3275
- }
3276
- }
3277
- canStartOrStopServerGroup() {
3278
- var _a;
3279
- const isFlex = this.serverGroup.env === "FLEXIBLE";
3280
- return isFlex || ["MANUAL", "BASIC"].includes((_a = this.serverGroup.scalingPolicy) == null ? void 0 : _a.type);
3281
- }
3282
- getBodyTemplate(serverGroup, app) {
3283
- let template = "";
3284
- const params = {};
3285
- ServerGroupWarningMessageService.addDestroyWarningMessage(app, serverGroup, params);
3286
- if (params.body) {
3287
- template += params.body;
3288
- }
3289
- if (!serverGroup.disabled) {
3290
- const expectedAllocations = this.expectedAllocationsAfterDisableOperation(serverGroup, app);
3291
- template += `
3292
- <div class="well well-sm">
3293
- <p>
3294
- A destroy operation will first disable this server group.
3295
- </p>
3296
- <p>
3297
- For App Engine, a disable operation sets this server group's allocation
3298
- to 0% and sets the other enabled server groups' allocations to their relative proportions
3299
- before the disable operation. The approximate allocations that will result from this operation are shown below.
3300
- </p>
3301
- <p>
3302
- If you would like more fine-grained control over your server groups' allocations,
3303
- edit <b>${serverGroup.loadBalancers[0]}</b> under the <b>Load Balancers</b> tab.
3304
- </p>
3305
- <div class="row">
3306
- <div class="col-md-12">
3307
- ${_AppengineServerGroupDetailsController.buildExpectedAllocationsTable(expectedAllocations)}
3308
- </div>
3309
- </div>
3310
- </div>
3311
- `;
3312
- }
3313
- return template;
3314
- }
3315
- expectedAllocationsAfterDisableOperation(serverGroup, app) {
3316
- const loadBalancer = app.getDataSource("loadBalancers").data.find((toCheck) => {
3317
- var _a, _b;
3318
- const allocations = (_b = (_a = toCheck.split) == null ? void 0 : _a.allocations) != null ? _b : {};
3319
- const enabledServerGroups = Object.keys(allocations);
3320
- return enabledServerGroups.includes(serverGroup.name);
3321
- });
3322
- if (loadBalancer) {
3323
- let allocations = cloneDeep(loadBalancer.split.allocations);
3324
- delete allocations[serverGroup.name];
3325
- const denominator = reduce(allocations, (partialSum, allocation) => partialSum + allocation, 0);
3326
- const precision = loadBalancer.split.shardBy === "COOKIE" ? 1e3 : 100;
3327
- allocations = mapValues(allocations, (allocation) => Math.round(allocation / denominator * precision) / precision);
3328
- return allocations;
3329
- } else {
3330
- return null;
3331
- }
3332
- }
3333
- autoClose() {
3334
- if (this.$scope.$$destroyed) {
3335
- return;
3336
- } else {
3337
- this.$state.params.allowModalToStayOpen = true;
3338
- this.$state.go("^", null, { location: "replace" });
3339
- }
3340
- }
3341
- extractServerGroup(fromParams) {
3342
- return ServerGroupReader.getServerGroup(this.app.name, fromParams.accountId, fromParams.region, fromParams.name).then((serverGroupDetails) => {
3343
- let fromApp = this.app.getDataSource("serverGroups").data.find((toCheck) => {
3344
- return toCheck.name === fromParams.name && toCheck.account === fromParams.accountId && toCheck.region === fromParams.region;
3345
- });
3346
- if (!fromApp) {
3347
- this.app.getDataSource("loadBalancers").data.some((loadBalancer) => {
3348
- if (loadBalancer.account === fromParams.accountId) {
3349
- return loadBalancer.serverGroups.some((toCheck) => {
3350
- let result = false;
3351
- if (toCheck.name === fromParams.name) {
3352
- fromApp = toCheck;
3353
- result = true;
3354
- }
3355
- return result;
3356
- });
3357
- } else {
3358
- return false;
3359
- }
3360
- });
3361
- }
3362
- this.serverGroup = { ...serverGroupDetails, ...fromApp };
3363
- this.state.loading = false;
3364
- });
3365
- }
3366
- };
3367
- let AppengineServerGroupDetailsController = _AppengineServerGroupDetailsController;
3368
- AppengineServerGroupDetailsController.$inject = [
3369
- "$state",
3370
- "$scope",
3371
- "$uibModal",
3372
- "serverGroup",
3373
- "app",
3374
- "serverGroupWriter",
3375
- "appengineServerGroupWriter",
3376
- "appengineServerGroupCommandBuilder"
3377
- ];
3378
- const APPENGINE_SERVER_GROUP_DETAILS_CTRL = "spinnaker.appengine.serverGroup.details.controller";
3379
- module(APPENGINE_SERVER_GROUP_DETAILS_CTRL, [APPENGINE_SERVER_GROUP_WRITER, SERVER_GROUP_WRITER]).controller("appengineServerGroupDetailsCtrl", AppengineServerGroupDetailsController);
3380
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
3381
- templateCache.put("appengine/src/serverGroup/configure/wizard/serverGroupWizard.html", `<form name="form" class="form-horizontal appengine-server-group-wizard" novalidate>
3382
- <div ng-if="ctrl.state.loading" style="height: 200px" class="horizontal center middle">
3383
- <loading-spinner size="'small'"></loading-spinner>
3384
- </div>
3385
- <div>
3386
- <v2-modal-wizard
3387
- ng-if="!ctrl.state.loading"
3388
- heading="Create Server Group"
3389
- task-monitor="ctrl.taskMonitor"
3390
- dismiss="$dismiss()"
3391
- >
3392
- <v2-wizard-page key="basic-settings" label="Basic Settings" mark-complete-on-view="false">
3393
- <ng-include src="ctrl.pages.basicSettings"></ng-include>
3394
- </v2-wizard-page>
3395
- <v2-wizard-page key="load-balancer" label="Config Files" mark-complete-on-view="false">
3396
- <appengine-config-file-configurer command="command"></appengine-config-file-configurer>
3397
- </v2-wizard-page>
3398
- <v2-wizard-page key="load-balancer" label="Load Balancer" done="true">
3399
- <appengine-load-balancer-message show-create-message="false"></appengine-load-balancer-message>
3400
- </v2-wizard-page>
3401
- <v2-wizard-page key="advanced-settings" label="Advanced Settings" done="true">
3402
- <ng-include src="ctrl.pages.advancedSettings"></ng-include>
3403
- </v2-wizard-page>
3404
- </v2-modal-wizard>
3405
- <div class="modal-footer" ng-if="!state.loading">
3406
- <button ng-disabled="ctrl.taskMonitor.submitting" class="btn btn-default btn-cancel" ng-click="ctrl.cancel()">
3407
- Cancel
3408
- </button>
3409
- <submit-button
3410
- ng-if="form.$valid"
3411
- is-disabled="form.$invalid || ctrl.taskMonitor.submitting"
3412
- label="command.viewState.submitButtonLabel"
3413
- submitting="ctrl.taskMonitor.submitting"
3414
- on-click="ctrl.submit()"
3415
- is-new="true"
3416
- ></submit-button>
3417
- </div>
3418
- </div>
3419
- </form>
3420
- `);
3421
- }]);
3422
-
3423
- class AppengineDeployDescription {
3424
- constructor(command) {
3425
- this.cloudProvider = "appengine";
3426
- this.provider = "appengine";
3427
- this.credentials = command.credentials;
3428
- this.account = command.credentials;
3429
- this.application = command.application;
3430
- this.stack = command.stack;
3431
- this.freeFormDetails = command.freeFormDetails;
3432
- this.repositoryUrl = command.repositoryUrl;
3433
- this.branch = command.branch;
3434
- this.configFilepaths = command.configFilepaths;
3435
- this.promote = command.promote;
3436
- this.stopPreviousVersion = command.stopPreviousVersion;
3437
- this.type = command.type;
3438
- this.region = command.region;
3439
- this.strategy = command.strategy;
3440
- this.strategyApplication = command.strategyApplication;
3441
- this.strategyPipeline = command.strategyPipeline;
3442
- this.fromTrigger = command.fromTrigger;
3443
- this.trigger = command.trigger;
3444
- this.gitCredentialType = command.gitCredentialType;
3445
- this.configFiles = command.configFiles;
3446
- this.configArtifacts = command.configArtifacts.filter((a) => !!a.id || !!a.artifact);
3447
- this.applicationDirectoryRoot = command.applicationDirectoryRoot;
3448
- this.interestingHealthProviderNames = command.interestingHealthProviderNames || [];
3449
- this.expectedArtifactId = command.expectedArtifactId;
3450
- this.expectedArtifact = command.expectedArtifact;
3451
- this.fromArtifact = command.fromArtifact;
3452
- this.sourceType = command.sourceType;
3453
- this.storageAccountName = command.storageAccountName;
3454
- this.containerImageUrl = command.containerImageUrl;
3455
- this.suppressVersionString = command.suppressVersionString;
3456
- }
3457
- }
3458
- class AppengineServerGroupTransformer {
3459
- constructor($q) {
3460
- this.$q = $q;
3461
- }
3462
- normalizeServerGroup(serverGroup) {
3463
- return this.$q.resolve(serverGroup);
3464
- }
3465
- convertServerGroupCommandToDeployConfiguration(command) {
3466
- return new AppengineDeployDescription(command);
3467
- }
3468
- }
3469
- AppengineServerGroupTransformer.$inject = ["$q"];
3470
- const APPENGINE_SERVER_GROUP_TRANSFORMER = "spinnaker.appengine.serverGroup.transformer.service";
3471
- module(APPENGINE_SERVER_GROUP_TRANSFORMER, []).service("appengineServerGroupTransformer", AppengineServerGroupTransformer);
3472
-
3473
- class AppengineApplicationNameValidator {
3474
- validate(name = "") {
3475
- const warnings = [];
3476
- const errors = [];
3477
- if (name.length) {
3478
- this.validateSpecialCharacters(name, errors);
3479
- this.validateLength(name, warnings, errors);
3480
- }
3481
- return { warnings, errors };
3482
- }
3483
- validateSpecialCharacters(name, errors) {
3484
- const pattern = /^[a-z0-9]*$/g;
3485
- if (!pattern.test(name)) {
3486
- errors.push("Only numbers and lowercase letters are allowed.");
3487
- }
3488
- }
3489
- validateLength(name, warnings, errors) {
3490
- if (name.length > 58) {
3491
- errors.push("The maximum length for an App Engine application name is 63 characters.");
3492
- return;
3493
- }
3494
- if (name.length > 48) {
3495
- if (name.length >= 56) {
3496
- warnings.push("You will not be able to include a stack or detail field for clusters.");
3497
- } else {
3498
- const remaining = 56 - name.length;
3499
- warnings.push(`If you plan to include a stack or detail field for clusters, you will only have
3500
- ${remaining} character${remaining > 1 ? "s" : ""} to do so.`);
3501
- }
3502
- }
3503
- }
3504
- }
3505
- ApplicationNameValidator.registerValidator("appengine", new AppengineApplicationNameValidator());
3506
-
3507
- var css_248z = ".cloud-provider-logo .icon-appengine {\n -webkit-mask-image: url(\"data:image/svg+xml,%3C%3Fxml version%3D%221.0%22 encoding%3D%22utf-8%22%3F%3E%3C!-- Generator%3A Adobe Illustrator 18.1.1%2C SVG Export Plug-In . SVG Version%3A 6.00 Build 0) --%3E%3Csvg version%3D%221.1%22 xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22 x%3D%220px%22 y%3D%220px%22 width%3D%22128px%22%09 height%3D%22128px%22 viewBox%3D%2215 15 90 90%22 xml%3Aspace%3D%22preserve%22%3E%3Cfilter id%3D%22invert%22%3E %3CfeColorMatrix in%3D%22SourceGraphic%22 type%3D%22matrix%22 values%3D%22-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0%22%2F%3E%3C%2Ffilter%3E%3Cg id%3D%22art%22 filter%3D%22url(%23invert)%22%3E%09%3Cg%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M63.9988%2C40.9577c-12.7278%2C0-23.0452%2C10.3167-23.0452%2C23.0464%09%09%09c0%2C12.726%2C10.3174%2C23.0463%2C23.0452%2C23.0463c12.7273%2C0%2C23.0446-10.3203%2C23.0446-23.0463%09%09%09C87.0434%2C51.2744%2C76.7261%2C40.9577%2C63.9988%2C40.9577 M63.9988%2C81.4887c-9.6584%2C0-17.4876-7.8286-17.4876-17.4846%09%09%09c0-9.6584%2C7.8291-17.4877%2C17.4876-17.4877c9.6578%2C0%2C17.4858%2C7.8292%2C17.4858%2C17.4877%09%09%09C81.4846%2C73.6601%2C73.6566%2C81.4887%2C63.9988%2C81.4887%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M100.1431%2C61.274l-9.6079-3.0195c0.3997%2C1.8539%2C0.615%2C3.7759%2C0.615%2C5.7478%09%09%09c0%2C1.3791-0.1047%2C2.7337-0.3021%2C4.0569h9.295c0.8587-0.2515%2C1.4327-0.7846%2C1.4327-1.5765v-3.6345%09%09%09C101.5759%2C62.0575%2C101.0018%2C61.5094%2C100.1431%2C61.274%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M63.9923%2C36.8497c1.9303%2C0%2C3.8119%2C0.2046%2C5.6285%2C0.5884l-3.4443-9.5729%09%09%09c-0.2515-0.8606-0.7852-1.434-1.5765-1.434h-1.4392c-0.7913%2C0-1.3407%2C0.5734-1.5761%2C1.434l-2.9924%2C9.5236%09%09%09C60.3372%2C37.0368%2C62.1425%2C36.8497%2C63.9923%2C36.8497%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M36.8468%2C64.0023c0-1.9719%2C0.2154-3.8932%2C0.6145-5.7478l-9.6073%2C3.0195%09%09%09c-0.8587%2C0.2354-1.4322%2C0.7835-1.4322%2C1.5754v3.6344c0%2C0.7907%2C0.5735%2C1.3238%2C1.4322%2C1.5753h9.2955%09%09%09C36.9515%2C66.7366%2C36.8468%2C65.3814%2C36.8468%2C64.0023%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M72.2276%2C56.4819l-2.1802%2C2.1794v-0.0048c-1.5651-1.5639-3.6549-2.4274-5.8687-2.4274%09%09%09c-2.2192%2C0-4.303%2C0.8659-5.8704%2C2.4322c-3.2362%2C3.2355-3.2362%2C8.5037%2C0%2C11.7399l-2.1802%2C2.1783%09%09%09c2.0627%2C2.0609%2C4.9096%2C3.3383%2C8.0483%2C3.3383c6.2803%2C0%2C11.3896-5.1081%2C11.3896-11.3895%09%09%09C75.566%2C61.3883%2C74.289%2C58.5421%2C72.2276%2C56.4819 M67.7284%2C67.7342c-1.0278%2C1.0302-2.3799%2C1.5459-3.7284%2C1.5459%09%09%09c-1.3526%2C0-2.7012-0.5156-3.7313-1.5459c-2.0616-2.0615-2.0616-5.4018%2C0-7.4639c1.0301-1.0314%2C2.3787-1.544%2C3.7313-1.544%09%09%09c1.3485%2C0%2C2.7006%2C0.5127%2C3.7284%2C1.544C69.79%2C62.3312%2C69.79%2C65.6727%2C67.7284%2C67.7342%22%2F%3E%09%3C%2Fg%3E%3C%2Fg%3E%3Cg id%3D%22Guides%22 style%3D%22display%3Anone%3B%22%3E%3C%2Fg%3E%3C%2Fsvg%3E\");\n mask-image: url(\"data:image/svg+xml,%3C%3Fxml version%3D%221.0%22 encoding%3D%22utf-8%22%3F%3E%3C!-- Generator%3A Adobe Illustrator 18.1.1%2C SVG Export Plug-In . SVG Version%3A 6.00 Build 0) --%3E%3Csvg version%3D%221.1%22 xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22 x%3D%220px%22 y%3D%220px%22 width%3D%22128px%22%09 height%3D%22128px%22 viewBox%3D%2215 15 90 90%22 xml%3Aspace%3D%22preserve%22%3E%3Cfilter id%3D%22invert%22%3E %3CfeColorMatrix in%3D%22SourceGraphic%22 type%3D%22matrix%22 values%3D%22-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0%22%2F%3E%3C%2Ffilter%3E%3Cg id%3D%22art%22 filter%3D%22url(%23invert)%22%3E%09%3Cg%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M63.9988%2C40.9577c-12.7278%2C0-23.0452%2C10.3167-23.0452%2C23.0464%09%09%09c0%2C12.726%2C10.3174%2C23.0463%2C23.0452%2C23.0463c12.7273%2C0%2C23.0446-10.3203%2C23.0446-23.0463%09%09%09C87.0434%2C51.2744%2C76.7261%2C40.9577%2C63.9988%2C40.9577 M63.9988%2C81.4887c-9.6584%2C0-17.4876-7.8286-17.4876-17.4846%09%09%09c0-9.6584%2C7.8291-17.4877%2C17.4876-17.4877c9.6578%2C0%2C17.4858%2C7.8292%2C17.4858%2C17.4877%09%09%09C81.4846%2C73.6601%2C73.6566%2C81.4887%2C63.9988%2C81.4887%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M100.1431%2C61.274l-9.6079-3.0195c0.3997%2C1.8539%2C0.615%2C3.7759%2C0.615%2C5.7478%09%09%09c0%2C1.3791-0.1047%2C2.7337-0.3021%2C4.0569h9.295c0.8587-0.2515%2C1.4327-0.7846%2C1.4327-1.5765v-3.6345%09%09%09C101.5759%2C62.0575%2C101.0018%2C61.5094%2C100.1431%2C61.274%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M63.9923%2C36.8497c1.9303%2C0%2C3.8119%2C0.2046%2C5.6285%2C0.5884l-3.4443-9.5729%09%09%09c-0.2515-0.8606-0.7852-1.434-1.5765-1.434h-1.4392c-0.7913%2C0-1.3407%2C0.5734-1.5761%2C1.434l-2.9924%2C9.5236%09%09%09C60.3372%2C37.0368%2C62.1425%2C36.8497%2C63.9923%2C36.8497%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M36.8468%2C64.0023c0-1.9719%2C0.2154-3.8932%2C0.6145-5.7478l-9.6073%2C3.0195%09%09%09c-0.8587%2C0.2354-1.4322%2C0.7835-1.4322%2C1.5754v3.6344c0%2C0.7907%2C0.5735%2C1.3238%2C1.4322%2C1.5753h9.2955%09%09%09C36.9515%2C66.7366%2C36.8468%2C65.3814%2C36.8468%2C64.0023%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M72.2276%2C56.4819l-2.1802%2C2.1794v-0.0048c-1.5651-1.5639-3.6549-2.4274-5.8687-2.4274%09%09%09c-2.2192%2C0-4.303%2C0.8659-5.8704%2C2.4322c-3.2362%2C3.2355-3.2362%2C8.5037%2C0%2C11.7399l-2.1802%2C2.1783%09%09%09c2.0627%2C2.0609%2C4.9096%2C3.3383%2C8.0483%2C3.3383c6.2803%2C0%2C11.3896-5.1081%2C11.3896-11.3895%09%09%09C75.566%2C61.3883%2C74.289%2C58.5421%2C72.2276%2C56.4819 M67.7284%2C67.7342c-1.0278%2C1.0302-2.3799%2C1.5459-3.7284%2C1.5459%09%09%09c-1.3526%2C0-2.7012-0.5156-3.7313-1.5459c-2.0616-2.0615-2.0616-5.4018%2C0-7.4639c1.0301-1.0314%2C2.3787-1.544%2C3.7313-1.544%09%09%09c1.3485%2C0%2C2.7006%2C0.5127%2C3.7284%2C1.544C69.79%2C62.3312%2C69.79%2C65.6727%2C67.7284%2C67.7342%22%2F%3E%09%3C%2Fg%3E%3C%2Fg%3E%3Cg id%3D%22Guides%22 style%3D%22display%3Anone%3B%22%3E%3C%2Fg%3E%3C%2Fsvg%3E\");\n background-color: #4285f4;\n}\n";
3508
- styleInject(css_248z);
3509
-
3510
- const APPENGINE_MODULE = "spinnaker.appengine";
3511
- module(APPENGINE_MODULE, [
3512
- APPENGINE_CLONE_SERVER_GROUP_CTRL,
3513
- APPENGINE_COMPONENT_URL_DETAILS,
3514
- APPENGINE_CONDITIONAL_DESCRIPTION_LIST_ITEM,
3515
- APPENGINE_INSTANCE_DETAILS_CTRL,
3516
- APPENGINE_LOAD_BALANCER_CREATE_MESSAGE,
3517
- APPENGINE_LOAD_BALANCER_MODULE,
3518
- APPENGINE_PIPELINE_MODULE,
3519
- APPENGINE_SERVER_GROUP_BASIC_SETTINGS_CTRL,
3520
- APPENGINE_SERVER_GROUP_COMMAND_BUILDER,
3521
- APPENGINE_SERVER_GROUP_DETAILS_CTRL,
3522
- APPENGINE_SERVER_GROUP_TRANSFORMER,
3523
- APPENGINE_SERVER_GROUP_WRITER,
3524
- CONFIG_FILE_ARTIFACT_LIST
3525
- ]).config(() => {
3526
- CloudProviderRegistry.registerProvider("appengine", {
3527
- name: "App Engine",
3528
- instance: {
3529
- detailsTemplateUrl: "appengine/src/instance/details/details.html",
3530
- detailsController: "appengineInstanceDetailsCtrl"
3531
- },
3532
- serverGroup: {
3533
- transformer: "appengineServerGroupTransformer",
3534
- detailsController: "appengineServerGroupDetailsCtrl",
3535
- detailsTemplateUrl: "appengine/src/serverGroup/details/details.html",
3536
- commandBuilder: "appengineServerGroupCommandBuilder",
3537
- cloneServerGroupController: "appengineCloneServerGroupCtrl",
3538
- cloneServerGroupTemplateUrl: "appengine/src/serverGroup/configure/wizard/serverGroupWizard.html",
3539
- skipUpstreamStageCheck: true
3540
- },
3541
- loadBalancer: {
3542
- transformer: "appengineLoadBalancerTransformer",
3543
- createLoadBalancerTemplateUrl: "appengine/src/loadBalancer/configure/wizard/wizard.html",
3544
- createLoadBalancerController: "appengineLoadBalancerWizardCtrl",
3545
- detailsTemplateUrl: "appengine/src/loadBalancer/details/details.html",
3546
- detailsController: "appengineLoadBalancerDetailsCtrl"
3547
- },
3548
- logo: {
3549
- path: logo
3550
- }
3551
- });
3552
- });
3553
- DeploymentStrategyRegistry.registerProvider("appengine", ["custom"]);
3554
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
3555
- templateCache.put("appengine/src/instance/details/details.html", `<div class="details-panel">
3556
- <div class="header">
3557
- <instance-details-header
3558
- health-state="ctrl.instance.healthState"
3559
- instance-id="ctrl.instance ? ctrl.instance.name : ctrl.instanceIdNotFound"
3560
- loading="ctrl.state.loading"
3561
- standalone="false"
3562
- ></instance-details-header>
3563
- <div ng-if="!ctrl.state.loading">
3564
- <div class="actions">
3565
- <div class="dropdown" uib-dropdown dropdown-append-to-body>
3566
- <button type="button" class="btn btn-sm btn-primary dropdown-toggle" uib-dropdown-toggle>
3567
- Instance Actions <span class="caret"></span>
3568
- </button>
3569
- <ul class="dropdown-menu" uib-dropdown-menu role="menu">
3570
- <li><a href ng-click="ctrl.terminateInstance()">Terminate</a></li>
3571
- </ul>
3572
- </div>
3573
- </div>
3574
- </div>
3575
- </div>
3576
- <div class="content" ng-if="!ctrl.state.loading && ctrl.instance">
3577
- <collapsible-section heading="Instance Information" expanded="true">
3578
- <dl class="dl-horizontal dl-narrow">
3579
- <dt>Launched</dt>
3580
- <dd ng-if="ctrl.instance.launchTime">{{ctrl.instance.launchTime | timestamp}}</dd>
3581
- <dt>In</dt>
3582
- <dd><account-tag account="ctrl.instance.account" pad="right"></account-tag>{{}}</dd>
3583
- <dt ng-if="ctrl.instance.serverGroup">Server Group</dt>
3584
- <dd ng-if="ctrl.instance.serverGroup">
3585
- <a
3586
- ui-sref="^.serverGroup({region: ctrl.instance.region,
3587
- accountId: ctrl.instance.account,
3588
- serverGroup: ctrl.instance.serverGroup,
3589
- provider: 'appengine'})"
3590
- >{{ctrl.instance.serverGroup}}</a
3591
- >
3592
- </dd>
3593
- <dt>Region</dt>
3594
- <dd>{{ctrl.instance.region}}</dd>
3595
- <appengine-conditional-dt-dd
3596
- component="ctrl.instance"
3597
- key="vmZoneName"
3598
- label="Zone"
3599
- ></appengine-conditional-dt-dd>
3600
- </dl>
3601
- </collapsible-section>
3602
- <collapsible-section heading="Status" expanded="true">
3603
- <dl>
3604
- <dt>Load Balancer</dt>
3605
- <dd>
3606
- <span
3607
- class="pull-left"
3608
- uib-tooltip="{{ctrl.instance.healthState === 'Up' ? ctrl.upToolTip : ctrl.outOfServiceToolTip}}"
3609
- tooltip-placement="right"
3610
- >
3611
- <span class="glyphicon glyphicon-{{ctrl.instance.healthState}}-triangle"></span>
3612
- {{ctrl.instance.loadBalancers[0]}}
3613
- </span>
3614
- </dd>
3615
- </dl>
3616
- </collapsible-section>
3617
- <collapsible-section heading="Metrics" expanded="true">
3618
- <dl>
3619
- <appengine-conditional-dt-dd component="ctrl.instance" key="instanceStatus" label="Availability">
3620
- <key-label><help-field key="appengine.instance.availability"></help-field></key-label>
3621
- </appengine-conditional-dt-dd>
3622
- <appengine-conditional-dt-dd component="ctrl.instance" key="averageLatency">
3623
- <key-label><help-field key="appengine.instance.averageLatency"></help-field></key-label>
3624
- <value-label>ms</value-label>
3625
- </appengine-conditional-dt-dd>
3626
- <appengine-conditional-dt-dd component="ctrl.instance" key="errors">
3627
- <key-label><help-field key="appengine.instance.errors"></help-field></key-label>
3628
- </appengine-conditional-dt-dd>
3629
- <appengine-conditional-dt-dd component="ctrl.instance" key="qps" label="QPS">
3630
- <key-label><help-field key="appengine.instance.qps"></help-field></key-label>
3631
- </appengine-conditional-dt-dd>
3632
- <appengine-conditional-dt-dd component="ctrl.instance" key="requests">
3633
- <key-label><help-field key="appengine.instance.requests"></help-field></key-label>
3634
- </appengine-conditional-dt-dd>
3635
- <appengine-conditional-dt-dd component="ctrl.instance" key="vmStatus" label="VM Status">
3636
- <key-label><help-field key="appengine.instance.vmStatus"></help-field></key-label>
3637
- </appengine-conditional-dt-dd>
3638
- <appengine-conditional-dt-dd
3639
- component="ctrl.instance"
3640
- key="vmDebugEnabled"
3641
- label="Debug Enabled"
3642
- ></appengine-conditional-dt-dd>
3643
- </dl>
3644
- </collapsible-section>
3645
- </div>
3646
- <div class="content" ng-if="!ctrl.state.loading && !ctrl.instance">
3647
- <div class="content-section">
3648
- <div class="content-body text-center">
3649
- <h3>Instance not found.</h3>
3650
- </div>
3651
- </div>
3652
- </div>
3653
- </div>
3654
- `);
3655
- }]);
3656
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
3657
- templateCache.put("appengine/src/serverGroup/details/details.html", `<div class="details-panel" ng-class="{ disabled: ctrl.serverGroup.isDisabled || ctrl.serverGroup.disabled}">
3658
- <div class="header" ng-if="ctrl.state.loading">
3659
- <div class="close-button">
3660
- <a class="btn btn-link" ui-sref="^">
3661
- <span class="glyphicon glyphicon-remove"></span>
3662
- </a>
3663
- </div>
3664
- <div class="horizontal center middle">
3665
- <loading-spinner size="'small'"></loading-spinner>
3666
- </div>
3667
- </div>
3668
-
3669
- <div class="header" ng-if="!ctrl.state.loading">
3670
- <div class="close-button">
3671
- <a class="btn btn-link" ui-sref="^">
3672
- <span class="glyphicon glyphicon-remove"></span>
3673
- </a>
3674
- </div>
3675
- <div class="header-text horizontal middle">
3676
- <cloud-provider-logo provider="ctrl.serverGroup.type" height="'36px'" width="'36px'"></cloud-provider-logo>
3677
- <h3 class="horizontal middle space-between flex-1" select-on-dbl-click>{{ctrl.serverGroup.name}}</h3>
3678
- </div>
3679
- <div class="actions">
3680
- <div class="dropdown" uib-dropdown dropdown-append-to-body>
3681
- <button type="button" class="btn btn-sm btn-primary dropdown-toggle" uib-dropdown-toggle>
3682
- Server Group Actions <span class="caret"></span>
3683
- </button>
3684
- <ul class="dropdown-menu" uib-dropdown-menu role="menu">
3685
- <li ng-if="ctrl.canStopServerGroup()">
3686
- <a href ng-click="ctrl.stopServerGroup()"> Stop </a>
3687
- </li>
3688
- <li ng-if="ctrl.canStartServerGroup()">
3689
- <a href ng-click="ctrl.startServerGroup()"> Start </a>
3690
- </li>
3691
- <li ng-if="ctrl.serverGroup.disabled">
3692
- <a href ng-click="ctrl.enableServerGroup()"> Enable </a>
3693
- </li>
3694
- <li ng-if="!ctrl.serverGroup.disabled && ctrl.canDisableServerGroup()">
3695
- <a href ng-click="ctrl.disableServerGroup()"> Disable </a>
3696
- </li>
3697
- <li
3698
- ng-if="!ctrl.serverGroup.disabled && !ctrl.canDisableServerGroup()"
3699
- uib-tooltip="You cannot disable a server group if it is the
3700
- only server group receiving traffic from a load balancer."
3701
- class="disabled"
3702
- >
3703
- <a href> Disable </a>
3704
- </li>
3705
- <li ng-if="ctrl.canDestroyServerGroup()">
3706
- <a href ng-click="ctrl.destroyServerGroup()">Destroy</a>
3707
- </li>
3708
- <li
3709
- ng-if="!ctrl.canDestroyServerGroup()"
3710
- uib-tooltip="You cannot destroy a server group if it is the only server group
3711
- receiving traffic from a load balancer. You may be able to delete
3712
- this server group's load balancer."
3713
- class="disabled"
3714
- >
3715
- <a href>Destroy</a>
3716
- </li>
3717
- <li
3718
- uib-tooltip="It is not possible to clone an App Engine server group's full
3719
- launch configuration. However, clicking this button will allow
3720
- you to deploy into this server group's cluster."
3721
- >
3722
- <a href ng-click="ctrl.cloneServerGroup()">Clone</a>
3723
- </li>
3724
- </ul>
3725
- </div>
3726
- </div>
3727
- </div>
3728
-
3729
- <div class="content" ng-if="!ctrl.state.loading">
3730
- <div class="band band-info" ng-if="ctrl.serverGroup.isDisabled || ctrl.serverGroup.disabled">Disabled</div>
3731
- <server-group-running-tasks-details
3732
- server-group="ctrl.serverGroup"
3733
- application="ctrl.app"
3734
- ></server-group-running-tasks-details>
3735
- <collapsible-section heading="Server Group Information" expanded="true">
3736
- <dl class="dl-horizontal dl-narrow">
3737
- <dt>Created</dt>
3738
- <dd>{{ctrl.serverGroup.createdTime | timestamp}}</dd>
3739
- <dt>In</dt>
3740
- <dd><account-tag account="ctrl.serverGroup.account"></account-tag></dd>
3741
- <dt>Region</dt>
3742
- <dd>{{ctrl.serverGroup.region}}</dd>
3743
- <appengine-conditional-dt-dd
3744
- component="ctrl.serverGroup"
3745
- key="env"
3746
- label="Environment"
3747
- ></appengine-conditional-dt-dd>
3748
- <appengine-conditional-dt-dd component="ctrl.serverGroup" key="instanceClass"></appengine-conditional-dt-dd>
3749
- </dl>
3750
- </collapsible-section>
3751
- <collapsible-section heading="Size" expanded="true">
3752
- <dl class="dl-horizontal dl-narrow" ng-if="ctrl.serverGroup.capacity.min === ctrl.serverGroup.capacity.max">
3753
- <dt>Min/Max</dt>
3754
- <dd>{{ctrl.serverGroup.capacity.min}}</dd>
3755
- <dt>Current</dt>
3756
- <dd>{{ctrl.serverGroup.instances.length}}</dd>
3757
- </dl>
3758
- <dl class="dl-horizontal dl-narrow" ng-if="ctrl.serverGroup.capacity.min !== ctrl.serverGroup.capacity.max">
3759
- <dt>Min</dt>
3760
- <dd>{{ctrl.serverGroup.capacity.min}}</dd>
3761
- <dt>Max</dt>
3762
- <dd>{{ctrl.serverGroup.capacity.max}}</dd>
3763
- <dt>Current</dt>
3764
- <dd>{{ctrl.serverGroup.instances.length}}</dd>
3765
- </dl>
3766
- </collapsible-section>
3767
- <collapsible-section heading="Health" expanded="true">
3768
- <dl class="dl-horizontal dl-narrow" ng-if="ctrl.serverGroup">
3769
- <dt>Instances</dt>
3770
- <dd>
3771
- <health-counts container="ctrl.serverGroup.instanceCounts" class="pull-left"></health-counts>
3772
- </dd>
3773
- </dl>
3774
- </collapsible-section>
3775
- <collapsible-section heading="DNS" expanded="true">
3776
- <dl class="dl-narrow">
3777
- <appengine-component-url-details component="ctrl.serverGroup"></appengine-component-url-details>
3778
- </dl>
3779
- </collapsible-section>
3780
- <collapsible-section heading="Scaling Policy" expanded="true" ng-if="ctrl.serverGroup.scalingPolicy">
3781
- <dl>
3782
- <appengine-conditional-dt-dd
3783
- component="ctrl.serverGroup.scalingPolicy"
3784
- key="type"
3785
- ></appengine-conditional-dt-dd>
3786
- <!--MANUAL SCALING PROPERTIES-->
3787
- <appengine-conditional-dt-dd
3788
- component="ctrl.serverGroup.scalingPolicy"
3789
- key="instances"
3790
- ></appengine-conditional-dt-dd>
3791
- <!--BASIC SCALING PROPERTIES-->
3792
- <appengine-conditional-dt-dd
3793
- component="ctrl.serverGroup.scalingPolicy"
3794
- key="idleTimeout"
3795
- ></appengine-conditional-dt-dd>
3796
- <appengine-conditional-dt-dd
3797
- component="ctrl.serverGroup.scalingPolicy"
3798
- key="maxInstances"
3799
- ></appengine-conditional-dt-dd>
3800
- <!--AUTOMATIC SCALING PROPERTIES-->
3801
- <appengine-conditional-dt-dd
3802
- component="ctrl.serverGroup.scalingPolicy"
3803
- key="coolDownPeriod"
3804
- ></appengine-conditional-dt-dd>
3805
- <appengine-conditional-dt-dd
3806
- component="ctrl.serverGroup.scalingPolicy"
3807
- key="maxConcurrentRequests"
3808
- ></appengine-conditional-dt-dd>
3809
- <appengine-conditional-dt-dd
3810
- component="ctrl.serverGroup.scalingPolicy"
3811
- key="maxTotalInstances"
3812
- ></appengine-conditional-dt-dd>
3813
- <appengine-conditional-dt-dd
3814
- component="ctrl.serverGroup.scalingPolicy"
3815
- key="minTotalInstances"
3816
- ></appengine-conditional-dt-dd>
3817
- <appengine-conditional-dt-dd
3818
- component="ctrl.serverGroup.scalingPolicy"
3819
- key="maxIdleInstances"
3820
- ></appengine-conditional-dt-dd>
3821
- <appengine-conditional-dt-dd
3822
- component="ctrl.serverGroup.scalingPolicy"
3823
- key="minIdleInstances"
3824
- ></appengine-conditional-dt-dd>
3825
- <appengine-conditional-dt-dd
3826
- component="ctrl.serverGroup.scalingPolicy"
3827
- key="maxPendingLatency"
3828
- ></appengine-conditional-dt-dd>
3829
- <appengine-conditional-dt-dd
3830
- component="ctrl.serverGroup.scalingPolicy"
3831
- key="minPendingLatency"
3832
- ></appengine-conditional-dt-dd>
3833
- <appengine-conditional-dt-dd
3834
- component="ctrl.serverGroup.scalingPolicy.cpuUtilization"
3835
- key="aggregationWindowLength"
3836
- ></appengine-conditional-dt-dd>
3837
- <appengine-conditional-dt-dd
3838
- component="ctrl.serverGroup.scalingPolicy.cpuUtilization"
3839
- key="targetUtilization"
3840
- label="Target CPU Utilization"
3841
- ></appengine-conditional-dt-dd>
3842
- <appengine-conditional-dt-dd
3843
- component="ctrl.serverGroup.scalingPolicy.diskUtilization"
3844
- key="targetReadBytesPerSecond"
3845
- ></appengine-conditional-dt-dd>
3846
- <appengine-conditional-dt-dd
3847
- component="ctrl.serverGroup.scalingPolicy.diskUtilization"
3848
- key="targetReadOpsPerSecond"
3849
- ></appengine-conditional-dt-dd>
3850
- <appengine-conditional-dt-dd
3851
- component="ctrl.serverGroup.scalingPolicy.diskUtilization"
3852
- key="targetWriteBytesPerSecond"
3853
- ></appengine-conditional-dt-dd>
3854
- <appengine-conditional-dt-dd
3855
- component="ctrl.serverGroup.scalingPolicy.diskUtilization"
3856
- key="targetWriteOpsPerSecond"
3857
- ></appengine-conditional-dt-dd>
3858
- <appengine-conditional-dt-dd
3859
- component="ctrl.serverGroup.scalingPolicy.networkUtilization"
3860
- key="targetReceivedBytesPerSecond"
3861
- ></appengine-conditional-dt-dd>
3862
- <appengine-conditional-dt-dd
3863
- component="ctrl.serverGroup.scalingPolicy.networkUtilization"
3864
- key="targetReceivedPacketsPerSecond"
3865
- ></appengine-conditional-dt-dd>
3866
- <appengine-conditional-dt-dd
3867
- component="ctrl.serverGroup.scalingPolicy.networkUtilization"
3868
- key="targetSentBytesPerSecond"
3869
- ></appengine-conditional-dt-dd>
3870
- <appengine-conditional-dt-dd
3871
- component="ctrl.serverGroup.scalingPolicy.networkUtilization"
3872
- key="targetSentPacketsPerSecond"
3873
- ></appengine-conditional-dt-dd>
3874
- <appengine-conditional-dt-dd
3875
- component="ctrl.serverGroup.scalingPolicy.requestUtilization"
3876
- key="targetConcurrentRequests"
3877
- ></appengine-conditional-dt-dd>
3878
- <appengine-conditional-dt-dd
3879
- component="ctrl.serverGroup.scalingPolicy.requestUtilization"
3880
- key="targetRequestCountPerSecond"
3881
- ></appengine-conditional-dt-dd>
3882
- </dl>
3883
- </collapsible-section>
3884
- </div>
3885
- </div>
3886
- `);
3887
- }]);
3888
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
3889
- templateCache.put("appengine/src/serverGroup/configure/wizard/serverGroupWizard.html", `<form name="form" class="form-horizontal appengine-server-group-wizard" novalidate>
3890
- <div ng-if="ctrl.state.loading" style="height: 200px" class="horizontal center middle">
3891
- <loading-spinner size="'small'"></loading-spinner>
3892
- </div>
3893
- <div>
3894
- <v2-modal-wizard
3895
- ng-if="!ctrl.state.loading"
3896
- heading="Create Server Group"
3897
- task-monitor="ctrl.taskMonitor"
3898
- dismiss="$dismiss()"
3899
- >
3900
- <v2-wizard-page key="basic-settings" label="Basic Settings" mark-complete-on-view="false">
3901
- <ng-include src="ctrl.pages.basicSettings"></ng-include>
3902
- </v2-wizard-page>
3903
- <v2-wizard-page key="load-balancer" label="Config Files" mark-complete-on-view="false">
3904
- <appengine-config-file-configurer command="command"></appengine-config-file-configurer>
3905
- </v2-wizard-page>
3906
- <v2-wizard-page key="load-balancer" label="Load Balancer" done="true">
3907
- <appengine-load-balancer-message show-create-message="false"></appengine-load-balancer-message>
3908
- </v2-wizard-page>
3909
- <v2-wizard-page key="advanced-settings" label="Advanced Settings" done="true">
3910
- <ng-include src="ctrl.pages.advancedSettings"></ng-include>
3911
- </v2-wizard-page>
3912
- </v2-modal-wizard>
3913
- <div class="modal-footer" ng-if="!state.loading">
3914
- <button ng-disabled="ctrl.taskMonitor.submitting" class="btn btn-default btn-cancel" ng-click="ctrl.cancel()">
3915
- Cancel
3916
- </button>
3917
- <submit-button
3918
- ng-if="form.$valid"
3919
- is-disabled="form.$invalid || ctrl.taskMonitor.submitting"
3920
- label="command.viewState.submitButtonLabel"
3921
- submitting="ctrl.taskMonitor.submitting"
3922
- on-click="ctrl.submit()"
3923
- is-new="true"
3924
- ></submit-button>
3925
- </div>
3926
- </div>
3927
- </form>
3928
- `);
3929
- }]);
3930
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
3931
- templateCache.put("appengine/src/loadBalancer/configure/wizard/wizard.html", `<form name="form">
3932
- <div ng-if="ctrl.state.loading && !ctrl.isNew" style="height: 200px" class="horizontal center middle">
3933
- <loading-spinner size="'small'"></loading-spinner>
3934
- </div>
3935
- <v2-modal-wizard
3936
- heading="{{::ctrl.heading}}"
3937
- task-monitor="ctrl.taskMonitor"
3938
- dismiss="$dismiss()"
3939
- ng-if="!ctrl.state.loading || ctrl.isNew"
3940
- >
3941
- <div ng-if="!ctrl.isNew">
3942
- <v2-wizard-page key="basic-settings" label="Basic Settings" mark-complete-on-view="false">
3943
- <appengine-load-balancer-basic-settings
3944
- load-balancer="ctrl.loadBalancer"
3945
- application="ctrl.application"
3946
- for-pipeline-config="ctrl.forPipelineConfig"
3947
- ></appengine-load-balancer-basic-settings>
3948
- </v2-wizard-page>
3949
- <v2-wizard-page key="advanced-settings" label="Advanced Settings" mark-complete-on-view="false">
3950
- <appengine-load-balancer-advanced-settings
3951
- load-balancer="ctrl.loadBalancer"
3952
- ></appengine-load-balancer-advanced-settings>
3953
- </v2-wizard-page>
3954
- </div>
3955
- </v2-modal-wizard>
3956
- <appengine-load-balancer-message
3957
- ng-if="ctrl.isNew"
3958
- column-offset="1"
3959
- columns="10"
3960
- show-create-message="true"
3961
- ></appengine-load-balancer-message>
3962
- <div class="modal-footer">
3963
- <button class="btn btn-default" ng-click="ctrl.cancel()">Cancel</button>
3964
- <submit-button
3965
- ng-if="!ctrl.isNew && ctrl.showSubmitButton()"
3966
- label="ctrl.submitButtonLabel"
3967
- is-disabled="appengineLoadBalancerForm.$invalid || ctrl.taskMonitor.submitting || ctrl.state.loading"
3968
- submitting="ctrl.taskMonitor.submitting"
3969
- on-click="ctrl.submit()"
3970
- is-new="ctrl.isNew"
3971
- >
3972
- </submit-button>
3973
- </div>
3974
- </form>
3975
- `);
3976
- }]);
3977
- window.angular.module("ng").run(["$templateCache", function(templateCache) {
3978
- templateCache.put("appengine/src/loadBalancer/details/details.html", `<div class="details-panel">
3979
- <div ng-if="ctrl.state.loading" class="header">
3980
- <div class="close-button">
3981
- <a class="btn btn-link" ui-sref="^">
3982
- <span class="glyphicon glyphicon-remove"></span>
3983
- </a>
3984
- </div>
3985
- <div class="horizontal center middle">
3986
- <loading-spinner size="'small'"></loading-spinner>
3987
- </div>
3988
- </div>
3989
-
3990
- <div ng-if="!ctrl.state.loading" class="header">
3991
- <div class="close-button">
3992
- <a class="btn btn-link" ui-sref="^">
3993
- <span class="glyphicon glyphicon-remove"></span>
3994
- </a>
3995
- </div>
3996
- <div class="header-text horizontal middle">
3997
- <i class="fa icon-sitemap"></i>
3998
- <h3 class="horizontal middle space-between flex-1" select-on-dbl-click>{{ctrl.loadBalancer.name}}</h3>
3999
- </div>
4000
- <div>
4001
- <div class="actions">
4002
- <div class="dropdown" uib-dropdown dropdown-append-to-body>
4003
- <button type="button" class="btn btn-sm btn-primary dropdown-toggle" uib-dropdown-toggle>
4004
- Load Balancer Actions <span class="caret"></span>
4005
- </button>
4006
- <ul class="dropdown-menu" uib-dropdown-menu role="menu">
4007
- <li><a href ng-click="ctrl.editLoadBalancer()">Edit Load Balancer</a></li>
4008
- <li ng-if="ctrl.canDeleteLoadBalancer()">
4009
- <a href ng-click="ctrl.deleteLoadBalancer()">Delete Load Balancer</a>
4010
- </li>
4011
- <li
4012
- ng-if="!ctrl.canDeleteLoadBalancer()"
4013
- uib-tooltip="You cannot delete a default service."
4014
- class="disabled"
4015
- >
4016
- <a href>Delete Load Balancer</a>
4017
- </li>
4018
- </ul>
4019
- </div>
4020
- </div>
4021
- </div>
4022
- </div>
4023
-
4024
- <div ng-if="!ctrl.state.loading" class="content">
4025
- <collapsible-section heading="Load Balancer Details" expanded="true">
4026
- <dl class="dl-horizontal dl-narrow">
4027
- <dt>In</dt>
4028
- <dd><account-tag account="ctrl.loadBalancer.account" pad="right"></account-tag></dd>
4029
- <dt>Region</dt>
4030
- <dd>{{ctrl.loadBalancer.region}}</dd>
4031
- <dt ng-if="ctrl.loadBalancer.serverGroups.length">Server Groups</dt>
4032
- <dd ng-if="ctrl.loadBalancer.serverGroups.length">
4033
- <ul>
4034
- <li ng-repeat="serverGroup in ctrl.loadBalancer.serverGroups | orderBy: ['isDisabled', '-name']">
4035
- <a
4036
- ui-sref="^.serverGroup({region: serverGroup.region,
4037
- accountId: serverGroup.account,
4038
- serverGroup: serverGroup.name,
4039
- provider: 'appengine'})"
4040
- >
4041
- {{serverGroup.name}}
4042
- </a>
4043
- </li>
4044
- </ul>
4045
- </dd>
4046
- </dl>
4047
- </collapsible-section>
4048
- <collapsible-section heading="Status" expanded="true">
4049
- <health-counts class="pull-left" container="ctrl.loadBalancer.instanceCounts"></health-counts>
4050
- </collapsible-section>
4051
- <collapsible-section heading="Traffic Split" expanded="true">
4052
- <dl class="dl-horizontal dl-narrow">
4053
- <dt ng-if="ctrl.loadBalancer.split.shardBy">Shard By</dt>
4054
- <dd ng-if="ctrl.loadBalancer.split.shardBy">
4055
- {{ctrl.loadBalancer.split.shardBy}}
4056
- <help-field
4057
- key="appengine.loadBalancer.shardBy.{{ctrl.loadBalancer.split.shardBy.toLowerCase()}}"
4058
- ></help-field>
4059
- </dd>
4060
- <hr ng-if="ctrl.loadBalancer.split.shardBy" />
4061
- <ul>
4062
- <li ng-repeat="(serverGroup, percent) in ctrl.loadBalancer.split.allocations">
4063
- {{serverGroup}}:<span class="pull-right">{{percent | decimalToPercent}}</span>
4064
- </li>
4065
- </ul>
4066
- </dl>
4067
- </collapsible-section>
4068
- <collapsible-section heading="DNS" expanded="true">
4069
- <dl class="dl-narrow">
4070
- <appengine-component-url-details component="ctrl.loadBalancer"></appengine-component-url-details>
4071
- </dl>
4072
- </collapsible-section>
4073
- <collapsible-section heading="Dispatch Rules" expanded="true" ng-if="ctrl.dispatchRules.length > 0">
4074
- <dl class="dl-horizontal dl-narrow">
4075
- <span ng-repeat-start="rule in ctrl.dispatchRules">{{rule}}</span><br ng-repeat-end />
4076
- </dl>
4077
- </collapsible-section>
4078
- </div>
4079
- </div>
4080
- `);
4081
- }]);
4082
-
4083
- export { APPENGINE_MODULE };
1
+ import{module as e,extend as n,copy as t}from"angular";import{HelpContentsRegistry as a,ConfirmationModalService as i,RecentHistoryService as r,InstanceReader as o,InstanceWriter as l,StageConstants as s,AppListExtractor as c,LoadBalancerWriter as p,TaskMonitor as d,AccountService as g,Registry as u,CloudProviderRegistry as m,FormikFormField as h,ReactSelectInput as v,StageArtifactSelectorDelegate as f,excludeAllTypesExcept as b,ArtifactTypePatterns as y,FormikStageConfig as C,FormValidator as S,ExecutionDetailsTasks as w,ExecutionArtifactTab as G,SETTINGS as A,StorageAccountReader as k,NgAppEngineDeployArtifactDelegate as $,ExpectedArtifactSelectorViewController as B,NgAppengineConfigArtifactDelegate as x,SERVER_GROUP_WRITER as T,StageArtifactSelector as D,withErrorBoundary as E,TaskExecutor as F,ServerGroupWarningMessageService as L,ServerGroupReader as P,ApplicationNameValidator as I,DeploymentStrategyRegistry as O}from"@spinnaker/core";import{cloneDeep as N,flattenDeep as z,uniq as R,difference as M,filter as U,chain as H,has as q,camelCase as V,get as j,reduce as W,set as _,merge as J,map as K,mapValues as Y}from"lodash";import Q from"react";import{Subject as X,from as Z}from"rxjs";import{takeUntil as ee}from"rxjs/operators";import{react2angular as ne}from"react2angular";import te from"classnames";const ae={bindings:{component:"<"},template:'\n <dt>HTTPS</dt>\n <dl class="small">\n <a href="{{$ctrl.component.httpsUrl}}" target="_blank">{{$ctrl.component.httpsUrl}}</a>\n <copy-to-clipboard class="copy-to-clipboard copy-to-clipboard-sm"\n tool-tip="\'Copy URL to clipboard\'"\n text="$ctrl.component.httpsUrl"></copy-to-clipboard>\n </dl>\n <dt>HTTP</dt>\n <dl class="small">\n <a href="{{$ctrl.component.httpUrl}}" target="_blank">{{$ctrl.component.httpUrl}}</a>\n <copy-to-clipboard class="copy-to-clipboard copy-to-clipboard-sm"\n tool-tip="\'Copy URL to clipboard\'"\n text="$ctrl.component.httpUrl"></copy-to-clipboard>\n </dl>\n '},ie="spinnaker.appengine.componentUrlDetails.component";e(ie,[]).component("appengineComponentUrlDetails",ae);class re{constructor(e){this.$filter=e}$onInit(){this.label||(this.label=this.$filter("robotToHuman")(this.key))}}re.$inject=["$filter"];const oe={bindings:{label:"@",key:"@",component:"<"},transclude:{keyLabel:"?keyText",valueLabel:"?valueLabel"},template:'\n <dt ng-if="$ctrl.component[$ctrl.key]">{{$ctrl.label}}<span ng-transclude="keyLabel"></span></dt>\n <dd ng-if="$ctrl.component[$ctrl.key]">{{$ctrl.component[$ctrl.key]}}<span ng-transclude="valueLabel"></span></dd>\n ',controller:re},le="spinnaker.appengine.conditionalDescriptionListItem";e(le,[]).component("appengineConditionalDtDd",oe);const se="spinnaker.appengine.loadBalancer.createMessage.component";e(se,[]).component("appengineLoadBalancerMessage",{bindings:{showCreateMessage:"<",columnOffset:"@",columns:"@"},templateUrl:"appengine/src/common/loadBalancerMessage.component.html"}),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/common/loadBalancerMessage.component.html",'<div class="row">\n <div class="col-md-offset-{{ $ctrl.columnOffset || 0 }} col-md-{{ $ctrl.columns || 12 }}">\n <div class="well">\n <p>\n <span ng-if="$ctrl.showCreateMessage">Spinnaker cannot create a load balancer for App Engine.</span>\n A Spinnaker load balancer maps to an App Engine service, which is specified in a version\'s\n <code>app.yaml</code>.\n </p>\n <p>For example, the following <code>app.yaml</code></p>\n <pre class="text-left" style="white-space: pre-line">\n \t\t\truntime: python27\n \t\t\tapi_version: 1\n &hellip;\n \t\t\tservice: mobile\n &hellip;\n \t\t</pre\n >\n <p>\n deploys to the <code>mobile</code> service. If you do not specify a service, your version will be deployed to\n the <code>default</code> service.\n </p>\n <p>\n If a service does not exist when a version is deployed, it will be created. It will then be editable as a load\n balancer within Spinnaker.\n </p>\n <p>\n <a href="https://cloud.google.com/appengine/docs/python/config/appref" target="_blank"\n >App Engine Documentation</a\n >\n </p>\n </div>\n </div>\n</div>\n')}]);[{key:"appengine.serverGroup.gcs.repositoryUrl",value:"The full URL to the GCS bucket or TAR file containing the source files for this deployment,\n including 'gs://'. For example, <b>gs://my-bucket/my-app</b> or <b>gs://my-bucket/app.tar</b>."},{key:"appengine.serverGroup.git.repositoryUrl",value:"The full URL to the git repository containing the source files for this deployment,\n including protocol. For example, <b>https://github.com/spinnaker/deck.git</b>"},{key:"appengine.serverGroup.gitCredentialType",value:"The credential type that will be used to access the git repository for this deployment.\n You can configure these credentials alongside your App Engine credentials."},{key:"appengine.serverGroup.branch",value:"The name of the branch in the above git repository to be used for this deployment."},{key:"appengine.serverGroup.applicationDirectoryRoot",value:"(Optional) Path to the directory root of the application to be deployed,\n starting from the root of the git repository. This is the directory from which\n <code>gcloud app deploy</code> will be run."},{key:"appengine.serverGroup.configFilepaths",value:"Paths to App Engine application configuration files, starting from the application directory root,\n specified above. In most cases, this will be <code>app.yaml</code> or <code>cron.yaml</code>,\n but could be <code>path/to/app.yaml</code> or <code>path/to/cron.yaml</code>."},{key:"appengine.serverGroup.configFiles",value:"<p>(Optional) The contents of an App Engine config file (e.g., an <code>app.yaml</code> or\n <code>cron.yaml</code> file). These files should not conflict with the config filepaths above:\n if you include, for example, the contents of an <code>app.yaml</code>\n file here, you should <b>not</b> specify the file path to an <code>app.yaml</code> above.<br></p>\n <p>If this is a pipeline stage, you can use Spinnaker Pipeline Expressions here.</p>"},{key:"appengine.serverGroup.configFilesRequired",value:"<p>The contents of an App Engine config file (e.g., an <code>app.yaml</code> or\n <code>cron.yaml</code> file).</p>\n <p>An <code>app.yaml</code> file is required and must have <code>runtime: custom</code> in\n order to deploy successfully.</p>\n <p>If this is a pipeline stage, you can use Spinnaker Pipeline Expressions here.</p>"},{key:"appengine.serverGroup.matchBranchOnRegex",value:"(Optional) A Jenkins trigger may produce details from multiple repositories and branches.\n Spinnaker will use the regex specified here to help resolve a branch for the deployment.\n If Spinnaker cannot resolve exactly one branch from the trigger, this pipeline will fail."},{key:"appengine.serverGroup.promote",value:"If selected, the newly deployed server group will receive all traffic."},{key:"appengine.serverGroup.stopPreviousVersion",value:"If selected, the previously running server group in this server group's <b>service</b>\n (Spinnaker load balancer) will be stopped. This option will be respected only if this server group will\n be receiving all traffic and the previous server group is using manual scaling."},{key:"appengine.serverGroup.containerImageUrl",value:"The full URL to the container image to use for deployment. The URL must be one of the valid GCR hostnames,\n for example <b>gcr.io/my-project/image:tag</b>"},{key:"appengine.serverGroup.suppress-version-string",value:'Spinnaker automatically versions your server groups. This means deployments through Spinnaker receive a\n short version string at the end of their name, like "v001". In most cases you will want to keep this\n version as part of the name. Preventing this string from being added to your server group will stop\n it from being considered when rolling back, promoting new versions or executing deployment strategies.\n If you are certain that you do not want the version string applied to this server group then check\n this box.'},{key:"appengine.loadBalancer.shardBy.cookie",value:'Diversion based on a specially named cookie, "GOOGAPPUID." The cookie must be set by the application itself or no diversion will occur.'},{key:"appengine.loadBalancer.shardBy.ip",value:"Diversion based on applying the modulus operation to a fingerprint of the IP address."},{key:"appengine.loadBalancer.migrateTraffic",value:"If selected, traffic will be gradually shifted to a single version. For gradual traffic migration,\n the target version must be located within instances that are configured for\n both warmup requests and automatic scaling.\n Gradual traffic migration is not supported in the App Engine flexible environment."},{key:"appengine.loadBalancer.allocations",value:"An allocation is the percent of traffic directed to a server group."},{key:"appengine.loadBalancer.textLocator",value:"Either the name of a server group, or a Spinnaker Pipeline Expression\n that resolves to the name of a server group."},{key:"appengine.instance.availability",value:"\n An instance's <b>availability</b> is determined by its version (Spinnaker server group).\n <ul>\n <li>Manual scaling versions use resident instances</li>\n <li>Basic scaling versions use dynamic instances</li>\n <li>Auto scaling versions use dynamic instances - but if you specify a number, N,\n of minimum idle instances, the first N instances will be resident,\n and additional dynamic instances will be created as necessary.\n </li>\n </ul>"},{key:"appengine.instance.averageLatency",value:"Average latency over the last minute in milliseconds."},{key:"appengine.instance.vmStatus",value:"Status of the virtual machine where this instance lives."},{key:"appengine.instance.qps",value:"Average queries per second over the last minute."},{key:"appengine.instance.errors",value:"Number of errors since this instance was started."},{key:"appengine.instance.requests",value:"Number of requests since this instance was started."}].forEach((e=>a.register(e.key,e.value)));class ce{constructor(e,n,t){this.$q=e,this.app=n,this.state={loading:!0},this.upToolTip="An App Engine instance is 'Up' if a load balancer is directing traffic to its server group.",this.outOfServiceToolTip="\n An App Engine instance is 'Out Of Service' if no load balancers are directing traffic to its server group.",this.app.ready().then((()=>this.retrieveInstance(t))).then((e=>{this.instance=e,this.state.loading=!1})).catch((()=>{this.instanceIdNotFound=t.instanceId,this.state.loading=!1}))}terminateInstance(){const e=N(this.instance),n=`${this.instance.name.substring(0,10)}...`;e.placement={},e.instanceId=e.name;const t={application:this.app,title:"Terminating "+n,onTaskComplete(){this.$state.includes("**.instanceDetails",{instanceId:e.name})&&this.$state.go("^")}};i.confirm({header:"Really terminate "+n+"?",buttonText:"Terminate "+n,account:e.account,taskMonitorConfig:t,submitMethod:()=>l.terminateInstance(e,this.app,{cloudProvider:"appengine"})})}retrieveInstance(e){const n=z([this.app.getDataSource("serverGroups").data,this.app.getDataSource("loadBalancers").data,this.app.getDataSource("loadBalancers").data.map((e=>e.serverGroups))]).find((n=>n.instances.some((n=>n.id===e.instanceId))));if(n){const t={region:n.region,account:n.account};return"serverGroup"===n.category&&(t.serverGroup=n.name),r.addExtraDataToLatest("instances",t),o.getInstanceDetails(n.account,n.region,e.instanceId).then((e=>(e.account=n.account,e.region=n.region,e)))}return this.$q.reject()}}ce.$inject=["$q","app","instance"];const pe="spinnaker.appengine.instanceDetails.controller";e(pe,[]).controller("appengineInstanceDetailsCtrl",ce);const de={bindings:{loadBalancer:"=",application:"<"},template:'\n <ng-form name="advancedSettingsForm">\n <div class="row">\n <div class="form-group">\n <div class="col-md-3 sm-label-right">\n Migrate Traffic <help-field key="appengine.loadBalancer.migrateTraffic"></help-field>\n </div>\n <div class="col-md-9">\n <div class="checkbox">\n <input type="checkbox" ng-disabled="$ctrl.disableMigrateTraffic() && !($ctrl.loadBalancer.migrateTraffic = false)" ng-model="$ctrl.loadBalancer.migrateTraffic">\n </div>\n </div>\n </div>\n </div>\n </ng-form>\n ',controller:class{constructor(){this.state={error:!1}}disableMigrateTraffic(){if(1!==this.loadBalancer.splitDescription.allocationDescriptions.length)return!0;{const e=this.loadBalancer.splitDescription.allocationDescriptions[0].serverGroupName,n=this.loadBalancer.serverGroups.find((n=>n.name===e));return!!n&&!n.allowsGradualTrafficMigration}}}},ge="spinnaker.appengine.loadBalancer.advancedSettings.component";e(ge,[]).component("appengineLoadBalancerAdvancedSettings",de);const ue={bindings:{allocationDescription:"<",removeAllocation:"&",serverGroupOptions:"<",onAllocationChange:"&"},template:'\n <div class="form-group">\n <div class="row">\n <div class="col-md-7">\n <ui-select ng-model="$ctrl.allocationDescription.serverGroupName"\n on-select="$ctrl.onAllocationChange()"\n class="form-control input-sm">\n <ui-select-match placeholder="Select...">\n {{$select.selected}}\n </ui-select-match>\n <ui-select-choices repeat="serverGroup as serverGroup in $ctrl.getServerGroupOptions() | filter: $select.search">\n <div ng-bind-html="serverGroup | highlight: $select.search"></div>\n </ui-select-choices>\n </ui-select>\n </div>\n <div class="col-md-3">\n <div class="input-group input-group-sm">\n <input type="number"\n ng-model="$ctrl.allocationDescription.allocation"\n required\n class="form-control input-sm"\n min="0"\n ng-change="$ctrl.onAllocationChange()"\n max="100"/>\n <span class="input-group-addon">%</span>\n </div>\n </div>\n <div class="col-md-2">\n <a class="btn btn-link sm-label" ng-click="$ctrl.removeAllocation()">\n <span class="glyphicon glyphicon-trash"></span>\n </a>\n </div>\n </div>\n </div>\n ',controller:class{getServerGroupOptions(){return this.allocationDescription.serverGroupName?R(this.serverGroupOptions.concat(this.allocationDescription.serverGroupName)):this.serverGroupOptions}}},me="spinnaker.appengine.allocationConfigurationRow.component";e(me,[]).component("appengineAllocationConfigurationRow",ue);const he={bindings:{loadBalancer:"=",forPipelineConfig:"<",application:"<"},controller:class{$onInit(){this.updateServerGroupOptions()}addAllocation(){const e=this.serverGroupsWithoutAllocation();e.length?(this.loadBalancer.splitDescription.allocationDescriptions.push({serverGroupName:e[0],allocation:0,locatorType:"fromExisting"}),this.loadBalancer.splitDescription.allocationDescriptions.length>1&&!this.loadBalancer.splitDescription.shardBy&&(this.loadBalancer.splitDescription.shardBy="IP"),this.updateServerGroupOptions()):this.forPipelineConfig&&this.loadBalancer.splitDescription.allocationDescriptions.push({allocation:0,locatorType:"text",serverGroupName:""})}removeAllocation(e){this.loadBalancer.splitDescription.allocationDescriptions.splice(e,1),this.updateServerGroupOptions()}allocationIsInvalid(){return 100!==this.loadBalancer.splitDescription.allocationDescriptions.reduce(((e,n)=>e+n.allocation),0)}updateServerGroupOptions(){this.serverGroupOptions=this.serverGroupsWithoutAllocation()}showAddButton(){return!!this.forPipelineConfig||this.serverGroupsWithoutAllocation().length>0}showShardByOptions(){return this.loadBalancer.splitDescription.allocationDescriptions.length>1||this.loadBalancer.migrateTraffic}initializeAsTextInput(e){return!!this.forPipelineConfig&&!this.loadBalancer.serverGroups.map((e=>e.name)).includes(e)}serverGroupsWithoutAllocation(){const e=this.loadBalancer.splitDescription.allocationDescriptions.map((e=>e.serverGroupName)),n=this.loadBalancer.serverGroups.map((e=>e.name));return M(n,e)}},templateUrl:"appengine/src/loadBalancer/configure/wizard/basicSettings.component.html"},ve="spinnaker.appengine.loadBalancerSettings.component";e(ve,[]).component("appengineLoadBalancerBasicSettings",he),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/loadBalancer/configure/wizard/basicSettings.component.html",'<ng-form name="basicSettingsForm">\n <div class="row">\n <div class="form-group">\n <div class="col-md-3 sm-label-right">\n Allocations\n <help-field key="appengine.loadBalancer.allocations"></help-field>\n </div>\n <div class="col-md-9">\n <div ng-if="!$ctrl.forPipelineConfig">\n <appengine-allocation-configuration-row\n ng-repeat="description in $ctrl.loadBalancer.splitDescription.allocationDescriptions"\n allocation-description="description"\n server-group-options="$ctrl.serverGroupOptions"\n on-allocation-change="$ctrl.updateServerGroupOptions()"\n remove-allocation="$ctrl.removeAllocation($index)"\n >\n </appengine-allocation-configuration-row>\n </div>\n <div ng-if="$ctrl.forPipelineConfig">\n <appengine-stage-allocation-configuration-row\n ng-repeat="description in $ctrl.loadBalancer.splitDescription.allocationDescriptions"\n allocation-description="description"\n application="$ctrl.application"\n region="{{ $ctrl.loadBalancer.region }}"\n account="{{ $ctrl.loadBalancer.account || $ctrl.loadBalancer.credentials }}"\n server-group-options="$ctrl.serverGroupOptions"\n on-allocation-change="$ctrl.updateServerGroupOptions()"\n remove-allocation="$ctrl.removeAllocation($index)"\n >\n </appengine-stage-allocation-configuration-row>\n </div>\n <button class="add-new col-md-11" ng-if="$ctrl.showAddButton()" ng-click="$ctrl.addAllocation()">\n <span class="glyphicon glyphicon-plus-sign"></span> Add allocation\n </button>\n </div>\n </div>\n <div class="form-group" ng-if="$ctrl.allocationIsInvalid()">\n <div class="col-md-12 text-center">\n <p class="alert alert-warning">Allocations must sum to 100%.</p>\n </div>\n </div>\n </div>\n\n <div class="row" ng-if="$ctrl.showShardByOptions()">\n <div class="form-group">\n <div class="col-md-3 sm-label-right">Shard By</div>\n <div class="col-md-9">\n <form class="form-inline">\n <div class="form-group">\n <label class="radio-inline">\n <input type="radio" ng-model="$ctrl.loadBalancer.splitDescription.shardBy" value="IP" />\n IP\n <help-field key="appengine.loadBalancer.shardBy.ip"></help-field>\n </label>\n <label class="radio-inline">\n <input type="radio" ng-model="$ctrl.loadBalancer.splitDescription.shardBy" value="COOKIE" />\n Cookie\n <help-field key="appengine.loadBalancer.shardBy.cookie"></help-field>\n </label>\n </div>\n </form>\n </div>\n </div>\n </div>\n</ng-form>\n')}]);class fe{static mapTargetCoordinateToLabel(e){const n=s.TARGET_LIST.find((n=>n.val===e));return n?n.label:null}$doCheck(){this.setInputViewValue()}setInputViewValue(){switch(this.allocationDescription.locatorType){case"text":case"fromExisting":this.inputViewValue=this.allocationDescription.serverGroupName;break;case"targetCoordinate":if(this.allocationDescription.cluster&&this.allocationDescription.target){const e=fe.mapTargetCoordinateToLabel(this.allocationDescription.target);this.inputViewValue=`${e} (${this.allocationDescription.cluster})`}else this.inputViewValue=null;break;default:this.inputViewValue=null}}}const be={bindings:{allocationDescription:"<"},controller:fe,template:'<input ng-model="$ctrl.inputViewValue" type="text" class="form-control input-sm" readonly/>'};const ye={bindings:{application:"<",region:"@",account:"@",allocationDescription:"<",removeAllocation:"&",serverGroupOptions:"<",onAllocationChange:"&"},controller:class{constructor(){this.targets=s.TARGET_LIST}$onInit(){const e=c.clusterFilterForCredentialsAndRegion(this.account,this.region);this.clusterList=c.getClusters([this.application],e)}getServerGroupOptions(){return this.allocationDescription.serverGroupName?R(this.serverGroupOptions.concat(this.allocationDescription.serverGroupName)):this.serverGroupOptions}onLocatorTypeChange(){this.serverGroupOptions.includes(this.allocationDescription.serverGroupName)||delete this.allocationDescription.serverGroupName,this.onAllocationChange()}},templateUrl:"appengine/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html"},Ce="spinnaker.appengine.stageAllocationConfigurationRow.component";e(Ce,[]).component("appengineStageAllocationConfigurationRow",ye).component("appengineStageAllocationLabel",be),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html",'<div class="form-group">\n <div class="row">\n <div class="col-md-7">\n <appengine-stage-allocation-label allocation-description="$ctrl.allocationDescription">\n </appengine-stage-allocation-label>\n </div>\n <div class="col-md-3">\n <div class="input-group input-group-sm">\n <input\n type="number"\n ng-model="$ctrl.allocationDescription.allocation"\n required\n class="form-control input-sm"\n min="0"\n ng-change="$ctrl.onAllocationChange()"\n max="100"\n />\n <span class="input-group-addon">%</span>\n </div>\n </div>\n <div class="col-md-2">\n <a class="btn btn-link sm-label" ng-click="$ctrl.removeAllocation()">\n <span class="glyphicon glyphicon-trash"></span>\n </a>\n </div>\n </div>\n</div>\n<div class="form-group">\n <div class="well col-md-11" style="padding-top: 5px; padding-bottom: 10px">\n <div class="row">\n <div class="form-group">\n <div class="col-md-3 sm-label-right">Locator</div>\n <div class="col-md-7">\n <form>\n <div class="form-group">\n <label class="small" style="font-weight: normal">\n <input\n type="radio"\n ng-change="$ctrl.onLocatorTypeChange()"\n ng-model="$ctrl.allocationDescription.locatorType"\n value="fromExisting"\n />\n Existing server group\n </label>\n <br />\n <label class="small" style="font-weight: normal">\n <input\n type="radio"\n ng-change="$ctrl.onLocatorTypeChange()"\n ng-model="$ctrl.allocationDescription.locatorType"\n value="targetCoordinate"\n />\n Coordinates\n </label>\n <br />\n <label class="small" style="font-weight: normal">\n <input\n type="radio"\n ng-change="$ctrl.onLocatorTypeChange()"\n ng-model="$ctrl.allocationDescription.locatorType"\n value="text"\n />\n Text input\n </label>\n </div>\n </form>\n </div>\n </div>\n </div>\n <div class="row">\n <div class="form-group" ng-switch on="$ctrl.allocationDescription.locatorType">\n <div ng-switch-when="fromExisting">\n <div class="col-md-3 sm-label-right" style="padding-left: 0px">Server group</div>\n <div class="col-md-7">\n <ui-select\n ng-model="$ctrl.allocationDescription.serverGroupName"\n on-select="$ctrl.onAllocationChange()"\n class="form-control input-sm"\n >\n <ui-select-match placeholder="Select...">\n {{ $select.selected }}\n </ui-select-match>\n <ui-select-choices\n repeat="serverGroup as serverGroup in $ctrl.getServerGroupOptions() | filter: $select.search"\n >\n <div ng-bind-html="serverGroup | highlight: $select.search"></div>\n </ui-select-choices>\n </ui-select>\n </div>\n </div>\n <div ng-switch-when="targetCoordinate">\n <div class="col-md-3 sm-label-right">Cluster</div>\n <div class="col-md-7">\n <cluster-selector\n class="small"\n clusters="$ctrl.clusterList"\n model="$ctrl.allocationDescription.cluster"\n ></cluster-selector>\n </div>\n <div class="col-md-3 sm-label-right">Target</div>\n <div class="col-md-7">\n <target-select model="$ctrl.allocationDescription" options="$ctrl.targets"></target-select>\n </div>\n </div>\n <div ng-switch-when="text">\n <div class="col-md-3 sm-label-right">\n Text\n <help-field key="appengine.loadBalancer.textLocator"></help-field>\n </div>\n <div class="col-md-7">\n <input class="form-control input-sm" type="text" ng-model="$ctrl.allocationDescription.serverGroupName" />\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n')}]);class Se{static convertTrafficSplitToTrafficSplitDescription(e){const n=W(e.allocations,((e,n,t)=>e.concat({serverGroupName:t,allocation:n,locatorType:"fromExisting"})),[]);return{shardBy:e.shardBy,allocationDescriptions:n}}constructor(e){this.credentials=e.account||e.credentials,this.account=this.credentials,this.cloudProvider=e.cloudProvider,this.loadBalancerName=e.name,this.name=e.name,this.region=e.region,this.migrateTraffic=e.migrateTraffic||!1,this.serverGroups=e.serverGroups}mapAllocationsToDecimals(){this.splitDescription.allocationDescriptions.forEach((e=>{e.allocation=e.allocation/100}))}mapAllocationsToPercentages(){this.splitDescription.allocationDescriptions.forEach((e=>{e.allocation=Math.round(1e3*e.allocation)/10}))}}class we{constructor(e){this.$q=e}normalizeLoadBalancer(e){e.provider=e.type,e.instanceCounts=this.buildInstanceCounts(e.serverGroups),e.instances=[],e.serverGroups.forEach((n=>{n.account=e.account,n.region=e.region,n.detachedInstances&&(n.detachedInstances=n.detachedInstances.map((e=>({id:e})))),n.instances=n.instances.concat(n.detachedInstances||[]).map((n=>this.transformInstance(n,e)))}));const n=U(e.serverGroups,{isDisabled:!1});return e.instances=H(n).map("instances").flatten().value(),this.$q.resolve(e)}convertLoadBalancerForEditing(e,n){return n.getDataSource("loadBalancers").ready().then((()=>{const t=n.getDataSource("loadBalancers").data.find((n=>n.name===e.name&&(n.account===e.account||n.account===e.credentials)));return t&&(e.serverGroups=N(t.serverGroups)),e}))}convertLoadBalancerToUpsertDescription(e){return new Se(e)}buildInstanceCounts(e){const n=H(e).map("instances").flatten().reduce(((e,n)=>(q(n,"health.state")&&e[V(n.health.state)]++,e)),{up:0,down:0,outOfService:0,succeeded:0,failed:0,starting:0,unknown:0}).value();return n.outOfService+=H(e).map("detachedInstances").flatten().value().length,n}transformInstance(e,n){e.provider=n.type,e.account=n.account,e.region=n.region,e.loadBalancers=[n.name];const t=e.health||{};return e.healthState=j(e,"health.state")||"OutOfService",e.health=[t],e}}we.$inject=["$q"];const Ge="spinnaker.appengine.loadBalancer.transformer.service";function Ae(e,n){void 0===n&&(n={});var t=n.insertAt;if(e&&"undefined"!=typeof document){var a=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===t&&a.firstChild?a.insertBefore(i,a.firstChild):a.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}e(Ge,[]).service("appengineLoadBalancerTransformer",we);Ae("appengine-load-balancer-basic-settings a.btn.btn-link {\n padding: 0;\n}\nappengine-load-balancer-basic-settings .form-group {\n margin-top: 0.4rem;\n}\nappengine-load-balancer-advanced-settings .checkbox input[type='checkbox'] {\n margin: 0 0 0.1rem 0;\n}\n");class ke{constructor(e,n,t,a,i,r,o,l,s){this.$scope=e,this.$state=n,this.$uibModalInstance=t,this.application=a,this.isNew=r,this.forPipelineConfig=o,this.appengineLoadBalancerTransformer=l,this.wizardSubFormValidation=s,this.state={loading:!0},this.submitButtonLabel=this.forPipelineConfig?"Done":"Update",this.isNew?this.heading="Create New Load Balancer":(this.heading=`Edit ${[i.name,i.region,i.account||i.credentials].join(":")}`,this.appengineLoadBalancerTransformer.convertLoadBalancerForEditing(i,a).then((e=>{this.loadBalancer=this.appengineLoadBalancerTransformer.convertLoadBalancerToUpsertDescription(e),i.split&&!this.loadBalancer.splitDescription?this.loadBalancer.splitDescription=Se.convertTrafficSplitToTrafficSplitDescription(i.split):this.loadBalancer.splitDescription=i.splitDescription,this.loadBalancer.mapAllocationsToPercentages(),this.setTaskMonitor(),this.initializeFormValidation(),this.state.loading=!1})))}submit(){const e=N(this.loadBalancer);return e.mapAllocationsToDecimals(),delete e.serverGroups,this.forPipelineConfig?this.$uibModalInstance.close(e):this.taskMonitor.submit((()=>p.upsertLoadBalancer(e,this.application,"Update")))}cancel(){this.$uibModalInstance.dismiss()}showSubmitButton(){return this.wizardSubFormValidation.subFormsAreValid()}setTaskMonitor(){this.taskMonitor=new d({application:this.application,title:"Updating your load balancer",modalInstance:this.$uibModalInstance,onTaskComplete:()=>this.onTaskComplete()})}initializeFormValidation(){this.wizardSubFormValidation.config({form:"form",scope:this.$scope}).register({page:"basic-settings",subForm:"basicSettingsForm",validators:[{watchString:"ctrl.loadBalancer.splitDescription",validator:e=>100===e.allocationDescriptions.reduce(((e,n)=>e+n.allocation),0),watchDeep:!0}]}).register({page:"advanced-settings",subForm:"advancedSettingsForm"})}onTaskComplete(){this.application.getDataSource("loadBalancers").refresh(),this.application.getDataSource("loadBalancers").onNextRefresh(this.$scope,(()=>this.onApplicationRefresh()))}onApplicationRefresh(){if(this.$scope.$$destroyed)return;this.$uibModalInstance.dismiss();const e={name:this.loadBalancer.name,accountId:this.loadBalancer.credentials,region:this.loadBalancer.region,provider:"appengine"};this.$state.includes("**.loadBalancerDetails")?this.$state.go("^.loadBalancerDetails",e):this.$state.go(".loadBalancerDetails",e)}}ke.$inject=["$scope","$state","$uibModalInstance","application","loadBalancer","isNew","forPipelineConfig","appengineLoadBalancerTransformer","wizardSubFormValidation"];const $e="spinnaker.appengine.loadBalancer.wizard.controller";e($e,[]).controller("appengineLoadBalancerWizardCtrl",ke);class Be{constructor(e,n,t,a,i){this.$uibModal=e,this.$state=n,this.$scope=t,this.app=i,this.state={loading:!0},this.dispatchRules=[],this.loadBalancerFromParams=a,this.app.getDataSource("loadBalancers").ready().then((()=>this.extractLoadBalancer()))}editLoadBalancer(){this.$uibModal.open({templateUrl:"appengine/src/loadBalancer/configure/wizard/wizard.html",controller:"appengineLoadBalancerWizardCtrl as ctrl",size:"lg",resolve:{application:()=>this.app,loadBalancer:()=>N(this.loadBalancer),isNew:()=>!1,forPipelineConfig:()=>!1}})}deleteLoadBalancer(){const e={application:this.app,title:"Deleting "+this.loadBalancer.name};i.confirm({header:"Really delete "+this.loadBalancer.name+"?",buttonText:"Delete "+this.loadBalancer.name,body:this.getConfirmationModalBodyHtml(),account:this.loadBalancer.account,taskMonitorConfig:e,submitMethod:()=>{const e={cloudProvider:this.loadBalancer.cloudProvider,loadBalancerName:this.loadBalancer.name,credentials:this.loadBalancer.account};return p.deleteLoadBalancer(e,this.app)}})}canDeleteLoadBalancer(){return"default"!==this.loadBalancer.name}extractLoadBalancer(){this.loadBalancer=this.app.getDataSource("loadBalancers").data.find((e=>e.name===this.loadBalancerFromParams.name&&e.account===this.loadBalancerFromParams.accountId)),this.loadBalancer?(this.state.loading=!1,this.buildDispatchRules(),this.app.getDataSource("loadBalancers").onRefresh(this.$scope,(()=>this.extractLoadBalancer()))):this.autoClose()}buildDispatchRules(){this.dispatchRules=[],this.loadBalancer&&this.loadBalancer.dispatchRules&&this.loadBalancer.dispatchRules.forEach((e=>{e.service===this.loadBalancer.name&&this.dispatchRules.push(e.domain+e.path)}))}getConfirmationModalBodyHtml(){const e=this.loadBalancer.serverGroups.map((e=>e.name)),n=!!e&&e.length>0,t=!!e&&e.length>1;if(n){if(t){const n=e.map((e=>`<li>${e}</li>`)).join("");return`<div class="alert alert-warning">\n <p>\n Deleting <b>${this.loadBalancer.name}</b> will destroy the following server groups:\n <ul>\n ${n}\n </ul>\n </p>\n </div>\n `}return`<div class="alert alert-warning">\n <p>\n Deleting <b>${this.loadBalancer.name}</b> will destroy <b>${e[0]}</b>.\n </p>\n </div>\n `}return null}autoClose(){this.$scope.$$destroyed||(this.$state.params.allowModalToStayOpen=!0,this.$state.go("^",null,{location:"replace"}))}}Be.$inject=["$uibModal","$state","$scope","loadBalancer","app"];const xe="spinnaker.appengine.loadBalancerDetails.controller";e(xe,[]).controller("appengineLoadBalancerDetailsCtrl",Be),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/loadBalancer/configure/wizard/wizard.html",'<form name="form">\n <div ng-if="ctrl.state.loading && !ctrl.isNew" style="height: 200px" class="horizontal center middle">\n <loading-spinner size="\'small\'"></loading-spinner>\n </div>\n <v2-modal-wizard\n heading="{{::ctrl.heading}}"\n task-monitor="ctrl.taskMonitor"\n dismiss="$dismiss()"\n ng-if="!ctrl.state.loading || ctrl.isNew"\n >\n <div ng-if="!ctrl.isNew">\n <v2-wizard-page key="basic-settings" label="Basic Settings" mark-complete-on-view="false">\n <appengine-load-balancer-basic-settings\n load-balancer="ctrl.loadBalancer"\n application="ctrl.application"\n for-pipeline-config="ctrl.forPipelineConfig"\n ></appengine-load-balancer-basic-settings>\n </v2-wizard-page>\n <v2-wizard-page key="advanced-settings" label="Advanced Settings" mark-complete-on-view="false">\n <appengine-load-balancer-advanced-settings\n load-balancer="ctrl.loadBalancer"\n ></appengine-load-balancer-advanced-settings>\n </v2-wizard-page>\n </div>\n </v2-modal-wizard>\n <appengine-load-balancer-message\n ng-if="ctrl.isNew"\n column-offset="1"\n columns="10"\n show-create-message="true"\n ></appengine-load-balancer-message>\n <div class="modal-footer">\n <button class="btn btn-default" ng-click="ctrl.cancel()">Cancel</button>\n <submit-button\n ng-if="!ctrl.isNew && ctrl.showSubmitButton()"\n label="ctrl.submitButtonLabel"\n is-disabled="appengineLoadBalancerForm.$invalid || ctrl.taskMonitor.submitting || ctrl.state.loading"\n submitting="ctrl.taskMonitor.submitting"\n on-click="ctrl.submit()"\n is-new="ctrl.isNew"\n >\n </submit-button>\n </div>\n</form>\n')}]);const Te="spinnaker.appengine.loadBalancer.module";e(Te,[me,xe,ge,ve,Ge,$e,Ce]);class De{}De.PLATFORM="App Engine Service";class Ee{constructor(e){this.$scope=e,e.platformHealth=De.PLATFORM}setStageRegion(){const e=this.$scope.accounts.find((e=>e.name===this.$scope.stage.credentials));e&&e.name&&g.getAccountDetails(e.name).then((e=>{this.$scope.stage.region=e.region}))}setStageCloudProvider(){this.$scope.stage.cloudProvider="appengine"}setAccounts(){return g.listAccounts("appengine").then((e=>{this.$scope.accounts=e}))}setTargets(){this.$scope.targets=s.TARGET_LIST,this.$scope.stage.target||(this.$scope.stage.target=this.$scope.targets[0].val)}setStageCredentials(){!this.$scope.stage.credentials&&this.$scope.application.defaultCredentials.appengine&&(this.$scope.stage.credentials=this.$scope.application.defaultCredentials.appengine)}}Ee.$inject=["$scope"];class Fe extends Ee{constructor(e){super(e),this.$scope=e,super.setAccounts().then((()=>{super.setStageRegion()})),super.setStageCloudProvider(),super.setTargets(),super.setStageCredentials()}}Fe.$inject=["$scope"];const Le="spinnaker.appengine.pipeline.stage.destroyAsgStage";e(Le,[]).config((()=>{u.pipeline.registerStage({provides:"destroyServerGroup",key:"destroyServerGroup",cloudProvider:"appengine",templateUrl:"appengine/src/pipeline/stages/destroyAsg/destroyAsgStage.html",executionStepLabelUrl:"appengine/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html",validators:[{type:"targetImpedance",message:"This pipeline will attempt to destroy a server group without deploying a new version into the same cluster."},{type:"requiredField",fieldName:"cluster"},{type:"requiredField",fieldName:"target"},{type:"requiredField",fieldName:"credentials",fieldLabel:"account"}]})})).controller("appengineDestroyAsgStageCtrl",Fe),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/destroyAsg/destroyAsgStage.html",'<div ng-controller="appengineDestroyAsgStageCtrl as destroyAsgStageCtrl" class="form-horizontal">\n <div ng-if="!pipeline.strategy">\n <account-region-cluster-selector\n application="application"\n single-region="true"\n disable-region-select="true"\n on-account-update="destroyAsgStageCtrl.setStageRegion()"\n component="stage"\n accounts="accounts"\n >\n </account-region-cluster-selector>\n </div>\n <stage-config-field label="Target">\n <target-select model="stage" options="targets"></target-select>\n </stage-config-field>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html",'<span class="task-label"> Destroy Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>\n')}]);class Pe extends Ee{constructor(e){super(e),this.$scope=e,super.setAccounts().then((()=>{super.setStageRegion()})),super.setStageCloudProvider(),super.setTargets(),super.setStageCredentials(),e.stage.isNew&&e.application.attributes.platformHealthOnlyShowOverride&&e.application.attributes.platformHealthOnly&&(e.stage.interestingHealthProviderNames=[De.PLATFORM])}}Pe.$inject=["$scope"];const Ie="spinnaker.appengine.pipeline.stage.disableAsgStage";e(Ie,[]).config((()=>{u.pipeline.registerStage({provides:"disableServerGroup",key:"disableServerGroup",cloudProvider:"appengine",templateUrl:"appengine/src/pipeline/stages/disableAsg/disableAsgStage.html",executionStepLabelUrl:"appengine/src/pipeline/stages/disableAsg/disableAsgStepLabel.html",validators:[{type:"targetImpedance",message:"This pipeline will attempt to disable a server group without deploying a new version into the same cluster."},{type:"requiredField",fieldName:"cluster"},{type:"requiredField",fieldName:"target"},{type:"requiredField",fieldName:"credentials",fieldLabel:"account"}]})})).controller("appengineDisableAsgStageCtrl",Pe),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/disableAsg/disableAsgStage.html",'<div ng-controller="appengineDisableAsgStageCtrl as disableAsgStageCtrl" class="form-horizontal">\n <div ng-if="!pipeline.strategy">\n <account-region-cluster-selector\n application="application"\n component="stage"\n single-region="true"\n disable-region-select="true"\n on-account-update="disableAsgStageCtrl.setStageRegion()"\n accounts="accounts"\n >\n </account-region-cluster-selector>\n </div>\n <stage-config-field label="Target">\n <target-select model="stage" options="targets"></target-select>\n </stage-config-field>\n <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">\n </stage-platform-health-override>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/disableAsg/disableAsgStepLabel.html",'<span class="task-label"> Disable Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>\n')}]);class Oe{constructor(e,n,t){this.$uibModal=e,this.$uibModalInstance=n,this.application=t,this.state={loading:!0},this.initialize()}submit(){const e=m.getValue("appengine","loadBalancer"),n=this.$uibModal.open({templateUrl:e.createLoadBalancerTemplateUrl,controller:`${e.createLoadBalancerController} as ctrl`,size:"lg",resolve:{application:()=>this.application,loadBalancer:()=>N(this.selectedLoadBalancer),isNew:()=>!1,forPipelineConfig:()=>!0}}).result;this.$uibModalInstance.close(n)}cancel(){this.$uibModalInstance.dismiss()}initialize(){this.application.getDataSource("loadBalancers").ready().then((()=>{this.loadBalancers=this.application.loadBalancers.data.filter((e=>"appengine"===e.cloudProvider)),this.loadBalancers.length&&(this.selectedLoadBalancer=this.loadBalancers[0]),this.state.loading=!1}))}}Oe.$inject=["$uibModal","$uibModalInstance","application"];const Ne="spinnaker.appengine.loadBalancerChoiceModal.controller";e(Ne,[]).controller("appengineLoadBalancerChoiceModelCtrl",Oe);class ze{constructor(e,n){this.$scope=e,this.$uibModal=n,e.stage.loadBalancers=e.stage.loadBalancers||[],e.stage.cloudProvider="appengine"}addLoadBalancer(){this.$uibModal.open({templateUrl:"appengine/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html",controller:"appengineLoadBalancerChoiceModelCtrl as ctrl",resolve:{application:()=>this.$scope.application}}).result.then((e=>{this.$scope.stage.loadBalancers.push(e)})).catch((()=>{}))}editLoadBalancer(e){const n=m.getValue("appengine","loadBalancer");this.$uibModal.open({templateUrl:n.createLoadBalancerTemplateUrl,controller:`${n.createLoadBalancerController} as ctrl`,size:"lg",resolve:{application:()=>this.$scope.application,loadBalancer:()=>N(this.$scope.stage.loadBalancers[e]),isNew:()=>!1,forPipelineConfig:()=>!0}}).result.then((n=>{this.$scope.stage.loadBalancers[e]=n})).catch((()=>{}))}removeLoadBalancer(e){this.$scope.stage.loadBalancers.splice(e,1)}}ze.$inject=["$scope","$uibModal"];const Re="spinnaker.appengine.pipeline.stage.editLoadBalancerStage";e(Re,[Ne]).config((()=>{u.pipeline.registerStage({label:"Edit Load Balancer",description:"Edits a load balancer",key:"upsertAppEngineLoadBalancers",cloudProvider:"appengine",templateUrl:"appengine/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html",executionDetailsUrl:"appengine/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html",executionConfigSections:["editLoadBalancerConfig","taskStatus"],controller:"appengineEditLoadBalancerStageCtrl",controllerAs:"editLoadBalancerStageCtrl",validators:[]})})).controller("appengineEditLoadBalancerStageCtrl",ze),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html",'<div modal-page>\n <modal-close dismiss="$dismiss()"></modal-close>\n <div class="modal-header">\n <h4 class="modal-title">Select Load Balancer</h4>\n </div>\n <div class="modal-body" ng-if="ctrl.state.loading" style="height: 200px" class="horizontal center middle">\n <loading-spinner size="\'small\'"></loading-spinner>\n </div>\n <div class="modal-body" ng-if="!ctrl.state.loading">\n <div class="alert alert-warning" ng-if="ctrl.loadBalancers.length === 0">\n <p>This application has no App Engine load balancers.</p>\n </div>\n <form\n role="form"\n name="form"\n class="form-horizontal"\n ng-submit="ctrl.submit()"\n ng-if="ctrl.loadBalancers.length > 0"\n >\n <div class="form-group">\n <div class="col-md-3 sm-label-right">\n <b>Load Balancer</b>\n </div>\n <div class="col-md-7">\n <ui-select class="form-control input-sm" ng-model="ctrl.selectedLoadBalancer">\n <ui-select-match>\n <account-tag account="$select.selected.account"></account-tag>\n <span style="margin-left: 5px">{{$select.selected.name}}</span>\n </ui-select-match>\n <ui-select-choices repeat="loadBalancer in ctrl.loadBalancers | filter: $select.search">\n <account-tag account="loadBalancer.account"></account-tag>\n <span style="margin-left: 5px" ng-bind-html="loadBalancer.name"></span>\n </ui-select-choices>\n </ui-select>\n </div>\n </div>\n </form>\n </div>\n <div class="modal-footer">\n <button class="btn btn-default" ng-click="ctrl.cancel()">Cancel</button>\n <button class="btn btn-primary" ng-if="ctrl.loadBalancers.length > 0" ng-click="ctrl.submit()">\n <span class="far fa-check-circle"></span> Edit\n </button>\n </div>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html",'<div class="well well-sm clearfix" ng-if="!pipeline.strategy">\n <div class="row">\n <div class="col-md-12">\n <h4 class="text-left">Load Balancers</h4>\n </div>\n </div>\n <div class="row">\n <div class="col-md-12">\n <table class="table table-condensed">\n <thead>\n <tr>\n <th>Account</th>\n <th>Name</th>\n <th>Region</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n <tr ng-repeat="loadBalancer in stage.loadBalancers">\n <td>\n <account-tag account="loadBalancer.credentials"></account-tag>\n </td>\n <td>{{ loadBalancer.name }}</td>\n <td>{{ loadBalancer.region }}</td>\n <td class="condensed-actions">\n <a class="btn btn-sm btn-link" href ng-click="editLoadBalancerStageCtrl.editLoadBalancer($index)">\n <span class="glyphicon glyphicon-edit" uib-tooltip="Edit"></span\n ></a>\n <a\n class="btn btn-sm btn-link pad-left"\n href\n ng-click="editLoadBalancerStageCtrl.removeLoadBalancer($index)"\n >\n <span class="glyphicon glyphicon-trash" uib-tooltip="Remove"></span>\n </a>\n </td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td colspan="8">\n <button class="btn btn-block btn-sm add-new" ng-click="editLoadBalancerStageCtrl.addLoadBalancer()">\n <span class="glyphicon glyphicon-plus-sign"></span> Add load balancer\n </button>\n </td>\n </tr>\n </tfoot>\n </table>\n </div>\n </div>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html",'<div ng-controller="BaseExecutionDetailsCtrl">\n <execution-details-section-nav sections="configSections"></execution-details-section-nav>\n <div class="step-section-details" ng-if="detailsSection === \'editLoadBalancerConfig\'">\n <div class="row">\n <div class="col-md-12">\n <table class="table table-condensed">\n <thead>\n <tr>\n <th>Account</th>\n <th>Name</th>\n <th>Region</th>\n </tr>\n </thead>\n <tbody>\n <tr ng-repeat="loadBalancer in stage.context.loadBalancers">\n <td>\n <account-tag account="loadBalancer.credentials"></account-tag>\n </td>\n <td>{{ loadBalancer.name }}</td>\n <td>{{ loadBalancer.region }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n <stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>\n </div>\n <div class="step-section-details" ng-if="detailsSection === \'taskStatus\'">\n <div class="row">\n <execution-step-details item="stage"></execution-step-details>\n </div>\n </div>\n</div>\n')}]);class Me extends Ee{constructor(e){super(e),this.$scope=e,super.setAccounts().then((()=>{super.setStageRegion()})),super.setStageCloudProvider(),super.setTargets(),super.setStageCredentials(),e.stage.isNew&&e.application.attributes.platformHealthOnlyShowOverride&&e.application.attributes.platformHealthOnly&&(e.stage.interestingHealthProviderNames=[De.PLATFORM])}}Me.$inject=["$scope"];const Ue="spinnaker.appengine.pipeline.stage.enableAsgStage";e(Ue,[]).config((()=>{u.pipeline.registerStage({provides:"enableServerGroup",key:"enableServerGroup",cloudProvider:"appengine",templateUrl:"appengine/src/pipeline/stages/enableAsg/enableAsgStage.html",executionStepLabelUrl:"appengine/src/pipeline/stages/enableAsg/enableAsgStepLabel.html",validators:[{type:"requiredField",fieldName:"cluster"},{type:"requiredField",fieldName:"target"},{type:"requiredField",fieldName:"credentials",fieldLabel:"account"}]})})).controller("appengineEnableAsgStageCtrl",Me),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/enableAsg/enableAsgStage.html",'<div ng-controller="appengineEnableAsgStageCtrl as enableAsgStageCtrl" class="form-horizontal">\n <div ng-if="!pipeline.strategy">\n <account-region-cluster-selector\n application="application"\n component="stage"\n single-region="true"\n disable-region-select="true"\n on-account-update="enableAsgStageCtrl.setStageRegion()"\n accounts="accounts"\n >\n </account-region-cluster-selector>\n </div>\n <stage-config-field label="Target">\n <target-select model="stage" options="targets"></target-select>\n </stage-config-field>\n <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">\n </stage-platform-health-override>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/enableAsg/enableAsgStepLabel.html",'<span class="task-label"> Enable Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>\n')}]);class He extends Ee{constructor(e){super(e),this.$scope=e,super.setAccounts().then((()=>{super.setStageRegion()})),super.setStageCloudProvider(),super.setStageCredentials();const n=e.stage;void 0===n.shrinkToSize&&(n.shrinkToSize=1),void 0===n.allowDeleteActive&&(n.allowDeleteActive=!1),void 0===n.retainLargerOverNewer&&(n.retainLargerOverNewer="false"),n.retainLargerOverNewer=n.retainLargerOverNewer.toString()}pluralize(e,n){return 1===n?e:e+"s"}}He.$inject=["$scope"];const qe="spinnaker.appengine.pipeline.stage.shrinkClusterStage";e(qe,[]).config((function(){u.pipeline.registerStage({provides:"shrinkCluster",key:"shrinkCluster",cloudProvider:"appengine",templateUrl:"appengine/src/pipeline/stages/shrinkCluster/shrinkClusterStage.html",validators:[{type:"requiredField",fieldName:"cluster"},{type:"requiredField",fieldName:"shrinkToSize",fieldLabel:"shrink to [X] Server Groups"},{type:"requiredField",fieldName:"credentials",fieldLabel:"account"}]})})).controller("appengineShrinkClusterStageCtrl",He),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/shrinkCluster/shrinkClusterStage.html",'<div ng-controller="appengineShrinkClusterStageCtrl as shrinkClusterStageCtrl" class="form-horizontal">\n <div ng-if="!pipeline.strategy">\n <account-region-cluster-selector\n application="application"\n component="stage"\n single-region="true"\n disable-region-select="true"\n on-account-update="shrinkClusterStageCtrl.setStageRegion()"\n accounts="accounts"\n >\n </account-region-cluster-selector>\n </div>\n <stage-config-field label="Shrink Options">\n <div class="form-inline">\n Shrink to\n <input\n type="number"\n min="0"\n required\n ng-model="stage.shrinkToSize"\n class="form-control input-sm"\n style="width: 50px"\n />\n {{shrinkClusterStageCtrl.pluralize(\'server group\', stage.shrinkToSize)}}, keeping the\n <select class="form-control input-sm" ng-model="stage.retainLargerOverNewer" style="width: 100px">\n <option value="true">largest</option>\n <option value="false">newest</option>\n </select>\n </div>\n </stage-config-field>\n <div class="form-group">\n <div class="col-md-offset-3 col-md-6 checkbox">\n <label>\n <input type="checkbox" ng-model="stage.allowDeleteActive" />\n Allow deletion of active server groups\n </label>\n </div>\n </div>\n <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">\n </stage-platform-health-override>\n</div>\n')}]);class Ve extends Ee{constructor(e){super(e),this.$scope=e,super.setAccounts().then((()=>{super.setStageRegion()})),super.setStageCloudProvider(),super.setTargets(),super.setStageCredentials(),e.stage.isNew&&e.application.attributes.platformHealthOnlyShowOverride&&e.application.attributes.platformHealthOnly&&(e.stage.interestingHealthProviderNames=[De.PLATFORM])}}Ve.$inject=["$scope"];const je="spinnaker.appengine.pipeline.stage.startServerGroupStage";e(je,[]).config((()=>{u.pipeline.registerStage({label:"Start Server Group",description:"Starts a server group.",key:"startAppEngineServerGroup",templateUrl:"appengine/src/pipeline/stages/startServerGroup/startServerGroupStage.html",executionDetailsUrl:"appengine/src/pipeline/stages/startServerGroup/startServerGroupExecutionDetails.html",executionConfigSections:["startServerGroupConfig","taskStatus"],executionStepLabelUrl:"appengine/src/pipeline/stages/startServerGroup/startServerGroupStepLabel.html",controller:"appengineStartServerGroupStageCtrl",controllerAs:"startServerGroupStageCtrl",validators:[{type:"requiredField",fieldName:"cluster"},{type:"requiredField",fieldName:"target"},{type:"requiredField",fieldName:"credentials",fieldLabel:"account"}],cloudProvider:"appengine"})})).controller("appengineStartServerGroupStageCtrl",Ve),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/startServerGroup/startServerGroupStage.html",'<div ng-controller="appengineStartServerGroupStageCtrl as startServerGroupStageCtrl" class="form-horizontal">\n <div ng-if="!pipeline.strategy">\n <account-region-cluster-selector\n application="application"\n component="stage"\n single-region="true"\n disable-region-select="true"\n on-account-update="startServerGroupStageCtrl.setStageRegion()"\n accounts="accounts"\n >\n </account-region-cluster-selector>\n </div>\n <stage-config-field label="Target">\n <target-select model="stage" options="targets"></target-select>\n </stage-config-field>\n <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">\n </stage-platform-health-override>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/startServerGroup/startServerGroupExecutionDetails.html",'<div ng-controller="BaseExecutionDetailsCtrl">\n <execution-details-section-nav sections="configSections"></execution-details-section-nav>\n <div class="step-section-details" ng-if="detailsSection === \'startServerGroupConfig\'">\n <div class="row">\n <div class="col-md-9">\n <dl class="dl-narrow dl-horizontal">\n <dt>Account</dt>\n <dd>\n <account-tag account="stage.context.credentials"></account-tag>\n </dd>\n <dt>Region</dt>\n <dd>{{stage.context.region}}</dd>\n <dt>Server Group</dt>\n <dd>{{stage.context.serverGroupName}}</dd>\n </dl>\n </div>\n </div>\n <stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>\n </div>\n\n <div class="step-section-details" ng-if="detailsSection === \'taskStatus\'">\n <div class="row">\n <execution-step-details item="stage"></execution-step-details>\n </div>\n </div>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/startServerGroup/startServerGroupStepLabel.html",'<span class="task-label"> Start Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>\n')}]);class We extends Ee{constructor(e){super(e),this.$scope=e,super.setAccounts().then((()=>{super.setStageRegion()})),super.setStageCloudProvider(),super.setTargets(),super.setStageCredentials(),e.stage.isNew&&e.application.attributes.platformHealthOnlyShowOverride&&e.application.attributes.platformHealthOnly&&(e.stage.interestingHealthProviderNames=[De.PLATFORM])}}We.$inject=["$scope"];const _e="spinnaker.appengine.pipeline.stage.stopServerGroupStage";e(_e,[]).config((()=>{u.pipeline.registerStage({label:"Stop Server Group",description:"Stops a server group.",key:"stopAppEngineServerGroup",templateUrl:"appengine/src/pipeline/stages/stopServerGroup/stopServerGroupStage.html",executionDetailsUrl:"appengine/src/pipeline/stages/stopServerGroup/stopServerGroupExecutionDetails.html",executionConfigSections:["stopServerGroupConfig","taskStatus"],executionStepLabelUrl:"appengine/src/pipeline/stages/stopServerGroup/stopServerGroupStepLabel.html",controller:"appengineStopServerGroupStageCtrl",controllerAs:"stopServerGroupStageCtrl",validators:[{type:"requiredField",fieldName:"cluster"},{type:"requiredField",fieldName:"target"},{type:"requiredField",fieldName:"credentials",fieldLabel:"account"}],cloudProvider:"appengine"})})).controller("appengineStopServerGroupStageCtrl",We),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/stopServerGroup/stopServerGroupStage.html",'<div ng-controller="appengineStopServerGroupStageCtrl as stopServerGroupStageCtrl" class="form-horizontal">\n <div ng-if="!pipeline.strategy">\n <account-region-cluster-selector\n application="application"\n component="stage"\n single-region="true"\n disable-region-select="true"\n on-account-update="stopServerGroupStageCtrl.setStageRegion()"\n accounts="accounts"\n >\n </account-region-cluster-selector>\n </div>\n <stage-config-field label="Target">\n <target-select model="stage" options="targets"></target-select>\n </stage-config-field>\n <stage-platform-health-override application="application" stage="stage" platform-health-type="platformHealth">\n </stage-platform-health-override>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/stopServerGroup/stopServerGroupExecutionDetails.html",'<div ng-controller="appengineStopServerGroupExecutionDetailsCtrl">\n <execution-details-section-nav sections="configSections"></execution-details-section-nav>\n <div class="step-section-details" ng-if="detailsSection === \'stopServerGroupConfig\'">\n <div class="row">\n <div class="col-md-9">\n <dl class="dl-narrow dl-horizontal">\n <dt>Account</dt>\n <dd>\n <account-tag account="stage.context.credentials"></account-tag>\n </dd>\n <dt>Region</dt>\n <dd>{{stage.context.region}}</dd>\n <dt>Server Group</dt>\n <dd>{{stage.context.serverGroupName}}</dd>\n </dl>\n </div>\n </div>\n <stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>\n </div>\n\n <div class="step-section-details" ng-if="detailsSection === \'taskStatus\'">\n <div class="row">\n <execution-step-details item="stage"></execution-step-details>\n </div>\n </div>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/pipeline/stages/stopServerGroup/stopServerGroupStepLabel.html",'<span class="task-label"> Stop Server Group: {{step.context.serverGroupName}} ({{step.context.region}}) </span>\n')}]);const Je="spinnaker.appengine.pipeline.module";e(Je,[Le,Ie,Re,Ue,qe,je,_e]);class Ke extends Q.Component{constructor(e){super(e),this.destroy$=new X,this.setRegionList=e=>{const{application:n}=this.props,t=n=>!n||n.account===e;Z(n.ready()).pipe(ee(this.destroy$)).subscribe((()=>{const e=c.getRegions([n],t);e.sort(),this.setState({availableRegions:e})}))},this.accountChanged=e=>{this.setRegionList(e)};const n=e.credentialsField||"credentials";this.state={availableRegions:[],cloudProvider:e.cloudProvider,componentName:e.componentName||"",credentialsField:n}}componentDidMount(){const{componentName:e,formik:n}=this.props,{credentialsField:t}=this.state,a=j(n.values,e?`${e}.${t}`:`${t}`,void 0);this.setRegionList(a)}componentWillUnmount(){this.destroy$.next()}render(){const{accounts:e}=this.props,{credentialsField:n,availableRegions:t,componentName:a}=this.state;return Q.createElement("div",{className:"col-md-9"},Q.createElement("div",{className:"sp-margin-m-bottom"},Q.createElement(h,{name:a?`${a}.${n}`:`${n}`,label:"Account",input:n=>Q.createElement(v,{...n,stringOptions:e&&e.map((e=>e.name)),clearable:!1}),onChange:this.accountChanged,required:!0})),Q.createElement("div",{className:"sp-margin-m-bottom"},Q.createElement(h,{name:a?`${a}.region`:"region",label:"Region",input:e=>Q.createElement(v,{...e,stringOptions:t,clearable:!1}),required:!0})))}}const Ye=class extends Q.Component{constructor(){super(...arguments),this.destroy$=new X,this.state={accounts:[]},this.onTemplateArtifactEdited=(e,n)=>{this.props.formik.setFieldValue(`${n}.id`,null),this.props.formik.setFieldValue(`${n}.artifact`,e),this.props.formik.setFieldValue(`${n}.account`,e.artifactAccount)},this.onTemplateArtifactSelected=(e,n)=>{this.props.formik.setFieldValue(`${n}.id`,e),this.props.formik.setFieldValue(`${n}.artifact`,null)},this.removeInputArtifact=e=>{this.props.formik.setFieldValue(e,null)},this.getInputArtifact=(e,n)=>e[n]?e[n]:{account:"",id:""}}componentDidMount(){Z(g.listAccounts("appengine")).pipe(ee(this.destroy$)).subscribe((e=>this.setState({accounts:e})))}render(){const e=this.props.formik.values,n=this.state.accounts;return Q.createElement("div",null,Q.createElement("div",{className:"col-md-offset-0 col-md-9"},Q.createElement("h4",null,"Basic Settings")),Q.createElement("div",null,Q.createElement(Ke,{componentName:"",accounts:n,application:this.props.application,cloudProvider:"appengine",credentialsField:"account",formik:this.props.formik})),Q.createElement("div",{className:"col-md-offset-0 col-md-9"},Q.createElement("h4",null,"Configuration Settings")),Q.createElement("div",null,Q.createElement("div",{className:"col-md-offset-1 col-md-9"},Q.createElement(f,{artifact:this.getInputArtifact(e,"cronArtifact").artifact,excludedArtifactTypePatterns:Ye.excludedArtifactTypes,expectedArtifactId:this.getInputArtifact(e,"cronArtifact").id,label:"Cron Artifact",onArtifactEdited:e=>{this.onTemplateArtifactEdited(e,"cronArtifact")},helpKey:"",onExpectedArtifactSelected:e=>this.onTemplateArtifactSelected(e.id,"cronArtifact"),pipeline:this.props.pipeline,stage:e})),Q.createElement("div",{className:"col-md-1"},Q.createElement("div",{className:"form-control-static"},Q.createElement("button",{onClick:()=>this.removeInputArtifact("cronArtifact")},Q.createElement("span",{className:"glyphicon glyphicon-trash"}),Q.createElement("span",{className:"sr-only"},"Remove field"))))),Q.createElement("div",null,Q.createElement("div",{className:"col-md-offset-1 col-md-9"},Q.createElement(f,{artifact:this.getInputArtifact(e,"dispatchArtifact").artifact,excludedArtifactTypePatterns:Ye.excludedArtifactTypes,expectedArtifactId:this.getInputArtifact(e,"dispatchArtifact").id,label:"Dispatch Artifact",onArtifactEdited:e=>{this.onTemplateArtifactEdited(e,"dispatchArtifact")},helpKey:"",onExpectedArtifactSelected:e=>this.onTemplateArtifactSelected(e.id,"dispatchArtifact"),pipeline:this.props.pipeline,stage:e})),Q.createElement("div",{className:"col-md-1"},Q.createElement("div",{className:"form-control-static"},Q.createElement("button",{onClick:()=>this.removeInputArtifact("dispatchArtifact")},Q.createElement("span",{className:"glyphicon glyphicon-trash"}),Q.createElement("span",{className:"sr-only"},"Remove field"))))),Q.createElement("div",null,Q.createElement("div",{className:"col-md-offset-1 col-md-9"},Q.createElement(f,{artifact:this.getInputArtifact(e,"indexArtifact").artifact,excludedArtifactTypePatterns:Ye.excludedArtifactTypes,expectedArtifactId:this.getInputArtifact(e,"indexArtifact").id,label:"Index Artifact",onArtifactEdited:e=>{this.onTemplateArtifactEdited(e,"indexArtifact")},helpKey:"",onExpectedArtifactSelected:e=>this.onTemplateArtifactSelected(e.id,"indexArtifact"),pipeline:this.props.pipeline,stage:e})),Q.createElement("div",{className:"col-md-1"},Q.createElement("div",{className:"form-control-static"},Q.createElement("button",{onClick:()=>this.removeInputArtifact("indexArtifact")},Q.createElement("span",{className:"glyphicon glyphicon-trash"}),Q.createElement("span",{className:"sr-only"},"Remove field"))))),Q.createElement("div",null,Q.createElement("div",{className:"col-md-offset-1 col-md-9"},Q.createElement(f,{artifact:this.getInputArtifact(e,"queueArtifact").artifact,excludedArtifactTypePatterns:Ye.excludedArtifactTypes,expectedArtifactId:this.getInputArtifact(e,"queueArtifact").id,label:"Queue Artifact",onArtifactEdited:e=>{this.onTemplateArtifactEdited(e,"queueArtifact")},helpKey:"",onExpectedArtifactSelected:e=>this.onTemplateArtifactSelected(e.id,"queueArtifact"),pipeline:this.props.pipeline,stage:e})),Q.createElement("div",{className:"col-md-1"},Q.createElement("div",{className:"form-control-static"},Q.createElement("button",{onClick:()=>this.removeInputArtifact("queueArtifact")},Q.createElement("span",{className:"glyphicon glyphicon-trash"}),Q.createElement("span",{className:"sr-only"},"Remove field"))))))}};let Qe=Ye;function Xe(e){const n=new S(e);return n.field("account").required(),n.field("region").required(),n.validateForm()}Qe.excludedArtifactTypes=b(y.BITBUCKET_FILE,y.CUSTOM_OBJECT,y.EMBEDDED_BASE64,y.GCS_OBJECT,y.GITHUB_FILE,y.GITLAB_FILE,y.S3_OBJECT,y.HTTP_FILE,y.ORACLE_OBJECT);u.pipeline.registerStage({label:"Deploy App Engine Configuration",description:"Deploy index, dispatch, cron, and queue configuration to App Engine.",key:"deployAppEngineConfiguration",component:function({application:e,pipeline:n,stage:t,updateStage:a}){const i=Q.useMemo((()=>({...N(t)})),[]);return Q.createElement(C,{application:e,onChange:a,pipeline:n,stage:i,validate:Xe,render:e=>Q.createElement(Qe,{...e})})},producesArtifacts:!1,cloudProvider:"appengine",executionDetailsSections:[w,G],validateFn:Xe});const Ze=A.providers.appengine||{defaults:{}};var en,nn;Ze&&(Ze.resetToOriginal=A.resetProvider("appengine")),(nn=en||(en={})).GCS="gcs",nn.GIT="git",nn.ARTIFACT="artifact",nn.CONTAINER_IMAGE="containerImage";const tn=class{constructor(e){this.$q=e}static getTriggerOptions(e){return(e.triggers||[]).filter((e=>"git"===e.type||"jenkins"===e.type||"travis"===e.type)).map((e=>"git"===e.type?{source:e.source,project:e.project,slug:e.slug,branch:e.branch,type:"git"}:{master:e.master,job:e.job,type:e.type}))}static getExpectedArtifacts(e){return e.expectedArtifacts||[]}buildNewServerGroupCommand(e,n,t="create"){null==n&&(n="appengine");const a={accounts:g.getAllAccountDetailsForProvider("appengine"),storageAccounts:k.getStorageAccounts()},i={mode:t,submitButtonLabel:this.getSubmitButtonLabel(t),disableStrategySelection:"create"===t};return this.$q.all(a).then((t=>{const a=this.getCredentials(t.accounts),r=this.getRegion(t.accounts,a);return{application:e.name,backingData:t,viewState:i,fromArtifact:!1,credentials:a,region:r,selectedProvider:n,interestingHealthProviderNames:[],sourceType:en.GIT}}))}buildServerGroupCommandFromExisting(e,n){return this.buildNewServerGroupCommand(e,"appengine","clone").then((e=>(e.stack=n.stack,e.freeFormDetails=n.detail,e)))}buildNewServerGroupCommandForPipeline(e,n){return this.$q.when({viewState:{pipeline:n,stage:e},backingData:{triggerOptions:tn.getTriggerOptions(n),expectedArtifacts:tn.getExpectedArtifacts(n)}})}buildServerGroupCommandFromPipeline(e,n,t,a){return this.buildNewServerGroupCommand(e,"appengine","editPipeline").then((e=>e={...e,...n,backingData:{...e.backingData,triggerOptions:tn.getTriggerOptions(a),expectedArtifacts:tn.getExpectedArtifacts(a)},credentials:n.account||e.credentials,viewState:{...e.viewState,stage:t,pipeline:a}}))}getCredentials(e){const n=(e||[]).map((e=>e.name)),t=Ze.defaults.account;return n.includes(t)?t:n[0]}getRegion(e,n){const t=e.find((e=>e.name===n));return t?t.region:null}getSubmitButtonLabel(e){switch(e){case"createPipeline":return"Add";case"editPipeline":return"Done";case"clone":return"Clone";default:return"Create"}}};let an=tn;an.$inject=["$q"];const rn="spinnaker.appengine.serverGroupCommandBuilder.service";e(rn,[]).service("appengineServerGroupCommandBuilder",an);class on{constructor(e,t,a,i){this.$scope=e,this.excludedGcsArtifactTypes=b(y.GCS_OBJECT),this.excludedContainerArtifactTypes=b(y.DOCKER_IMAGE),this.onExpectedArtifactEdited=e=>{this.$scope.$applyAsync((()=>{this.$scope.command.expectedArtifactId=null,this.$scope.command.expectedArtifact=e}))},this.onExpectedArtifactSelected=e=>{this.onChangeExpectedArtifactId(e.id)},this.onChangeExpectedArtifactId=e=>{this.$scope.$applyAsync((()=>{this.$scope.command.expectedArtifactId=e,this.$scope.command.expectedArtifact=null}))},this.onExpectedArtifactAccountSelected=e=>{this.$scope.$applyAsync((()=>{this.$scope.command.storageAccountName=e}))},n(this,a("BasicSettingsMixin",{$scope:e,imageReader:null,$uibModalStack:i,$state:t})),this.$scope.command.gitCredentialType||this.onAccountChange(),this.$scope.containerArtifactDelegate=new $(e,[y.DOCKER_IMAGE]),this.$scope.containerArtifactController=new B(this.$scope.containerArtifactDelegate),this.$scope.gcsArtifactDelegate=new $(e,[y.GCS_OBJECT]),this.$scope.gcsArtifactController=new B(this.$scope.gcsArtifactDelegate)}isGitSource(){return this.$scope.command.sourceType===en.GIT}isGcsSource(){return this.$scope.command.sourceType===en.GCS}isContainerImageSource(){return this.$scope.command.sourceType===en.CONTAINER_IMAGE}toggleResolveViaTrigger(){this.$scope.command.fromTrigger=!this.$scope.command.fromTrigger,delete this.$scope.command.trigger,delete this.$scope.command.branch}onTriggerChange(){_(this,"$scope.command.trigger.matchBranchOnRegex",void 0)}onAccountChange(){const e=this.findAccountInBackingData();e?(this.$scope.command.gitCredentialType=this.getSupportedGitCredentialTypes()[0],this.$scope.command.region=e.region):(this.$scope.command.gitCredentialType="NONE",delete this.$scope.command.region)}getSupportedGitCredentialTypes(){const e=this.findAccountInBackingData();return e&&e.supportedGitCredentialTypes?e.supportedGitCredentialTypes:["NONE"]}humanReadableGitCredentialType(e){switch(e){case"HTTPS_USERNAME_PASSWORD":return"HTTPS with username and password";case"HTTPS_GITHUB_OAUTH_TOKEN":return"HTTPS with Github OAuth token";case"SSH":return"SSH";default:return"No credentials"}}findAccountInBackingData(){return this.$scope.command.backingData.accounts.find((e=>this.$scope.command.credentials===e.name))}}on.$inject=["$scope","$state","$controller","$uibModalStack"];const ln="spinnaker.appengine.basicSettings.controller";e(ln,[]).controller("appengineServerGroupBasicSettingsCtrl",on);Ae(".appengine-server-group-wizard input[type='checkbox'] {\n margin-top: 0.75rem;\n}\n.appengine-server-group-wizard help-field.help-field-absolute span {\n position: absolute;\n top: 7px;\n right: 2px;\n}\n.appengine-server-group-wizard .artifact-configuration-section {\n border-bottom: 1px solid #eee;\n padding: 0 0 4px 0;\n margin: 0 0 5px 0;\n}\n.appengine-server-group-wizard .artifact-configuration-section.last-entry {\n border-bottom: none;\n}\n.appengine-server-group-wizard .artifact-configuration-section .Select,\n.appengine-server-group-wizard .artifact-configuration-section input {\n margin: 0 0 1px;\n}\n");class sn{constructor(e,n={id:"",account:""}){var t;const a={configurable:!1,enumerable:!1,writable:!1};this.id=null==n?void 0:n.id,this.account=n.account||(null==(t=null==n?void 0:n.artifact)?void 0:t.artifactAccount),this.artifact=null==n?void 0:n.artifact,Object.defineProperty(this,"$scope",{...a,value:e});const i=new x(this),r=new B(i);Object.defineProperty(this,"delegate",{...a,value:i}),Object.defineProperty(this,"controller",{...a,value:r})}}class cn{constructor(e){this.$scope=e,this.artifactAccounts=[],this.updateConfigArtifacts=e=>{this.$scope.$applyAsync((()=>{this.command.configArtifacts=e}))}}$onInit(){this.command.configFiles||(this.command.configFiles=[]),this.command.configArtifacts||(this.command.configArtifacts=[]),this.$scope.command||(this.$scope.command=this.command),this.command.configArtifacts=this.command.configArtifacts.map((e=>new sn(this.$scope,e))),g.getArtifactAccounts().then((e=>{this.artifactAccounts=e,this.command.configArtifacts.forEach((n=>{n.delegate.setAccounts(e),n.controller.updateAccounts(n.delegate.getSelectedExpectedArtifact())}))}))}addConfigFile(){this.command.configFiles.push("")}addConfigArtifact(){const e=new sn(this.$scope,{id:"",account:""});e.delegate.setAccounts(this.artifactAccounts),e.controller.updateAccounts(e.delegate.getSelectedExpectedArtifact()),this.command.configArtifacts.push(e)}deleteConfigFile(e){this.command.configFiles.splice(e,1)}deleteConfigArtifact(e){this.command.configArtifacts.splice(e,1)}mapTabToSpaces(e){if(9===e.which){e.preventDefault();const n=e.target.selectionStart,t=e.target.value;e.target.value=`${t.substring(0,n)} ${t.substring(n)}`,e.target.selectionStart+=2}}isContainerImageSource(){return this.command.sourceType===en.CONTAINER_IMAGE}}cn.$inject=["$scope"];const pn={bindings:{command:"="},controller:cn,templateUrl:"appengine/src/serverGroup/configure/wizard/configFiles.component.html"},dn="spinnaker.appengine.configFileConfigurer.component";e(dn,[]).component("appengineConfigFileConfigurer",pn),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/serverGroup/configure/wizard/configFiles.component.html",'<div class="form-horizontal container-fluid">\n <div class="form-group" ng-if="!$ctrl.isContainerImageSource()">\n <div class="col-md-3 sm-label-right">\n Application Root\n <help-field class="help-field-absolute" key="appengine.serverGroup.applicationDirectoryRoot"></help-field>\n </div>\n <div class="col-md-7">\n <input\n type="text"\n class="form-control input-sm"\n name="applicationDirectoryRoot"\n ng-model="$ctrl.command.applicationDirectoryRoot"\n />\n </div>\n </div>\n\n <div class="form-group" ng-if="!$ctrl.isContainerImageSource()">\n <div class="col-md-3 sm-label-right">\n Config Filepaths <help-field key="appengine.serverGroup.configFilepaths"></help-field>\n </div>\n <div class="col-md-7">\n <ui-select\n multiple\n tagging\n tagging-label=""\n style="width: 380px"\n name="configFilepaths"\n ng-model="$ctrl.command.configFilepaths"\n class="form-control input-sm"\n >\n <ui-select-match>{{ $item }}</ui-select-match>\n <ui-select-choices repeat="filepath in []">\n <span ng-bind-html="filepath"></span>\n </ui-select-choices>\n </ui-select>\n </div>\n </div>\n\n <div class="form-group">\n <div class="col-md-3 sm-label-right">\n Config Files\n <span ng-if="!$ctrl.isContainerImageSource()">\n <help-field key="appengine.serverGroup.configFiles"></help-field>\n </span>\n <span ng-if="$ctrl.isContainerImageSource()">\n <help-field key="appengine.serverGroup.configFilesRequired"></help-field>\n </span>\n </div>\n <div ng-repeat="configFile in $ctrl.command.configFiles track by $index">\n <div class="col-md-7" ng-class="{ \'col-md-offset-3\': $index > 0 }" style="margin-top: 5px">\n <textarea\n cols="46"\n rows="10"\n class="small"\n spellcheck="false"\n ng-keydown="$ctrl.mapTabToSpaces($event)"\n style="font-family: Menlo, Monaco, Consolas, \'Courier New\', monospace"\n ng-model="$ctrl.command.configFiles[$index]"\n ></textarea>\n </div>\n <div class="col-md-1" style="margin-top: 5px">\n <button type="button" class="btn btn-sm btn-default" ng-click="$ctrl.deleteConfigFile($index)">\n <span class="glyphicon glyphicon-trash"></span> Delete\n </button>\n </div>\n </div>\n <div class="col-md-7" ng-class="{ \'col-md-offset-3\': $ctrl.command.configFiles.length > 0 }">\n <button class="btn btn-block btn-add-trigger add-new" ng-click="$ctrl.addConfigFile()">\n <span class="glyphicon glyphicon-plus-sign"></span> Add Config File\n </button>\n </div>\n <config-file-artifact-list\n ng-if="$ctrl.command.viewState.pipeline"\n config-artifacts="$ctrl.command.configArtifacts"\n pipeline="$ctrl.command.viewState.pipeline"\n stage="$ctrl.command.viewState.stage"\n update-config-artifacts="$ctrl.updateConfigArtifacts"\n >\n </config-file-artifact-list>\n </div>\n</div>\n')}]);const gn={bindings:{trigger:"<"},template:'\n <span ng-if="$ctrl.trigger.type === \'git\'">\n Resolved at runtime by <b>{{$ctrl.trigger.source}}</b> trigger: {{$ctrl.trigger.project}}/{{$ctrl.trigger.slug}}<span ng-if="$ctrl.trigger.branch">:{{$ctrl.trigger.branch}}</span>\n </span>\n <span ng-if="$ctrl.trigger.type === \'jenkins\'">\n Resolved at runtime by <b>Jenkins</b> trigger: {{$ctrl.trigger.master}}/{{$ctrl.trigger.job}}\n </span>\n '},un="spinnaker.appengine.dynamicBranchLabel.component";e(un,[]).component("appengineDynamicBranchLabel",gn);class mn{constructor(e,n,t,a,i,r){this.$scope=e,this.$uibModalInstance=n,this.serverGroupCommand=t,this.application=a,this.serverGroupWriter=i,this.pages={basicSettings:"appengine/src/serverGroup/configure/wizard/basicSettings.html",advancedSettings:"appengine/src/serverGroup/configure/wizard/advancedSettings.html"},this.state={loading:!0},["create","clone","editPipeline"].includes(j(t,"viewState.mode"))?(this.$scope.command=t,this.state.loading=!1,this.initialize()):r.buildNewServerGroupCommand(a,"appengine","createPipeline").then((e=>{this.$scope.command=J(e,t),this.$scope.command.viewState.pipeline=t.viewState.pipeline,this.$scope.command.viewState.stage=t.viewState.stage,this.state.loading=!1,this.initialize()}))}cancel(){this.$uibModalInstance.dismiss()}submit(){const e=this.$scope.command.viewState.mode;if(["editPipeline","createPipeline"].includes(e))return this.$uibModalInstance.close(this.$scope.command);{const e=t(this.$scope.command);e.viewState.mode="create";const n=()=>this.serverGroupWriter.cloneServerGroup(e,this.$scope.application);return this.taskMonitor.submit(n),null}}initialize(){this.$scope.application=this.application,this.taskMonitor=new d({application:this.application,title:"Creating your server group",modalInstance:this.$uibModalInstance}),this.$scope.showPlatformHealthOnlyOverride=this.application.attributes.platformHealthOnlyShowOverride,this.$scope.platformHealth=De.PLATFORM,this.application.attributes.platformHealthOnly&&(this.$scope.command.interestingHealthProviderNames=[De.PLATFORM])}}mn.$inject=["$scope","$uibModalInstance","serverGroupCommand","application","serverGroupWriter","appengineServerGroupCommandBuilder"];const hn="spinnaker.appengine.cloneServerGroup.controller";e(hn,[T,un,dn]).controller("appengineCloneServerGroupCtrl",mn),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/serverGroup/configure/wizard/basicSettings.html",'<div class="container-fluid form-horizontal" ng-controller="appengineServerGroupBasicSettingsCtrl as basicSettingsCtrl">\n <ng-form name="basicSettings">\n <div class="form-group">\n <div class="col-md-3 sm-label-right">Account</div>\n <div class="col-md-7">\n <account-select-field\n read-only="command.viewState.readOnlyFields.credentials"\n component="command"\n field="credentials"\n on-change="basicSettingsCtrl.onAccountChange()"\n accounts="command.backingData.accounts"\n provider="\'appengine\'"\n ></account-select-field>\n </div>\n </div>\n\n <div class="form-group">\n <div class="col-md-3 sm-label-right">Region</div>\n <div class="col-md-7">\n <input type="text" readonly class="form-control input-sm" name="region" ng-model="command.region" />\n </div>\n </div>\n\n <div class="form-group">\n <div class="col-md-3 sm-label-right">\n Stack\n <help-field key="aws.serverGroup.stack"></help-field>\n </div>\n <div class="col-md-7">\n <input\n type="text"\n class="form-control input-sm no-spel"\n ng-pattern="basicSettingsCtrl.stackPattern"\n name="stack"\n ng-model="command.stack"\n />\n </div>\n </div>\n <div class="form-group row slide-in" ng-if="basicSettings.stack.$error.pattern">\n <div class="col-sm-9 col-sm-offset-2 error-message">\n <span>Only dot(.) and underscore(_) special characters are allowed in the Stack field.</span>\n </div>\n </div>\n <div class="form-group">\n <div class="col-md-3 sm-label-right">\n Detail\n <help-field key="aws.serverGroup.detail"></help-field>\n </div>\n <div class="col-md-7">\n <input\n type="text"\n class="form-control input-sm no-spel"\n ng-pattern="basicSettingsCtrl.detailPattern"\n name="details"\n ng-model="command.freeFormDetails"\n />\n </div>\n </div>\n\n <div class="form-group row slide-in" ng-if="basicSettings.details.$error.pattern">\n <div class="col-sm-9 col-sm-offset-2 error-message">\n <span>Only dot(.), underscore(_), and dash(-) special characters are allowed in the Detail field.</span>\n </div>\n </div>\n\n <div class="form-group row">\n <label class="col-md-3 sm-label-right">Source Type</label>\n <div class="col-md-7">\n <div class="radio radio-inline">\n <label> <input type="radio" ng-model="command.sourceType" value="git" /> Git </label>\n </div>\n <div class="radio radio-inline">\n <label> <input type="radio" ng-model="command.sourceType" value="gcs" /> GCS </label>\n </div>\n <div class="radio radio-inline">\n <label> <input type="radio" ng-model="command.sourceType" value="containerImage" /> Container Image </label>\n </div>\n </div>\n </div>\n\n <div ng-if="basicSettingsCtrl.isGcsSource()">\n <div class="form-group row">\n <label class="col-md-3 sm-label-right">Resolve URL</label>\n <div class="col-md-7">\n <div class="radio radio-inline">\n <label> <input type="radio" ng-model="command.fromArtifact" ng-value="false" /> via text input </label>\n </div>\n <div class="radio radio-inline" ng-if="command.viewState.pipeline">\n <label>\n <input type="radio" ng-model="command.fromArtifact" ng-value="true" /> via pipeline artifact\n </label>\n </div>\n </div>\n </div>\n <stage-artifact-selector-delegate\n ng-if="command.fromArtifact"\n artifact="command.expectedArtifact"\n excluded-artifact-type-patterns="basicSettingsCtrl.excludedGcsArtifactTypes"\n expected-artifact-id="command.expectedArtifactId"\n field-columns="7"\n label="\'Expected Artifact\'"\n on-artifact-edited="basicSettingsCtrl.onExpectedArtifactEdited"\n on-expected-artifact-selected="basicSettingsCtrl.onExpectedArtifactSelected"\n pipeline="command.viewState.pipeline"\n stage="command.viewState.stage"\n >\n </stage-artifact-selector-delegate>\n <div class="form-group" ng-if="!command.fromArtifact">\n <div class="col-md-3 sm-label-right">\n GCS URL\n <help-field class="help-field-absolute" key="appengine.serverGroup.gcs.repositoryUrl"></help-field>\n </div>\n <div class="col-md-7">\n <input type="text" required class="form-control input-sm" name="gcsUrl" ng-model="command.repositoryUrl" />\n </div>\n </div>\n </div>\n\n <div ng-if="basicSettingsCtrl.isGitSource()">\n <div class="form-group">\n <div class="col-md-3 sm-label-right">\n Git Repository URL\n <help-field class="help-field-absolute" key="appengine.serverGroup.git.repositoryUrl"></help-field>\n </div>\n <div class="col-md-7">\n <input type="text" required class="form-control input-sm" name="gitRepo" ng-model="command.repositoryUrl" />\n </div>\n </div>\n\n <div class="form-group">\n <div class="col-md-3 sm-label-right">\n Git Credential Type\n <help-field class="help-field-absolute" key="appengine.serverGroup.gitCredentialType"></help-field>\n </div>\n <div class="col-md-7">\n <select\n class="form-control input-sm"\n ng-options="basicSettingsCtrl.humanReadableGitCredentialType(type) for type in basicSettingsCtrl.getSupportedGitCredentialTypes()"\n ng-model="command.gitCredentialType"\n ></select>\n </div>\n </div>\n\n <div class="form-group">\n <div class="col-md-3 sm-label-right">Branch <help-field key="appengine.serverGroup.branch"></help-field></div>\n <div class="col-md-7">\n <input\n ng-if="!command.fromTrigger"\n type="text"\n required\n class="form-control input-sm"\n name="branch"\n ng-model="command.branch"\n />\n\n <ui-select\n ng-if="command.fromTrigger"\n ng-model="command.trigger"\n class="form-control input-sm"\n on-select="basicSettingsCtrl.onTriggerChange()"\n required\n >\n <ui-select-match allow-clear>\n <appengine-dynamic-branch-label trigger="command.trigger"></appengine-dynamic-branch-label>\n </ui-select-match>\n <ui-select-choices repeat="trigger in command.backingData.triggerOptions">\n <appengine-dynamic-branch-label trigger="trigger"></appengine-dynamic-branch-label>\n </ui-select-choices>\n </ui-select>\n </div>\n\n <div\n class="col-md-7 col-md-offset-3"\n ng-if="command.viewState.mode === \'createPipeline\' || command.viewState.mode === \'editPipeline\'"\n >\n <span class="pull-right small" ng-if="!command.fromTrigger">\n <a href ng-click="basicSettingsCtrl.toggleResolveViaTrigger()">Resolve via trigger</a>\n </span>\n <span class="pull-right small" ng-if="command.fromTrigger">\n <a href ng-click="basicSettingsCtrl.toggleResolveViaTrigger()">Click for text input</a>\n </span>\n </div>\n </div>\n </div>\n\n <div ng-if="basicSettingsCtrl.isContainerImageSource()">\n <div class="form-group">\n <label class="col-md-3 sm-label-right">Resolve URL</label>\n <div class="col-md-7">\n <div class="radio radio-inline">\n <label> <input type="radio" ng-model="command.fromArtifact" ng-value="false" /> via text input </label>\n </div>\n <div class="radio radio-inline" ng-if="command.viewState.pipeline">\n <label>\n <input type="radio" ng-model="command.fromArtifact" ng-value="true" /> via pipeline artifact\n </label>\n </div>\n </div>\n </div>\n <stage-artifact-selector-delegate\n ng-if="command.fromArtifact"\n artifact="command.expectedArtifact"\n excluded-artifact-type-patterns="basicSettingsCtrl.excludedContainerArtifactTypes"\n expected-artifact-id="command.expectedArtifactId"\n field-columns="7"\n label="\'Expected Artifact\'"\n on-artifact-edited="basicSettingsCtrl.onExpectedArtifactEdited"\n on-expected-artifact-selected="basicSettingsCtrl.onExpectedArtifactSelected"\n pipeline="command.viewState.pipeline"\n stage="command.viewState.stage"\n >\n </stage-artifact-selector-delegate>\n <div class="form-group" ng-if="!command.fromArtifact">\n <div class="col-md-3 sm-label-right">\n Image URL\n <help-field key="appengine.serverGroup.containerImageUrl"></help-field>\n </div>\n <div class="col-md-7">\n <input\n type="text"\n required\n class="form-control input-sm"\n name="containerImageUrl"\n ng-model="command.containerImageUrl"\n />\n </div>\n </div>\n </div>\n\n <div ng-if="command.trigger.type === \'jenkins\'" class="form-group">\n <div class="col-md-7 col-md-offset-3">\n <div class="form-inline">\n <small>Match branch from trigger on regex</small>\n <help-field key="appengine.serverGroup.matchBranchOnRegex"></help-field>\n <input\n type="text"\n style="width: 140px"\n class="form-control input-sm pull-right"\n name="matchOnRegex"\n ng-model="command.trigger.matchBranchOnRegex"\n />\n </div>\n </div>\n </div>\n\n <deployment-strategy-selector\n field-columns="7"\n ng-if="!command.viewState.disableStrategySelection"\n command="command"\n ></deployment-strategy-selector>\n\n <div class="form-group" ng-if="!command.viewState.hideClusterNamePreview">\n <div class="col-md-12">\n <div class="well-compact" ng-class="basicSettingsCtrl.showPreviewAsWarning() ? \'alert alert-warning\' : \'well\'">\n <h5 class="text-center">\n <p>Your server group will be in the cluster:</p>\n <p>\n <strong>\n {{basicSettingsCtrl.getNamePreview()}}\n <span ng-if="basicSettingsCtrl.createsNewCluster()"> (new cluster)</span>\n </strong>\n </p>\n <div\n class="text-left"\n ng-if="!basicSettingsCtrl.createsNewCluster() && command.viewState.mode === \'create\' && latestServerGroup"\n >\n <p>There is already a server group in this cluster. Do you want to clone it?</p>\n <p>\n Cloning copies the entire configuration from the selected server group, allowing you to modify whichever\n fields (e.g. image) you need to change in the new server group.\n </p>\n <p>\n To clone a server group, select "Clone" from the "Server Group Actions" menu in the details view of the\n server group.\n </p>\n <p>\n <a href ng-click="basicSettingsCtrl.navigateToLatestServerGroup()">\n Go to details for {{latestServerGroup.name}}\n </a>\n </p>\n </div>\n </h5>\n </div>\n </div>\n </div>\n </ng-form>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/serverGroup/configure/wizard/advancedSettings.html",'<div class="form-horizontal container-fluid">\n <div class="form-group">\n <div class="col-md-4 sm-label-right">Promote <help-field key="appengine.serverGroup.promote"></help-field></div>\n <div class="col-md-7">\n <input type="checkbox" ng-model="command.promote" />\n </div>\n </div>\n\n <div class="form-group">\n <div class="col-md-4 sm-label-right">\n Stop Previous Version <help-field key="appengine.serverGroup.stopPreviousVersion"></help-field>\n </div>\n <div class="col-md-7">\n <input type="checkbox" ng-model="command.stopPreviousVersion" />\n </div>\n </div>\n <div class="form-group" ng-if="showPlatformHealthOnlyOverride">\n <div class="col-md-4 sm-label-right">Task Completion</div>\n <div class="col-md-7">\n <platform-health-override command="command" platform-health-type="platformHealth"> </platform-health-override>\n </div>\n </div>\n\n <div class="form-group">\n <div class="col-md-4 sm-label-right">\n Suppress Version String\n <help-field key="appengine.serverGroup.suppress-version-string"></help-field>\n </div>\n <div class="col-md-7">\n <input type="checkbox" ng-model="command.suppressVersionString" />\n </div>\n </div>\n</div>\n')}]);const vn="spinnaker.appengine.configFileArtifactList.component";e(vn,[]).component("configFileArtifactList",ne(E((e=>{const n=(n,t)=>{const a=[...e.configArtifacts];a.splice(t,1,{...a[t],id:n,artifact:null}),e.updateConfigArtifacts(a)};return Q.createElement(Q.Fragment,null,e.configArtifacts.map(((t,a)=>Q.createElement("div",{key:t.id,className:te("artifact-configuration-section col-md-12",{"last-entry":e.configArtifacts.length-1===a})},Q.createElement("div",{className:"col-md-9"},Q.createElement(D,{artifact:t.artifact,excludedArtifactTypePatterns:[],expectedArtifactId:null==t.artifact?t.id:null,onArtifactEdited:n=>{((n,t)=>{const a=[...e.configArtifacts];a.splice(t,1,{...a[t],id:null,artifact:n}),e.updateConfigArtifacts(a)})(n,a)},onExpectedArtifactSelected:e=>{((e,t)=>{n(e.id,t)})(e,a)},pipeline:e.pipeline,stage:e.stage})),Q.createElement("div",{className:"col-md-1"},Q.createElement("button",{type:"button",className:"btn btn-sm btn-default",onClick:()=>(n=>{const t=[...e.configArtifacts];t.splice(n,1),e.updateConfigArtifacts(t)})(a)},Q.createElement("span",{className:"glyphicon glyphicon-trash"})," Delete"))))),Q.createElement("div",{className:"col-md-7 col-md-offset-3"},Q.createElement("button",{className:"btn btn-block btn-add-trigger add-new",onClick:()=>{e.updateConfigArtifacts(e.configArtifacts.concat([{id:"",account:""}]))}},Q.createElement("span",{className:"glyphicon glyphicon-plus-sign"})," Add Config Artifact")))}),"configFileArtifactList"),["configArtifacts","pipeline","stage","updateConfigArtifacts"]));const fn="spinnaker.appengine.serverGroup.write.service";e(fn,[]).service("appengineServerGroupWriter",class{startServerGroup(e,n){const t={job:[this.buildJob(e,n,"startAppEngineServerGroup")],application:n,description:`Start Server Group: ${e.name}`};return F.executeTask(t)}stopServerGroup(e,n){const t={job:[this.buildJob(e,n,"stopAppEngineServerGroup")],application:n,description:`Stop Server Group: ${e.name}`};return F.executeTask(t)}buildJob(e,n,t){return{type:t,region:e.region,serverGroupName:e.name,credentials:e.account,cloudProvider:"appengine",application:n.name}}});const bn=class{constructor(e,n,t,a,i,r,o,l){this.$state=e,this.$scope=n,this.$uibModal=t,this.app=i,this.serverGroupWriter=r,this.appengineServerGroupWriter=o,this.appengineServerGroupCommandBuilder=l,this.state={loading:!0},this.app.ready().then((()=>this.extractServerGroup(a))).then((()=>{this.$scope.$$destroyed||this.app.getDataSource("serverGroups").onRefresh(this.$scope,(()=>this.extractServerGroup(a)))})).catch((()=>this.autoClose()))}static buildExpectedAllocationsTable(e){return`\n <table class="table table-condensed">\n <thead>\n <tr>\n <th>Server Group</th>\n <th>Allocation</th>\n </tr>\n </thead>\n <tbody>\n ${K(e,((e,n)=>`\n <tr>\n <td>${n}</td>\n <td>${100*e}%</td>\n </tr>`)).join("")}\n </tbody>\n </table>`}canDisableServerGroup(){if(this.serverGroup){if(this.serverGroup.disabled)return!1;const e=this.expectedAllocationsAfterDisableOperation(this.serverGroup,this.app);return!!e&&Object.keys(e).length>0}return!1}canDestroyServerGroup(){if(this.serverGroup){if(this.serverGroup.disabled)return!0;const e=this.expectedAllocationsAfterDisableOperation(this.serverGroup,this.app);return!!e&&Object.keys(e).length>0}return!1}destroyServerGroup(){const e={name:this.serverGroup.name,accountId:this.serverGroup.account,region:this.serverGroup.region},n={application:this.app,title:"Destroying "+this.serverGroup.name,onTaskComplete:()=>{this.$state.includes("**.serverGroup",e)&&this.$state.go("^")}},t={header:"Really destroy "+this.serverGroup.name+"?",buttonText:"Destroy "+this.serverGroup.name,account:this.serverGroup.account,taskMonitorConfig:n,submitMethod:e=>this.serverGroupWriter.destroyServerGroup(this.serverGroup,this.app,e),askForReason:!0,platformHealthOnlyShowOverride:this.app.attributes.platformHealthOnlyShowOverride,platformHealthType:De.PLATFORM,body:this.getBodyTemplate(this.serverGroup,this.app),interestingHealthProviderNames:[]};this.app.attributes.platformHealthOnlyShowOverride&&this.app.attributes.platformHealthOnly&&(t.interestingHealthProviderNames=[De.PLATFORM]),i.confirm(t)}enableServerGroup(){const e={application:this.app,title:"Enabling "+this.serverGroup.name},n=`<div class="well well-sm">\n <p>\n Enabling <b>${this.serverGroup.name}</b> will set its traffic allocation for\n <b>${this.serverGroup.loadBalancers[0]}</b> to 100%.\n </p>\n <p>\n If you would like more fine-grained control over your server groups' allocations,\n edit <b>${this.serverGroup.loadBalancers[0]}</b> under the <b>Load Balancers</b> tab.\n </p>\n </div>\n `,t={header:"Really enable "+this.serverGroup.name+"?",buttonText:"Enable "+this.serverGroup.name,body:n,account:this.serverGroup.account,taskMonitorConfig:e,platformHealthOnlyShowOverride:this.app.attributes.platformHealthOnlyShowOverride,platformHealthType:De.PLATFORM,submitMethod:e=>this.serverGroupWriter.enableServerGroup(this.serverGroup,this.app,{...e}),askForReason:!0,interestingHealthProviderNames:[]};this.app.attributes.platformHealthOnlyShowOverride&&this.app.attributes.platformHealthOnly&&(t.interestingHealthProviderNames=[De.PLATFORM]),i.confirm(t)}disableServerGroup(){const e={application:this.app,title:"Disabling "+this.serverGroup.name},n=this.expectedAllocationsAfterDisableOperation(this.serverGroup,this.app),t=`<div class="well well-sm">\n <p>\n For App Engine, a disable operation sets this server group's allocation\n to 0% and sets the other enabled server groups' allocations to their relative proportions\n before the disable operation. The approximate allocations that will result from this operation are shown below.\n </p>\n <p>\n If you would like more fine-grained control over your server groups' allocations,\n edit <b>${this.serverGroup.loadBalancers[0]}</b> under the <b>Load Balancers</b> tab.\n </p>\n <div class="row">\n <div class="col-md-12">\n ${bn.buildExpectedAllocationsTable(n)}\n </div>\n </div>\n </div>\n `,a={header:"Really disable "+this.serverGroup.name+"?",buttonText:"Disable "+this.serverGroup.name,body:t,account:this.serverGroup.account,taskMonitorConfig:e,platformHealthOnlyShowOverride:this.app.attributes.platformHealthOnlyShowOverride,platformHealthType:De.PLATFORM,submitMethod:e=>this.serverGroupWriter.disableServerGroup(this.serverGroup,this.app.name,e),askForReason:!0,interestingHealthProviderNames:[]};this.app.attributes.platformHealthOnlyShowOverride&&this.app.attributes.platformHealthOnly&&(a.interestingHealthProviderNames=[De.PLATFORM]),i.confirm(a)}stopServerGroup(){const e={application:this.app,title:"Stopping "+this.serverGroup.name};let n;this.serverGroup.disabled||(n=`<div class="alert alert-danger">\n <p>Stopping this server group will scale it down to zero instances.</p>\n <p>\n This server group is currently serving traffic from <b>${this.serverGroup.loadBalancers[0]}</b>.\n Traffic directed to this server group after it has been stopped will not be handled.\n </p>\n </div>`);const t={header:"Really stop "+this.serverGroup.name+"?",buttonText:"Stop "+this.serverGroup.name,account:this.serverGroup.account,body:n,platformHealthOnlyShowOverride:this.app.attributes.platformHealthOnlyShowOverride,platformHealthType:De.PLATFORM,taskMonitorConfig:e,submitMethod:()=>this.appengineServerGroupWriter.stopServerGroup(this.serverGroup,this.app),askForReason:!0};i.confirm(t)}startServerGroup(){const e={application:this.app,title:"Starting "+this.serverGroup.name},n={header:"Really start "+this.serverGroup.name+"?",buttonText:"Start "+this.serverGroup.name,account:this.serverGroup.account,platformHealthOnlyShowOverride:this.app.attributes.platformHealthOnlyShowOverride,platformHealthType:De.PLATFORM,taskMonitorConfig:e,submitMethod:()=>this.appengineServerGroupWriter.startServerGroup(this.serverGroup,this.app),askForReason:!0};i.confirm(n)}cloneServerGroup(){this.$uibModal.open({templateUrl:"appengine/src/serverGroup/configure/wizard/serverGroupWizard.html",controller:"appengineCloneServerGroupCtrl as ctrl",size:"lg",resolve:{title:()=>"Clone "+this.serverGroup.name,application:()=>this.app,serverGroup:()=>this.serverGroup,serverGroupCommand:()=>this.appengineServerGroupCommandBuilder.buildServerGroupCommandFromExisting(this.app,this.serverGroup)}})}canStartServerGroup(){return!!this.canStartOrStopServerGroup()&&"STOPPED"===this.serverGroup.servingStatus}canStopServerGroup(){return!!this.canStartOrStopServerGroup()&&"SERVING"===this.serverGroup.servingStatus}canStartOrStopServerGroup(){var e;return"FLEXIBLE"===this.serverGroup.env||["MANUAL","BASIC"].includes(null==(e=this.serverGroup.scalingPolicy)?void 0:e.type)}getBodyTemplate(e,n){let t="";const a={};if(L.addDestroyWarningMessage(n,e,a),a.body&&(t+=a.body),!e.disabled){const a=this.expectedAllocationsAfterDisableOperation(e,n);t+=`\n <div class="well well-sm">\n <p>\n A destroy operation will first disable this server group.\n </p>\n <p>\n For App Engine, a disable operation sets this server group's allocation\n to 0% and sets the other enabled server groups' allocations to their relative proportions\n before the disable operation. The approximate allocations that will result from this operation are shown below.\n </p>\n <p>\n If you would like more fine-grained control over your server groups' allocations,\n edit <b>${e.loadBalancers[0]}</b> under the <b>Load Balancers</b> tab.\n </p>\n <div class="row">\n <div class="col-md-12">\n ${bn.buildExpectedAllocationsTable(a)}\n </div>\n </div>\n </div>\n `}return t}expectedAllocationsAfterDisableOperation(e,n){const t=n.getDataSource("loadBalancers").data.find((n=>{var t,a;const i=null!=(a=null==(t=n.split)?void 0:t.allocations)?a:{};return Object.keys(i).includes(e.name)}));if(t){let n=N(t.split.allocations);delete n[e.name];const a=W(n,((e,n)=>e+n),0),i="COOKIE"===t.split.shardBy?1e3:100;return n=Y(n,(e=>Math.round(e/a*i)/i)),n}return null}autoClose(){this.$scope.$$destroyed||(this.$state.params.allowModalToStayOpen=!0,this.$state.go("^",null,{location:"replace"}))}extractServerGroup(e){return P.getServerGroup(this.app.name,e.accountId,e.region,e.name).then((n=>{let t=this.app.getDataSource("serverGroups").data.find((n=>n.name===e.name&&n.account===e.accountId&&n.region===e.region));t||this.app.getDataSource("loadBalancers").data.some((n=>n.account===e.accountId&&n.serverGroups.some((n=>{let a=!1;return n.name===e.name&&(t=n,a=!0),a})))),this.serverGroup={...n,...t},this.state.loading=!1}))}};let yn=bn;yn.$inject=["$state","$scope","$uibModal","serverGroup","app","serverGroupWriter","appengineServerGroupWriter","appengineServerGroupCommandBuilder"];const Cn="spinnaker.appengine.serverGroup.details.controller";e(Cn,[fn,T]).controller("appengineServerGroupDetailsCtrl",yn),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/serverGroup/configure/wizard/serverGroupWizard.html",'<form name="form" class="form-horizontal appengine-server-group-wizard" novalidate>\n <div ng-if="ctrl.state.loading" style="height: 200px" class="horizontal center middle">\n <loading-spinner size="\'small\'"></loading-spinner>\n </div>\n <div>\n <v2-modal-wizard\n ng-if="!ctrl.state.loading"\n heading="Create Server Group"\n task-monitor="ctrl.taskMonitor"\n dismiss="$dismiss()"\n >\n <v2-wizard-page key="basic-settings" label="Basic Settings" mark-complete-on-view="false">\n <ng-include src="ctrl.pages.basicSettings"></ng-include>\n </v2-wizard-page>\n <v2-wizard-page key="load-balancer" label="Config Files" mark-complete-on-view="false">\n <appengine-config-file-configurer command="command"></appengine-config-file-configurer>\n </v2-wizard-page>\n <v2-wizard-page key="load-balancer" label="Load Balancer" done="true">\n <appengine-load-balancer-message show-create-message="false"></appengine-load-balancer-message>\n </v2-wizard-page>\n <v2-wizard-page key="advanced-settings" label="Advanced Settings" done="true">\n <ng-include src="ctrl.pages.advancedSettings"></ng-include>\n </v2-wizard-page>\n </v2-modal-wizard>\n <div class="modal-footer" ng-if="!state.loading">\n <button ng-disabled="ctrl.taskMonitor.submitting" class="btn btn-default btn-cancel" ng-click="ctrl.cancel()">\n Cancel\n </button>\n <submit-button\n ng-if="form.$valid"\n is-disabled="form.$invalid || ctrl.taskMonitor.submitting"\n label="command.viewState.submitButtonLabel"\n submitting="ctrl.taskMonitor.submitting"\n on-click="ctrl.submit()"\n is-new="true"\n ></submit-button>\n </div>\n </div>\n</form>\n')}]);class Sn{constructor(e){this.cloudProvider="appengine",this.provider="appengine",this.credentials=e.credentials,this.account=e.credentials,this.application=e.application,this.stack=e.stack,this.freeFormDetails=e.freeFormDetails,this.repositoryUrl=e.repositoryUrl,this.branch=e.branch,this.configFilepaths=e.configFilepaths,this.promote=e.promote,this.stopPreviousVersion=e.stopPreviousVersion,this.type=e.type,this.region=e.region,this.strategy=e.strategy,this.strategyApplication=e.strategyApplication,this.strategyPipeline=e.strategyPipeline,this.fromTrigger=e.fromTrigger,this.trigger=e.trigger,this.gitCredentialType=e.gitCredentialType,this.configFiles=e.configFiles,this.configArtifacts=e.configArtifacts.filter((e=>!!e.id||!!e.artifact)),this.applicationDirectoryRoot=e.applicationDirectoryRoot,this.interestingHealthProviderNames=e.interestingHealthProviderNames||[],this.expectedArtifactId=e.expectedArtifactId,this.expectedArtifact=e.expectedArtifact,this.fromArtifact=e.fromArtifact,this.sourceType=e.sourceType,this.storageAccountName=e.storageAccountName,this.containerImageUrl=e.containerImageUrl,this.suppressVersionString=e.suppressVersionString}}class wn{constructor(e){this.$q=e}normalizeServerGroup(e){return this.$q.resolve(e)}convertServerGroupCommandToDeployConfiguration(e){return new Sn(e)}}wn.$inject=["$q"];const Gn="spinnaker.appengine.serverGroup.transformer.service";e(Gn,[]).service("appengineServerGroupTransformer",wn);I.registerValidator("appengine",new class{validate(e=""){const n=[],t=[];return e.length&&(this.validateSpecialCharacters(e,t),this.validateLength(e,n,t)),{warnings:n,errors:t}}validateSpecialCharacters(e,n){/^[a-z0-9]*$/g.test(e)||n.push("Only numbers and lowercase letters are allowed.")}validateLength(e,n,t){if(e.length>58)t.push("The maximum length for an App Engine application name is 63 characters.");else if(e.length>48)if(e.length>=56)n.push("You will not be able to include a stack or detail field for clusters.");else{const t=56-e.length;n.push(`If you plan to include a stack or detail field for clusters, you will only have\n ${t} character${t>1?"s":""} to do so.`)}}});Ae('.cloud-provider-logo .icon-appengine {\n -webkit-mask-image: url("data:image/svg+xml,%3C%3Fxml version%3D%221.0%22 encoding%3D%22utf-8%22%3F%3E%3C!-- Generator%3A Adobe Illustrator 18.1.1%2C SVG Export Plug-In . SVG Version%3A 6.00 Build 0) --%3E%3Csvg version%3D%221.1%22 xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22 x%3D%220px%22 y%3D%220px%22 width%3D%22128px%22%09 height%3D%22128px%22 viewBox%3D%2215 15 90 90%22 xml%3Aspace%3D%22preserve%22%3E%3Cfilter id%3D%22invert%22%3E %3CfeColorMatrix in%3D%22SourceGraphic%22 type%3D%22matrix%22 values%3D%22-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0%22%2F%3E%3C%2Ffilter%3E%3Cg id%3D%22art%22 filter%3D%22url(%23invert)%22%3E%09%3Cg%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M63.9988%2C40.9577c-12.7278%2C0-23.0452%2C10.3167-23.0452%2C23.0464%09%09%09c0%2C12.726%2C10.3174%2C23.0463%2C23.0452%2C23.0463c12.7273%2C0%2C23.0446-10.3203%2C23.0446-23.0463%09%09%09C87.0434%2C51.2744%2C76.7261%2C40.9577%2C63.9988%2C40.9577 M63.9988%2C81.4887c-9.6584%2C0-17.4876-7.8286-17.4876-17.4846%09%09%09c0-9.6584%2C7.8291-17.4877%2C17.4876-17.4877c9.6578%2C0%2C17.4858%2C7.8292%2C17.4858%2C17.4877%09%09%09C81.4846%2C73.6601%2C73.6566%2C81.4887%2C63.9988%2C81.4887%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M100.1431%2C61.274l-9.6079-3.0195c0.3997%2C1.8539%2C0.615%2C3.7759%2C0.615%2C5.7478%09%09%09c0%2C1.3791-0.1047%2C2.7337-0.3021%2C4.0569h9.295c0.8587-0.2515%2C1.4327-0.7846%2C1.4327-1.5765v-3.6345%09%09%09C101.5759%2C62.0575%2C101.0018%2C61.5094%2C100.1431%2C61.274%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M63.9923%2C36.8497c1.9303%2C0%2C3.8119%2C0.2046%2C5.6285%2C0.5884l-3.4443-9.5729%09%09%09c-0.2515-0.8606-0.7852-1.434-1.5765-1.434h-1.4392c-0.7913%2C0-1.3407%2C0.5734-1.5761%2C1.434l-2.9924%2C9.5236%09%09%09C60.3372%2C37.0368%2C62.1425%2C36.8497%2C63.9923%2C36.8497%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M36.8468%2C64.0023c0-1.9719%2C0.2154-3.8932%2C0.6145-5.7478l-9.6073%2C3.0195%09%09%09c-0.8587%2C0.2354-1.4322%2C0.7835-1.4322%2C1.5754v3.6344c0%2C0.7907%2C0.5735%2C1.3238%2C1.4322%2C1.5753h9.2955%09%09%09C36.9515%2C66.7366%2C36.8468%2C65.3814%2C36.8468%2C64.0023%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M72.2276%2C56.4819l-2.1802%2C2.1794v-0.0048c-1.5651-1.5639-3.6549-2.4274-5.8687-2.4274%09%09%09c-2.2192%2C0-4.303%2C0.8659-5.8704%2C2.4322c-3.2362%2C3.2355-3.2362%2C8.5037%2C0%2C11.7399l-2.1802%2C2.1783%09%09%09c2.0627%2C2.0609%2C4.9096%2C3.3383%2C8.0483%2C3.3383c6.2803%2C0%2C11.3896-5.1081%2C11.3896-11.3895%09%09%09C75.566%2C61.3883%2C74.289%2C58.5421%2C72.2276%2C56.4819 M67.7284%2C67.7342c-1.0278%2C1.0302-2.3799%2C1.5459-3.7284%2C1.5459%09%09%09c-1.3526%2C0-2.7012-0.5156-3.7313-1.5459c-2.0616-2.0615-2.0616-5.4018%2C0-7.4639c1.0301-1.0314%2C2.3787-1.544%2C3.7313-1.544%09%09%09c1.3485%2C0%2C2.7006%2C0.5127%2C3.7284%2C1.544C69.79%2C62.3312%2C69.79%2C65.6727%2C67.7284%2C67.7342%22%2F%3E%09%3C%2Fg%3E%3C%2Fg%3E%3Cg id%3D%22Guides%22 style%3D%22display%3Anone%3B%22%3E%3C%2Fg%3E%3C%2Fsvg%3E");\n mask-image: url("data:image/svg+xml,%3C%3Fxml version%3D%221.0%22 encoding%3D%22utf-8%22%3F%3E%3C!-- Generator%3A Adobe Illustrator 18.1.1%2C SVG Export Plug-In . SVG Version%3A 6.00 Build 0) --%3E%3Csvg version%3D%221.1%22 xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22 x%3D%220px%22 y%3D%220px%22 width%3D%22128px%22%09 height%3D%22128px%22 viewBox%3D%2215 15 90 90%22 xml%3Aspace%3D%22preserve%22%3E%3Cfilter id%3D%22invert%22%3E %3CfeColorMatrix in%3D%22SourceGraphic%22 type%3D%22matrix%22 values%3D%22-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0%22%2F%3E%3C%2Ffilter%3E%3Cg id%3D%22art%22 filter%3D%22url(%23invert)%22%3E%09%3Cg%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M63.9988%2C40.9577c-12.7278%2C0-23.0452%2C10.3167-23.0452%2C23.0464%09%09%09c0%2C12.726%2C10.3174%2C23.0463%2C23.0452%2C23.0463c12.7273%2C0%2C23.0446-10.3203%2C23.0446-23.0463%09%09%09C87.0434%2C51.2744%2C76.7261%2C40.9577%2C63.9988%2C40.9577 M63.9988%2C81.4887c-9.6584%2C0-17.4876-7.8286-17.4876-17.4846%09%09%09c0-9.6584%2C7.8291-17.4877%2C17.4876-17.4877c9.6578%2C0%2C17.4858%2C7.8292%2C17.4858%2C17.4877%09%09%09C81.4846%2C73.6601%2C73.6566%2C81.4887%2C63.9988%2C81.4887%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M100.1431%2C61.274l-9.6079-3.0195c0.3997%2C1.8539%2C0.615%2C3.7759%2C0.615%2C5.7478%09%09%09c0%2C1.3791-0.1047%2C2.7337-0.3021%2C4.0569h9.295c0.8587-0.2515%2C1.4327-0.7846%2C1.4327-1.5765v-3.6345%09%09%09C101.5759%2C62.0575%2C101.0018%2C61.5094%2C100.1431%2C61.274%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M63.9923%2C36.8497c1.9303%2C0%2C3.8119%2C0.2046%2C5.6285%2C0.5884l-3.4443-9.5729%09%09%09c-0.2515-0.8606-0.7852-1.434-1.5765-1.434h-1.4392c-0.7913%2C0-1.3407%2C0.5734-1.5761%2C1.434l-2.9924%2C9.5236%09%09%09C60.3372%2C37.0368%2C62.1425%2C36.8497%2C63.9923%2C36.8497%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M36.8468%2C64.0023c0-1.9719%2C0.2154-3.8932%2C0.6145-5.7478l-9.6073%2C3.0195%09%09%09c-0.8587%2C0.2354-1.4322%2C0.7835-1.4322%2C1.5754v3.6344c0%2C0.7907%2C0.5735%2C1.3238%2C1.4322%2C1.5753h9.2955%09%09%09C36.9515%2C66.7366%2C36.8468%2C65.3814%2C36.8468%2C64.0023%22%2F%3E%09%09%3Cpath style%3D%22fill%3A%23FFFFFF%3B%22 d%3D%22M72.2276%2C56.4819l-2.1802%2C2.1794v-0.0048c-1.5651-1.5639-3.6549-2.4274-5.8687-2.4274%09%09%09c-2.2192%2C0-4.303%2C0.8659-5.8704%2C2.4322c-3.2362%2C3.2355-3.2362%2C8.5037%2C0%2C11.7399l-2.1802%2C2.1783%09%09%09c2.0627%2C2.0609%2C4.9096%2C3.3383%2C8.0483%2C3.3383c6.2803%2C0%2C11.3896-5.1081%2C11.3896-11.3895%09%09%09C75.566%2C61.3883%2C74.289%2C58.5421%2C72.2276%2C56.4819 M67.7284%2C67.7342c-1.0278%2C1.0302-2.3799%2C1.5459-3.7284%2C1.5459%09%09%09c-1.3526%2C0-2.7012-0.5156-3.7313-1.5459c-2.0616-2.0615-2.0616-5.4018%2C0-7.4639c1.0301-1.0314%2C2.3787-1.544%2C3.7313-1.544%09%09%09c1.3485%2C0%2C2.7006%2C0.5127%2C3.7284%2C1.544C69.79%2C62.3312%2C69.79%2C65.6727%2C67.7284%2C67.7342%22%2F%3E%09%3C%2Fg%3E%3C%2Fg%3E%3Cg id%3D%22Guides%22 style%3D%22display%3Anone%3B%22%3E%3C%2Fg%3E%3C%2Fsvg%3E");\n background-color: #4285f4;\n}\n');const An="spinnaker.appengine";e(An,[hn,ie,le,pe,se,Te,Je,ln,rn,Cn,Gn,fn,vn]).config((()=>{m.registerProvider("appengine",{name:"App Engine",instance:{detailsTemplateUrl:"appengine/src/instance/details/details.html",detailsController:"appengineInstanceDetailsCtrl"},serverGroup:{transformer:"appengineServerGroupTransformer",detailsController:"appengineServerGroupDetailsCtrl",detailsTemplateUrl:"appengine/src/serverGroup/details/details.html",commandBuilder:"appengineServerGroupCommandBuilder",cloneServerGroupController:"appengineCloneServerGroupCtrl",cloneServerGroupTemplateUrl:"appengine/src/serverGroup/configure/wizard/serverGroupWizard.html",skipUpstreamStageCheck:!0},loadBalancer:{transformer:"appengineLoadBalancerTransformer",createLoadBalancerTemplateUrl:"appengine/src/loadBalancer/configure/wizard/wizard.html",createLoadBalancerController:"appengineLoadBalancerWizardCtrl",detailsTemplateUrl:"appengine/src/loadBalancer/details/details.html",detailsController:"appengineLoadBalancerDetailsCtrl"},logo:{path:"appengine.logoc2c312af6aa99037.png"}})})),O.registerProvider("appengine",["custom"]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/instance/details/details.html",'<div class="details-panel">\n <div class="header">\n <instance-details-header\n health-state="ctrl.instance.healthState"\n instance-id="ctrl.instance ? ctrl.instance.name : ctrl.instanceIdNotFound"\n loading="ctrl.state.loading"\n standalone="false"\n ></instance-details-header>\n <div ng-if="!ctrl.state.loading">\n <div class="actions">\n <div class="dropdown" uib-dropdown dropdown-append-to-body>\n <button type="button" class="btn btn-sm btn-primary dropdown-toggle" uib-dropdown-toggle>\n Instance Actions <span class="caret"></span>\n </button>\n <ul class="dropdown-menu" uib-dropdown-menu role="menu">\n <li><a href ng-click="ctrl.terminateInstance()">Terminate</a></li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n <div class="content" ng-if="!ctrl.state.loading && ctrl.instance">\n <collapsible-section heading="Instance Information" expanded="true">\n <dl class="dl-horizontal dl-narrow">\n <dt>Launched</dt>\n <dd ng-if="ctrl.instance.launchTime">{{ctrl.instance.launchTime | timestamp}}</dd>\n <dt>In</dt>\n <dd><account-tag account="ctrl.instance.account" pad="right"></account-tag>{{}}</dd>\n <dt ng-if="ctrl.instance.serverGroup">Server Group</dt>\n <dd ng-if="ctrl.instance.serverGroup">\n <a\n ui-sref="^.serverGroup({region: ctrl.instance.region,\n accountId: ctrl.instance.account,\n serverGroup: ctrl.instance.serverGroup,\n provider: \'appengine\'})"\n >{{ctrl.instance.serverGroup}}</a\n >\n </dd>\n <dt>Region</dt>\n <dd>{{ctrl.instance.region}}</dd>\n <appengine-conditional-dt-dd\n component="ctrl.instance"\n key="vmZoneName"\n label="Zone"\n ></appengine-conditional-dt-dd>\n </dl>\n </collapsible-section>\n <collapsible-section heading="Status" expanded="true">\n <dl>\n <dt>Load Balancer</dt>\n <dd>\n <span\n class="pull-left"\n uib-tooltip="{{ctrl.instance.healthState === \'Up\' ? ctrl.upToolTip : ctrl.outOfServiceToolTip}}"\n tooltip-placement="right"\n >\n <span class="glyphicon glyphicon-{{ctrl.instance.healthState}}-triangle"></span>\n {{ctrl.instance.loadBalancers[0]}}\n </span>\n </dd>\n </dl>\n </collapsible-section>\n <collapsible-section heading="Metrics" expanded="true">\n <dl>\n <appengine-conditional-dt-dd component="ctrl.instance" key="instanceStatus" label="Availability">\n <key-label><help-field key="appengine.instance.availability"></help-field></key-label>\n </appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd component="ctrl.instance" key="averageLatency">\n <key-label><help-field key="appengine.instance.averageLatency"></help-field></key-label>\n <value-label>ms</value-label>\n </appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd component="ctrl.instance" key="errors">\n <key-label><help-field key="appengine.instance.errors"></help-field></key-label>\n </appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd component="ctrl.instance" key="qps" label="QPS">\n <key-label><help-field key="appengine.instance.qps"></help-field></key-label>\n </appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd component="ctrl.instance" key="requests">\n <key-label><help-field key="appengine.instance.requests"></help-field></key-label>\n </appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd component="ctrl.instance" key="vmStatus" label="VM Status">\n <key-label><help-field key="appengine.instance.vmStatus"></help-field></key-label>\n </appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.instance"\n key="vmDebugEnabled"\n label="Debug Enabled"\n ></appengine-conditional-dt-dd>\n </dl>\n </collapsible-section>\n </div>\n <div class="content" ng-if="!ctrl.state.loading && !ctrl.instance">\n <div class="content-section">\n <div class="content-body text-center">\n <h3>Instance not found.</h3>\n </div>\n </div>\n </div>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/serverGroup/details/details.html",'<div class="details-panel" ng-class="{ disabled: ctrl.serverGroup.isDisabled || ctrl.serverGroup.disabled}">\n <div class="header" ng-if="ctrl.state.loading">\n <div class="close-button">\n <a class="btn btn-link" ui-sref="^">\n <span class="glyphicon glyphicon-remove"></span>\n </a>\n </div>\n <div class="horizontal center middle">\n <loading-spinner size="\'small\'"></loading-spinner>\n </div>\n </div>\n\n <div class="header" ng-if="!ctrl.state.loading">\n <div class="close-button">\n <a class="btn btn-link" ui-sref="^">\n <span class="glyphicon glyphicon-remove"></span>\n </a>\n </div>\n <div class="header-text horizontal middle">\n <cloud-provider-logo provider="ctrl.serverGroup.type" height="\'36px\'" width="\'36px\'"></cloud-provider-logo>\n <h3 class="horizontal middle space-between flex-1" select-on-dbl-click>{{ctrl.serverGroup.name}}</h3>\n </div>\n <div class="actions">\n <div class="dropdown" uib-dropdown dropdown-append-to-body>\n <button type="button" class="btn btn-sm btn-primary dropdown-toggle" uib-dropdown-toggle>\n Server Group Actions <span class="caret"></span>\n </button>\n <ul class="dropdown-menu" uib-dropdown-menu role="menu">\n <li ng-if="ctrl.canStopServerGroup()">\n <a href ng-click="ctrl.stopServerGroup()"> Stop </a>\n </li>\n <li ng-if="ctrl.canStartServerGroup()">\n <a href ng-click="ctrl.startServerGroup()"> Start </a>\n </li>\n <li ng-if="ctrl.serverGroup.disabled">\n <a href ng-click="ctrl.enableServerGroup()"> Enable </a>\n </li>\n <li ng-if="!ctrl.serverGroup.disabled && ctrl.canDisableServerGroup()">\n <a href ng-click="ctrl.disableServerGroup()"> Disable </a>\n </li>\n <li\n ng-if="!ctrl.serverGroup.disabled && !ctrl.canDisableServerGroup()"\n uib-tooltip="You cannot disable a server group if it is the\n only server group receiving traffic from a load balancer."\n class="disabled"\n >\n <a href> Disable </a>\n </li>\n <li ng-if="ctrl.canDestroyServerGroup()">\n <a href ng-click="ctrl.destroyServerGroup()">Destroy</a>\n </li>\n <li\n ng-if="!ctrl.canDestroyServerGroup()"\n uib-tooltip="You cannot destroy a server group if it is the only server group\n receiving traffic from a load balancer. You may be able to delete\n this server group\'s load balancer."\n class="disabled"\n >\n <a href>Destroy</a>\n </li>\n <li\n uib-tooltip="It is not possible to clone an App Engine server group\'s full\n launch configuration. However, clicking this button will allow\n you to deploy into this server group\'s cluster."\n >\n <a href ng-click="ctrl.cloneServerGroup()">Clone</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n\n <div class="content" ng-if="!ctrl.state.loading">\n <div class="band band-info" ng-if="ctrl.serverGroup.isDisabled || ctrl.serverGroup.disabled">Disabled</div>\n <server-group-running-tasks-details\n server-group="ctrl.serverGroup"\n application="ctrl.app"\n ></server-group-running-tasks-details>\n <collapsible-section heading="Server Group Information" expanded="true">\n <dl class="dl-horizontal dl-narrow">\n <dt>Created</dt>\n <dd>{{ctrl.serverGroup.createdTime | timestamp}}</dd>\n <dt>In</dt>\n <dd><account-tag account="ctrl.serverGroup.account"></account-tag></dd>\n <dt>Region</dt>\n <dd>{{ctrl.serverGroup.region}}</dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup"\n key="env"\n label="Environment"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd component="ctrl.serverGroup" key="instanceClass"></appengine-conditional-dt-dd>\n </dl>\n </collapsible-section>\n <collapsible-section heading="Size" expanded="true">\n <dl class="dl-horizontal dl-narrow" ng-if="ctrl.serverGroup.capacity.min === ctrl.serverGroup.capacity.max">\n <dt>Min/Max</dt>\n <dd>{{ctrl.serverGroup.capacity.min}}</dd>\n <dt>Current</dt>\n <dd>{{ctrl.serverGroup.instances.length}}</dd>\n </dl>\n <dl class="dl-horizontal dl-narrow" ng-if="ctrl.serverGroup.capacity.min !== ctrl.serverGroup.capacity.max">\n <dt>Min</dt>\n <dd>{{ctrl.serverGroup.capacity.min}}</dd>\n <dt>Max</dt>\n <dd>{{ctrl.serverGroup.capacity.max}}</dd>\n <dt>Current</dt>\n <dd>{{ctrl.serverGroup.instances.length}}</dd>\n </dl>\n </collapsible-section>\n <collapsible-section heading="Health" expanded="true">\n <dl class="dl-horizontal dl-narrow" ng-if="ctrl.serverGroup">\n <dt>Instances</dt>\n <dd>\n <health-counts container="ctrl.serverGroup.instanceCounts" class="pull-left"></health-counts>\n </dd>\n </dl>\n </collapsible-section>\n <collapsible-section heading="DNS" expanded="true">\n <dl class="dl-narrow">\n <appengine-component-url-details component="ctrl.serverGroup"></appengine-component-url-details>\n </dl>\n </collapsible-section>\n <collapsible-section heading="Scaling Policy" expanded="true" ng-if="ctrl.serverGroup.scalingPolicy">\n <dl>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="type"\n ></appengine-conditional-dt-dd>\n \x3c!--MANUAL SCALING PROPERTIES--\x3e\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="instances"\n ></appengine-conditional-dt-dd>\n \x3c!--BASIC SCALING PROPERTIES--\x3e\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="idleTimeout"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="maxInstances"\n ></appengine-conditional-dt-dd>\n \x3c!--AUTOMATIC SCALING PROPERTIES--\x3e\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="coolDownPeriod"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="maxConcurrentRequests"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="maxTotalInstances"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="minTotalInstances"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="maxIdleInstances"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="minIdleInstances"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="maxPendingLatency"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy"\n key="minPendingLatency"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.cpuUtilization"\n key="aggregationWindowLength"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.cpuUtilization"\n key="targetUtilization"\n label="Target CPU Utilization"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.diskUtilization"\n key="targetReadBytesPerSecond"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.diskUtilization"\n key="targetReadOpsPerSecond"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.diskUtilization"\n key="targetWriteBytesPerSecond"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.diskUtilization"\n key="targetWriteOpsPerSecond"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.networkUtilization"\n key="targetReceivedBytesPerSecond"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.networkUtilization"\n key="targetReceivedPacketsPerSecond"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.networkUtilization"\n key="targetSentBytesPerSecond"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.networkUtilization"\n key="targetSentPacketsPerSecond"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.requestUtilization"\n key="targetConcurrentRequests"\n ></appengine-conditional-dt-dd>\n <appengine-conditional-dt-dd\n component="ctrl.serverGroup.scalingPolicy.requestUtilization"\n key="targetRequestCountPerSecond"\n ></appengine-conditional-dt-dd>\n </dl>\n </collapsible-section>\n </div>\n</div>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/serverGroup/configure/wizard/serverGroupWizard.html",'<form name="form" class="form-horizontal appengine-server-group-wizard" novalidate>\n <div ng-if="ctrl.state.loading" style="height: 200px" class="horizontal center middle">\n <loading-spinner size="\'small\'"></loading-spinner>\n </div>\n <div>\n <v2-modal-wizard\n ng-if="!ctrl.state.loading"\n heading="Create Server Group"\n task-monitor="ctrl.taskMonitor"\n dismiss="$dismiss()"\n >\n <v2-wizard-page key="basic-settings" label="Basic Settings" mark-complete-on-view="false">\n <ng-include src="ctrl.pages.basicSettings"></ng-include>\n </v2-wizard-page>\n <v2-wizard-page key="load-balancer" label="Config Files" mark-complete-on-view="false">\n <appengine-config-file-configurer command="command"></appengine-config-file-configurer>\n </v2-wizard-page>\n <v2-wizard-page key="load-balancer" label="Load Balancer" done="true">\n <appengine-load-balancer-message show-create-message="false"></appengine-load-balancer-message>\n </v2-wizard-page>\n <v2-wizard-page key="advanced-settings" label="Advanced Settings" done="true">\n <ng-include src="ctrl.pages.advancedSettings"></ng-include>\n </v2-wizard-page>\n </v2-modal-wizard>\n <div class="modal-footer" ng-if="!state.loading">\n <button ng-disabled="ctrl.taskMonitor.submitting" class="btn btn-default btn-cancel" ng-click="ctrl.cancel()">\n Cancel\n </button>\n <submit-button\n ng-if="form.$valid"\n is-disabled="form.$invalid || ctrl.taskMonitor.submitting"\n label="command.viewState.submitButtonLabel"\n submitting="ctrl.taskMonitor.submitting"\n on-click="ctrl.submit()"\n is-new="true"\n ></submit-button>\n </div>\n </div>\n</form>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/loadBalancer/configure/wizard/wizard.html",'<form name="form">\n <div ng-if="ctrl.state.loading && !ctrl.isNew" style="height: 200px" class="horizontal center middle">\n <loading-spinner size="\'small\'"></loading-spinner>\n </div>\n <v2-modal-wizard\n heading="{{::ctrl.heading}}"\n task-monitor="ctrl.taskMonitor"\n dismiss="$dismiss()"\n ng-if="!ctrl.state.loading || ctrl.isNew"\n >\n <div ng-if="!ctrl.isNew">\n <v2-wizard-page key="basic-settings" label="Basic Settings" mark-complete-on-view="false">\n <appengine-load-balancer-basic-settings\n load-balancer="ctrl.loadBalancer"\n application="ctrl.application"\n for-pipeline-config="ctrl.forPipelineConfig"\n ></appengine-load-balancer-basic-settings>\n </v2-wizard-page>\n <v2-wizard-page key="advanced-settings" label="Advanced Settings" mark-complete-on-view="false">\n <appengine-load-balancer-advanced-settings\n load-balancer="ctrl.loadBalancer"\n ></appengine-load-balancer-advanced-settings>\n </v2-wizard-page>\n </div>\n </v2-modal-wizard>\n <appengine-load-balancer-message\n ng-if="ctrl.isNew"\n column-offset="1"\n columns="10"\n show-create-message="true"\n ></appengine-load-balancer-message>\n <div class="modal-footer">\n <button class="btn btn-default" ng-click="ctrl.cancel()">Cancel</button>\n <submit-button\n ng-if="!ctrl.isNew && ctrl.showSubmitButton()"\n label="ctrl.submitButtonLabel"\n is-disabled="appengineLoadBalancerForm.$invalid || ctrl.taskMonitor.submitting || ctrl.state.loading"\n submitting="ctrl.taskMonitor.submitting"\n on-click="ctrl.submit()"\n is-new="ctrl.isNew"\n >\n </submit-button>\n </div>\n</form>\n')}]),window.angular.module("ng").run(["$templateCache",function(e){e.put("appengine/src/loadBalancer/details/details.html",'<div class="details-panel">\n <div ng-if="ctrl.state.loading" class="header">\n <div class="close-button">\n <a class="btn btn-link" ui-sref="^">\n <span class="glyphicon glyphicon-remove"></span>\n </a>\n </div>\n <div class="horizontal center middle">\n <loading-spinner size="\'small\'"></loading-spinner>\n </div>\n </div>\n\n <div ng-if="!ctrl.state.loading" class="header">\n <div class="close-button">\n <a class="btn btn-link" ui-sref="^">\n <span class="glyphicon glyphicon-remove"></span>\n </a>\n </div>\n <div class="header-text horizontal middle">\n <i class="fa icon-sitemap"></i>\n <h3 class="horizontal middle space-between flex-1" select-on-dbl-click>{{ctrl.loadBalancer.name}}</h3>\n </div>\n <div>\n <div class="actions">\n <div class="dropdown" uib-dropdown dropdown-append-to-body>\n <button type="button" class="btn btn-sm btn-primary dropdown-toggle" uib-dropdown-toggle>\n Load Balancer Actions <span class="caret"></span>\n </button>\n <ul class="dropdown-menu" uib-dropdown-menu role="menu">\n <li><a href ng-click="ctrl.editLoadBalancer()">Edit Load Balancer</a></li>\n <li ng-if="ctrl.canDeleteLoadBalancer()">\n <a href ng-click="ctrl.deleteLoadBalancer()">Delete Load Balancer</a>\n </li>\n <li\n ng-if="!ctrl.canDeleteLoadBalancer()"\n uib-tooltip="You cannot delete a default service."\n class="disabled"\n >\n <a href>Delete Load Balancer</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n\n <div ng-if="!ctrl.state.loading" class="content">\n <collapsible-section heading="Load Balancer Details" expanded="true">\n <dl class="dl-horizontal dl-narrow">\n <dt>In</dt>\n <dd><account-tag account="ctrl.loadBalancer.account" pad="right"></account-tag></dd>\n <dt>Region</dt>\n <dd>{{ctrl.loadBalancer.region}}</dd>\n <dt ng-if="ctrl.loadBalancer.serverGroups.length">Server Groups</dt>\n <dd ng-if="ctrl.loadBalancer.serverGroups.length">\n <ul>\n <li ng-repeat="serverGroup in ctrl.loadBalancer.serverGroups | orderBy: [\'isDisabled\', \'-name\']">\n <a\n ui-sref="^.serverGroup({region: serverGroup.region,\n accountId: serverGroup.account,\n serverGroup: serverGroup.name,\n provider: \'appengine\'})"\n >\n {{serverGroup.name}}\n </a>\n </li>\n </ul>\n </dd>\n </dl>\n </collapsible-section>\n <collapsible-section heading="Status" expanded="true">\n <health-counts class="pull-left" container="ctrl.loadBalancer.instanceCounts"></health-counts>\n </collapsible-section>\n <collapsible-section heading="Traffic Split" expanded="true">\n <dl class="dl-horizontal dl-narrow">\n <dt ng-if="ctrl.loadBalancer.split.shardBy">Shard By</dt>\n <dd ng-if="ctrl.loadBalancer.split.shardBy">\n {{ctrl.loadBalancer.split.shardBy}}\n <help-field\n key="appengine.loadBalancer.shardBy.{{ctrl.loadBalancer.split.shardBy.toLowerCase()}}"\n ></help-field>\n </dd>\n <hr ng-if="ctrl.loadBalancer.split.shardBy" />\n <ul>\n <li ng-repeat="(serverGroup, percent) in ctrl.loadBalancer.split.allocations">\n {{serverGroup}}:<span class="pull-right">{{percent | decimalToPercent}}</span>\n </li>\n </ul>\n </dl>\n </collapsible-section>\n <collapsible-section heading="DNS" expanded="true">\n <dl class="dl-narrow">\n <appengine-component-url-details component="ctrl.loadBalancer"></appengine-component-url-details>\n </dl>\n </collapsible-section>\n <collapsible-section heading="Dispatch Rules" expanded="true" ng-if="ctrl.dispatchRules.length > 0">\n <dl class="dl-horizontal dl-narrow">\n <span ng-repeat-start="rule in ctrl.dispatchRules">{{rule}}</span><br ng-repeat-end />\n </dl>\n </collapsible-section>\n </div>\n</div>\n')}]);export{An as APPENGINE_MODULE};
4084
2
  //# sourceMappingURL=index.js.map