From 89f08ae6c77d709c9807890cb12de725dd5a8071 Mon Sep 17 00:00:00 2001 From: jamesread Date: Thu, 23 Mar 2023 22:33:40 +0000 Subject: [PATCH 1/5] feature: log-popup --- OliveTin.proto | 25 ++++++-- internal/acl/acl.go | 5 +- internal/config/config.go | 1 + internal/executor/executor.go | 98 ++++++++++++++++++------------ internal/grpcapi/grpcApi.go | 47 +++++++++++--- internal/grpcapi/grpcApiActions.go | 9 +-- 6 files changed, 128 insertions(+), 57 deletions(-) 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/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 { From 8736f5e387d52bf01567528e6e1d85be0d1b6eeb Mon Sep 17 00:00:00 2001 From: jamesread Date: Sat, 13 May 2023 19:59:01 +0100 Subject: [PATCH 2/5] feature: websocket wip --- internal/httpservers/websocket.go | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 internal/httpservers/websocket.go diff --git a/internal/httpservers/websocket.go b/internal/httpservers/websocket.go new file mode 100644 index 0000000..099b52f --- /dev/null +++ b/internal/httpservers/websocket.go @@ -0,0 +1,36 @@ +package httpservers + +import ( + "time" + log "github.com/sirupsen/logrus" + "net/http" + "nhooyr.io/websocket" +) + +func handleWebsocket(w http.ResponseWriter, r *http.Request) { + c, err := websocket.Accept(w, r, nil) + + if err != nil { + Log.Warnf("Websocket issue: %v", err) + return + } + + 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) + + if err != nil { + Log.Warnf("Websocket issue: %v", err) + return + } + + log.Printf("recv: %v", v) + + c.Close(websocket.StatusNormalClosure, "") +} From d74734972b5ff5bd8d53eca534e226d063397259 Mon Sep 17 00:00:00 2001 From: jamesread Date: Wed, 9 Aug 2023 21:23:25 +0100 Subject: [PATCH 3/5] depbump: General dependency bump --- go.mod | 1 + go.sum | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) 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= From f7fd8af1248f3442e08b1aeb4b58ac943679ecde Mon Sep 17 00:00:00 2001 From: jamesread Date: Wed, 9 Aug 2023 21:11:13 +0100 Subject: [PATCH 4/5] feature: Websocket early impl --- internal/httpservers/singleFrontend.go | 9 +++++-- internal/httpservers/websocket.go | 22 +++++++++------- internal/httpservers/webuiServer_test.go | 3 +++ webui/js/websocket.js | 32 ++++++++++++++++++++++++ webui/main.js | 3 +++ 5 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 webui/js/websocket.js 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 index 099b52f..c0ef7a1 100644 --- a/internal/httpservers/websocket.go +++ b/internal/httpservers/websocket.go @@ -5,14 +5,16 @@ import ( log "github.com/sirupsen/logrus" "net/http" "nhooyr.io/websocket" + "nhooyr.io/websocket/wsjson" + "context" ) -func handleWebsocket(w http.ResponseWriter, r *http.Request) { +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 + log.Warnf("Websocket issue: %v", err) + return false } defer c.Close(websocket.StatusInternalError, "Goodbye") @@ -23,14 +25,16 @@ func handleWebsocket(w http.ResponseWriter, r *http.Request) { var v interface{} - err = wsjson.Read(Ctx, c, v) - - if err != nil { - Log.Warnf("Websocket issue: %v", err) - return - } + 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/js/websocket.js b/webui/js/websocket.js new file mode 100644 index 0000000..355e755 --- /dev/null +++ b/webui/js/websocket.js @@ -0,0 +1,32 @@ +export function setupWebsocket() { + window.websocketAvailable = false + + window.ws = new WebSocket('ws://localhost:1337') + + ws.addEventListener('open', websocketOnOpen) + ws.addEventListener('message', websocketOnMessage) + ws.addEventListener('error', websocketOnError) + ws.addEventListener('close', websocketOnClose) +} + +function websocketOnOpen(evt) { + window.websocketAvailable = true + console.log("open") + + const foo = '{}' + + ws.send(foo) +} + +function websocketOnMessage(msg) { + console.log(msg) +} + +function websocketOnError(err) { + window.websocketAvailable = false + console.log(err) +} + +function websocketOnClose() { + window.websocketAvailable = false +} diff --git a/webui/main.js b/webui/main.js index 7853e76..033c68b 100644 --- a/webui/main.js +++ b/webui/main.js @@ -1,6 +1,7 @@ 'use strict' import { marshalActionButtonsJsonToHtml, marshalLogsJsonToHtml } from './js/marshaller.js' +import { setupWebsocket } from './js/websocket.js' function showSection (name) { for (const otherName of ['Actions', 'Logs']) { @@ -75,6 +76,8 @@ function processWebuiSettingsJson (settings) { function main () { setupSections() + setupWebsocket() + window.fetch('webUiSettings.json').then(res => { return res.json() }).then(res => { From ed949d1dd8255f8fe8595560db1aecead0ed807a Mon Sep 17 00:00:00 2001 From: jamesread Date: Thu, 24 Aug 2023 11:45:29 +0100 Subject: [PATCH 5/5] feature: websocket progress --- webui/index.html | 1 + webui/js/websocket.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 webui/js/websocket.js 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 @@ + diff --git a/webui/js/websocket.js b/webui/js/websocket.js new file mode 100644 index 0000000..f507cef --- /dev/null +++ b/webui/js/websocket.js @@ -0,0 +1,29 @@ +function websocketInit() { + let proto = "ws:" + + if (window.location.protocol == "https:") { + proto = "wss:" + } + + let wsUrl = proto + window.location.host + '/websocket' + + console.log(wsUrl) + + window.ws = new WebSocket(wsUrl) + + window.ws.addEventListener("open", () => { + console.log("socket opened!") + window.ws.send("Hi!") + }) + + window.ws.addEventListener("error", () => { + console.error("ws error") + }) + + window.ws.addEventListener("message", (msg) => { + console.log("ws msg", msg) + }) + +} + +websocketInit()