Bläddra i källkod

feature: Kill support in the UI (#297)

* feature: Kill command API (wip)

* feature: Kill support in the UI
James Read 2 år sedan
förälder
incheckning
dc6f6c2896

+ 18 - 0
OliveTin.proto

@@ -189,6 +189,17 @@ message EventExecutionFinished {
 	LogEntry log_entry = 1;
 }
 
+message KillActionRequest {
+	string execution_tracking_id = 1;
+}
+
+message KillActionResponse {
+	string execution_tracking_id = 1;
+	bool killed = 2;
+	bool already_completed = 3;
+	bool found = 4;
+}
+
 service OliveTinApiService {
 	rpc GetDashboardComponents(GetDashboardComponentsRequest) returns (GetDashboardComponentsResponse) {
 		option (google.api.http) = {
@@ -222,6 +233,13 @@ service OliveTinApiService {
 		};
 	}
 
+	rpc KillAction(KillActionRequest) returns (KillActionResponse) {
+		option (google.api.http) = {
+			post: "/api/KillAction"
+			body: "*"
+		};
+	}
+
 	rpc ExecutionStatus(ExecutionStatusRequest) returns (ExecutionStatusResponse) {
 		option (google.api.http) = {
 			post: "/api/ExecutionStatus"

+ 3 - 0
internal/executor/executor.go

@@ -74,6 +74,7 @@ type InternalLogEntry struct {
 	ExecutionStarted    bool
 	ExecutionFinished   bool
 	ExecutionTrackingID string
+	Process             *os.Process
 
 	/*
 		The following 3 properties are obviously on Action normally, but it's useful
@@ -382,6 +383,8 @@ func stepExec(req *ExecutionRequest) bool {
 
 	runerr := cmd.Start()
 
+	req.logEntry.Process = cmd.Process
+
 	waiterr := cmd.Wait()
 
 	req.logEntry.ExitCode = int32(cmd.ProcessState.ExitCode())

+ 22 - 0
internal/grpcapi/grpcApi.go

@@ -30,6 +30,28 @@ type oliveTinAPI struct {
 	executor *executor.Executor
 }
 
+func (api *oliveTinAPI) KillAction(ctx ctx.Context, req *pb.KillActionRequest) (*pb.KillActionResponse, error) {
+	ret := &pb.KillActionResponse{
+		ExecutionTrackingId: req.ExecutionTrackingId,
+	}
+
+	execReq, found := api.executor.Logs[req.ExecutionTrackingId]
+
+	ret.Found = found
+
+	if found {
+		err := execReq.Process.Kill()
+
+		if err == nil {
+			ret.AlreadyCompleted = true
+		} else {
+			ret.Killed = true
+		}
+	}
+
+	return ret, nil
+}
+
 func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest) (*pb.StartActionResponse, error) {
 	args := make(map[string]string)
 

+ 6 - 4
webui.dev/index.html

@@ -125,11 +125,13 @@
 				</details>
 			</div>
 
-			<form method = "dialog">
-				<div class = "buttons">
+			<div class = "buttons">
+				<button name = "kill" title = "Kill" id = "execution-dialog-kill-action">Kill</button>
+
+				<form method = "dialog">
 					<button name = "Cancel" title = "Close">Close</button>
-				</div>
-			</form>
+				</form>
+			</div>
 		</dialog>
 
 		<template id = "tplArgumentForm">

+ 1 - 0
webui.dev/js/ActionButton.js

@@ -146,6 +146,7 @@ class ActionButton extends ExecutionFeedbackButton {
         window.executionDialog.hideEverythingApartFromOutput()
       }
 
+      window.executionDialog.executionTrackingId = executionTrackingId
       window.executionDialog.show(this)
     }
 

+ 33 - 2
webui.dev/js/ExecutionDialog.js

@@ -14,6 +14,8 @@ export class ExecutionDialog {
       this.toggleSize()
     }
 
+    this.domBtnKill = document.getElementById('execution-dialog-kill-action')
+
     this.domDatetimeStarted = document.getElementById('execution-dialog-datetime-started')
     this.domDatetimeFinished = document.getElementById('execution-dialog-datetime-finished')
     this.domExitCode = document.getElementById('execution-dialog-exit-code')
@@ -36,6 +38,7 @@ export class ExecutionDialog {
 
   reset () {
     this.executionSeconds = 0
+    this.executionTrackingId = 'notset'
 
     this.dlg.classList.remove('big')
     this.dlg.style.maxWidth = 'calc(100vw - 2em)'
@@ -54,6 +57,9 @@ export class ExecutionDialog {
     this.domStdout.innerText = ''
     this.domStderr.innerText = ''
 
+    this.domBtnKill.disabled = true
+    this.domBtnKill.onclick = () => {}
+
     this.hideDetailsOnResult = false
     this.domExecutionBasics.hidden = false
 
@@ -68,6 +74,11 @@ export class ExecutionDialog {
       this.domIcon.innerText = actionButton.domIcon.innerText
     }
 
+    this.domBtnKill.disabled = false
+    this.domBtnKill.onclick = () => {
+      this.killAction()
+    }
+
     clearInterval(window.executionDialogTicker)
     this.executionSeconds = 0
     this.executionTick()
@@ -82,6 +93,24 @@ export class ExecutionDialog {
     this.dlg.showModal()
   }
 
+  killAction () {
+    const killActionArgs = {
+      executionTrackingId: this.executionTrackingId
+    }
+
+    window.fetch(window.restBaseUrl + 'KillAction', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json'
+      },
+      body: JSON.stringify(killActionArgs)
+    }).then((res) => {
+      console.log(res)
+    }).catch(err => {
+      throw err
+    })
+  }
+
   executionTick () {
     this.executionSeconds++
 
@@ -93,8 +122,8 @@ export class ExecutionDialog {
     this.domExecutionBasics.hidden = true
   }
 
-  fetchExecutionResult (uuid) {
-    this.executionTrackingId = uuid
+  fetchExecutionResult (executionTrackingId) {
+    this.executionTrackingId = executionTrackingId
 
     const executionStatusArgs = {
       executionTrackingId: this.executionTrackingId
@@ -133,6 +162,8 @@ export class ExecutionDialog {
 
     this.executionTrackingId = res.logEntry.executionTrackingId
 
+    this.domBtnKill.disabled = res.logEntry.executionFinished
+
     if (res.logEntry.executionFinished) {
       this.domStatus.innerText = 'Completed'
       this.domStatus.classList.add('action-success')

+ 4 - 2
webui.dev/style.css

@@ -424,8 +424,10 @@ p.argument-wrapper {
   display: flex;
 }
 
-form div.buttons {
-  text-align: right;
+div.buttons {
+  display: flex;
+  justify-content: end;
+  gap: 1em;
 }
 
 pre {