github.com/in4it/ecs-deploy@v0.0.42-0.20240508120354-ed77ff16df25/webapp/src/app/service-detail/service-detail.component.ts (about) 1 import { Component, OnInit, ViewChild } from '@angular/core'; 2 import { ActivatedRoute, Router } from '@angular/router'; 3 import { Observable } from 'rxjs'; 4 5 import { ServiceDetail, ServiceDetailService } from './service-detail.service'; 6 import { InspectChildComponent } from './inspect.component'; 7 import { DeployChildComponent } from './deploy.component'; 8 import { ConfirmChildComponent } from './confirm.component'; 9 10 import { AlertService } from '../services/index'; 11 12 import * as moment from 'moment'; 13 14 @Component({ 15 selector: 'app-service-detail', 16 templateUrl: './service-detail.component.html', 17 styleUrls: ['./service-detail.component.css'] 18 }) 19 export class ServiceDetailComponent implements OnInit { 20 21 service: any = {}; 22 versions: any = {}; 23 parameters: any = {}; 24 loading: boolean = false; 25 saving: boolean = false; 26 loadingLogs: boolean = false; 27 28 selectedParameter: string = ""; 29 newParameter: boolean = false; 30 newParameterInput: any = {}; 31 parameterInput: any = {}; 32 33 selectedVersion: any; 34 35 editManualScaling: boolean = false; 36 scalingInput: any = {}; 37 scalingInputPolicy: any = {}; 38 39 runTaskInput: any = {}; 40 runTaskConfig: any = { "maxExecutionTime": 900}; 41 42 logsInput: any = {}; 43 44 tab = "service" 45 46 @ViewChild(InspectChildComponent, { static: true }) inspectChild; 47 @ViewChild(DeployChildComponent, { static: true }) deployChild; 48 @ViewChild(ConfirmChildComponent, { static: true }) confirmChild; 49 50 constructor( 51 private route: ActivatedRoute, 52 private router: Router, 53 private sds: ServiceDetailService, 54 private alertService: AlertService 55 ) {} 56 57 ngOnInit(): void { 58 this.route.data 59 .subscribe((data: { sd: ServiceDetail }) => { 60 this.formatServiceData(data.sd.service) 61 }); 62 } 63 64 onClickVersions() { 65 this.versions = []; 66 this.tab = "versions" 67 this.loading = true 68 this.sds.getVersions().subscribe(data => { 69 this.loading = false 70 let versionMap = {} 71 data['versions'].forEach((version, index) => { 72 let lastDeployMoment = moment(version.lastDeploy); 73 data['versions'][index]['lastDeployMoment'] = lastDeployMoment.fromNow() 74 versionMap[version.lastDeploy] = version 75 }) 76 this.versions = data['versions']; 77 this.deployChild.setVersionMap(versionMap) 78 }); 79 } 80 onClickService() { 81 this.tab = "service" 82 } 83 onClickEvents() { 84 this.tab = "events" 85 } 86 onClickScaling() { 87 this.tab = "scaling" 88 this.loading = true 89 this.sds.getAutoscaling().subscribe(data => { 90 this.loading = false 91 if(data["autoscaling"]["minimumCount"] != 0 && data["autoscaling"]["maximumCount"] != 0) { 92 this.scalingInput.minimumCount = data["autoscaling"]["minimumCount"] 93 this.scalingInput.maximumCount = data["autoscaling"]["maximumCount"] 94 if(data["autoscaling"]["policies"] && data["autoscaling"]["policies"].constructor === Array) { 95 this.scalingInput.policyCount = data["autoscaling"]["policies"].length 96 } else { 97 this.scalingInput.policyCount = 0 98 } 99 this.scalingInput.desiredCount = this.service.desiredCount 100 this.scalingInput.autoscaling = true 101 if(data["autoscaling"]["policies"]) { 102 this.scalingInput.policies = data["autoscaling"]["policies"] 103 this.scalingInput.policies.forEach((policy, index) => { 104 if(policy["scalingAdjustment"] > 0) { 105 this.scalingInput.policies[index]["scalingAdjustment"] = "Up (+"+policy["scalingAdjustment"]+")" 106 } else { 107 this.scalingInput.policies[index]["scalingAdjustment"] = "Down ("+policy["scalingAdjustment"]+")" 108 } 109 switch(policy["comparisonOperator"]) { 110 case "GreaterThanOrEqualToThreshold": { 111 this.scalingInput.policies[index]["comparisonOperator"] = ">=" 112 break; 113 } 114 case "LessThanOrEqualToThreshold": { 115 this.scalingInput.policies[index]["comparisonOperator"] = "<=" 116 break; 117 } 118 case "GreaterThanThreshold": { 119 this.scalingInput.policies[index]["comparisonOperator"] = ">" 120 break; 121 } 122 case "LessThanThreshold": { 123 this.scalingInput.policies[index]["comparisonOperator"] = "<" 124 break; 125 } 126 } 127 }) 128 } 129 } 130 }) 131 } 132 onClickRunTask() { 133 this.tab = "runTask" 134 this.loading = true 135 this.sds.getTaskDefinition().subscribe(data => { 136 this.loading = false 137 if("taskDefinition" in data) { 138 this.service["taskDefinition"] = data["taskDefinition"] 139 this.service["taskDefinition"]["containerDefinitions"].forEach((container, index) => { 140 this.runTaskInput[container["name"]] = {} 141 if(container["name"] == this.service.serviceName) { 142 this.runTaskInput[container["name"]]["enabled"] = true 143 } else { 144 this.runTaskInput[container["name"]]["enabled"] = false 145 } 146 }) 147 } 148 }); 149 } 150 onClickLogs(loading) { 151 this.tab = "logs" 152 this.loading = loading 153 // default timeranges 154 this.logsInput["timerange"] = [ 155 { id: "last-24h", name: "Last 24 hours" }, 156 { id: "last-7d", name: "Last 7 days" }, 157 { id: "last-14d", name: "Last 14 days" }, 158 { id: "last-30d", name: "Last 30 days" }, 159 { id: "last-1y", name: "Last 1 year" }, 160 ] 161 if(!("selectedTimerange" in this.logsInput)) { 162 this.logsInput.selectedTimerange = this.logsInput["timerange"][0] 163 } 164 this.sds.getTaskDefinition().subscribe(taskData => { 165 if("taskDefinition" in taskData) { 166 this.service["taskDefinition"] = taskData["taskDefinition"] 167 this.logsInput["containers"] = [{ "id": "", "name": "Select Container" }] 168 if(!("selectedContainer" in this.logsInput)) { 169 this.logsInput["selectedContainer"] = this.logsInput["containers"][0] 170 } 171 this.service["taskDefinition"]["containerDefinitions"].forEach((container, index) => { 172 this.logsInput["containers"].push({ "id": container["name"], "name": container["name"] }) 173 }) 174 } 175 this.sds.describeTasks().subscribe(data => { 176 this.loading = false 177 this.logsInput["taskArns"] = [{ "id": "", "name": "Select Task" }] 178 data["tasks"].forEach((task, index) => { 179 let s = task["taskArn"].split("/") 180 let startedBy 181 if(task["startedBy"].substring(0, 3) == "ecs") { 182 startedBy = "ecs" 183 } else { 184 let b = task["startedBy"].split("-") 185 startedBy = b[0] 186 } 187 let startedAt = moment(task["startedAt"]) 188 let n = s[1] + " (" + task["lastStatus"] + ")" 189 if(task["lastStatus"] == "PENDING") { 190 n = n + ", started by " + startedBy 191 } else { 192 n = n + ", started " + startedAt.fromNow() + " by " + startedBy 193 } 194 this.logsInput["taskArns"].push( 195 { 196 "id": s[1], 197 "name": n, 198 } 199 ) 200 }) 201 if(!("selectedTaskArn" in this.logsInput)) { 202 this.logsInput["selectedTaskArn"] = this.logsInput["taskArns"][0] 203 } 204 }) 205 }); 206 } 207 refresh() { 208 this.loading = true 209 this.sds.getService(this.service.serviceName).subscribe(data => { 210 this.loading = false 211 this.formatServiceData(data["service"]) 212 }); 213 } 214 215 formatServiceData(service): void { 216 service["deploymentMap"] = {} 217 // format deployments 218 service["deployments"].forEach((deployment, index) => { 219 // make a map per status of deployments 220 let lastDeploy = moment(deployment.createdAt).format('YYYY-MM-DD hh:mm:ss Z'); 221 deployment.lastDeploy = lastDeploy; 222 service["deploymentMap"][deployment["status"]] = deployment 223 }) 224 // format events 225 service["events"].forEach((serviceEvent, index) => { 226 let eventFormatted = moment(serviceEvent.createdAt).format('YYYY-MM-DD hh:mm:ss Z'); 227 service["events"][index]["createdAtFormatted"] = eventFormatted 228 }) 229 // format tasks 230 service["taskStatus"] = {} 231 service["taskTotal"] = 0 232 service["containerStatus"] = {} 233 service["containerTotal"] = 0 234 service["tasks"].forEach((task, index) => { 235 service["taskTotal"]++ 236 if(service["taskStatus"][task["lastStatus"]]) { 237 service["taskStatus"][task["lastStatus"]]++ 238 } else { 239 service["taskStatus"][task["lastStatus"]] = 1 240 } 241 task["containers"].forEach((container, index) => { 242 service["containerTotal"]++ 243 if(service["containerStatus"][container["lastStatus"]]) { 244 service["containerStatus"][container["lastStatus"]]++ 245 } else { 246 service["containerStatus"][container["lastStatus"]] = 1 247 } 248 }) 249 }) 250 this.service = service 251 } 252 253 deploying(loading) { 254 if(loading) { 255 this.loading = loading 256 } 257 } 258 deployed(deployResult) { 259 this.loading = true 260 this.tab = "service" 261 this.sds.getService(this.service.serviceName).subscribe(data => { 262 this.loading = false 263 this.formatServiceData(data["service"]) 264 }); 265 } 266 267 /* 268 * 269 * Parameters 270 * 271 */ 272 onClickParameters() { 273 this.parameters = []; 274 this.tab = "parameters" 275 this.loading = true 276 this.sds.listParameters().subscribe(data => { 277 this.loading = false 278 this.parameters["keys"] = [] 279 this.parameters["map"] = data['parameters']; 280 for (let key in this.parameters["map"]) { 281 this.parameters["keys"].push(key) 282 } 283 }); 284 } 285 286 showNewParameter() { 287 this.newParameter = true 288 } 289 saveNewParameter() { 290 if("name" in this.newParameterInput && "value" in this.newParameterInput) { 291 this.saving = true 292 this.sds.putParameter(this.newParameterInput).subscribe(data => { 293 this.saving = false 294 this.newParameterInput = {} 295 this.newParameter = false 296 this.onClickParameters() 297 }); 298 } 299 } 300 editParameter(parameter) { 301 this.selectedParameter = parameter 302 this.parameterInput["value"] = this.parameters["map"][parameter]["value"] 303 if(this.parameters["map"][parameter]["type"] == "SecureString") { 304 this.parameterInput["encrypted"] = true 305 } else { 306 this.parameterInput["encrypted"] = false 307 } 308 this.parameterInput["name"] = parameter 309 } 310 saveParameter(parameter): void { 311 if("value" in this.parameterInput) { 312 this.saving = true 313 this.sds.putParameter(this.parameterInput).subscribe(data => { 314 if(this.parameters["map"][parameter]["type"] == "SecureString") { 315 this.parameters["map"][parameter]["value"] = "***" 316 } else { 317 this.parameters["map"][parameter]["value"] = this.parameterInput["value"] 318 } 319 this.saving = false 320 this.selectedParameter = "" 321 this.parameterInput = {} 322 }); 323 } 324 } 325 326 editDesiredCount() { 327 this.scalingInput.desiredCount = this.service.desiredCount 328 this.editManualScaling = true 329 } 330 saveDesiredCount(): void { 331 if("desiredCount" in this.scalingInput) { 332 this.saving = true 333 this.sds.setDesiredCount(this.scalingInput).subscribe(data => { 334 if(data["message"] != "OK") { 335 this.alertService.error(data["error"]); 336 } 337 this.service["desiredCount"] = this.scalingInput["desiredCount"] 338 this.saving = false 339 this.editManualScaling = false 340 }); 341 } 342 } 343 runTask(): void { 344 let valid = false 345 let runTaskData = { 346 "containerOverrides": [] 347 } 348 let enabledContainers = [] 349 this.service["taskDefinition"]["containerDefinitions"].forEach((v, i) => { 350 let containerName = v.name 351 let container = this.runTaskInput[containerName] 352 if(container["enabled"]) { 353 if("containerCommand" in container) { 354 valid = true 355 enabledContainers.push(containerName) 356 } 357 if(container["environmentVariables"]) { 358 runTaskData["containerOverrides"].push({ 359 "name": containerName, 360 "command": ["bash", "-c", "eval $(aws-env) && " + container["containerCommand"]] 361 }) 362 } else { 363 runTaskData["containerOverrides"].push({ 364 "name": containerName, 365 "command": ["sh", "-c", container["containerCommand"]] 366 }) 367 } 368 } else { 369 // check if essential, otherwise sleep until timeout 370 if(v.essential) { 371 runTaskData["containerOverrides"].push({ 372 "name": containerName, 373 "command": ["sh", "-c", "echo 'Container disabled' && sleep "+(this.runTaskConfig.maxExecutionTime+60) ] 374 }) 375 } else { 376 runTaskData["containerOverrides"].push({ 377 "name": containerName, 378 "command": ["sh", "-c", "echo 'Container disabled'"] 379 }) 380 } 381 } 382 }) 383 if(valid) { 384 this.saving = true 385 this.sds.runTask(runTaskData).subscribe(data => { 386 if("taskArn" in data) { 387 //console.log("Taskarn: ", data["taskArn"]) 388 } else { 389 this.alertService.error(data["error"]); 390 } 391 let t = data["taskArn"].split("/") 392 this.saving = false 393 this.logsInput["selectedContainer"] = { "id": enabledContainers[0], "name": enabledContainers[0] } 394 this.logsInput["selectedTaskArn"] = { "id": t[1] } 395 this.onClickLogs(true) 396 this.updateLogs() 397 }); 398 } else { 399 this.alertService.error("Invalid task configuration") 400 } 401 } 402 updateLogs(): void { 403 if(this.logsInput["selectedContainer"]["id"] == "" || this.logsInput["selectedTaskArn"]["id"] == "" || this.logsInput["selectedTimerange"]["id"] == "") { 404 return 405 } 406 let start 407 switch(this.logsInput["selectedTimerange"]["id"]) { 408 case "last-7d": { 409 start = moment().subtract(7, 'days').toISOString() 410 } 411 case "last-14d": { 412 start = moment().subtract(14, 'days').toISOString() 413 } 414 case "last-30d": { 415 start = moment().subtract(30, 'days').toISOString() 416 } 417 case "last-1y": { 418 start = moment().subtract(1, 'year').toISOString() 419 } 420 default: { 421 start = moment().subtract(1, 'day').toISOString() 422 } 423 } 424 let params = { 425 "containerName": this.logsInput["selectedContainer"]["id"], 426 "taskArn": this.logsInput["selectedTaskArn"]["id"], 427 "start": start, 428 "end": moment().toISOString(), 429 } 430 this.loadingLogs = true 431 delete this.service["logs"] 432 this.sds.getServiceLog(params).subscribe(data => { 433 this.loadingLogs = false 434 if("error" in data) { 435 let errorMsg: string = data["error"] as string 436 if(errorMsg == "") { 437 this.alertService.error("Error, but error message was empty"); 438 } else { 439 if(errorMsg.startsWith("ResourceNotFoundException")) { 440 this.service["logs"] = { "count": 0 } 441 } else { 442 this.alertService.error(errorMsg); 443 } 444 } 445 return 446 } 447 this.service["logs"] = data["logs"] 448 if(!this.service["logs"]["logEvents"]) { 449 this.service["logs"]["count"] = 0 450 } else { 451 this.service["logs"]["count"] = this.service["logs"]["logEvents"].length 452 } 453 }); 454 } 455 refreshLogs(): void { 456 this.onClickLogs(false) 457 this.updateLogs() 458 } 459 compareByID(v1, v2) { 460 return v1 && v2 && v1["id"] == v2["id"]; 461 } 462 463 newAutoscalingPolicy() { 464 this.scalingInput.newAutoscalingPolicy = true 465 this.scalingInputPolicy.metric = "cpu" 466 this.scalingInputPolicy.comparisonOperator = "greaterThanOrEqualToThreshold" 467 this.scalingInputPolicy.thresholdStatistic = "average" 468 this.scalingInputPolicy.evaluationPeriods = "3" 469 this.scalingInputPolicy.period = "60" 470 this.scalingInputPolicy.scalingAdjustment = "1" 471 } 472 473 addAutoscalingPolicy() { 474 this.loading = true 475 this.scalingInput.serviceName = this.service.serviceName 476 this.scalingInput.minimumCount = Number(this.scalingInput.minimumCount) || 0 477 this.scalingInput.desiredCount = Number(this.scalingInput.desiredCount) || 0 478 this.scalingInput.maximumCount = Number(this.scalingInput.maximumCount) || 0 479 this.scalingInput.policies = [] 480 this.scalingInput.policies[0] = this.scalingInputPolicy 481 this.scalingInput.policies[0].datapointsToAlarm = Number(this.scalingInputPolicy.datapointsToAlarm) || 0 482 this.scalingInput.policies[0].evaluationPeriods = Number(this.scalingInputPolicy.datapointsToAlarm) || 0 // same evaluationperiods as datapointsToAlarm 483 this.scalingInput.policies[0].period = Number(this.scalingInputPolicy.period) 484 this.scalingInput.policies[0].scalingAdjustment = Number(this.scalingInputPolicy.scalingAdjustment) 485 this.scalingInput.policies[0].threshold = Number(this.scalingInputPolicy.threshold) || 0 486 this.sds.putAutoscaling(this.scalingInput).subscribe( 487 data => { 488 this.loading = false 489 this.scalingInput.newAutoscalingPolicy = false 490 this.onClickScaling() 491 }, 492 err => { 493 this.loading = false 494 this.alertService.error(err["error"]["error"]) 495 } 496 ); 497 } 498 saveAutoscalingPolicy() { 499 this.loading = true 500 let putData = this.scalingInput 501 putData.serviceName = this.service.serviceName 502 putData.minimumCount = Number(this.scalingInput.minimumCount) || 0 503 putData.desiredCount = Number(this.scalingInput.desiredCount) || 0 504 putData.maximumCount = Number(this.scalingInput.maximumCount) || 0 505 putData.policies = [] 506 this.sds.putAutoscaling(putData).subscribe( 507 data => { 508 this.loading = false 509 this.scalingInput.newAutoscalingPolicy = false 510 console.log(data) 511 this.alertService.success("Succesfully applied autoscaling settings", 5) 512 }, 513 err => { 514 this.loading = false 515 this.alertService.error(err["error"]["error"]) 516 } 517 ); 518 } 519 520 enableAutoscaling() { 521 this.scalingInput.autoscaling = true 522 } 523 deletingAutoscalingPolicy(loading) { 524 if(loading) { 525 this.loading = loading 526 } 527 } 528 529 deletingItem(loading) { 530 if(loading) { 531 this.loading = loading 532 } 533 } 534 deletedItem(data) { 535 if(data.action == 'deleteParameter') { 536 let selectedParameter = data.selectedItem 537 this.loading = true 538 delete this.parameters["map"][selectedParameter] 539 this.parameters["keys"] = [] 540 for (let key in this.parameters["map"]) { 541 this.parameters["keys"].push(key) 542 } 543 this.loading = false 544 } else if(data.action == 'deleteAutoscalingPolicy') { 545 let selectedAutoscalingPolicy = data.selectedItem 546 this.loading = true 547 let index = -1 548 this.scalingInput.policies.forEach((policy, i) => { 549 if(policy.policyName == selectedAutoscalingPolicy) { 550 index = i 551 } 552 }) 553 if (index > -1) { 554 this.scalingInput.policies.splice(index, 1); 555 } 556 this.scalingInput.policyCount-- 557 this.loading = false 558 } else if(data.action == 'disableAutoscaling') { 559 this.scalingInput.autoscaling = false 560 this.scalingInput.newAutoscalingPolicy = false 561 this.scalingInput.policies = [] 562 this.scalingInput.policyCount = 0 563 this.loading = false 564 this.alertService.success("Succesfully disabled autoscaling", 5) 565 } 566 } 567 }