diff --git a/README.md b/README.md index 4ce449f..5da4f65 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,15 @@ GoShell is a simple terminal GUI client, written in Go,via [Fyne](https://fyne.i ![GoShell SSH](screenshot/ssh-conf.png) ### Docker Config ![GoShell Docker](screenshot/docker-conf.png) -### Docker Container +### Docker Select Container ![GoShell Docker](screenshot/docker-container.png) +### K8S Config +![GoShell Docker](screenshot/k8s-conf.png) +### K8S Select Container +![GoShell Docker](screenshot/k8s-container.png) # TODOs - UI/UX optimization -- Configuration encryption -- Supports K8S pod. +- Configuration encryption +- ~~Supports K8S pod.~~ diff --git a/config.go b/config.go index 3061b06..ee22cb4 100644 --- a/config.go +++ b/config.go @@ -6,13 +6,13 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" - "github.com/jinzhu/copier" "log" ) type Config interface { Name() string Type() string + Load(string) error Data() interface{} Form() *widget.Form Term(*Window) @@ -97,15 +97,19 @@ func (w *Window) load() { var cfg Config switch t { case "ssh": - cfg = &SSHConfig{} + cfg = &SSHConfig{data: &SSHConfigData{}} case "docker": - cfg = &DockerConfig{} + cfg = &DockerConfig{data: &DockerConfigData{}} case "k8s": - cfg = &K8SConfig{} + cfg = &K8SConfig{data: &K8SConfigData{}} default: continue } - err := copier.Copy(cfg, data) + bytes, err := json.Marshal(data) + if err != nil { + continue + } + err = cfg.Load(string(bytes)) if err != nil { continue } diff --git a/docker.go b/docker.go index 3b81df6..b56d60b 100644 --- a/docker.go +++ b/docker.go @@ -2,19 +2,19 @@ package main import ( "context" + "encoding/json" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" "github.com/docker/docker/api/types" "github.com/docker/docker/client" - "github.com/fyne-io/terminal" ) type DockerConfigData struct { - Name string - Type string - Host string + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Host string `json:"host,omitempty"` } type DockerConfig struct { @@ -27,9 +27,19 @@ func (c *DockerConfig) Name() string { } func (c *DockerConfig) Type() string { - return "ssh" + return "docker" } +func (c *DockerConfig) Load(s string) error { + data := &DockerConfigData{} + + err := json.Unmarshal([]byte(s), data) + if err != nil { + return err + } + c.data = data + return nil +} func (c *DockerConfig) Data() interface{} { return c.data } @@ -102,18 +112,19 @@ func (c *DockerConfig) Term(win *Window) { win.showError(err) return } - term := terminal.New() + + term := NewTerm(c.Name(), c) + go func() { defer attach.Close() - err = term.RunWithConnection(attach.Conn, attach.Reader) + err = term.RunWithConnection(attach.Conn) if err != nil { win.showError(err) return } }() - tab := &Term{name: c.Name(), term: term} - win.AddTermTab(tab) + win.AddTermTab(term) if dlg != nil { dlg.Hide() } diff --git a/go.mod b/go.mod index 813e5be..8357259 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,13 @@ require ( github.com/docker/docker v17.12.0-ce-rc1.0.20201201034508-7d75c1d40d88+incompatible github.com/docker/go-units v0.5.0 // indirect github.com/fyne-io/terminal v0.0.0-20210419192104-b6a609f9c1bd - github.com/gorilla/mux v1.8.0 // indirect - github.com/jinzhu/copier v0.3.5 github.com/sirupsen/logrus v1.9.0 // indirect github.com/srwiley/oksvg v0.0.0-20210320200257-875f767ac39a // indirect - golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 + github.com/tk103331/stream v1.0.2 + golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/grpc v1.49.0 // indirect + k8s.io/api v0.22.5 + k8s.io/apimachinery v0.22.5 + k8s.io/client-go v0.22.5 ) diff --git a/go.sum b/go.sum index e852de5..4d71ab2 100644 --- a/go.sum +++ b/go.sum @@ -329,6 +329,7 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -383,6 +384,7 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= @@ -474,6 +476,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.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= @@ -499,13 +502,13 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -554,8 +557,6 @@ github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9 github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= -github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= -github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= @@ -568,6 +569,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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= @@ -627,6 +629,7 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= @@ -637,9 +640,11 @@ github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6 github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= @@ -803,6 +808,7 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= @@ -830,6 +836,8 @@ github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tk103331/stream v1.0.2 h1:+yf8gdvhdYlAo42SFmuy2LiS2Lnem33RPfhlnrSMRM8= +github.com/tk103331/stream v1.0.2/go.mod h1:Sr7nLQQwyJrBZl1gwzFzdjTn6OVkMf2ZvmvZn+LKlM0= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -928,8 +936,9 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1021,6 +1030,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1036,6 +1046,7 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1274,6 +1285,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -1383,6 +1395,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -1399,6 +1412,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -1418,11 +1432,13 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/api v0.22.5 h1:xk7C+rMjF/EGELiD560jdmwzrB788mfcHiNbMQLIVI8= k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.22.5 h1:cIPwldOYm1Slq9VLBRPtEYpyhjIm1C6aAMAoENuvN9s= k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= @@ -1431,6 +1447,7 @@ k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/client-go v0.22.5 h1:I8Zn/UqIdi2r02aZmhaJ1hqMxcpfJ3t5VqvHtctHYFo= k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= @@ -1449,6 +1466,7 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= @@ -1457,6 +1475,7 @@ k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2R k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 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= @@ -1467,6 +1486,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyz sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/k8s.go b/k8s.go index 5651e8f..161ae64 100644 --- a/k8s.go +++ b/k8s.go @@ -1,19 +1,31 @@ package main import ( + "context" + "encoding/json" + "fmt" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" + "github.com/fyne-io/terminal" + "github.com/tk103331/stream" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/remotecommand" ) type K8SConfigData struct { - Name string - Type string - Server string - Token string - User string - Pswd string + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Server string `json:"server,omitempty"` + Token string `json:"token,omitempty"` } type K8SConfig struct { data *K8SConfigData + onOk func() } func (c *K8SConfig) Name() string { @@ -23,19 +35,202 @@ func (c *K8SConfig) Name() string { func (c *K8SConfig) Type() string { return "k8s" } +func (c *K8SConfig) Load(s string) error { + data := &K8SConfigData{} + err := json.Unmarshal([]byte(s), data) + if err != nil { + return err + } + c.data = data + return nil +} func (c *K8SConfig) Data() interface{} { return c.data } func (c *K8SConfig) Form() *widget.Form { - return widget.NewForm() + nameEntry := widget.NewEntry() + serverEntry := widget.NewEntry() + tokenEntry := widget.NewEntry() + tokenEntry.MultiLine = true + tokenEntry.Wrapping = fyne.TextWrapBreak + data := c.data + if data != nil { + nameEntry.Text = data.Name + nameEntry.Disable() + serverEntry.Text = data.Server + tokenEntry.Text = data.Token + } + c.onOk = func() { + if c.data == nil { + c.data = &K8SConfigData{Type: c.Type()} + } + c.data.Name = nameEntry.Text + c.data.Server = serverEntry.Text + c.data.Token = tokenEntry.Text + } + return widget.NewForm([]*widget.FormItem{ + widget.NewFormItem("Name", nameEntry), + widget.NewFormItem("Server", serverEntry), + widget.NewFormItem("Token", tokenEntry), + }...) } func (c *K8SConfig) OnOk() { + c.onOk() +} +type ExecOpt struct { + Namespace string + PodName string + Container string } func (c *K8SConfig) Term(win *Window) { - panic("implement me") + cfg := c.data + restCfg := &rest.Config{ + Host: cfg.Server, + BearerToken: cfg.Token, + BearerTokenFile: "", + TLSClientConfig: rest.TLSClientConfig{ + Insecure: true, + }, + } + clientset, err := kubernetes.NewForConfig(restCfg) + if err != nil { + win.showError(err) + return + } + namespaceList, err := clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) + if err != nil { + win.showError(err) + return + } + s, _ := stream.New(namespaceList.Items) + namespaces := make([]string, 0) + s.Map(func(n corev1.Namespace) string { + return n.ObjectMeta.Name + }).ToSlice(&namespaces) + + execOpt := &ExecOpt{} + + containerSelect := widget.NewSelect(namespaces, func(container string) { + execOpt.Container = container + }) + + podSelect := widget.NewSelect(namespaces, func(pod string) { + execOpt.PodName = pod + containerSelect.ClearSelected() + containerSelect.Options = []string{} + + go func() { + pod, err2 := clientset.CoreV1().Pods(execOpt.Namespace).Get(context.Background(), pod, metav1.GetOptions{}) + if err2 != nil { + win.showError(err2) + return + } + s, _ := stream.New(pod.Spec.Containers) + containers := make([]string, 0) + s.Map(func(n corev1.Container) string { + return n.Name + }).Limit(20).ToSlice(&containers) + containerSelect.Options = containers + }() + + }) + + namespaceSelect := widget.NewSelect(namespaces, func(namespace string) { + execOpt.Namespace = namespace + podSelect.ClearSelected() + podSelect.Options = []string{} + + go func() { + podList, err2 := clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{}) + if err2 != nil { + win.showError(err2) + return + } + s, _ := stream.New(podList.Items) + pods := make([]string, 0) + s.Map(func(n corev1.Pod) string { + return n.ObjectMeta.Name + }).Limit(20).ToSlice(&pods) + podSelect.Options = pods + }() + }) + namespaceSelect.Resize(fyne.Size{Height: 200}) + + form := widget.NewForm(widget.NewFormItem("Namespace", namespaceSelect), widget.NewFormItem("Pod", podSelect), widget.NewFormItem("Container", containerSelect)) + dlg := dialog.NewCustomConfirm("Select a container", "Connect", "Cancel", form, func(b bool) { + if b { + req := clientset.CoreV1().RESTClient().Post(). + Resource("pods"). + Name(execOpt.PodName). + Namespace(execOpt.Namespace). + SubResource("exec"). + VersionedParams(&corev1.PodExecOptions{ + Container: execOpt.Container, + Command: []string{"bash"}, + Stdin: true, + Stdout: true, + Stderr: true, + TTY: true, + }, scheme.ParameterCodec) + + executor, err := remotecommand.NewSPDYExecutor(restCfg, "POST", req.URL()) + if err != nil { + win.showError(err) + return + } + + termCfgChan := make(chan terminal.Config) + + term := NewTerm(execOpt.PodName, c) + + writer, reader := term.StartWithPipe(func(err error) { + if err != nil { + fmt.Println(err.Error()) + win.showError(err) + } + }) + + go func() { + + err = executor.Stream(remotecommand.StreamOptions{ + Stdin: reader, + Stdout: writer, + Stderr: writer, + Tty: true, + TerminalSizeQueue: TermConfigSizeQueue(termCfgChan), + }) + if err != nil { + win.showError(err) + return + } + }() + + term.AddConfigListener(func(config *terminal.Config) { + if config != nil { + go func() { + termCfgChan <- *config + }() + } + }) + win.AddTermTab(term) + } + }, win.win) + dlg.Resize(fyne.Size{Width: 400}) + dlg.Show() +} + +type TermConfigSizeQueue chan terminal.Config + +func (t TermConfigSizeQueue) Next() *remotecommand.TerminalSize { + cfg := <-t + return &remotecommand.TerminalSize{Width: uint16(cfg.Columns), Height: uint16(cfg.Rows)} +} + +func (t TermConfigSizeQueue) Send(cfg terminal.Config) { + t <- cfg } diff --git a/local.go b/local.go index da87051..3530484 100644 --- a/local.go +++ b/local.go @@ -1,12 +1,6 @@ package main -import "fyne.io/fyne/v2/dialog" - func (w *Window) createLocalTermTab() { - tab, err := newLocalTerm() - if err != nil { - dialog.NewError(err, w.win) - return - } + tab := NewLocalTerm() w.AddTermTab(tab) } diff --git a/screenshot/k8s-conf.png b/screenshot/k8s-conf.png new file mode 100644 index 0000000..ffdd0b8 Binary files /dev/null and b/screenshot/k8s-conf.png differ diff --git a/screenshot/k8s-container.png b/screenshot/k8s-container.png new file mode 100644 index 0000000..b0af218 Binary files /dev/null and b/screenshot/k8s-container.png differ diff --git a/ssh.go b/ssh.go index 19876a8..3c33ea1 100644 --- a/ssh.go +++ b/ssh.go @@ -1,8 +1,8 @@ package main import ( + "encoding/json" "fyne.io/fyne/v2/widget" - "github.com/fyne-io/terminal" "golang.org/x/crypto/ssh" "log" "net" @@ -10,12 +10,12 @@ import ( ) type SSHConfigData struct { - Name string - Type string - Host string - Port int - User string - Pswd string + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` + User string `json:"user,omitempty"` + Pswd string `json:"pswd,omitempty"` } type SSHConfigForm struct { @@ -40,6 +40,17 @@ func (c *SSHConfig) Type() string { return "ssh" } +func (c *SSHConfig) Load(s string) error { + data := &SSHConfigData{} + + err := json.Unmarshal([]byte(s), data) + if err != nil { + return err + } + c.data = data + return nil +} + func (c *SSHConfig) Data() interface{} { return c.data } @@ -134,9 +145,11 @@ func (c *SSHConfig) Term(win *Window) { return } - term := terminal.New() + term := NewTerm(conf.Name, c) + go func() { - err = term.RunWithConnection(in, out) + defer session.Close() + err = term.RunWithReaderAndWriter(in, out) if err != nil { log.Println(err) } @@ -149,6 +162,6 @@ func (c *SSHConfig) Term(win *Window) { log.Println(err) } }() - tab := &Term{name: conf.Name, term: term, local: false} - win.AddTermTab(tab) + + win.AddTermTab(term) } diff --git a/term.go b/term.go index cc31c64..a87227f 100644 --- a/term.go +++ b/term.go @@ -1,20 +1,76 @@ package main import ( - "fyne.io/fyne/v2/container" "github.com/fyne-io/terminal" + "io" "log" + "net" ) type Term struct { - name string - term *terminal.Terminal - tab container.TabItem - stat string - local bool + name string + term *terminal.Terminal + stat string + local bool + sessionConfig Config + termConfig *terminal.Config + configListeners []func(*terminal.Config) + closeListeners []func() } -func (t *Term) send(txt string) { +func NewTerm(name string, cfg Config) *Term { + term := terminal.New() + tab := &Term{name: name, term: term, sessionConfig: cfg} + tab.watchConfig() + return tab +} + +func (t *Term) Name() string { + return t.name +} + +func (t *Term) TermConfig() *terminal.Config { + return t.termConfig +} + +func (t *Term) SessionConfig() Config { + return t.sessionConfig +} + +func (t *Term) StartWithPipe(callback func(err error)) (io.WriteCloser, io.Reader) { + + pipe1Reader, pipe1Writer := io.Pipe() + pipe2Reader, pipe2Writer := io.Pipe() + + go func() { + err := t.term.RunWithConnection(pipe1Writer, pipe2Reader) + if err != nil { + callback(err) + } + }() + return pipe2Writer, pipe1Reader +} + +func (t *Term) RunWithConnection(conn net.Conn) error { + return t.term.RunWithConnection(conn, conn) +} + +func (t *Term) RunWithReaderAndWriter(in io.WriteCloser, out io.Reader) error { + return t.term.RunWithConnection(in, out) +} + +func (t *Term) RunWithReadWriteCloser(wr io.ReadWriteCloser) error { + return t.term.RunWithConnection(wr, wr) +} + +func (t *Term) Exit() { + t.term.Exit() + for _, listener := range t.closeListeners { + listener() + } +} + +func (t *Term) Send(txt string) { t.term.Write([]byte(txt)) } @@ -24,7 +80,35 @@ func (t *Term) FocusGained() { } } -func newLocalTerm() (*Term, error) { +func (t *Term) AddConfigListener(fn func(config *terminal.Config)) { + if fn != nil { + t.configListeners = append(t.configListeners, fn) + } +} + +func (t *Term) AddCloseListener(fn func()) { + if fn != nil { + t.closeListeners = append(t.closeListeners, fn) + } +} + +func (t *Term) watchConfig() { + cfgChan := make(chan terminal.Config) + t.term.AddListener(cfgChan) + go func() { + for { + select { + case cfg := <-cfgChan: + t.termConfig = &cfg + for _, listener := range t.configListeners { + listener(t.termConfig) + } + } + } + }() +} + +func NewLocalTerm() *Term { term := terminal.New() go func() { err := term.RunLocalShell() @@ -33,5 +117,8 @@ func newLocalTerm() (*Term, error) { return } }() - return &Term{name: "local", term: term, local: true}, nil + + t := &Term{name: "local", term: term} + t.watchConfig() + return t } diff --git a/window.go b/window.go index 962fa24..880b483 100644 --- a/window.go +++ b/window.go @@ -8,6 +8,7 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "github.com/fyne-io/terminal" ) const APP_NAME = "Go Shell" @@ -36,7 +37,14 @@ type Window struct { } func (w *Window) AddTermTab(tab *Term) { - tabItem := container.TabItem{Text: tab.name, Icon: theme.ComputerIcon(), Content: tab.term} + tabItem := container.TabItem{Text: tab.Name(), Icon: theme.ComputerIcon(), Content: tab.term} + tab.AddConfigListener(func(config *terminal.Config) { + if len(config.Title) > 0 { + tabItem.Text = config.Title + } else { + tabItem.Text = tab.Name() + } + }) w.tabs.Append(&tabItem) w.terms[&tabItem] = tab w.tabs.Select(&tabItem) @@ -85,11 +93,7 @@ func (w *Window) Run(stop <-chan struct{}) { func (w *Window) initUI() { toolbar := widget.NewToolbar(widget.NewToolbarAction(theme.ComputerIcon(), func() { - tab, err := newLocalTerm() - if err != nil { - dialog.NewError(err, w.win) - return - } + tab := NewLocalTerm() w.AddTermTab(tab) }), widget.NewToolbarAction(theme.DocumentIcon(), func() { w.showCreateConfigDialog() @@ -144,7 +148,9 @@ func (w *Window) initUI() { w.tabs = container.NewDocTabs() w.createLocalTermTab() w.tabs.OnClosed = func(item *container.TabItem) { - + if term, ok := w.terms[item]; ok { + term.Exit() + } } center := container.NewHSplit(sidebar, w.tabs) center.Offset = 0.2 @@ -166,7 +172,7 @@ func (w *Window) sendCmd(cmd *Cmd) { tabItem := w.tabs.Selected() if tabItem != nil { if term, ok := w.terms[tabItem]; ok { - term.send(cmd.Text) + term.Send(cmd.Text) } } }