feature: websocket implemenation at last!
This commit is contained in:
parent
6116e954ba
commit
e5a870ed94
|
|
@ -8,16 +8,16 @@ message Action {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string title = 2;
|
string title = 2;
|
||||||
string icon = 3;
|
string icon = 3;
|
||||||
bool canExec = 4;
|
bool can_exec = 4;
|
||||||
repeated ActionArgument arguments = 5;
|
repeated ActionArgument arguments = 5;
|
||||||
bool popupOnStart = 6;
|
bool popup_on_start = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ActionArgument {
|
message ActionArgument {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
string title = 2;
|
string title = 2;
|
||||||
string type = 3;
|
string type = 3;
|
||||||
string defaultValue = 4;
|
string default_value = 4;
|
||||||
|
|
||||||
repeated ActionArgumentChoice choices = 5;
|
repeated ActionArgumentChoice choices = 5;
|
||||||
|
|
||||||
|
|
@ -44,9 +44,11 @@ message GetDashboardComponentsResponse {
|
||||||
message GetDashboardComponentsRequest {}
|
message GetDashboardComponentsRequest {}
|
||||||
|
|
||||||
message StartActionRequest {
|
message StartActionRequest {
|
||||||
string actionName = 1;
|
string action_name = 1;
|
||||||
|
|
||||||
repeated StartActionArgument arguments = 2;
|
repeated StartActionArgument arguments = 2;
|
||||||
|
|
||||||
|
string uuid = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartActionArgument {
|
message StartActionArgument {
|
||||||
|
|
@ -55,24 +57,25 @@ message StartActionArgument {
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartActionResponse {
|
message StartActionResponse {
|
||||||
string executionUuid = 2;
|
string execution_uuid = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetLogsRequest{};
|
message GetLogsRequest{};
|
||||||
|
|
||||||
message LogEntry {
|
message LogEntry {
|
||||||
string datetimeStarted = 1;
|
string datetime_started = 1;
|
||||||
string actionTitle = 2;
|
string action_title = 2;
|
||||||
string stdout = 3;
|
string stdout = 3;
|
||||||
string stderr = 4;
|
string stderr = 4;
|
||||||
bool timedOut = 5;
|
bool timed_out = 5;
|
||||||
int32 exitCode = 6;
|
int32 exit_code = 6;
|
||||||
string user = 7;
|
string user = 7;
|
||||||
string userClass = 8;
|
string user_class = 8;
|
||||||
string actionIcon = 9;
|
string action_icon = 9;
|
||||||
repeated string tags = 10;
|
repeated string tags = 10;
|
||||||
string executionUuid = 11;
|
string execution_uuid = 11;
|
||||||
string datetimeFinished = 12;
|
string datetime_finished = 12;
|
||||||
|
string uuid = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetLogsResponse {
|
message GetLogsResponse {
|
||||||
|
|
@ -90,17 +93,25 @@ message ValidateArgumentTypeResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
message WatchExecutionRequest {
|
message WatchExecutionRequest {
|
||||||
string executionUuid = 1;
|
string execution_uuid = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message WatchExecutionUpdate {
|
message WatchExecutionUpdate {
|
||||||
string update = 1;
|
string update = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ExecutionStatusRequest {
|
||||||
|
string execution_uuid = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExecutionStatusResponse {
|
||||||
|
LogEntry log_entry = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message WhoAmIRequest {}
|
message WhoAmIRequest {}
|
||||||
|
|
||||||
message WhoAmIResponse {
|
message WhoAmIResponse {
|
||||||
string authenticatedUser = 1;
|
string authenticated_user = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SosReportRequest {}
|
message SosReportRequest {}
|
||||||
|
|
@ -109,7 +120,7 @@ message SosReportResponse {
|
||||||
string alert = 1;
|
string alert = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
service OliveTinApi {
|
service OliveTinApiService {
|
||||||
rpc GetDashboardComponents(GetDashboardComponentsRequest) returns (GetDashboardComponentsResponse) {
|
rpc GetDashboardComponents(GetDashboardComponentsRequest) returns (GetDashboardComponentsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
get: "/api/GetDashboardComponents"
|
get: "/api/GetDashboardComponents"
|
||||||
|
|
@ -123,9 +134,9 @@ service OliveTinApi {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc WatchExecution(WatchExecutionRequest) returns (stream WatchExecutionUpdate) {
|
rpc ExecutionStatus(ExecutionStatusRequest) returns (ExecutionStatusResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/api/WatchExecution"
|
post: "/api/ExecutionStatus"
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/OliveTin/OliveTin/internal/oncron"
|
"github.com/OliveTin/OliveTin/internal/oncron"
|
||||||
"github.com/OliveTin/OliveTin/internal/onstartup"
|
"github.com/OliveTin/OliveTin/internal/onstartup"
|
||||||
updatecheck "github.com/OliveTin/OliveTin/internal/updatecheck"
|
updatecheck "github.com/OliveTin/OliveTin/internal/updatecheck"
|
||||||
|
"github.com/OliveTin/OliveTin/internal/websocket"
|
||||||
|
|
||||||
"github.com/OliveTin/OliveTin/internal/httpservers"
|
"github.com/OliveTin/OliveTin/internal/httpservers"
|
||||||
|
|
||||||
|
|
@ -110,6 +111,7 @@ func main() {
|
||||||
log.Debugf("Config: %+v", cfg)
|
log.Debugf("Config: %+v", cfg)
|
||||||
|
|
||||||
executor := executor.DefaultExecutor()
|
executor := executor.DefaultExecutor()
|
||||||
|
executor.AddListener(websocket.ExecutionListener)
|
||||||
|
|
||||||
go onstartup.Execute(cfg, executor)
|
go onstartup.Execute(cfg, executor)
|
||||||
go oncron.Schedule(cfg, executor)
|
go oncron.Schedule(cfg, executor)
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/go-critic/go-critic v0.8.2
|
github.com/go-critic/go-critic v0.8.2
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/gorilla/websocket v1.4.1
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
|
@ -20,7 +21,6 @@ require (
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
|
||||||
google.golang.org/protobuf v1.31.0
|
google.golang.org/protobuf v1.31.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
nhooyr.io/websocket v1.8.7
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
|
||||||
39
go.sum
39
go.sum
|
|
@ -94,10 +94,6 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
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 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
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 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
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=
|
github.com/go-critic/go-critic v0.8.2 h1:mekhZ9jw5NBEj3I8o/EywXw5zBfGAJuMo4VVVjtxF80=
|
||||||
|
|
@ -110,13 +106,6 @@ 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/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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
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 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
|
||||||
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
|
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=
|
github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
|
||||||
|
|
@ -135,12 +124,6 @@ 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/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 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
|
||||||
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
|
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/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 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
|
|
@ -194,7 +177,6 @@ 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-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 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
|
||||||
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
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 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.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=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
|
@ -234,13 +216,10 @@ 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 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co=
|
||||||
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw=
|
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/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.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/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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
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 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
|
@ -251,22 +230,14 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
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 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
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 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
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 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
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 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
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=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
|
@ -320,7 +291,6 @@ 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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
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.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.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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|
@ -333,10 +303,6 @@ 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/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 h1:9/MirYvmkJ/zSUOygKY/ia3t+e+RqIZXKbylIby1WYk=
|
||||||
github.com/tetratelabs/wazero v1.4.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
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 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
|
||||||
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
|
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
@ -482,7 +448,6 @@ 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-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-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-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-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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -691,8 +656,6 @@ 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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
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.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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
@ -704,8 +667,6 @@ 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-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.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/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/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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"username": ret.Username,
|
"username": ret.Username,
|
||||||
"usergroup": ret.Usergroup,
|
"usergroup": ret.Usergroup,
|
||||||
}).Infof("UserFromContext")
|
}).Debugf("UserFromContext")
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ var (
|
||||||
|
|
||||||
func parseActionArguments(rawShellCommand string, values map[string]string, action *config.Action) (string, error) {
|
func parseActionArguments(rawShellCommand string, values map[string]string, action *config.Action) (string, error) {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
|
"actionTitle": action.Title,
|
||||||
"cmd": rawShellCommand,
|
"cmd": rawShellCommand,
|
||||||
}).Infof("Before Parse Args")
|
}).Infof("Action parse args - Before")
|
||||||
|
|
||||||
r := regexp.MustCompile("{{ *?([a-zA-Z0-9_]+?) *?}}")
|
r := regexp.MustCompile("{{ *?([a-zA-Z0-9_]+?) *?}}")
|
||||||
matches := r.FindAllStringSubmatch(rawShellCommand, -1)
|
matches := r.FindAllStringSubmatch(rawShellCommand, -1)
|
||||||
|
|
@ -51,8 +52,9 @@ func parseActionArguments(rawShellCommand string, values map[string]string, acti
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
|
"actionTitle": action.Title,
|
||||||
"cmd": rawShellCommand,
|
"cmd": rawShellCommand,
|
||||||
}).Infof("After Parse Args")
|
}).Infof("Action parse args - After")
|
||||||
|
|
||||||
return rawShellCommand, nil
|
return rawShellCommand, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
pb "github.com/OliveTin/OliveTin/gen/grpc"
|
pb "github.com/OliveTin/OliveTin/gen/grpc"
|
||||||
acl "github.com/OliveTin/OliveTin/internal/acl"
|
acl "github.com/OliveTin/OliveTin/internal/acl"
|
||||||
config "github.com/OliveTin/OliveTin/internal/config"
|
config "github.com/OliveTin/OliveTin/internal/config"
|
||||||
"github.com/google/uuid"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
@ -15,18 +14,29 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Executor represents a helper class for executing commands. It's main method
|
||||||
|
// is ExecRequest
|
||||||
|
type Executor struct {
|
||||||
|
Logs map[string]*InternalLogEntry
|
||||||
|
|
||||||
|
listeners []listener
|
||||||
|
|
||||||
|
chainOfCommand []executorStepFunc
|
||||||
|
}
|
||||||
|
|
||||||
// ExecutionRequest is a request to execute an action. It's passed to an
|
// ExecutionRequest is a request to execute an action. It's passed to an
|
||||||
// Executor. They're created from the grpcapi.
|
// Executor. They're created from the grpcapi.
|
||||||
type ExecutionRequest struct {
|
type ExecutionRequest struct {
|
||||||
ActionName string
|
ActionName string
|
||||||
Arguments map[string]string
|
Arguments map[string]string
|
||||||
|
UUID string
|
||||||
Tags []string
|
Tags []string
|
||||||
action *config.Action
|
action *config.Action
|
||||||
Cfg *config.Config
|
Cfg *config.Config
|
||||||
AuthenticatedUser *acl.AuthenticatedUser
|
AuthenticatedUser *acl.AuthenticatedUser
|
||||||
logEntry *InternalLogEntry
|
logEntry *InternalLogEntry
|
||||||
finalParsedCommand string
|
finalParsedCommand string
|
||||||
uuid string
|
executor *Executor
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalLogEntry objects are created by an Executor, and represent the final
|
// InternalLogEntry objects are created by an Executor, and represent the final
|
||||||
|
|
@ -42,29 +52,21 @@ type InternalLogEntry struct {
|
||||||
TimedOut bool
|
TimedOut bool
|
||||||
ExitCode int32
|
ExitCode int32
|
||||||
Tags []string
|
Tags []string
|
||||||
|
ExecutionStarted bool
|
||||||
|
ExecutionCompleted bool
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The following two properties are obviously on Action normally, but it's useful
|
The following 3 properties are obviously on Action normally, but it's useful
|
||||||
that logs are lightweight (so we don't need to have an action associated to
|
that logs are lightweight (so we don't need to have an action associated to
|
||||||
logs, etc. Therefore, we duplicate those values here.
|
logs, etc. Therefore, we duplicate those values here.
|
||||||
*/
|
*/
|
||||||
ActionTitle string
|
ActionTitle string
|
||||||
ActionIcon string
|
ActionIcon string
|
||||||
|
UUID string
|
||||||
ExecutionStarted bool
|
|
||||||
ExecutionCompleted bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type executorStepFunc func(*ExecutionRequest) bool
|
type executorStepFunc func(*ExecutionRequest) bool
|
||||||
|
|
||||||
// Executor represents a helper class for executing commands. It's main method
|
|
||||||
// is ExecRequest
|
|
||||||
type Executor struct {
|
|
||||||
Logs map[string]*InternalLogEntry
|
|
||||||
|
|
||||||
chainOfCommand []executorStepFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultExecutor returns an Executor, with a sensible "chain of command" for
|
// DefaultExecutor returns an Executor, with a sensible "chain of command" for
|
||||||
// executing actions.
|
// executing actions.
|
||||||
func DefaultExecutor() *Executor {
|
func DefaultExecutor() *Executor {
|
||||||
|
|
@ -72,23 +74,38 @@ func DefaultExecutor() *Executor {
|
||||||
e.Logs = make(map[string]*InternalLogEntry)
|
e.Logs = make(map[string]*InternalLogEntry)
|
||||||
|
|
||||||
e.chainOfCommand = []executorStepFunc{
|
e.chainOfCommand = []executorStepFunc{
|
||||||
|
stepLogRequested,
|
||||||
stepFindAction,
|
stepFindAction,
|
||||||
stepACLCheck,
|
stepACLCheck,
|
||||||
stepParseArgs,
|
stepParseArgs,
|
||||||
stepLogStart,
|
stepLogStart,
|
||||||
stepExec,
|
stepExec,
|
||||||
|
stepNotifyListeners,
|
||||||
stepLogFinish,
|
stepLogFinish,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &e
|
return &e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type listener interface {
|
||||||
|
OnExecutionStarted(actionName string)
|
||||||
|
OnExecutionFinished(logEntry *InternalLogEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Executor) AddListener(m listener) {
|
||||||
|
e.listeners = append(e.listeners, m)
|
||||||
|
}
|
||||||
|
|
||||||
// ExecRequest processes an ExecutionRequest
|
// ExecRequest processes an ExecutionRequest
|
||||||
func (e *Executor) ExecRequest(req *ExecutionRequest) *pb.StartActionResponse {
|
func (e *Executor) ExecRequest(req *ExecutionRequest) *pb.StartActionResponse {
|
||||||
req.uuid = uuid.New().String()
|
// req.UUID is now set by the client, so that they can track the request
|
||||||
|
// from start to finish. This means that a malicious client could send
|
||||||
|
// duplicate UUIDs (or just random strings), but this is the only way.
|
||||||
|
req.executor = e
|
||||||
req.logEntry = &InternalLogEntry{
|
req.logEntry = &InternalLogEntry{
|
||||||
DatetimeStarted: time.Now().Format("2006-01-02 15:04:05"),
|
DatetimeStarted: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
ActionTitle: req.ActionName,
|
ActionTitle: req.ActionName,
|
||||||
|
UUID: req.UUID,
|
||||||
Stdout: "",
|
Stdout: "",
|
||||||
Stderr: "",
|
Stderr: "",
|
||||||
ExitCode: -1337, // If an Action is not actually executed, this is the default exit code.
|
ExitCode: -1337, // If an Action is not actually executed, this is the default exit code.
|
||||||
|
|
@ -96,12 +113,16 @@ func (e *Executor) ExecRequest(req *ExecutionRequest) *pb.StartActionResponse {
|
||||||
ExecutionCompleted: false,
|
ExecutionCompleted: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Logs[req.uuid] = req.logEntry
|
e.Logs[req.UUID] = req.logEntry
|
||||||
|
|
||||||
|
for _, listener := range e.listeners {
|
||||||
|
listener.OnExecutionStarted(req.ActionName)
|
||||||
|
}
|
||||||
|
|
||||||
go e.execChain(req)
|
go e.execChain(req)
|
||||||
|
|
||||||
return &pb.StartActionResponse{
|
return &pb.StartActionResponse{
|
||||||
ExecutionUuid: req.uuid,
|
ExecutionUuid: req.UUID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,9 +173,17 @@ func stepParseArgs(req *ExecutionRequest) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stepLogRequested(req *ExecutionRequest) bool {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"actionTitle": req.ActionName,
|
||||||
|
}).Infof("Action requested")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func stepLogStart(req *ExecutionRequest) bool {
|
func stepLogStart(req *ExecutionRequest) bool {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"title": req.action.Title,
|
"actionTitle": req.action.Title,
|
||||||
"timeout": req.action.Timeout,
|
"timeout": req.action.Timeout,
|
||||||
}).Infof("Action starting")
|
}).Infof("Action starting")
|
||||||
|
|
||||||
|
|
@ -163,7 +192,7 @@ func stepLogStart(req *ExecutionRequest) bool {
|
||||||
|
|
||||||
func stepLogFinish(req *ExecutionRequest) bool {
|
func stepLogFinish(req *ExecutionRequest) bool {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"title": req.action.Title,
|
"actionTitle": req.action.Title,
|
||||||
"stdout": req.logEntry.Stdout,
|
"stdout": req.logEntry.Stdout,
|
||||||
"stderr": req.logEntry.Stderr,
|
"stderr": req.logEntry.Stderr,
|
||||||
"timedOut": req.logEntry.TimedOut,
|
"timedOut": req.logEntry.TimedOut,
|
||||||
|
|
@ -173,6 +202,14 @@ func stepLogFinish(req *ExecutionRequest) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stepNotifyListeners(req *ExecutionRequest) bool {
|
||||||
|
for _, listener := range req.executor.listeners {
|
||||||
|
listener.OnExecutionFinished(req.logEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func wrapCommandInShell(ctx context.Context, finalParsedCommand string) *exec.Cmd {
|
func wrapCommandInShell(ctx context.Context, finalParsedCommand string) *exec.Cmd {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return exec.CommandContext(ctx, "cmd", "/C", finalParsedCommand)
|
return exec.CommandContext(ctx, "cmd", "/C", finalParsedCommand)
|
||||||
|
|
@ -217,6 +254,7 @@ func stepExec(req *ExecutionRequest) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
req.logEntry.Tags = req.Tags
|
req.logEntry.Tags = req.Tags
|
||||||
|
req.logEntry.DatetimeFinished = time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
|
|
||||||
acl "github.com/OliveTin/OliveTin/internal/acl"
|
acl "github.com/OliveTin/OliveTin/internal/acl"
|
||||||
config "github.com/OliveTin/OliveTin/internal/config"
|
config "github.com/OliveTin/OliveTin/internal/config"
|
||||||
|
|
@ -20,7 +20,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type oliveTinAPI struct {
|
type oliveTinAPI struct {
|
||||||
pb.UnimplementedOliveTinApiServer
|
pb.UnimplementedOliveTinApiServiceServer
|
||||||
|
|
||||||
executor *executor.Executor
|
executor *executor.Executor
|
||||||
}
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest)
|
||||||
|
|
||||||
execReq := executor.ExecutionRequest{
|
execReq := executor.ExecutionRequest{
|
||||||
ActionName: req.ActionName,
|
ActionName: req.ActionName,
|
||||||
|
UUID: req.Uuid,
|
||||||
Arguments: args,
|
Arguments: args,
|
||||||
AuthenticatedUser: acl.UserFromContext(ctx, cfg),
|
AuthenticatedUser: acl.UserFromContext(ctx, cfg),
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
|
|
@ -44,6 +45,32 @@ func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest)
|
||||||
return api.executor.ExecRequest(&execReq), nil
|
return api.executor.ExecRequest(&execReq), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *pb.ExecutionStatusRequest) (*pb.ExecutionStatusResponse, error) {
|
||||||
|
res := &pb.ExecutionStatusResponse{}
|
||||||
|
|
||||||
|
logEntry, ok := api.executor.Logs[req.ExecutionUuid]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.LogEntry = &pb.LogEntry{
|
||||||
|
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: logEntry.UUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
func (api *oliveTinAPI) WatchExecution(req *pb.WatchExecutionRequest, srv pb.OliveTinApi_WatchExecutionServer) error {
|
func (api *oliveTinAPI) WatchExecution(req *pb.WatchExecutionRequest, srv pb.OliveTinApi_WatchExecutionServer) error {
|
||||||
log.Infof("Watch")
|
log.Infof("Watch")
|
||||||
|
|
||||||
|
|
@ -69,6 +96,7 @@ func (api *oliveTinAPI) WatchExecution(req *pb.WatchExecutionRequest, srv pb.Oli
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *pb.GetDashboardComponentsRequest) (*pb.GetDashboardComponentsResponse, error) {
|
func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *pb.GetDashboardComponentsRequest) (*pb.GetDashboardComponentsResponse, error) {
|
||||||
user := acl.UserFromContext(ctx, cfg)
|
user := acl.UserFromContext(ctx, cfg)
|
||||||
|
|
@ -104,6 +132,12 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *pb.GetLogsRequest) (*pb.Ge
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sorter := func(i, j int) bool {
|
||||||
|
return ret.Logs[i].DatetimeStarted < ret.Logs[j].DatetimeStarted
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(ret.Logs, sorter);
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +193,7 @@ func Start(globalConfig *config.Config, ex *executor.Executor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
grpcServer := grpc.NewServer()
|
grpcServer := grpc.NewServer()
|
||||||
pb.RegisterOliveTinApiServer(grpcServer, newServer(ex))
|
pb.RegisterOliveTinApiServiceServer(grpcServer, newServer(ex))
|
||||||
|
|
||||||
err = grpcServer.Serve(lis)
|
err = grpcServer.Serve(lis)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func init() {
|
||||||
|
|
||||||
lis = bufconn.Listen(bufSize)
|
lis = bufconn.Listen(bufSize)
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
pb.RegisterOliveTinApiServer(s, newServer(ex))
|
pb.RegisterOliveTinApiServiceServer(s, newServer(ex))
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.Serve(lis); err != nil {
|
if err := s.Serve(lis); err != nil {
|
||||||
|
|
@ -39,7 +39,7 @@ func bufDialer(context.Context, string) (net.Conn, error) {
|
||||||
return lis.Dial()
|
return lis.Dial()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNewTestServerAndClient(t *testing.T, injectedConfig *config.Config) (*grpc.ClientConn, pb.OliveTinApiClient) {
|
func getNewTestServerAndClient(t *testing.T, injectedConfig *config.Config) (*grpc.ClientConn, pb.OliveTinApiServiceClient) {
|
||||||
cfg = injectedConfig
|
cfg = injectedConfig
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
@ -50,7 +50,7 @@ func getNewTestServerAndClient(t *testing.T, injectedConfig *config.Config) (*gr
|
||||||
t.Fatalf("Failed to dial bufnet: %v", err)
|
t.Fatalf("Failed to dial bufnet: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := pb.NewOliveTinApiClient(conn)
|
client := pb.NewOliveTinApiServiceClient(conn)
|
||||||
|
|
||||||
return conn, client
|
return conn, client
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ func startRestAPIServer(globalConfig *config.Config) error {
|
||||||
runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
|
runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
|
||||||
Marshaler: &runtime.JSONPb{
|
Marshaler: &runtime.JSONPb{
|
||||||
MarshalOptions: protojson.MarshalOptions{
|
MarshalOptions: protojson.MarshalOptions{
|
||||||
UseProtoNames: true,
|
UseProtoNames: false, // eg: canExec for js instead of can_exec from protobuf
|
||||||
EmitUnpopulated: true,
|
EmitUnpopulated: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -88,7 +88,7 @@ func startRestAPIServer(globalConfig *config.Config) error {
|
||||||
)
|
)
|
||||||
opts := []grpc.DialOption{grpc.WithInsecure()}
|
opts := []grpc.DialOption{grpc.WithInsecure()}
|
||||||
|
|
||||||
err := gw.RegisterOliveTinApiHandlerFromEndpoint(ctx, mux, cfg.ListenAddressGrpcActions, opts)
|
err := gw.RegisterOliveTinApiServiceHandlerFromEndpoint(ctx, mux, cfg.ListenAddressGrpcActions, opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Could not register REST API Handler %v", err)
|
log.Errorf("Could not register REST API Handler %v", err)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ away, and several other issues.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
config "github.com/OliveTin/OliveTin/internal/config"
|
config "github.com/OliveTin/OliveTin/internal/config"
|
||||||
|
"github.com/OliveTin/OliveTin/internal/websocket"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
|
@ -39,7 +40,7 @@ func StartSingleHTTPFrontend(cfg *config.Config) {
|
||||||
|
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if strings.Contains(r.Header.Get("Connection"), "Upgrade") {
|
if strings.Contains(r.Header.Get("Connection"), "Upgrade") {
|
||||||
handleWebsocket(w, r)
|
websocket.HandleWebsocket(w, r)
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("ui req: %q", r.URL)
|
log.Debugf("ui req: %q", r.URL)
|
||||||
webuiProxy.ServeHTTP(w, r)
|
webuiProxy.ServeHTTP(w, r)
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package httpservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net/http"
|
|
||||||
"nhooyr.io/websocket"
|
|
||||||
"nhooyr.io/websocket/wsjson"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
pb "github.com/OliveTin/OliveTin/gen/grpc"
|
||||||
|
"github.com/OliveTin/OliveTin/internal/executor"
|
||||||
|
ws "github.com/gorilla/websocket"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var upgrader = ws.Upgrader{}
|
||||||
|
|
||||||
|
type WebsocketClient struct {
|
||||||
|
conn *ws.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
var clients []*WebsocketClient
|
||||||
|
|
||||||
|
var marshalOptions = protojson.MarshalOptions{
|
||||||
|
UseProtoNames: false, // eg: canExec for js instead of can_exec from protobuf
|
||||||
|
EmitUnpopulated: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var ExecutionListener WebsocketExecutionListener
|
||||||
|
|
||||||
|
type WebsocketExecutionListener struct{}
|
||||||
|
|
||||||
|
func (WebsocketExecutionListener) OnExecutionStarted(title string) {
|
||||||
|
/*
|
||||||
|
broadcast(ExecutionStarted{
|
||||||
|
Type: "ExecutionStarted",
|
||||||
|
Action: title,
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (WebsocketExecutionListener) OnExecutionFinished(logEntry *executor.InternalLogEntry) {
|
||||||
|
le := &pb.LogEntry{
|
||||||
|
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,
|
||||||
|
Uuid: logEntry.UUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast("ExecutionFinished", le)
|
||||||
|
}
|
||||||
|
|
||||||
|
func broadcast(messageType string, pbmsg *pb.LogEntry) {
|
||||||
|
payload, err := marshalOptions.Marshal(pbmsg)
|
||||||
|
|
||||||
|
// <EVIL>
|
||||||
|
// So, the websocket wants to encode messages using the same protomarshaller
|
||||||
|
// as the REST API - this gives consistency instead of using encoding/json
|
||||||
|
// and allows us to set specific marshalOptions.
|
||||||
|
//
|
||||||
|
// However, the protomarshaller will marshal the type, but the JavaScript at
|
||||||
|
// the other end has no idea what type this object is - as we're just sending
|
||||||
|
// it as JSON over the websocket.
|
||||||
|
//
|
||||||
|
// Therefore, we wrap the nicely marsheled bytes in a hacky JSON string
|
||||||
|
// literal and encode that string just with a byte array cast.
|
||||||
|
hackyMessageEnvelope := "{\"type\": \"" + messageType + "\", \"payload\": "
|
||||||
|
|
||||||
|
hackyMessage := []byte{}
|
||||||
|
hackyMessage = append(hackyMessage, []byte(hackyMessageEnvelope)...)
|
||||||
|
hackyMessage = append(hackyMessage, payload...)
|
||||||
|
hackyMessage = append(hackyMessage, []byte("}")...)
|
||||||
|
// </EVIL>
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("websocket marshal error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
client.conn.WriteMessage(1, hackyMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebsocketClient) messageLoop() {
|
||||||
|
for {
|
||||||
|
mt, message, err := c.conn.ReadMessage()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("err: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("websocket recv: %s %d", message, mt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleWebsocket(w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
c, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Websocket issue: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// defer c.Close()
|
||||||
|
|
||||||
|
wsclient := &WebsocketClient{
|
||||||
|
conn: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
clients = append(clients, wsclient)
|
||||||
|
|
||||||
|
go wsclient.messageLoop()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -62,19 +62,58 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer title = "footer">
|
<footer title = "footer">
|
||||||
<p><img title = "application icon" src = "OliveTinLogo.png" height = "1em" class = "logo" /> OliveTin</p>
|
<p><img title = "application icon" src = "OliveTinLogo.png" alt = "OliveTin logo" height = "1em" class = "logo" /> OliveTin</p>
|
||||||
<p>
|
<p>
|
||||||
<a href = "https://docs.olivetin.app" target = "_new">Documentation</a> |
|
<a href = "https://docs.olivetin.app" target = "_new">Documentation</a> |
|
||||||
<a href = "https://github.com/OliveTin/OliveTin/issues/new/choose" target = "_new">Raise an issue on GitHub</a> |
|
<a href = "https://github.com/OliveTin/OliveTin/issues/new/choose" target = "_new">Raise an issue on GitHub</a> |
|
||||||
<span id = "currentVersion">Version: ?</p>
|
<span>Version: <span id = "currentVersion">?</span></span> |
|
||||||
|
<span>Server connection: <span id = "serverConnection">?</span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
<a id = "available-version" href = "http://olivetin.app" target = "_blank" hidden>?</a>
|
<a id = "available-version" href = "http://olivetin.app" target = "_blank" hidden>?</a>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<dialog id = "executionResults">
|
||||||
|
<div class = "action-header">
|
||||||
|
<span class = "icon" role = "icon"></span>
|
||||||
|
|
||||||
|
<h2>Log:
|
||||||
|
<span class = "title">?</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<strong>Started: </strong><span class = "datetimeStarted">unknown</span>
|
||||||
|
<strong>Finished: </strong><span class = "datetimeFinished">unknown</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Exit Code: </strong><span class = "exitCode">unknown</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>stdout</summary>
|
||||||
|
<pre class = "stdout">
|
||||||
|
?
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>stderr</summary>
|
||||||
|
<pre class = "stderr">
|
||||||
|
?
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<form method = "dialog">
|
||||||
|
<button name = "cancel">Close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<template id = "tplArgumentForm">
|
<template id = "tplArgumentForm">
|
||||||
|
<dialog>
|
||||||
<form class = "action-arguments">
|
<form class = "action-arguments">
|
||||||
<div class = "wrapper">
|
<div class = "wrapper">
|
||||||
<div>
|
<div class = "action-header">
|
||||||
<span class = "icon" role = "icon"></span>
|
<span class = "icon" role = "icon"></span>
|
||||||
<h2>Argument form</h2>
|
<h2>Argument form</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,14 +125,20 @@
|
||||||
<button name = "cancel">Cancel</button>
|
<button name = "cancel">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form>
|
</form>
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id = "tplActionButton">
|
<template id = "tplActionButton">
|
||||||
|
<div>
|
||||||
<button>
|
<button>
|
||||||
<span role = "icon" title = "button icon" class = "icon">💩</span>
|
<span role = "icon" title = "button icon" class = "icon">💩</span>
|
||||||
<p role = "title" class = "title">Untitled Button</p>
|
<span role = "title" class = "title">Untitled Button</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class = "executions">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id = "tplLogRow">
|
<template id = "tplLogRow">
|
||||||
|
|
@ -129,7 +174,10 @@
|
||||||
to at least display a helpful error message if we can't use OliveTin.
|
to at least display a helpful error message if we can't use OliveTin.
|
||||||
*/
|
*/
|
||||||
function showBigError (type, friendlyType, message) {
|
function showBigError (type, friendlyType, message) {
|
||||||
clearInterval(window.buttonInterval)
|
for (const oldError of document.querySelectorAll('div.error').values()) {
|
||||||
|
window.old = oldError;
|
||||||
|
oldError.remove();
|
||||||
|
}
|
||||||
|
|
||||||
console.error('Error ' + type + ': ', message)
|
console.error('Error ' + type + ': ', message)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { marshalLogsJsonToHtml } from './marshaller.js'
|
|
||||||
import './ArgumentForm.js'
|
import './ArgumentForm.js'
|
||||||
|
import './ExecutionButton.js'
|
||||||
|
|
||||||
class ActionButton extends window.HTMLElement {
|
class ActionButton extends window.HTMLElement {
|
||||||
constructFromJson (json) {
|
constructFromJson (json) {
|
||||||
|
|
@ -25,6 +25,7 @@ class ActionButton extends window.HTMLElement {
|
||||||
})
|
})
|
||||||
|
|
||||||
document.body.appendChild(frm)
|
document.body.appendChild(frm)
|
||||||
|
frm.querySelector('dialog').showModal()
|
||||||
} else {
|
} else {
|
||||||
this.startAction()
|
this.startAction()
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +33,8 @@ class ActionButton extends window.HTMLElement {
|
||||||
|
|
||||||
this.updateFromJson(json)
|
this.updateFromJson(json)
|
||||||
|
|
||||||
this.updateDom()
|
this.domTitle.innerText = this.btn.title
|
||||||
|
this.domIcon.innerHTML = this.unicodeIcon
|
||||||
|
|
||||||
this.setAttribute('id', 'actionButton_' + json.id)
|
this.setAttribute('id', 'actionButton_' + json.id)
|
||||||
}
|
}
|
||||||
|
|
@ -52,20 +54,27 @@ class ActionButton extends window.HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
startAction (actionArgs) {
|
startAction (actionArgs) {
|
||||||
this.btn.disabled = true
|
// this.btn.disabled = true
|
||||||
this.isWaiting = true
|
// this.isWaiting = true
|
||||||
this.updateDom()
|
// this.updateDom()
|
||||||
this.btn.classList = [] // Removes old animation classes
|
this.btn.classList = [] // Removes old animation classes
|
||||||
|
|
||||||
if (actionArgs === undefined) {
|
if (actionArgs === undefined) {
|
||||||
actionArgs = []
|
actionArgs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UUIDs are create client side, so that we can setup a "execution-button"
|
||||||
|
// to track the execution before we send the request to the server.
|
||||||
const startActionArgs = {
|
const startActionArgs = {
|
||||||
actionName: this.btn.title,
|
actionName: this.btn.title,
|
||||||
arguments: actionArgs
|
arguments: actionArgs,
|
||||||
|
uuid: window.crypto.randomUUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const btnExecution = document.createElement('execution-button')
|
||||||
|
btnExecution.constructFromJson(startActionArgs.uuid)
|
||||||
|
this.querySelector('div.executions').appendChild(btnExecution)
|
||||||
|
|
||||||
window.fetch(this.actionCallUrl, {
|
window.fetch(this.actionCallUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -80,45 +89,12 @@ class ActionButton extends window.HTMLElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then((json) => {
|
).then((json) => {
|
||||||
marshalLogsJsonToHtml({ logs: [json.logEntry] })
|
// The button used to wait for the action to finish, but now it is fire & forget
|
||||||
|
|
||||||
if (json.logEntry.timedOut) {
|
|
||||||
this.onActionResult('action-timeout', 'Timed out')
|
|
||||||
} else if (json.logEntry.exitCode === -1337) {
|
|
||||||
this.onActionError('Error')
|
|
||||||
} else if (json.logEntry.exitCode !== 0) {
|
|
||||||
this.onActionResult('action-nonzero-exit', 'Exit code ' + json.logEntry.exitCode)
|
|
||||||
} else {
|
|
||||||
this.onActionResult('action-success', 'Success!')
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
this.onActionError(err)
|
this.onActionError(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onActionResult (cssClass, temporaryStatusMessage) {
|
|
||||||
this.btn.disabled = false
|
|
||||||
this.temporaryStatusMessage = '[ ' + temporaryStatusMessage + ' ]'
|
|
||||||
this.updateDom()
|
|
||||||
this.btn.classList.add(cssClass)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.btn.classList.remove(cssClass)
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
onActionError (err) {
|
|
||||||
console.error('callback error', err)
|
|
||||||
this.btn.disabled = false
|
|
||||||
this.isWaiting = false
|
|
||||||
this.updateDom()
|
|
||||||
this.btn.classList.add('action-failed')
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.btn.classList.remove('action-failed')
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructDomFromTemplate () {
|
constructDomFromTemplate () {
|
||||||
const tpl = document.getElementById('tplActionButton')
|
const tpl = document.getElementById('tplActionButton')
|
||||||
const content = tpl.content.cloneNode(true)
|
const content = tpl.content.cloneNode(true)
|
||||||
|
|
@ -134,27 +110,6 @@ class ActionButton extends window.HTMLElement {
|
||||||
this.domTitle = this.btn.querySelector('.title')
|
this.domTitle = this.btn.querySelector('.title')
|
||||||
this.domIcon = this.btn.querySelector('.icon')
|
this.domIcon = this.btn.querySelector('.icon')
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDom () {
|
|
||||||
if (this.temporaryStatusMessage != null) {
|
|
||||||
this.domTitle.innerText = this.temporaryStatusMessage
|
|
||||||
this.domTitle.classList.add('temporary-status-message')
|
|
||||||
this.isWaiting = false
|
|
||||||
this.disabled = false
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.temporaryStatusMessage = null
|
|
||||||
this.domTitle.classList.remove('temporary-status-message')
|
|
||||||
this.updateDom()
|
|
||||||
}, 2000)
|
|
||||||
} else if (this.isWaiting) {
|
|
||||||
this.domTitle.innerText = 'Waiting...'
|
|
||||||
} else {
|
|
||||||
this.domTitle.innerText = this.btn.title
|
|
||||||
}
|
|
||||||
|
|
||||||
this.domIcon.innerHTML = this.unicodeIcon
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.customElements.define('action-button', ActionButton)
|
window.customElements.define('action-button', ActionButton)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
import { ExecutionDialog } from './ExecutionDialog.js'
|
||||||
|
|
||||||
|
class ExecutionButton extends window.HTMLElement {
|
||||||
|
constructFromJson (json) {
|
||||||
|
this.executionUuid = json
|
||||||
|
|
||||||
|
this.appendChild(document.createElement('button'))
|
||||||
|
this.isWaiting = true
|
||||||
|
|
||||||
|
this.setAttribute('id', 'execution-' + json)
|
||||||
|
|
||||||
|
this.btn = this.querySelector('button')
|
||||||
|
this.btn.innerText = 'Executing...'
|
||||||
|
this.btn.onclick = () => {
|
||||||
|
this.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
show () {
|
||||||
|
if (window.executionDialog === undefined) {
|
||||||
|
window.executionDialog = new ExecutionDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionStatusArgs = {
|
||||||
|
executionUuid: this.executionUuid
|
||||||
|
}
|
||||||
|
|
||||||
|
window.executionDialog.constructFromJson(this.executionUuid)
|
||||||
|
window.executionDialog.show()
|
||||||
|
|
||||||
|
window.fetch(window.restBaseUrl + 'ExecutionStatus', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(executionStatusArgs)
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
return res.json()
|
||||||
|
} else {
|
||||||
|
throw new Error(res.statusText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then((json) => {
|
||||||
|
window.executionDialog.renderResult(json)
|
||||||
|
}).catch(err => {
|
||||||
|
window.executionDialog.renderError(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinished (LogEntry) {
|
||||||
|
if (LogEntry.timedOut) {
|
||||||
|
this.onActionResult('action-timeout', 'Timed out')
|
||||||
|
} else if (LogEntry.exitCode === -1337) {
|
||||||
|
this.onActionError('Error')
|
||||||
|
} else if (LogEntry.exitCode !== 0) {
|
||||||
|
this.onActionResult('action-nonzero-exit', 'Exit code ' + LogEntry.exitCode)
|
||||||
|
} else {
|
||||||
|
this.onActionResult('action-success', 'Success!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onActionResult (cssClass, temporaryStatusMessage) {
|
||||||
|
this.btn.disabled = false
|
||||||
|
this.temporaryStatusMessage = '[ ' + temporaryStatusMessage + ' ]'
|
||||||
|
this.updateDom()
|
||||||
|
this.btn.classList.add(cssClass)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.btn.classList.remove(cssClass)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
onActionError (err) {
|
||||||
|
console.error('callback error', err)
|
||||||
|
this.btn.disabled = false
|
||||||
|
this.isWaiting = false
|
||||||
|
this.updateDom()
|
||||||
|
this.btn.classList.add('action-failed')
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.btn.classList.remove('action-failed')
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDom () {
|
||||||
|
if (this.temporaryStatusMessage != null) {
|
||||||
|
this.btn.innerText = this.temporaryStatusMessage
|
||||||
|
this.btn.classList.add('temporary-status-message')
|
||||||
|
this.isWaiting = false
|
||||||
|
this.disabled = false
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.temporaryStatusMessage = null
|
||||||
|
this.btn.classList.remove('temporary-status-message')
|
||||||
|
this.updateDom()
|
||||||
|
}, 2000)
|
||||||
|
} else if (this.isWaiting) {
|
||||||
|
this.btn.innerText = 'Waiting...'
|
||||||
|
} else {
|
||||||
|
this.btn.innerText = 'Finished'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.customElements.define('execution-button', ExecutionButton)
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
// This ExecutionDialog is NOT a custom HTML element, but rather just picks up
|
||||||
|
// the <dialog /> element out of index.html and just re-uses that - as only
|
||||||
|
// one dialog can be shown at a time.
|
||||||
|
export class ExecutionDialog {
|
||||||
|
constructFromJson (json) {
|
||||||
|
this.executionUuid = json
|
||||||
|
|
||||||
|
this.dlg = document.querySelector('dialog#executionResults')
|
||||||
|
|
||||||
|
this.domIcon = this.dlg.querySelector('.icon')
|
||||||
|
this.domTitle = this.dlg.querySelector('.title')
|
||||||
|
this.domStdout = this.dlg.querySelector('.stdout')
|
||||||
|
this.domStderr = this.dlg.querySelector('.stderr')
|
||||||
|
this.domDatetimeStarted = this.dlg.querySelector('.datetimeStarted')
|
||||||
|
this.domDatetimeFinished = this.dlg.querySelector('.datetimeFinished')
|
||||||
|
this.domExitCode = this.dlg.querySelector('.exitCode')
|
||||||
|
}
|
||||||
|
|
||||||
|
show () {
|
||||||
|
this.dlg.showModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResult (res) {
|
||||||
|
this.executionUuid = res.logEntry.executionUuid
|
||||||
|
|
||||||
|
if (res.logEntry.datetimeFinished === '') {
|
||||||
|
this.domExitCode.innerText = 'Still running...'
|
||||||
|
this.domDatetimeFinished.innerText = 'Still running...'
|
||||||
|
} else {
|
||||||
|
if (res.logEntry.timedOut) {
|
||||||
|
this.domExitCode.innerText = 'Timed out'
|
||||||
|
} else {
|
||||||
|
this.domExitCode.innerText = res.logEntry.exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
this.domDatetimeFinished.innerText = res.logEntry.datetimeFinished
|
||||||
|
}
|
||||||
|
|
||||||
|
this.domIcon.innerHTML = res.logEntry.actionIcon
|
||||||
|
this.domTitle.innerText = res.logEntry.actionTitle
|
||||||
|
|
||||||
|
this.domStdout.innerText = res.logEntry.stdout
|
||||||
|
|
||||||
|
if (res.logEntry.stderr === '') {
|
||||||
|
this.domStderr.parentElement.hidden = true
|
||||||
|
this.domStderr.innerText = res.logEntry.stderr
|
||||||
|
} else {
|
||||||
|
this.domStderr.parentElement.hidden = false
|
||||||
|
this.domStderr.innerText = res.logEntry.stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
this.domDatetimeStarted.innerText = res.logEntry.datetimeStarted
|
||||||
|
}
|
||||||
|
|
||||||
|
renderError (err) {
|
||||||
|
this.dlg.querySelector('pre').innerText = JSON.stringify(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ export function marshalLogsJsonToHtml (json) {
|
||||||
logTableExitCode += ' (timed out)'
|
logTableExitCode += ' (timed out)'
|
||||||
}
|
}
|
||||||
|
|
||||||
row.querySelector('.timestamp').innerText = logEntry.datetime
|
row.querySelector('.timestamp').innerText = logEntry.datetimeStarted
|
||||||
row.querySelector('.content').innerText = logEntry.actionTitle
|
row.querySelector('.content').innerText = logEntry.actionTitle
|
||||||
row.querySelector('.icon').innerHTML = logEntry.actionIcon
|
row.querySelector('.icon').innerHTML = logEntry.actionIcon
|
||||||
row.querySelector('pre.stdout').innerText = logEntry.stdout
|
row.querySelector('pre.stdout').innerText = logEntry.stdout
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
export function setupWebsocket () {
|
import { marshalLogsJsonToHtml } from './marshaller.js'
|
||||||
|
|
||||||
|
window.ws = null
|
||||||
|
|
||||||
|
export function checkWebsocketConnection () {
|
||||||
|
if (window.ws === null || window.ws.readyState === 3) {
|
||||||
|
reconnectWebsocket()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reconnectWebsocket () {
|
||||||
window.websocketAvailable = false
|
window.websocketAvailable = false
|
||||||
|
|
||||||
let proto = 'ws:'
|
let proto = 'ws:'
|
||||||
|
|
@ -18,20 +28,44 @@ export function setupWebsocket () {
|
||||||
|
|
||||||
function websocketOnOpen (evt) {
|
function websocketOnOpen (evt) {
|
||||||
window.websocketAvailable = true
|
window.websocketAvailable = true
|
||||||
console.log('open')
|
|
||||||
|
|
||||||
const foo = '{}'
|
window.ws.send('monitor')
|
||||||
|
|
||||||
window.ws.send(foo)
|
window.refreshLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
function websocketOnMessage (msg) {
|
function websocketOnMessage (msg) {
|
||||||
console.log(msg)
|
// FIXME check msg status is OK
|
||||||
|
const j = JSON.parse(msg.data)
|
||||||
|
|
||||||
|
switch (j.type) {
|
||||||
|
case 'ExecutionFinished':
|
||||||
|
updatePageAfterFinished(j.payload)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
window.showBigError('Unknown message type from server: ' + j.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePageAfterFinished (logEntry) {
|
||||||
|
document.querySelector('execution-button#execution-' + logEntry.uuid).onFinished(logEntry)
|
||||||
|
|
||||||
|
marshalLogsJsonToHtml({
|
||||||
|
logs: [logEntry]
|
||||||
|
})
|
||||||
|
|
||||||
|
// If the current execution dialog is open, update that too
|
||||||
|
if (window.executionDialog != null && window.executionDialog.dlg.open && window.executionDialog.executionUuid === logEntry.uuid) {
|
||||||
|
window.executionDialog.renderResult({
|
||||||
|
logEntry: logEntry
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function websocketOnError (err) {
|
function websocketOnError (err) {
|
||||||
window.websocketAvailable = false
|
window.websocketAvailable = false
|
||||||
console.log(err)
|
window.refreshLoop()
|
||||||
|
console.error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
function websocketOnClose () {
|
function websocketOnClose () {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
import { marshalActionButtonsJsonToHtml, marshalLogsJsonToHtml } from './js/marshaller.js'
|
import { marshalActionButtonsJsonToHtml, marshalLogsJsonToHtml } from './js/marshaller.js'
|
||||||
import { setupWebsocket } from './js/websocket.js'
|
import { checkWebsocketConnection } from './js/websocket.js'
|
||||||
|
|
||||||
function showSection (name) {
|
function showSection (name) {
|
||||||
for (const otherName of ['Actions', 'Logs']) {
|
for (const otherName of ['Actions', 'Logs']) {
|
||||||
|
|
@ -20,15 +20,42 @@ function setupSections () {
|
||||||
showSection('Actions')
|
showSection('Actions')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshLoop () {
|
||||||
|
if (window.websocketAvailable) {
|
||||||
|
document.querySelector('#serverConnection').classList.remove('error')
|
||||||
|
document.querySelector('#serverConnection').innerText = 'websocket'
|
||||||
|
|
||||||
|
// Websocket updates are streamed live, not updated on a loop.
|
||||||
|
} else if (window.restAvailable) {
|
||||||
|
// Fallback to rest, but try to reconnect the websocket anyway.
|
||||||
|
|
||||||
|
fetchGetDashboardComponents()
|
||||||
|
checkWebsocketConnection()
|
||||||
|
|
||||||
|
document.querySelector('#serverConnection').classList.remove('error')
|
||||||
|
document.querySelector('#serverConnection').innerText = 'rest'
|
||||||
|
|
||||||
|
fetchGetLogs()
|
||||||
|
} else {
|
||||||
|
document.querySelector('#serverConnection').innerText = 'disconnected, trying to reconnect...'
|
||||||
|
document.querySelector('#serverConnection').classList.add('error')
|
||||||
|
|
||||||
|
// Still try to fetch the dashboard, if successfull window.restAvailable = true
|
||||||
|
fetchGetDashboardComponents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fetchGetDashboardComponents () {
|
function fetchGetDashboardComponents () {
|
||||||
window.fetch(window.restBaseUrl + 'GetDashboardComponents', {
|
window.fetch(window.restBaseUrl + 'GetDashboardComponents', {
|
||||||
cors: 'cors'
|
cors: 'cors'
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
return res.json()
|
return res.json()
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
window.restAvailable = true
|
||||||
marshalActionButtonsJsonToHtml(res)
|
marshalActionButtonsJsonToHtml(res)
|
||||||
}).catch(err => {
|
}).catch(() => { // err is 1st arg
|
||||||
window.showBigError('fetch-buttons', 'getting buttons', err, 'blat')
|
window.restAvailable = false
|
||||||
|
// window.showBigError('fetch-buttons', 'getting buttons', err, 'blat')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +83,7 @@ function processWebuiSettingsJson (settings) {
|
||||||
document.head.appendChild(themeCss)
|
document.head.appendChild(themeCss)
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector('#currentVersion').innerText = 'Version: ' + settings.CurrentVersion
|
document.querySelector('#currentVersion').innerText = settings.CurrentVersion
|
||||||
|
|
||||||
if (settings.ShowNewVersions && settings.AvailableVersion !== 'none') {
|
if (settings.ShowNewVersions && settings.AvailableVersion !== 'none') {
|
||||||
document.querySelector('#available-version').innerText = 'New Version Available: ' + settings.AvailableVersion
|
document.querySelector('#available-version').innerText = 'New Version Available: ' + settings.AvailableVersion
|
||||||
|
|
@ -76,17 +103,16 @@ function processWebuiSettingsJson (settings) {
|
||||||
function main () {
|
function main () {
|
||||||
setupSections()
|
setupSections()
|
||||||
|
|
||||||
setupWebsocket()
|
|
||||||
|
|
||||||
window.fetch('webUiSettings.json').then(res => {
|
window.fetch('webUiSettings.json').then(res => {
|
||||||
return res.json()
|
return res.json()
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
processWebuiSettingsJson(res)
|
processWebuiSettingsJson(res)
|
||||||
|
|
||||||
fetchGetDashboardComponents()
|
window.restAvailable = true
|
||||||
fetchGetLogs()
|
window.refreshLoop = refreshLoop
|
||||||
|
window.refreshLoop()
|
||||||
|
|
||||||
window.buttonInterval = setInterval(fetchGetDashboardComponents, 3000)
|
setInterval(refreshLoop, 3000)
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
window.showBigError('fetch-webui-settings', 'getting webui settings', err)
|
window.showBigError('fetch-webui-settings', 'getting webui settings', err)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
min-width: 600px;
|
||||||
|
text-align: left;
|
||||||
|
padding: 1em;
|
||||||
|
box-shadow: 0 0 6px 0 #444;
|
||||||
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +127,7 @@ span[role="icon"] {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
form span[role="icon"],
|
.action-header span[role="icon"],
|
||||||
tr.log-row span[role="icon"] {
|
tr.log-row span[role="icon"] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding-right: 0.2em;
|
padding-right: 0.2em;
|
||||||
|
|
@ -128,6 +135,9 @@ tr.log-row span[role="icon"] {
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background-color: salmon;
|
background-color: salmon;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.error {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +156,7 @@ div.entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
margin-top: 0;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
@ -185,6 +196,11 @@ input[type="submit"]:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.executions button {
|
||||||
|
margin-top: .2em;
|
||||||
|
margin-bottom: .2em;
|
||||||
|
}
|
||||||
|
|
||||||
fieldset#section-switcher {
|
fieldset#section-switcher {
|
||||||
border: 0;
|
border: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
@ -282,21 +298,9 @@ details[open] {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.action-arguments {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
padding: 1em;
|
|
||||||
box-shadow: 0 0 6px 0 #aaa;
|
|
||||||
background-color: #dee3e7;
|
|
||||||
}
|
|
||||||
|
|
||||||
form div.wrapper {
|
form div.wrapper {
|
||||||
border-radius: 1em;
|
|
||||||
box-shadow: 0 0 10px 0 #444;
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid #999;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue