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  }