├── .gitignore
├── front
├── .browserslistrc
├── babel.config.js
├── postcss.config.js
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── assets
│ │ └── logo.png
│ ├── main.js
│ ├── App.vue
│ └── components
│ │ └── Index.vue
├── .gitignore
├── .eslintrc.js
├── README.md
└── package.json
├── README.md
├── go.mod
├── main.go
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 |
3 | dwt
--------------------------------------------------------------------------------
/front/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/front/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/app"]
3 | };
4 |
--------------------------------------------------------------------------------
/front/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/front/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigheadbros/docker-web-terminal/HEAD/front/public/favicon.ico
--------------------------------------------------------------------------------
/front/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigheadbros/docker-web-terminal/HEAD/front/src/assets/logo.png
--------------------------------------------------------------------------------
/front/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import App from "./App.vue";
3 |
4 | Vue.config.productionTip = false;
5 |
6 | new Vue({
7 | render: h => h(App)
8 | }).$mount("#app");
9 |
--------------------------------------------------------------------------------
/front/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # docker-web-terminal
2 | Docker web terminal demo.
3 |
4 | # run
5 | ```
6 | docker run -d -it --name test ubuntu
7 | ```
8 | ```
9 | go run main.go
10 | ```
11 | ```
12 | cd front
13 | npm i
14 | npm run serve
15 | ```
16 |
17 | # link
18 | [https://monkeywie.github.io/2019/07/19/docker-web-terminal](https://monkeywie.github.io/2019/07/19/docker-web-terminal)
--------------------------------------------------------------------------------
/front/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: ["plugin:vue/essential", "@vue/prettier"],
7 | rules: {
8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off"
10 | },
11 | parserOptions: {
12 | parser: "babel-eslint"
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/front/README.md:
--------------------------------------------------------------------------------
1 | # front
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Run your tests
19 | ```
20 | npm run test
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | npm run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/front/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | front
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/front/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^2.6.5",
12 | "vue": "^2.6.10",
13 | "xterm": "^3.14.5"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-plugin-babel": "^3.9.0",
17 | "@vue/cli-plugin-eslint": "^3.9.0",
18 | "@vue/cli-service": "^3.9.0",
19 | "@vue/eslint-config-prettier": "^4.0.1",
20 | "babel-eslint": "^10.0.1",
21 | "eslint": "^5.16.0",
22 | "eslint-plugin-vue": "^5.0.0",
23 | "vue-template-compiler": "^2.6.10"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/monkeyWie/docker-web-terminal
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
7 | github.com/Microsoft/go-winio v0.4.12 // indirect
8 | github.com/docker/distribution v2.7.1+incompatible // indirect
9 | github.com/docker/docker v1.4.2-0.20190513124817-8c8457b0f2f8
10 | github.com/docker/go-connections v0.4.0 // indirect
11 | github.com/docker/go-units v0.4.0 // indirect
12 | github.com/gogo/protobuf v1.2.1 // indirect
13 | github.com/gorilla/mux v1.7.3 // indirect
14 | github.com/gorilla/websocket v1.4.0
15 | github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
16 | github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
17 | github.com/opencontainers/image-spec v1.0.1 // indirect
18 | github.com/pkg/errors v0.8.1 // indirect
19 | github.com/sirupsen/logrus v1.4.2
20 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
21 | google.golang.org/grpc v1.22.0 // indirect
22 | gotest.tools v2.2.0+incompatible // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/front/src/components/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
36 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "github.com/docker/docker/api/types"
6 | "github.com/docker/docker/client"
7 | "github.com/gorilla/websocket"
8 | log "github.com/sirupsen/logrus"
9 | "io"
10 | "net/http"
11 | "time"
12 | )
13 |
14 | var (
15 | ctx context.Context
16 | cli *client.Client
17 | )
18 |
19 | func main() {
20 | initDockerAPI()
21 |
22 | http.HandleFunc("/ping", ping)
23 | http.HandleFunc("/terminal", terminal)
24 | srv := &http.Server{
25 | Addr: "0.0.0.0:8000",
26 | // Good practice: enforce timeouts for servers you create!
27 | WriteTimeout: 15 * time.Second,
28 | ReadTimeout: 15 * time.Second,
29 | }
30 | log.Fatal(srv.ListenAndServe())
31 | }
32 |
33 | func initDockerAPI() {
34 | ctx = context.Background()
35 | newCli, err := client.NewClientWithOpts(client.FromEnv)
36 | cli = newCli
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | cli.NegotiateAPIVersion(ctx)
42 | }
43 |
44 | var upgrader = websocket.Upgrader{
45 | CheckOrigin: func(r *http.Request) bool {
46 | return true
47 | },
48 | }
49 |
50 | func ping(w http.ResponseWriter, r *http.Request) {
51 | w.WriteHeader(200)
52 | w.Write([]byte("pong"))
53 | }
54 |
55 | func terminal(w http.ResponseWriter, r *http.Request) {
56 | // websocket握手
57 | conn, err := upgrader.Upgrade(w, r, nil)
58 | if err != nil {
59 | log.Error(err)
60 | return
61 | }
62 | defer conn.Close()
63 |
64 | r.ParseForm()
65 | // 获取容器ID或name
66 | container := r.Form.Get("container")
67 | // 执行exec,获取到容器终端的连接
68 | hr, err := exec(container, r.Form.Get("workdir"))
69 | if err != nil {
70 | log.Error(err)
71 | return
72 | }
73 | // 关闭I/O流
74 | defer hr.Close()
75 | // 退出进程
76 | defer func() {
77 | hr.Conn.Write([]byte("exit\r"))
78 | }()
79 |
80 | go func() {
81 | wsWriterCopy(hr.Conn, conn)
82 | }()
83 | wsReaderCopy(conn, hr.Conn)
84 | }
85 |
86 | func exec(container string, workdir string) (hr types.HijackedResponse, err error) {
87 | // 执行/bin/bash命令
88 | ir, err := cli.ContainerExecCreate(ctx, container, types.ExecConfig{
89 | AttachStdin: true,
90 | AttachStdout: true,
91 | AttachStderr: true,
92 | WorkingDir: workdir,
93 | Cmd: []string{"/bin/bash"},
94 | Tty: true,
95 | })
96 | if err != nil {
97 | return
98 | }
99 |
100 | // 附加到上面创建的/bin/bash进程中
101 | hr, err = cli.ContainerExecAttach(ctx, ir.ID, types.ExecStartCheck{Detach: false, Tty: true})
102 | if err != nil {
103 | return
104 | }
105 | return
106 | }
107 |
108 | func wsWriterCopy(reader io.Reader, writer *websocket.Conn) {
109 | buf := make([]byte, 8192)
110 | for {
111 | nr, err := reader.Read(buf)
112 | if nr > 0 {
113 | err := writer.WriteMessage(websocket.BinaryMessage, buf[0:nr])
114 | if err != nil {
115 | return
116 | }
117 | }
118 | if err != nil {
119 | return
120 | }
121 | }
122 | }
123 |
124 | func wsReaderCopy(reader *websocket.Conn, writer io.Writer) {
125 | for {
126 | messageType, p, err := reader.ReadMessage()
127 | if err != nil {
128 | return
129 | }
130 | if messageType == websocket.TextMessage {
131 | writer.Write(p)
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
3 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
5 | github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
6 | github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
7 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
11 | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
12 | github.com/docker/docker v1.4.2-0.20190513124817-8c8457b0f2f8 h1:vyqIlE9fpJ+cdE95qkW9ihHas6QT87AFLE72W5bGUEY=
13 | github.com/docker/docker v1.4.2-0.20190513124817-8c8457b0f2f8/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
14 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
15 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
16 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
17 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
18 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
19 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
20 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
21 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
22 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
23 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
24 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
25 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
26 | github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
27 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
28 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
29 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
30 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
31 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
32 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
33 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
34 | github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
35 | github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
36 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
37 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
38 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
39 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
40 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
41 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
42 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
43 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
44 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
45 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
46 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
47 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
48 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
49 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
50 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
51 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
52 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
53 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
54 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
55 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
56 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
57 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
58 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
59 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
60 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
61 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
62 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
63 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
64 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
65 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
66 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
67 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
68 | google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
69 | google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
70 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
71 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
72 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
73 |
--------------------------------------------------------------------------------