diff --git a/OliveTin.proto b/OliveTin.proto
index 38fa1ab..49e0946 100644
--- a/OliveTin.proto
+++ b/OliveTin.proto
@@ -9,13 +9,13 @@ message Action {
string title = 2;
string icon = 3;
bool canExec = 4;
-
repeated ActionArgument arguments = 5;
+ bool popupOnStart = 6;
}
message ActionArgument {
string name = 1;
- string title = 2;
+ string title = 2;
string type = 3;
string defaultValue = 4;
@@ -55,13 +55,13 @@ message StartActionArgument {
}
message StartActionResponse {
- LogEntry logEntry = 1;
+ string executionUuid = 2;
}
message GetLogsRequest{};
message LogEntry {
- string datetime = 1;
+ string datetimeStarted = 1;
string actionTitle = 2;
string stdout = 3;
string stderr = 4;
@@ -71,6 +71,8 @@ message LogEntry {
string userClass = 8;
string actionIcon = 9;
repeated string tags = 10;
+ string executionUuid = 11;
+ string datetimeFinished = 12;
}
message GetLogsResponse {
@@ -87,6 +89,14 @@ message ValidateArgumentTypeResponse {
string description = 2;
}
+message WatchExecutionRequest {
+ string executionUuid = 1;
+}
+
+message WatchExecutionUpdate {
+ string update = 1;
+}
+
message WhoAmIRequest {}
message WhoAmIResponse {
@@ -113,6 +123,13 @@ service OliveTinApi {
};
}
+ rpc WatchExecution(WatchExecutionRequest) returns (stream WatchExecutionUpdate) {
+ option (google.api.http) = {
+ post: "/api/WatchExecution"
+ body: "*"
+ };
+ }
+
rpc GetLogs(GetLogsRequest) returns (GetLogsResponse) {
option (google.api.http) = {
get: "/api/GetLogs"
diff --git a/go.mod b/go.mod
index f8d16f6..4d66088 100644
--- a/go.mod
+++ b/go.mod
@@ -20,6 +20,7 @@ require (
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
+ nhooyr.io/websocket v1.8.7
)
require (
diff --git a/go.sum b/go.sum
index 3b0b56a..17147d5 100644
--- a/go.sum
+++ b/go.sum
@@ -94,6 +94,10 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-critic/go-critic v0.8.2 h1:mekhZ9jw5NBEj3I8o/EywXw5zBfGAJuMo4VVVjtxF80=
@@ -106,6 +110,13 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
@@ -124,6 +135,12 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
@@ -177,6 +194,7 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -200,6 +218,8 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -214,10 +234,13 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
@@ -228,14 +251,22 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@@ -289,6 +320,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -301,6 +333,10 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tetratelabs/wazero v1.4.0 h1:9/MirYvmkJ/zSUOygKY/ia3t+e+RqIZXKbylIby1WYk=
github.com/tetratelabs/wazero v1.4.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
+github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -446,6 +482,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -654,6 +691,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -665,6 +704,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
+nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/internal/acl/acl.go b/internal/acl/acl.go
index 5640fa2..a91e399 100644
--- a/internal/acl/acl.go
+++ b/internal/acl/acl.go
@@ -75,7 +75,10 @@ func getMetdataKeyOrEmpty(md metadata.MD, key string) string {
func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser {
md, ok := metadata.FromIncomingContext(ctx)
- ret := &AuthenticatedUser{}
+ ret := &AuthenticatedUser{
+ Username: "guest",
+ Usergroup: "guest",
+ }
if ok {
ret.Username = getMetdataKeyOrEmpty(md, "username")
diff --git a/internal/config/config.go b/internal/config/config.go
index a2707d5..f69a653 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -13,6 +13,7 @@ type Action struct {
ExecOnStartup bool
ExecOnCron []string
Arguments []ActionArgument
+ PopupOnStart bool
}
// ActionArgument objects appear on Actions.
diff --git a/internal/executor/executor.go b/internal/executor/executor.go
index 92a0ce3..92e4847 100644
--- a/internal/executor/executor.go
+++ b/internal/executor/executor.go
@@ -4,10 +4,12 @@ import (
pb "github.com/OliveTin/OliveTin/gen/grpc"
acl "github.com/OliveTin/OliveTin/internal/acl"
config "github.com/OliveTin/OliveTin/internal/config"
+ "github.com/google/uuid"
log "github.com/sirupsen/logrus"
"bytes"
"context"
+ "io"
"os/exec"
"runtime"
"time"
@@ -24,18 +26,22 @@ type ExecutionRequest struct {
AuthenticatedUser *acl.AuthenticatedUser
logEntry *InternalLogEntry
finalParsedCommand string
+ uuid string
}
// InternalLogEntry objects are created by an Executor, and represent the final
// state of execution (even if the command is not executed). It's designed to be
// easily serializable.
type InternalLogEntry struct {
- Datetime string
- Stdout string
- Stderr string
- TimedOut bool
- ExitCode int32
- Tags []string
+ DatetimeStarted string
+ DatetimeFinished string
+ Stdout string
+ Stderr string
+ StdoutBuffer io.ReadCloser
+ StderrBuffer io.ReadCloser
+ TimedOut bool
+ ExitCode int32
+ Tags []string
/*
The following two properties are obviously on Action normally, but it's useful
@@ -44,6 +50,9 @@ type InternalLogEntry struct {
*/
ActionTitle string
ActionIcon string
+
+ ExecutionStarted bool
+ ExecutionCompleted bool
}
type executorStepFunc func(*ExecutionRequest) bool
@@ -51,46 +60,17 @@ type executorStepFunc func(*ExecutionRequest) bool
// Executor represents a helper class for executing commands. It's main method
// is ExecRequest
type Executor struct {
- Logs []InternalLogEntry
+ Logs map[string]*InternalLogEntry
chainOfCommand []executorStepFunc
}
-// ExecRequest processes an ExecutionRequest
-func (e *Executor) ExecRequest(req *ExecutionRequest) *pb.StartActionResponse {
- req.logEntry = &InternalLogEntry{
- Datetime: time.Now().Format("2006-01-02 15:04:05"),
- ActionTitle: req.ActionName,
- Stdout: "",
- Stderr: "",
- ExitCode: -1337, // If an Action is not actually executed, this is the default exit code.
- }
-
- for _, step := range e.chainOfCommand {
- if !step(req) {
- break
- }
- }
-
- e.Logs = append(e.Logs, *req.logEntry)
-
- return &pb.StartActionResponse{
- LogEntry: &pb.LogEntry{
- ActionTitle: req.logEntry.ActionTitle,
- ActionIcon: req.logEntry.ActionIcon,
- Datetime: req.logEntry.Datetime,
- Stderr: req.logEntry.Stderr,
- Stdout: req.logEntry.Stdout,
- TimedOut: req.logEntry.TimedOut,
- ExitCode: req.logEntry.ExitCode,
- },
- }
-}
-
// DefaultExecutor returns an Executor, with a sensible "chain of command" for
// executing actions.
func DefaultExecutor() *Executor {
e := Executor{}
+ e.Logs = make(map[string]*InternalLogEntry)
+
e.chainOfCommand = []executorStepFunc{
stepFindAction,
stepACLCheck,
@@ -103,6 +83,36 @@ func DefaultExecutor() *Executor {
return &e
}
+// ExecRequest processes an ExecutionRequest
+func (e *Executor) ExecRequest(req *ExecutionRequest) *pb.StartActionResponse {
+ req.uuid = uuid.New().String()
+ req.logEntry = &InternalLogEntry{
+ DatetimeStarted: time.Now().Format("2006-01-02 15:04:05"),
+ ActionTitle: req.ActionName,
+ Stdout: "",
+ Stderr: "",
+ ExitCode: -1337, // If an Action is not actually executed, this is the default exit code.
+ ExecutionStarted: false,
+ ExecutionCompleted: false,
+ }
+
+ e.Logs[req.uuid] = req.logEntry
+
+ go e.execChain(req)
+
+ return &pb.StartActionResponse{
+ ExecutionUuid: req.uuid,
+ }
+}
+
+func (e *Executor) execChain(req *ExecutionRequest) {
+ for _, step := range e.chainOfCommand {
+ if !step(req) {
+ break
+ }
+ }
+}
+
func stepFindAction(req *ExecutionRequest) bool {
actualAction := req.Cfg.FindAction(req.ActionName)
@@ -181,9 +191,19 @@ func stepExec(req *ExecutionRequest) bool {
cmd := wrapCommandInShell(ctx, req.finalParsedCommand)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
+ req.logEntry.StdoutBuffer, _ = cmd.StdoutPipe()
+ req.logEntry.StderrBuffer, _ = cmd.StderrPipe()
- runerr := cmd.Run()
+ req.logEntry.ExecutionStarted = true
+ runerr := cmd.Start()
+
+ cmd.Wait()
+
+ //req.logEntry.Stdout = req.logEntry.StdoutBuffer.String()
+ //req.logEntry.Stderr = req.logEntry.StderrBuffer.String()
+
+ req.logEntry.ExecutionCompleted = true
req.logEntry.ExitCode = int32(cmd.ProcessState.ExitCode())
req.logEntry.Stdout = stdout.String()
req.logEntry.Stderr = stderr.String()
diff --git a/internal/grpcapi/grpcApi.go b/internal/grpcapi/grpcApi.go
index d6f1353..65a5f6f 100644
--- a/internal/grpcapi/grpcApi.go
+++ b/internal/grpcapi/grpcApi.go
@@ -6,6 +6,7 @@ import (
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
+ "io"
"net"
acl "github.com/OliveTin/OliveTin/internal/acl"
@@ -43,6 +44,32 @@ func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest)
return api.executor.ExecRequest(&execReq), nil
}
+func (api *oliveTinAPI) WatchExecution(req *pb.WatchExecutionRequest, srv pb.OliveTinApi_WatchExecutionServer) error {
+ log.Infof("Watch")
+
+ if logEntry, ok := api.executor.Logs[req.ExecutionUuid]; !ok {
+ log.Errorf("Execution not found: %v", req.ExecutionUuid)
+
+ return nil
+ } else {
+ if logEntry.ExecutionStarted {
+ for !logEntry.ExecutionCompleted {
+ tmp := make([]byte, 256)
+
+ red, err := io.ReadAtLeast(logEntry.StdoutBuffer, tmp, 1)
+
+ log.Infof("%v %v", red, err)
+
+ srv.Send(&pb.WatchExecutionUpdate{
+ Update: string(tmp),
+ })
+ }
+ }
+
+ return nil
+ }
+}
+
func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *pb.GetDashboardComponentsRequest) (*pb.GetDashboardComponentsResponse, error) {
user := acl.UserFromContext(ctx, cfg)
@@ -62,16 +89,18 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *pb.GetLogsRequest) (*pb.Ge
// TODO Limit to 10 entries or something to prevent browser lag.
- for _, logEntry := range api.executor.Logs {
+ for uuid, logEntry := range api.executor.Logs {
ret.Logs = append(ret.Logs, &pb.LogEntry{
- ActionTitle: logEntry.ActionTitle,
- ActionIcon: logEntry.ActionIcon,
- Datetime: logEntry.Datetime,
- Stdout: logEntry.Stdout,
- Stderr: logEntry.Stderr,
- TimedOut: logEntry.TimedOut,
- ExitCode: logEntry.ExitCode,
- Tags: logEntry.Tags,
+ ActionTitle: logEntry.ActionTitle,
+ ActionIcon: logEntry.ActionIcon,
+ DatetimeStarted: logEntry.DatetimeStarted,
+ DatetimeFinished: logEntry.DatetimeFinished,
+ Stdout: logEntry.Stdout,
+ Stderr: logEntry.Stderr,
+ TimedOut: logEntry.TimedOut,
+ ExitCode: logEntry.ExitCode,
+ Tags: logEntry.Tags,
+ ExecutionUuid: uuid,
})
}
diff --git a/internal/grpcapi/grpcApiActions.go b/internal/grpcapi/grpcApiActions.go
index e70a75f..5fb1f4b 100644
--- a/internal/grpcapi/grpcApiActions.go
+++ b/internal/grpcapi/grpcApiActions.go
@@ -25,10 +25,11 @@ func actionsCfgToPb(cfgActions []config.Action, user *acl.AuthenticatedUser) *pb
func actionCfgToPb(action config.Action, user *acl.AuthenticatedUser) *pb.Action {
btn := pb.Action{
- Id: fmt.Sprintf("%x", md5.Sum([]byte(action.Title))),
- Title: action.Title,
- Icon: action.Icon,
- CanExec: acl.IsAllowedExec(cfg, user, &action),
+ Id: fmt.Sprintf("%x", md5.Sum([]byte(action.Title))),
+ Title: action.Title,
+ Icon: action.Icon,
+ CanExec: acl.IsAllowedExec(cfg, user, &action),
+ PopupOnStart: action.PopupOnStart,
}
for _, cfgArg := range action.Arguments {
diff --git a/internal/httpservers/singleFrontend.go b/internal/httpservers/singleFrontend.go
index 6e4a926..cb39c82 100644
--- a/internal/httpservers/singleFrontend.go
+++ b/internal/httpservers/singleFrontend.go
@@ -14,6 +14,7 @@ import (
"net/http"
"net/http/httputil"
"net/url"
+ "strings"
)
// StartSingleHTTPFrontend will create a reverse proxy that proxies the API
@@ -37,8 +38,12 @@ func StartSingleHTTPFrontend(cfg *config.Config) {
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- log.Debugf("ui req: %q", r.URL)
- webuiProxy.ServeHTTP(w, r)
+ if strings.Contains(r.Header.Get("Connection"), "Upgrade") {
+ handleWebsocket(w, r)
+ } else {
+ log.Debugf("ui req: %q", r.URL)
+ webuiProxy.ServeHTTP(w, r)
+ }
})
srv := &http.Server{
diff --git a/internal/httpservers/websocket.go b/internal/httpservers/websocket.go
new file mode 100644
index 0000000..c0ef7a1
--- /dev/null
+++ b/internal/httpservers/websocket.go
@@ -0,0 +1,40 @@
+package httpservers
+
+import (
+ "time"
+ log "github.com/sirupsen/logrus"
+ "net/http"
+ "nhooyr.io/websocket"
+ "nhooyr.io/websocket/wsjson"
+ "context"
+)
+
+func handleWebsocket(w http.ResponseWriter, r *http.Request) bool {
+ c, err := websocket.Accept(w, r, nil)
+
+ if err != nil {
+ log.Warnf("Websocket issue: %v", err)
+ return false
+ }
+
+ defer c.Close(websocket.StatusInternalError, "Goodbye")
+
+ ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
+
+ defer cancel()
+
+ var v interface{}
+
+ err = wsjson.Read(ctx, c, v)
+
+ log.Printf("recv: %v", v)
+
+ if err != nil {
+ log.Warnf("Websocket issue: %v", err)
+ return false
+ }
+
+
+ c.Close(websocket.StatusNormalClosure, "")
+ return true
+}
diff --git a/internal/httpservers/webuiServer_test.go b/internal/httpservers/webuiServer_test.go
index 94ea73c..a4843b7 100644
--- a/internal/httpservers/webuiServer_test.go
+++ b/internal/httpservers/webuiServer_test.go
@@ -4,11 +4,14 @@ import (
"github.com/stretchr/testify/assert"
"os"
"testing"
+ config "github.com/OliveTin/OliveTin/internal/config"
)
func TestGetWebuiDir(t *testing.T) {
os.Chdir("../../") // go test sets the cwd to "httpservers" by default
+ cfg = config.DefaultConfig();
+
dir := findWebuiDir()
assert.Equal(t, "./webui", dir, "Finding the webui dir")
diff --git a/webui/index.html b/webui/index.html
index 12343ad..f806fca 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -146,5 +146,6 @@
+