├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app.js ├── assets ├── containers.png ├── images.png ├── jetbrains-variant-4.svg ├── login.png ├── newContainer.png ├── overview.png ├── pull.png ├── pull2.png └── terminal.png ├── bin └── www ├── config.js ├── fabfile.py ├── images ├── kalilinux │ ├── kali-rolling │ └── kali │ │ └── latest ├── library │ ├── alpine │ │ ├── base │ │ │ ├── Dockerfile │ │ │ └── supervisor.conf │ │ └── latest │ │ │ └── Dockerfile │ ├── archlinux │ │ └── latest │ │ │ └── Dockerfile │ ├── centos │ │ ├── 7 │ │ ├── 8 │ │ └── latest │ │ │ └── Dockerfile │ ├── debian │ │ ├── 9 │ │ ├── 10 │ │ ├── 11 │ │ ├── latest │ │ │ └── Dockerfile │ │ └── sid │ ├── fedora │ │ ├── 33 │ │ ├── 34 │ │ ├── 35 │ │ └── latest │ └── ubuntu │ │ ├── 16.04 │ │ ├── 18.04 │ │ ├── 20.04 │ │ ├── devel │ │ └── latest ├── opensuse │ ├── leap │ └── tumbleweed │ │ └── latest │ │ └── Dockerfile └── rockylinux │ └── rockylinux │ └── latest ├── middlewares └── security.js ├── package.json ├── public ├── css │ ├── bootstrap-select.css.map │ ├── bootstrap-select.min.css │ ├── bootstrap-table.min.css │ ├── bootstrap.min.css │ ├── style.css │ └── xterm.css ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── img │ └── hero-bg.jpg └── js │ ├── addons │ ├── attach │ │ ├── attach.js │ │ └── attach.js.map │ ├── fit │ │ ├── fit.js │ │ └── fit.js.map │ ├── fullscreen │ │ ├── fullscreen.css │ │ ├── fullscreen.js │ │ └── fullscreen.js.map │ ├── search │ │ ├── search.js │ │ └── search.js.map │ ├── terminado │ │ ├── terminado.js │ │ └── terminado.js.map │ ├── webLinks │ │ ├── webLinks.js │ │ └── webLinks.js.map │ ├── winptyCompat │ │ ├── winptyCompat.js │ │ └── winptyCompat.js.map │ └── zmodem │ │ ├── zmodem.js │ │ └── zmodem.js.map │ ├── app.js │ ├── bootstrap-select.js.map │ ├── bootstrap-select.min.js │ ├── bootstrap-table.min.js │ ├── bootstrap.min.js │ ├── bootstrap3-typeahead.min.js │ ├── jquery.min.js │ ├── socket.io.js │ ├── xterm.js │ └── xterm.js.map ├── routes ├── api.js ├── containers.js ├── images.js ├── index.js └── overview.js ├── views ├── containers.html ├── error.html ├── images.html ├── include │ ├── footer.html │ └── header.html ├── login.html ├── logs.html ├── overview.html └── terminal.html └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,linux,macos,windows,visualstudiocode 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### macOS ### 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear in the root of a volume 31 | .DocumentRevisions-V100 32 | .fseventsd 33 | .Spotlight-V100 34 | .TemporaryItems 35 | .Trashes 36 | .VolumeIcon.icns 37 | .com.apple.timemachine.donotpresent 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | ### Node ### 47 | # Logs 48 | logs 49 | *.log 50 | npm-debug.log* 51 | yarn-debug.log* 52 | yarn-error.log* 53 | 54 | # Runtime data 55 | pids 56 | *.pid 57 | *.seed 58 | *.pid.lock 59 | 60 | # Directory for instrumented libs generated by jscoverage/JSCover 61 | lib-cov 62 | 63 | # Coverage directory used by tools like istanbul 64 | coverage 65 | 66 | # nyc test coverage 67 | .nyc_output 68 | 69 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 70 | .grunt 71 | 72 | # Bower dependency directory (https://bower.io/) 73 | bower_components 74 | 75 | # node-waf configuration 76 | .lock-wscript 77 | 78 | # Compiled binary addons (http://nodejs.org/api/addons.html) 79 | build/Release 80 | 81 | # Dependency directories 82 | node_modules/ 83 | jspm_packages/ 84 | 85 | # Typescript v1 declaration files 86 | typings/ 87 | 88 | # Optional npm cache directory 89 | .npm 90 | 91 | # Optional eslint cache 92 | .eslintcache 93 | 94 | # Optional REPL history 95 | .node_repl_history 96 | 97 | # Output of 'npm pack' 98 | *.tgz 99 | 100 | # Yarn Integrity file 101 | .yarn-integrity 102 | 103 | # dotenv environment variables file 104 | .env 105 | 106 | 107 | ### VisualStudioCode ### 108 | .vscode/* 109 | !.vscode/settings.json 110 | !.vscode/tasks.json 111 | !.vscode/launch.json 112 | !.vscode/extensions.json 113 | .history 114 | 115 | ### Windows ### 116 | # Windows thumbnail cache files 117 | Thumbs.db 118 | ehthumbs.db 119 | ehthumbs_vista.db 120 | 121 | # Folder config file 122 | Desktop.ini 123 | 124 | # Recycle Bin used on file shares 125 | $RECYCLE.BIN/ 126 | 127 | # Windows Installer files 128 | *.cab 129 | *.msi 130 | *.msm 131 | *.msp 132 | 133 | # Windows shortcuts 134 | *.lnk 135 | .idea 136 | 137 | # End of https://www.gitignore.io/api/node,linux,macos,windows,visualstudiocode -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:9-alpine 2 | ADD . /src 3 | 4 | RUN apk update && apk add bash && \ 5 | cd /src; yarn install && \ 6 | # Time zone option, if you live in China pleace set it to Asia/Shanghai 7 | ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime 8 | 9 | EXPOSE 3000 10 | CMD node /src/bin/www 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyDockerWeb 2 | 3 | A simple Web Ui for Docker using `xterm.js`, `Node.js` and `Socket.io`. 4 | 5 | With this solution you will be able to create your owner SAS service. 6 | 7 | 8 | - If you need to use docker cluster, [https://portainer.io/](https://portainer.io/) may be a good choice. 9 | - search image by name 10 | - terminal 11 | - log 12 | 13 | ## Quick start 14 | 15 | Set EDW_USERNAME and EDW_PASSWORD to overwrite the default username and password. 16 | 17 | *PS:* Default username and password are **admin/admin.** 18 | 19 | ```bash 20 | docker run -it -d -p 3000:3000 -e EDW_USERNAME='admin' -e EDW_PASSWORD='admin' -v /var/run/docker.sock:/var/run/docker.sock qfdk/easydockerweb 21 | ``` 22 | 23 | [http://localhost:3000](http://localhost:3000) enjoy ;) 24 | 25 | ## Requirement 26 | 27 | - Node.js 28 | - Docker remote api >= v1.24 29 | - macOS or Linux or windows 30 | 31 | ## Development mode 32 | 33 | ```bash 34 | git clone https://github.com/qfdk/EasyDockerWeb.git 35 | cd EasyDockerWeb 36 | yarn 37 | yarn start 38 | ``` 39 | 40 | ## Build your owner docker image 41 | 42 | ```bash 43 | git clone https://github.com/qfdk/EasyDockerWeb.git 44 | cd EasyDockerWeb 45 | docker build -t easy-docker-web . 46 | docker run -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock easy-docker-web 47 | ``` 48 | 49 | ## 中文 50 | 51 | 简单的 docker 管理程序,使用了express socket.io 来实现前后端通讯. 52 | 53 | - 容器增删改查 54 | - 容器交互 55 | - 日志查看 56 | - 系统状态查看 57 | - 镜像获取 58 | - 计划使用react重构 https://github.com/qfdk/EasyDockerWeb/tree/react 59 | 60 | ## Images 61 |  62 | 63 |  64 | 65 |  66 | 67 |  68 | 69 |  70 | 71 |  72 | 73 |  74 | 75 |  76 | 77 | ## Sponsor 78 | 79 | 80 | ## React.js web ui (beta) 81 | 82 | ```bash 83 | cd web-ui 84 | yarn install 85 | yarn start 86 | ``` 87 | [http://localhost:4000](http://localhost:4000) 88 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const bodyParser = require('body-parser'); 4 | const app = express(); 5 | const session = require('express-session'); 6 | 7 | const {checkUser} = require('./middlewares/security'); 8 | 9 | const io = require('socket.io')(); 10 | const favicon = require('serve-favicon'); 11 | app.io = io; 12 | 13 | const index = require('./routes/index'); 14 | const api = require('./routes/api'); 15 | const overview = require('./routes/overview')(io); 16 | const containers = require('./routes/containers')(io); 17 | const images = require('./routes/images')(io); 18 | 19 | // view engine setup 20 | app.set('views', path.join(__dirname, 'views')); 21 | app.set('view engine', 'html'); 22 | app.engine('html', require('ejs').renderFile); 23 | 24 | app.use(session({ 25 | saveUninitialized: false, 26 | resave: false, 27 | secret: 'easy-docker-web', 28 | cookie: { 29 | maxAge: 365 * 24 * 60 * 60 * 1000 30 | } 31 | })); 32 | 33 | // public files 34 | app.use('/static', express.static(__dirname + '/public')); 35 | app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 36 | app.use(bodyParser.json()); 37 | app.use(bodyParser.urlencoded({extended: false})); 38 | app.use(express.static(path.join(__dirname, 'public'))); 39 | 40 | app.all('*', (req, res, next) => { 41 | res.header('Access-Control-Allow-Origin', '*'); 42 | res.header('Access-Control-Allow-Headers', 43 | 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); 44 | res.header('Access-Control-Allow-Methods', 45 | 'PUT, POST, GET, DELETE, OPTIONS'); 46 | if (req.method == 'OPTIONS') { 47 | res.send(200); /* speedup options */ 48 | } else { 49 | next(); 50 | } 51 | }); 52 | 53 | app.use(checkUser); 54 | app.use((req, res, next) => { 55 | res.locals.isLogin = req.session.isLogin || false; 56 | next(); 57 | }); 58 | app.use('/', index); 59 | app.use('/api', api); 60 | app.use('/overview', overview); 61 | app.use('/containers', containers); 62 | app.use('/images', images); 63 | 64 | // catch 404 and forward to error handler 65 | app.use((req, res, next) => { 66 | const err = new Error('Not Found'); 67 | err.status = 404; 68 | next(err); 69 | }); 70 | 71 | // error handler 72 | app.use((err, req, res, next) => { 73 | // set locals, only providing error in development 74 | res.locals.message = err.message; 75 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 76 | 77 | // render the error page 78 | res.status(err.status || 500); 79 | res.render('error'); 80 | }); 81 | 82 | module.exports = app; 83 | -------------------------------------------------------------------------------- /assets/containers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/assets/containers.png -------------------------------------------------------------------------------- /assets/images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/assets/images.png -------------------------------------------------------------------------------- /assets/jetbrains-variant-4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /assets/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/assets/login.png -------------------------------------------------------------------------------- /assets/newContainer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/assets/newContainer.png -------------------------------------------------------------------------------- /assets/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/assets/overview.png -------------------------------------------------------------------------------- /assets/pull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/assets/pull.png -------------------------------------------------------------------------------- /assets/pull2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/assets/pull2.png -------------------------------------------------------------------------------- /assets/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/assets/terminal.png -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var io = app.io; 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | io.attach(server); 32 | 33 | /** 34 | * Normalize a port into a number, string, or false. 35 | */ 36 | 37 | function normalizePort(val) { 38 | var port = parseInt(val, 10); 39 | 40 | if (isNaN(port)) { 41 | // named pipe 42 | return val; 43 | } 44 | 45 | if (port >= 0) { 46 | // port number 47 | return port; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * Event listener for HTTP server "error" event. 55 | */ 56 | 57 | function onError(error) { 58 | if (error.syscall !== 'listen') { 59 | throw error; 60 | } 61 | 62 | var bind = typeof port === 'string' 63 | ? 'Pipe ' + port 64 | : 'Port ' + port; 65 | 66 | // handle specific listen errors with friendly messages 67 | switch (error.code) { 68 | case 'EACCES': 69 | console.log(bind + ' requires elevated privileges'); 70 | process.exit(1); 71 | break; 72 | case 'EADDRINUSE': 73 | console.log(bind + ' is already in use'); 74 | process.exit(1); 75 | break; 76 | default: 77 | throw error; 78 | } 79 | } 80 | 81 | /** 82 | * Event listener for HTTP server "listening" event. 83 | */ 84 | 85 | function onListening() { 86 | var addr = server.address(); 87 | var bind = typeof addr === 'string' 88 | ? 'pipe ' + addr 89 | : 'port ' + addr.port; 90 | console.log('Listening on ' + bind); 91 | } 92 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'username': process.env.EDW_USERNAME || 'admin', 3 | 'password': process.env.EDW_PASSWORD || 'admin' 4 | }; 5 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | 4 | from fabric.api import local 5 | 6 | registry = "ghcr.io/bennythink" 7 | 8 | 9 | # fabric3 10 | def base(): 11 | local(f"docker build -f images/library/alpine/base/Dockerfile -t {registry}/alpine:base .") 12 | local(f"docker push {registry}/alpine:base ") 13 | 14 | 15 | def build(): 16 | base() 17 | for fn in glob.iglob("images/**/Dockerfile", recursive=True): 18 | dirs: "list" = fn.split(os.sep) 19 | username = dirs[1] 20 | distro = dirs[2] 21 | tag = dirs[3] 22 | image_tag = f"{username}/{distro}:{tag}" 23 | with open("random", "w") as f: 24 | f.write(image_tag) 25 | local(f"docker build -f {fn} --build-arg image_tag={image_tag} -t {registry}/{distro}:{tag} .") 26 | local(f"docker push {registry}/{distro}:{tag}") 27 | os.remove("random") 28 | 29 | 30 | def prepare(): 31 | for fn in glob.iglob("images/**/Dockerfile", recursive=True): 32 | dirs: "list" = fn.split(os.sep) 33 | distro = dirs[2] 34 | tag = dirs[3] 35 | local(f"docker pull {registry}/{distro}:{tag}") 36 | -------------------------------------------------------------------------------- /images/kalilinux/kali-rolling: -------------------------------------------------------------------------------- 1 | kali -------------------------------------------------------------------------------- /images/kalilinux/kali/latest: -------------------------------------------------------------------------------- 1 | ../../library/debian/latest/ -------------------------------------------------------------------------------- /images/library/alpine/base/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | RUN wget https://github.com/ochinchina/supervisord/releases/download/v0.7.3/supervisord_0.7.3_Linux_64-bit.tar.gz &&\ 4 | tar -xzf supervisord_0.7.3_Linux_64-bit.tar.gz &&ls &&\ 5 | mv supervisord_0.7.3_Linux_64-bit/supervisord /usr/local/bin/supervisord &&\ 6 | rm -rf supervisord_0.7.3_Linux_64-bit.tar.gz 7 | 8 | COPY images/library/alpine/base/supervisor.conf /etc/ 9 | 10 | -------------------------------------------------------------------------------- /images/library/alpine/base/supervisor.conf: -------------------------------------------------------------------------------- 1 | [program:sshd] 2 | command = /usr/sbin/sshd -D -------------------------------------------------------------------------------- /images/library/alpine/latest/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG image_tag 2 | 3 | FROM ghcr.io/bennythink/alpine:base as mybase 4 | 5 | FROM ${image_tag} 6 | RUN apk update && apk add dropbear 7 | RUN mkdir /etc/dropbear && echo 'root:root' |chpasswd 8 | 9 | RUN echo -e "[program:dropbear]\ncommand = /usr/sbin/dropbear -RFE" > /etc/supervisor.conf 10 | COPY --from=mybase /usr/local/bin/supervisord /usr/local/bin/supervisord 11 | 12 | ENTRYPOINT ["supervisord", "-c" ,"/etc/supervisor.conf"] -------------------------------------------------------------------------------- /images/library/archlinux/latest/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG image_tag 2 | 3 | FROM ghcr.io/bennythink/alpine:base as mybase 4 | 5 | FROM ${image_tag} 6 | RUN pacman -Sy --noconfirm openssh && /usr/bin/ssh-keygen -A 7 | RUN echo 'root:root' |chpasswd && echo "PermitRootLogin yes" >> /etc/ssh/sshd_config 8 | 9 | COPY --from=mybase /etc/supervisor.conf /etc 10 | COPY --from=mybase /usr/local/bin/supervisord /usr/local/bin/supervisord 11 | 12 | ENTRYPOINT ["supervisord", "-c" ,"/etc/supervisor.conf"] -------------------------------------------------------------------------------- /images/library/centos/7: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/centos/8: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/centos/latest/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG image_tag 2 | 3 | FROM ghcr.io/bennythink/alpine:base as mybase 4 | 5 | FROM ${image_tag} 6 | RUN yum install -y openssh-server && /usr/bin/ssh-keygen -A 7 | RUN echo 'root:root' |chpasswd && echo "PermitRootLogin yes" >> /etc/ssh/sshd_config 8 | 9 | COPY --from=mybase /etc/supervisor.conf /etc 10 | COPY --from=mybase /usr/local/bin/supervisord /usr/local/bin/supervisord 11 | COPY random /tmp/random 12 | 13 | ENTRYPOINT ["supervisord", "-c" ,"/etc/supervisor.conf"] -------------------------------------------------------------------------------- /images/library/debian/10: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/debian/11: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/debian/9: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/debian/latest/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG image_tag 2 | 3 | FROM ghcr.io/bennythink/alpine:base as mybase 4 | 5 | FROM ${image_tag} 6 | RUN apt update && apt install -y --no-install-recommends openssh-server && mkdir /run/sshd 7 | RUN echo 'root:root' |chpasswd && echo "PermitRootLogin yes" >> /etc/ssh/sshd_config 8 | 9 | COPY --from=mybase /etc/supervisor.conf /etc 10 | COPY --from=mybase /usr/local/bin/supervisord /usr/local/bin/supervisord 11 | COPY random /tmp/random 12 | 13 | ENTRYPOINT ["supervisord", "-c" ,"/etc/supervisor.conf"] 14 | -------------------------------------------------------------------------------- /images/library/debian/sid: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/fedora/33: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/fedora/34: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/fedora/35: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/fedora/latest: -------------------------------------------------------------------------------- 1 | ../centos/latest -------------------------------------------------------------------------------- /images/library/ubuntu/16.04: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/ubuntu/18.04: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/ubuntu/20.04: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/ubuntu/devel: -------------------------------------------------------------------------------- 1 | latest -------------------------------------------------------------------------------- /images/library/ubuntu/latest: -------------------------------------------------------------------------------- 1 | ../debian/latest -------------------------------------------------------------------------------- /images/opensuse/leap: -------------------------------------------------------------------------------- 1 | tumbleweed -------------------------------------------------------------------------------- /images/opensuse/tumbleweed/latest/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG image_tag 2 | 3 | FROM ghcr.io/bennythink/alpine:base as mybase 4 | 5 | FROM ${image_tag} 6 | 7 | RUN zypper install -y openssh-server && /usr/bin/ssh-keygen -A 8 | RUN echo 'root:root' |chpasswd && echo "PermitRootLogin yes" >> /etc/ssh/sshd_config 9 | 10 | COPY --from=mybase /etc/supervisor.conf /etc 11 | COPY --from=mybase /usr/local/bin/supervisord /usr/local/bin/supervisord 12 | 13 | ENTRYPOINT ["supervisord", "-c" ,"/etc/supervisor.conf"] -------------------------------------------------------------------------------- /images/rockylinux/rockylinux/latest: -------------------------------------------------------------------------------- 1 | ../../library/centos/latest/ -------------------------------------------------------------------------------- /middlewares/security.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | 3 | const checkUser = (req, res, next) => { 4 | res.locals.isLogin = false; 5 | if (req.session.isLogin) { 6 | res.locals.isLogin = true; 7 | next(); 8 | } else { 9 | const username = config.username, 10 | password = config.password; 11 | if (req.body.username === username && req.body.password === password) { 12 | req.session.isLogin = true; 13 | res.redirect('/'); 14 | } else { 15 | res.render('login'); 16 | } 17 | } 18 | }; 19 | 20 | module.exports = { 21 | checkUser, 22 | }; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easy-docker-web", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "nodemon ./bin/www" 7 | }, 8 | "nodemonConfig": { 9 | "verbose": true, 10 | "ext": "js,html,css" 11 | }, 12 | "dependencies": { 13 | "babel-preset-react-app": "^9.1.1", 14 | "body-parser": "~1.18.2", 15 | "dockerode": "^2.5.4", 16 | "ejs": "^2.5.7", 17 | "express": "^4.16.4", 18 | "express-session": "^1.17.1", 19 | "express-status-monitor": "^1.2.8", 20 | "serve-favicon": "^2.4.5", 21 | "socket.io": "2.4.1" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^2.0.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/css/bootstrap-select.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/bootstrap-select.less","bootstrap-select.css"],"names":[],"mappings":"AAQA;;EAEE,yBAAA;CCPD;ADUD;EACE,gBAAA;ECRA,iBAAiB;CAClB;ADMD;EAKI,YAAA;EACA,oBAAA;EACA,WAAA;CCRH;ADUG;;;;EAG0B,YAAA;CCP7B;ADLD;EAgBI,8BAAA;EACA,UAAA;EACA,UAAA;EACA,0BAAA;EACA,wBAAA;EACA,wBAAA;EACA,sBAAA;EACA,sBAAA;EACA,aAAA;CCRH;ADUG;EACE,OAAA;EACA,QAAA;EACA,0BAAA;EACA,uBAAA;EACA,WAAA;CCRL;ADaC;;EAEE,sBAAA;CCXH;ADcC;EACE,uBAAA;CCZH;ADeC;EACE,aAAA;CCbH;ADjCD;EAkDI,wCAAA;EACA,sDAAA;EACA,qBAAA;CCdH;ADkBD;EACE,iBAAA;EACA,WAAA;EACA,aAAA;CChBD;ADkBC;EACE,YAAA;CChBH;ADmBC;EACE,cAAA;CCjBH;ADmBG;EAEI,iBAAA;CClBP;AD0BC;;EAEE,YAAA;EACA,sBAAA;EACA,eAAA;CCxBH;AD+BG;;;EACE,aAAA;CC3BL;AD+BC;;;EAGE,iBAAA;CC7BH;ADgCC;;EAEE,WAAA;CC9BH;AD4BC;;EAKI,aAAA;EACA,mBAAA;EACA,qBAAA;EACA,uBAAA;CC7BL;ADmCC;EACE,YAAA;CCjCH;ADoCC;;EA/HA,oBAAA;CC+FD;ADoCG;;EACE,yBAAA;CCjCL;ADqCC;EACE,mBAAA;EACA,qBAAA;EACA,sBAAA;CCnCH;ADgCC;EAMI,cAAA;CCnCL;ADrBD;EA+DM,sBAAA;EACA,iBAAA;EACA,YAAA;EACA,iBAAA;CCvCL;AD3BD;EAsEM,mBAAA;EACA,SAAA;EACA,YAAA;EACA,iBAAA;EACA,uBAAA;CCxCL;AD4CC;EACE,YAAA;CC1CH;ADrCD;EAoFI,gBAAA;EACA,+BAAA;KAAA,4BAAA;UAAA,uBAAA;CC5CH;AD8CG;EACE,iBAAA;EACA,YAAA;EACA,UAAA;EACA,WAAA;EACA,UAAA;EACA,iBAAA;EACA,yBAAA;UAAA,iBAAA;CC5CL;ADlDD;EAkGM,mBAAA;CC7CL;AD+CK;EACE,YAAA;CC7CP;ADgDK;EA9LJ,oBAAA;CCiJD;AD3DD;EA6GQ,gBAAA;EACA,0BAAA;KAAA,uBAAA;MAAA,sBAAA;UAAA,kBAAA;CC/CP;ADiDO;EACE,mBAAA;EACA,qBAAA;CC/CT;ADnED;EAsHU,cAAA;CChDT;ADtED;EA0HU,sBAAA;CCjDT;ADzED;EA+HQ,oBAAA;CCnDP;AD5ED;EAoIM,mBAAA;EACA,YAAA;EACA,WAAA;EACA,aAAA;EACA,iBAAA;EACA,iBAAA;EACA,oBAAA;EACA,0BAAA;EACA,wDAAA;UAAA,gDAAA;EACA,qBAAA;EACA,aAAA;EACA,+BAAA;KAAA,4BAAA;UAAA,uBAAA;CCrDL;AD1FD;EAoJI,aAAA;EACA,oBAAA;EACA,cAAA;EACA,oBAAA;CCvDH;AD0DC;EAEI,iBAAA;CCzDL;ADuDC;EAMI,iBAAA;EACA,UAAA;EACA,iBAAA;CC1DL;AD+DG;EACE,mBAAA;EACA,sBAAA;EACA,YAAA;EACA,gBAAA;CC7DL;ADwDC;EASI,mBAAA;CC9DL;ADoEC;EACE,cAAA;CClEH;ADsEG;EACE,YAAA;EACA,mCAAA;EACA,oCAAA;EACA,kDAAA;EACA,mBAAA;EACA,aAAA;EACA,UAAA;EACA,cAAA;CCpEL;ADuEG;EACE,YAAA;EACA,mCAAA;EACA,oCAAA;EACA,+BAAA;EACA,mBAAA;EACA,aAAA;EACA,WAAA;EACA,cAAA;CCrEL;AD0EG;EACE,aAAA;EACA,UAAA;EACA,+CAAA;EACA,iBAAA;CCxEL;AD2EG;EACE,aAAA;EACA,UAAA;EACA,4BAAA;EACA,iBAAA;CCzEL;AD8EG;EACE,YAAA;EACA,WAAA;CC5EL;AD+EG;EACE,YAAA;EACA,WAAA;CC7EL;ADkFG;;EAEE,eAAA;CChFL;ADqFD;;;EAGE,iBAAA;CCnFD;ADsFD;EACE,YAAA;EACA,+BAAA;KAAA,4BAAA;UAAA,uBAAA;CCpFD;ADsFC;EACE,WAAA;CCpFH;ADwFD;EACE,YAAA;EACA,YAAA;EACA,+BAAA;KAAA,4BAAA;UAAA,uBAAA;CCtFD;ADwFC;EACE,YAAA;CCtFH;AD2FC;EACE,mBAAA;CCzFH;AD4FC;EACE,iBAAA;EACA,YAAA;EACA,YAAA;CC1FH","file":"bootstrap-select.css","sourcesContent":["@import \"variables\";\n\n// Mixins\n.cursor-disabled() {\n cursor: not-allowed;\n}\n\n// Rules\nselect.bs-select-hidden,\nselect.selectpicker {\n display: none !important;\n}\n\n.bootstrap-select {\n width: 220px \\0; /*IE9 and below*/\n\n // The selectpicker button\n > .dropdown-toggle {\n width: 100%;\n padding-right: 25px;\n z-index: 1;\n\n &.bs-placeholder,\n &.bs-placeholder:hover,\n &.bs-placeholder:focus,\n &.bs-placeholder:active { color: @input-color-placeholder; }\n }\n\n > select {\n position: absolute !important;\n bottom: 0;\n left: 50%;\n display: block !important;\n width: 0.5px !important;\n height: 100% !important;\n padding: 0 !important;\n opacity: 0 !important;\n border: none;\n\n &.mobile-device {\n top: 0;\n left: 0;\n display: block !important;\n width: 100% !important;\n z-index: 2;\n }\n }\n\n // Error display\n .has-error & .dropdown-toggle,\n .error & .dropdown-toggle {\n border-color: @color-red-error;\n }\n\n &.fit-width {\n width: auto !important;\n }\n\n &:not([class*=\"col-\"]):not([class*=\"form-control\"]):not(.input-group-btn) {\n width: @width-default;\n }\n\n .dropdown-toggle:focus {\n outline: thin dotted #333333 !important;\n outline: 5px auto -webkit-focus-ring-color !important;\n outline-offset: -2px;\n }\n}\n\n.bootstrap-select.form-control {\n margin-bottom: 0;\n padding: 0;\n border: none;\n\n &:not([class*=\"col-\"]) {\n width: 100%;\n }\n\n &.input-group-btn {\n z-index: auto;\n\n &:not(:first-child):not(:last-child) {\n > .btn {\n border-radius: 0;\n }\n }\n }\n}\n\n// The selectpicker components\n.bootstrap-select.btn-group {\n &:not(.input-group-btn),\n &[class*=\"col-\"] {\n float: none;\n display: inline-block;\n margin-left: 0;\n }\n\n // Forces the pull to the right, if necessary\n &,\n &[class*=\"col-\"],\n .row &[class*=\"col-\"] {\n &.dropdown-menu-right {\n float: right;\n }\n }\n\n .form-inline &,\n .form-horizontal &,\n .form-group & {\n margin-bottom: 0;\n }\n\n .form-group-lg &.form-control,\n .form-group-sm &.form-control {\n padding: 0;\n\n .dropdown-toggle {\n height: 100%;\n font-size: inherit;\n line-height: inherit;\n border-radius: inherit;\n }\n }\n\n // Set the width of the live search (and any other form control within an inline form)\n // see https://github.com/silviomoreto/bootstrap-select/issues/685\n .form-inline & .form-control {\n width: 100%;\n }\n\n &.disabled,\n > .disabled {\n .cursor-disabled();\n\n &:focus {\n outline: none !important;\n }\n }\n\n &.bs-container {\n position: absolute;\n height: 0 !important;\n padding: 0 !important;\n \n .dropdown-menu {\n z-index: @zindex-select-dropdown;\n }\n }\n\n // The selectpicker button\n .dropdown-toggle {\n .filter-option {\n display: inline-block;\n overflow: hidden;\n width: 100%;\n text-align: left;\n }\n\n .caret {\n position: absolute;\n top: 50%;\n right: 12px;\n margin-top: -2px;\n vertical-align: middle;\n }\n }\n\n &[class*=\"col-\"] .dropdown-toggle {\n width: 100%;\n }\n\n // The selectpicker dropdown\n .dropdown-menu {\n min-width: 100%;\n box-sizing: border-box;\n\n &.inner {\n position: static;\n float: none;\n border: 0;\n padding: 0;\n margin: 0;\n border-radius: 0;\n box-shadow: none;\n }\n\n li {\n position: relative;\n\n &.active small {\n color: #fff;\n }\n\n &.disabled a {\n .cursor-disabled();\n }\n\n a {\n cursor: pointer;\n user-select: none;\n\n &.opt {\n position: relative;\n padding-left: 2.25em;\n }\n\n span.check-mark {\n display: none;\n }\n\n span.text {\n display: inline-block;\n }\n }\n\n small {\n padding-left: 0.5em;\n }\n }\n\n .notify {\n position: absolute;\n bottom: 5px;\n width: 96%;\n margin: 0 2%;\n min-height: 26px;\n padding: 3px 5px;\n background: rgb(245, 245, 245);\n border: 1px solid rgb(227, 227, 227);\n box-shadow: inset 0 1px 1px fade(rgb(0, 0, 0), 5%);\n pointer-events: none;\n opacity: 0.9;\n box-sizing: border-box;\n }\n }\n\n .no-results {\n padding: 3px;\n background: #f5f5f5;\n margin: 0 5px;\n white-space: nowrap;\n }\n\n &.fit-width .dropdown-toggle {\n .filter-option {\n position: static;\n }\n\n .caret {\n position: static;\n top: auto;\n margin-top: -1px;\n }\n }\n\n &.show-tick .dropdown-menu li {\n &.selected a span.check-mark {\n position: absolute;\n display: inline-block;\n right: 15px;\n margin-top: 5px;\n }\n\n a span.text {\n margin-right: 34px;\n }\n }\n}\n\n.bootstrap-select.show-menu-arrow {\n &.open > .dropdown-toggle {\n z-index: (@zindex-select-dropdown + 1);\n }\n\n .dropdown-toggle {\n &:before {\n content: '';\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-bottom: 7px solid @color-grey-arrow;\n position: absolute;\n bottom: -4px;\n left: 9px;\n display: none;\n }\n\n &:after {\n content: '';\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-bottom: 6px solid white;\n position: absolute;\n bottom: -4px;\n left: 10px;\n display: none;\n }\n }\n\n &.dropup .dropdown-toggle {\n &:before {\n bottom: auto;\n top: -3px;\n border-top: 7px solid @color-grey-arrow;\n border-bottom: 0;\n }\n\n &:after {\n bottom: auto;\n top: -3px;\n border-top: 6px solid white;\n border-bottom: 0;\n }\n }\n\n &.pull-right .dropdown-toggle {\n &:before {\n right: 12px;\n left: auto;\n }\n\n &:after {\n right: 13px;\n left: auto;\n }\n }\n\n &.open > .dropdown-toggle {\n &:before,\n &:after {\n display: block;\n }\n }\n}\n\n.bs-searchbox,\n.bs-actionsbox,\n.bs-donebutton {\n padding: 4px 8px;\n}\n\n.bs-actionsbox {\n width: 100%;\n box-sizing: border-box;\n\n & .btn-group button {\n width: 50%;\n }\n}\n\n.bs-donebutton {\n float: left;\n width: 100%;\n box-sizing: border-box;\n\n & .btn-group button {\n width: 100%;\n }\n}\n\n.bs-searchbox {\n & + .bs-actionsbox {\n padding: 0 8px 4px;\n }\n\n & .form-control {\n margin-bottom: 0;\n width: 100%;\n float: none;\n }\n}\n","select.bs-select-hidden,\nselect.selectpicker {\n display: none !important;\n}\n.bootstrap-select {\n width: 220px \\0;\n /*IE9 and below*/\n}\n.bootstrap-select > .dropdown-toggle {\n width: 100%;\n padding-right: 25px;\n z-index: 1;\n}\n.bootstrap-select > .dropdown-toggle.bs-placeholder,\n.bootstrap-select > .dropdown-toggle.bs-placeholder:hover,\n.bootstrap-select > .dropdown-toggle.bs-placeholder:focus,\n.bootstrap-select > .dropdown-toggle.bs-placeholder:active {\n color: #999;\n}\n.bootstrap-select > select {\n position: absolute !important;\n bottom: 0;\n left: 50%;\n display: block !important;\n width: 0.5px !important;\n height: 100% !important;\n padding: 0 !important;\n opacity: 0 !important;\n border: none;\n}\n.bootstrap-select > select.mobile-device {\n top: 0;\n left: 0;\n display: block !important;\n width: 100% !important;\n z-index: 2;\n}\n.has-error .bootstrap-select .dropdown-toggle,\n.error .bootstrap-select .dropdown-toggle {\n border-color: #b94a48;\n}\n.bootstrap-select.fit-width {\n width: auto !important;\n}\n.bootstrap-select:not([class*=\"col-\"]):not([class*=\"form-control\"]):not(.input-group-btn) {\n width: 220px;\n}\n.bootstrap-select .dropdown-toggle:focus {\n outline: thin dotted #333333 !important;\n outline: 5px auto -webkit-focus-ring-color !important;\n outline-offset: -2px;\n}\n.bootstrap-select.form-control {\n margin-bottom: 0;\n padding: 0;\n border: none;\n}\n.bootstrap-select.form-control:not([class*=\"col-\"]) {\n width: 100%;\n}\n.bootstrap-select.form-control.input-group-btn {\n z-index: auto;\n}\n.bootstrap-select.form-control.input-group-btn:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.bootstrap-select.btn-group:not(.input-group-btn),\n.bootstrap-select.btn-group[class*=\"col-\"] {\n float: none;\n display: inline-block;\n margin-left: 0;\n}\n.bootstrap-select.btn-group.dropdown-menu-right,\n.bootstrap-select.btn-group[class*=\"col-\"].dropdown-menu-right,\n.row .bootstrap-select.btn-group[class*=\"col-\"].dropdown-menu-right {\n float: right;\n}\n.form-inline .bootstrap-select.btn-group,\n.form-horizontal .bootstrap-select.btn-group,\n.form-group .bootstrap-select.btn-group {\n margin-bottom: 0;\n}\n.form-group-lg .bootstrap-select.btn-group.form-control,\n.form-group-sm .bootstrap-select.btn-group.form-control {\n padding: 0;\n}\n.form-group-lg .bootstrap-select.btn-group.form-control .dropdown-toggle,\n.form-group-sm .bootstrap-select.btn-group.form-control .dropdown-toggle {\n height: 100%;\n font-size: inherit;\n line-height: inherit;\n border-radius: inherit;\n}\n.form-inline .bootstrap-select.btn-group .form-control {\n width: 100%;\n}\n.bootstrap-select.btn-group.disabled,\n.bootstrap-select.btn-group > .disabled {\n cursor: not-allowed;\n}\n.bootstrap-select.btn-group.disabled:focus,\n.bootstrap-select.btn-group > .disabled:focus {\n outline: none !important;\n}\n.bootstrap-select.btn-group.bs-container {\n position: absolute;\n height: 0 !important;\n padding: 0 !important;\n}\n.bootstrap-select.btn-group.bs-container .dropdown-menu {\n z-index: 1060;\n}\n.bootstrap-select.btn-group .dropdown-toggle .filter-option {\n display: inline-block;\n overflow: hidden;\n width: 100%;\n text-align: left;\n}\n.bootstrap-select.btn-group .dropdown-toggle .caret {\n position: absolute;\n top: 50%;\n right: 12px;\n margin-top: -2px;\n vertical-align: middle;\n}\n.bootstrap-select.btn-group[class*=\"col-\"] .dropdown-toggle {\n width: 100%;\n}\n.bootstrap-select.btn-group .dropdown-menu {\n min-width: 100%;\n box-sizing: border-box;\n}\n.bootstrap-select.btn-group .dropdown-menu.inner {\n position: static;\n float: none;\n border: 0;\n padding: 0;\n margin: 0;\n border-radius: 0;\n box-shadow: none;\n}\n.bootstrap-select.btn-group .dropdown-menu li {\n position: relative;\n}\n.bootstrap-select.btn-group .dropdown-menu li.active small {\n color: #fff;\n}\n.bootstrap-select.btn-group .dropdown-menu li.disabled a {\n cursor: not-allowed;\n}\n.bootstrap-select.btn-group .dropdown-menu li a {\n cursor: pointer;\n user-select: none;\n}\n.bootstrap-select.btn-group .dropdown-menu li a.opt {\n position: relative;\n padding-left: 2.25em;\n}\n.bootstrap-select.btn-group .dropdown-menu li a span.check-mark {\n display: none;\n}\n.bootstrap-select.btn-group .dropdown-menu li a span.text {\n display: inline-block;\n}\n.bootstrap-select.btn-group .dropdown-menu li small {\n padding-left: 0.5em;\n}\n.bootstrap-select.btn-group .dropdown-menu .notify {\n position: absolute;\n bottom: 5px;\n width: 96%;\n margin: 0 2%;\n min-height: 26px;\n padding: 3px 5px;\n background: #f5f5f5;\n border: 1px solid #e3e3e3;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n pointer-events: none;\n opacity: 0.9;\n box-sizing: border-box;\n}\n.bootstrap-select.btn-group .no-results {\n padding: 3px;\n background: #f5f5f5;\n margin: 0 5px;\n white-space: nowrap;\n}\n.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option {\n position: static;\n}\n.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret {\n position: static;\n top: auto;\n margin-top: -1px;\n}\n.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark {\n position: absolute;\n display: inline-block;\n right: 15px;\n margin-top: 5px;\n}\n.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text {\n margin-right: 34px;\n}\n.bootstrap-select.show-menu-arrow.open > .dropdown-toggle {\n z-index: 1061;\n}\n.bootstrap-select.show-menu-arrow .dropdown-toggle:before {\n content: '';\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-bottom: 7px solid rgba(204, 204, 204, 0.2);\n position: absolute;\n bottom: -4px;\n left: 9px;\n display: none;\n}\n.bootstrap-select.show-menu-arrow .dropdown-toggle:after {\n content: '';\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-bottom: 6px solid white;\n position: absolute;\n bottom: -4px;\n left: 10px;\n display: none;\n}\n.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before {\n bottom: auto;\n top: -3px;\n border-top: 7px solid rgba(204, 204, 204, 0.2);\n border-bottom: 0;\n}\n.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after {\n bottom: auto;\n top: -3px;\n border-top: 6px solid white;\n border-bottom: 0;\n}\n.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before {\n right: 12px;\n left: auto;\n}\n.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after {\n right: 13px;\n left: auto;\n}\n.bootstrap-select.show-menu-arrow.open > .dropdown-toggle:before,\n.bootstrap-select.show-menu-arrow.open > .dropdown-toggle:after {\n display: block;\n}\n.bs-searchbox,\n.bs-actionsbox,\n.bs-donebutton {\n padding: 4px 8px;\n}\n.bs-actionsbox {\n width: 100%;\n box-sizing: border-box;\n}\n.bs-actionsbox .btn-group button {\n width: 50%;\n}\n.bs-donebutton {\n float: left;\n width: 100%;\n box-sizing: border-box;\n}\n.bs-donebutton .btn-group button {\n width: 100%;\n}\n.bs-searchbox + .bs-actionsbox {\n padding: 0 8px 4px;\n}\n.bs-searchbox .form-control {\n margin-bottom: 0;\n width: 100%;\n float: none;\n}\n/*# sourceMappingURL=bootstrap-select.css.map */"]} -------------------------------------------------------------------------------- /public/css/bootstrap-select.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap-select v1.12.4 (http://silviomoreto.github.io/bootstrap-select) 3 | * 4 | * Copyright 2013-2017 bootstrap-select 5 | * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) 6 | */select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\9}.bootstrap-select>.dropdown-toggle{width:100%;padding-right:25px;z-index:1}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2}.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.form-control.input-group-btn:not(:first-child):not(:last-child)>.btn{border-radius:0}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-group-lg .bootstrap-select.btn-group.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.btn-group.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.bootstrap-select.btn-group.disabled,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group.disabled:focus,.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group.bs-container{position:absolute;height:0!important;padding:0!important}.bootstrap-select.btn-group.bs-container .dropdown-menu{z-index:1060}.bootstrap-select.btn-group .dropdown-toggle .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li.active small{color:#fff}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option{position:static}.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} -------------------------------------------------------------------------------- /public/css/bootstrap-table.min.css: -------------------------------------------------------------------------------- 1 | .fixed-table-container .bs-checkbox,.fixed-table-container .no-records-found{text-align:center}.fixed-table-body thead th .th-inner,.table td,.table th{box-sizing:border-box}.bootstrap-table .table{margin-bottom:0!important;border-bottom:1px solid #ddd;border-collapse:collapse!important;border-radius:1px}.bootstrap-table .table:not(.table-condensed),.bootstrap-table .table:not(.table-condensed)>tbody>tr>td,.bootstrap-table .table:not(.table-condensed)>tbody>tr>th,.bootstrap-table .table:not(.table-condensed)>tfoot>tr>td,.bootstrap-table .table:not(.table-condensed)>tfoot>tr>th,.bootstrap-table .table:not(.table-condensed)>thead>tr>td{padding:8px}.bootstrap-table .table.table-no-bordered>tbody>tr>td,.bootstrap-table .table.table-no-bordered>thead>tr>th{border-right:2px solid transparent}.bootstrap-table .table.table-no-bordered>tbody>tr>td:last-child{border-right:none}.fixed-table-container{position:relative;clear:both;border:1px solid #ddd;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px}.fixed-table-container.table-no-bordered{border:1px solid transparent}.fixed-table-footer,.fixed-table-header{overflow:hidden}.fixed-table-footer{border-top:1px solid #ddd}.fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.fixed-table-container table{width:100%}.fixed-table-container thead th{height:0;padding:0;margin:0;border-left:1px solid #ddd}.fixed-table-container thead th:focus{outline:transparent solid 0}.fixed-table-container thead th:first-child{border-left:none;border-top-left-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px}.fixed-table-container tbody td .th-inner,.fixed-table-container thead th .th-inner{padding:8px;line-height:24px;vertical-align:top;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fixed-table-container thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px}.fixed-table-container thead th .both{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC')}.fixed-table-container thead th .asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}.fixed-table-container thead th .desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}.fixed-table-container th.detail{width:30px}.fixed-table-container tbody td{border-left:1px solid #ddd}.fixed-table-container tbody tr:first-child td{border-top:none}.fixed-table-container tbody td:first-child{border-left:none}.fixed-table-container tbody .selected td{background-color:#f5f5f5}.fixed-table-container .bs-checkbox .th-inner{padding:8px 0}.fixed-table-container input[type=radio],.fixed-table-container input[type=checkbox]{margin:0 auto!important}.fixed-table-pagination .pagination-detail,.fixed-table-pagination div.pagination{margin-top:10px;margin-bottom:10px}.fixed-table-pagination div.pagination .pagination{margin:0}.fixed-table-pagination .pagination a{padding:6px 12px;line-height:1.428571429}.fixed-table-pagination .pagination-info{line-height:34px;margin-right:5px}.fixed-table-pagination .btn-group{position:relative;display:inline-block;vertical-align:middle}.fixed-table-pagination .dropup .dropdown-menu{margin-bottom:0}.fixed-table-pagination .page-list{display:inline-block}.fixed-table-toolbar .columns-left{margin-right:5px}.fixed-table-toolbar .columns-right{margin-left:5px}.fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429}.fixed-table-toolbar .bs-bars,.fixed-table-toolbar .columns,.fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px;line-height:34px}.fixed-table-pagination li.disabled a{pointer-events:none;cursor:default}.fixed-table-loading{display:none;position:absolute;top:42px;right:0;bottom:0;left:0;z-index:99;background-color:#fff;text-align:center}.fixed-table-body .card-view .title{font-weight:700;display:inline-block;min-width:30%;text-align:left!important}.table td,.table th{vertical-align:middle}.fixed-table-toolbar .dropdown-menu{text-align:left;max-height:300px;overflow:auto}.fixed-table-toolbar .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.fixed-table-toolbar .btn-group>.btn-group>.btn{border-radius:0}.fixed-table-toolbar .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.fixed-table-toolbar .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .table>thead>tr>th{vertical-align:bottom;border-bottom:1px solid #ddd}.bootstrap-table .table thead>tr>th{padding:0;margin:0}.bootstrap-table .fixed-table-footer tbody>tr>td{padding:0!important}.bootstrap-table .fixed-table-footer .table{border-bottom:none;border-radius:0;padding:0!important}.bootstrap-table .pull-right .dropdown-menu{right:0;left:auto}p.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}.fixed-table-pagination:after,.fixed-table-toolbar:after{content:"";display:block;clear:both} -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | color: #666666; 4 | font-family: "Open Sans", sans-serif; 5 | margin-top: 70px; 6 | } -------------------------------------------------------------------------------- /public/css/xterm.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 The xterm.js authors. All rights reserved. 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | * https://github.com/chjj/term.js 5 | * @license MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | * Originally forked from (with the author's permission): 26 | * Fabrice Bellard's javascript vt100 for jslinux: 27 | * http://bellard.org/jslinux/ 28 | * Copyright (c) 2011 Fabrice Bellard 29 | * The original design remains. The terminal itself 30 | * has been extended to include xterm CSI codes, among 31 | * other features. 32 | */ 33 | 34 | /** 35 | * Default styles for xterm.js 36 | */ 37 | 38 | .xterm { 39 | font-family: courier-new, courier, monospace; 40 | font-feature-settings: "liga" 0; 41 | position: relative; 42 | user-select: none; 43 | -ms-user-select: none; 44 | -webkit-user-select: none; 45 | } 46 | 47 | .xterm.focus, 48 | .xterm:focus { 49 | outline: none; 50 | } 51 | 52 | .xterm .xterm-helpers { 53 | position: absolute; 54 | top: 0; 55 | /** 56 | * The z-index of the helpers must be higher than the canvases in order for 57 | * IMEs to appear on top. 58 | */ 59 | z-index: 10; 60 | } 61 | 62 | .xterm .xterm-helper-textarea { 63 | /* 64 | * HACK: to fix IE's blinking cursor 65 | * Move textarea out of the screen to the far left, so that the cursor is not visible. 66 | */ 67 | position: absolute; 68 | opacity: 0; 69 | left: -9999em; 70 | top: 0; 71 | width: 0; 72 | height: 0; 73 | z-index: -10; 74 | /** Prevent wrapping so the IME appears against the textarea at the correct position */ 75 | white-space: nowrap; 76 | overflow: hidden; 77 | resize: none; 78 | } 79 | 80 | .xterm .composition-view { 81 | /* TODO: Composition position got messed up somewhere */ 82 | background: #000; 83 | color: #FFF; 84 | display: none; 85 | position: absolute; 86 | white-space: nowrap; 87 | z-index: 1; 88 | } 89 | 90 | .xterm .composition-view.active { 91 | display: block; 92 | } 93 | 94 | .xterm .xterm-viewport { 95 | /* On OS X this is required in order for the scroll bar to appear fully opaque */ 96 | background-color: #000; 97 | overflow-y: scroll; 98 | cursor: default; 99 | position: absolute; 100 | right: 0; 101 | left: 0; 102 | top: 0; 103 | bottom: 0; 104 | } 105 | 106 | .xterm .xterm-screen { 107 | position: relative; 108 | } 109 | 110 | .xterm .xterm-screen canvas { 111 | position: absolute; 112 | left: 0; 113 | top: 0; 114 | } 115 | 116 | .xterm .xterm-scroll-area { 117 | visibility: hidden; 118 | } 119 | 120 | .xterm .xterm-char-measure-element { 121 | display: inline-block; 122 | visibility: hidden; 123 | position: absolute; 124 | left: -9999em; 125 | } 126 | 127 | .xterm.enable-mouse-events { 128 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ 129 | cursor: default; 130 | } 131 | 132 | .xterm:not(.enable-mouse-events) { 133 | cursor: text; 134 | } 135 | 136 | .xterm .xterm-accessibility, 137 | .xterm .xterm-message { 138 | position: absolute; 139 | left: 0; 140 | top: 0; 141 | bottom: 0; 142 | right: 0; 143 | z-index: 100; 144 | color: transparent; 145 | } 146 | 147 | .xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] { 148 | outline: 1px solid #F80; 149 | } 150 | 151 | .xterm .live-region { 152 | position: absolute; 153 | left: -9999px; 154 | width: 1px; 155 | height: 1px; 156 | overflow: hidden; 157 | } 158 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/img/hero-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BennyThink/EasyDockerWeb/b5206cafb3a44a62efe2f0fe409a0f3de9592c5e/public/img/hero-bg.jpg -------------------------------------------------------------------------------- /public/js/addons/attach/attach.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.attach = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o\n\nimport { Terminal } from 'xterm';\nimport { IAttachAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\nexport function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = term;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n let myTextDecoder;\n\n addonTerminal.__getMessage = function(ev: MessageEvent): void {\n let str;\n if (typeof ev.data === 'object') {\n if (ev.data instanceof ArrayBuffer) {\n if (!myTextDecoder) {\n myTextDecoder = new TextDecoder();\n }\n\n str = myTextDecoder.decode( ev.data );\n } else {\n throw 'TODO: handle Blob?';\n }\n }\n\n if (buffered) {\n addonTerminal.__pushToBuffer(str || ev.data);\n } else {\n addonTerminal.write(str || ev.data);\n }\n };\n\n addonTerminal.__sendData = (data: string) => {\n if (socket.readyState !== 1) {\n return;\n }\n socket.send(data);\n };\n\n socket.addEventListener('message', addonTerminal.__getMessage);\n\n if (bidirectional) {\n addonTerminal.on('data', addonTerminal.__sendData);\n }\n\n socket.addEventListener('close', () => detach(addonTerminal, socket));\n socket.addEventListener('error', () => detach(addonTerminal, socket));\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function detach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = term;\n addonTerminal.off('data', addonTerminal.__sendData);\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\n (terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n attach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (terminalConstructor.prototype).detach = function (socket: WebSocket): void {\n detach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADqBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAzDA;AAiEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAXA;AAcA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;;;"} -------------------------------------------------------------------------------- /public/js/addons/fit/fit.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fit = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o\n\nimport { Terminal } from 'xterm';\n\nexport interface IGeometry {\n rows: number;\n cols: number;\n}\n\nexport function proposeGeometry(term: Terminal): IGeometry {\n if (!term.element.parentElement) {\n return null;\n }\n const parentElementStyle = window.getComputedStyle(term.element.parentElement);\n const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n const elementStyle = window.getComputedStyle(term.element);\n const elementPadding = {\n top: parseInt(elementStyle.getPropertyValue('padding-top')),\n bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n right: parseInt(elementStyle.getPropertyValue('padding-right')),\n left: parseInt(elementStyle.getPropertyValue('padding-left'))\n };\n const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n const elementPaddingHor = elementPadding.right + elementPadding.left;\n const availableHeight = parentElementHeight - elementPaddingVer;\n const availableWidth = parentElementWidth - elementPaddingHor - (term).viewport.scrollBarWidth;\n const geometry = {\n cols: Math.floor(availableWidth / (term).renderer.dimensions.actualCellWidth),\n rows: Math.floor(availableHeight / (term).renderer.dimensions.actualCellHeight)\n };\n return geometry;\n}\n\nexport function fit(term: Terminal): void {\n const geometry = proposeGeometry(term);\n if (geometry) {\n // Force a full render\n if (term.rows !== geometry.rows || term.cols !== geometry.cols) {\n (term).renderer.clear();\n term.resize(geometry.cols, geometry.rows);\n }\n }\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).proposeGeometry = function (): IGeometry {\n return proposeGeometry(this);\n };\n\n (terminalConstructor.prototype).fit = function (): void {\n fit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADwBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;;;"} -------------------------------------------------------------------------------- /public/js/addons/fullscreen/fullscreen.css: -------------------------------------------------------------------------------- 1 | .xterm.fullscreen { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | width: auto; 8 | height: auto; 9 | z-index: 255; 10 | } 11 | -------------------------------------------------------------------------------- /public/js/addons/fullscreen/fullscreen.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fullscreen = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o\n\nimport { Terminal } from 'xterm';\n\n/**\n * Toggle the given terminal's fullscreen mode.\n * @param term The terminal to toggle full screen mode\n * @param fullscreen Toggle fullscreen on (true) or off (false)\n */\nexport function toggleFullScreen(term: Terminal, fullscreen: boolean): void {\n let fn: string;\n\n if (typeof fullscreen === 'undefined') {\n fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';\n } else if (!fullscreen) {\n fn = 'remove';\n } else {\n fn = 'add';\n }\n\n term.element.classList[fn]('fullscreen');\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {\n toggleFullScreen(this, fullscreen);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADcA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AAJA;;;"} -------------------------------------------------------------------------------- /public/js/addons/search/search.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.search = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0; y--) { 43 | result = this._findInLine(term, y); 44 | if (result) { 45 | break; 46 | } 47 | } 48 | if (!result) { 49 | for (var y = this._terminal.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) { 50 | result = this._findInLine(term, y); 51 | if (result) { 52 | break; 53 | } 54 | } 55 | } 56 | return this._selectResult(result); 57 | }; 58 | SearchHelper.prototype._findInLine = function (term, y) { 59 | var lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase(); 60 | var lowerTerm = term.toLowerCase(); 61 | var searchIndex = lowerStringLine.indexOf(lowerTerm); 62 | if (searchIndex >= 0) { 63 | var line = this._terminal.buffer.lines.get(y); 64 | for (var i = 0; i < searchIndex; i++) { 65 | var charData = line[i]; 66 | var char = charData[1]; 67 | if (char.length > 1) { 68 | searchIndex -= char.length - 1; 69 | } 70 | var charWidth = charData[2]; 71 | if (charWidth === 0) { 72 | searchIndex++; 73 | } 74 | } 75 | return { 76 | term: term, 77 | col: searchIndex, 78 | row: y 79 | }; 80 | } 81 | }; 82 | SearchHelper.prototype._selectResult = function (result) { 83 | if (!result) { 84 | return false; 85 | } 86 | this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length); 87 | this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp); 88 | return true; 89 | }; 90 | return SearchHelper; 91 | }()); 92 | exports.SearchHelper = SearchHelper; 93 | 94 | 95 | 96 | },{}],2:[function(require,module,exports){ 97 | "use strict"; 98 | Object.defineProperty(exports, "__esModule", { value: true }); 99 | var SearchHelper_1 = require("./SearchHelper"); 100 | function findNext(terminal, term) { 101 | var addonTerminal = terminal; 102 | if (!addonTerminal.__searchHelper) { 103 | addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal); 104 | } 105 | return addonTerminal.__searchHelper.findNext(term); 106 | } 107 | exports.findNext = findNext; 108 | function findPrevious(terminal, term) { 109 | var addonTerminal = terminal; 110 | if (!addonTerminal.__searchHelper) { 111 | addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal); 112 | } 113 | return addonTerminal.__searchHelper.findPrevious(term); 114 | } 115 | exports.findPrevious = findPrevious; 116 | function apply(terminalConstructor) { 117 | terminalConstructor.prototype.findNext = function (term) { 118 | return findNext(this, term); 119 | }; 120 | terminalConstructor.prototype.findPrevious = function (term) { 121 | return findPrevious(this, term); 122 | }; 123 | } 124 | exports.apply = apply; 125 | 126 | 127 | 128 | },{"./SearchHelper":1}]},{},[2])(2) 129 | }); 130 | //# sourceMappingURL=search.js.map 131 | -------------------------------------------------------------------------------- /public/js/addons/search/search.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"search.js","sources":["../../../src/addons/search/search.ts","../../../src/addons/search/SearchHelper.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/// \n\nimport { SearchHelper } from './SearchHelper';\nimport { Terminal } from 'xterm';\nimport { ISearchAddonTerminal } from './Interfaces';\n\n/**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\nexport function findNext(terminal: Terminal, term: string): boolean {\n const addonTerminal = terminal;\n if (!addonTerminal.__searchHelper) {\n addonTerminal.__searchHelper = new SearchHelper(addonTerminal);\n }\n return addonTerminal.__searchHelper.findNext(term);\n}\n\n/**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\nexport function findPrevious(terminal: Terminal, term: string): boolean {\n const addonTerminal = terminal;\n if (!addonTerminal.__searchHelper) {\n addonTerminal.__searchHelper = new SearchHelper(addonTerminal);\n }\n return addonTerminal.__searchHelper.findPrevious(term);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).findNext = function(term: string): boolean {\n return findNext(this, term);\n };\n\n (terminalConstructor.prototype).findPrevious = function(term: string): boolean {\n return findPrevious(this, term);\n };\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ISearchHelper, ISearchAddonTerminal } from './Interfaces';\n\ninterface ISearchResult {\n term: string;\n col: number;\n row: number;\n}\n\n/**\n * A class that knows how to search the terminal and how to display the results.\n */\nexport class SearchHelper implements ISearchHelper {\n constructor(private _terminal: ISearchAddonTerminal) {\n // TODO: Search for multiple instances on 1 line\n // TODO: Don't use the actual selection, instead use a \"find selection\" so multiple instances can be highlighted\n // TODO: Highlight other instances in the viewport\n // TODO: Support regex, case sensitivity, etc.\n }\n\n /**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n public findNext(term: string): boolean {\n if (!term || term.length === 0) {\n return false;\n }\n\n let result: ISearchResult;\n\n let startRow = this._terminal.buffer.ydisp;\n if (this._terminal.selectionManager.selectionEnd) {\n // Start from the selection end if there is a selection\n startRow = this._terminal.selectionManager.selectionEnd[1];\n }\n\n // Search from ydisp + 1 to end\n for (let y = startRow + 1; y < this._terminal.buffer.ybase + this._terminal.rows; y++) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n\n // Search from the top to the current ydisp\n if (!result) {\n for (let y = 0; y < startRow; y++) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n public findPrevious(term: string): boolean {\n if (!term || term.length === 0) {\n return false;\n }\n\n let result: ISearchResult;\n\n let startRow = this._terminal.buffer.ydisp;\n if (this._terminal.selectionManager.selectionStart) {\n // Start from the selection end if there is a selection\n startRow = this._terminal.selectionManager.selectionStart[1];\n }\n\n // Search from ydisp + 1 to end\n for (let y = startRow - 1; y >= 0; y--) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n\n // Search from the top to the current ydisp\n if (!result) {\n for (let y = this._terminal.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Searches a line for a search term.\n * @param term Tne search term.\n * @param y The line to search.\n * @return The search result if it was found.\n */\n private _findInLine(term: string, y: number): ISearchResult {\n const lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase();\n const lowerTerm = term.toLowerCase();\n let searchIndex = lowerStringLine.indexOf(lowerTerm);\n if (searchIndex >= 0) {\n const line = this._terminal.buffer.lines.get(y);\n for (let i = 0; i < searchIndex; i++) {\n const charData = line[i];\n // Adjust the searchIndex to normalize emoji into single chars\n const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];\n if (char.length > 1) {\n searchIndex -= char.length - 1;\n }\n // Adjust the searchIndex for empty characters following wide unicode\n // chars (eg. CJK)\n const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];\n if (charWidth === 0) {\n searchIndex++;\n }\n }\n return {\n term,\n col: searchIndex,\n row: y\n };\n }\n }\n\n /**\n * Selects and scrolls to a result.\n * @param result The result to select.\n * @return Whethera result was selected.\n */\n private _selectResult(result: ISearchResult): boolean {\n if (!result) {\n return false;\n }\n this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length);\n this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp);\n return true;\n }\n}\n",null],"names":[],"mappings":"AEAA;;;ADgBA;AACA;AAAA;AAKA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAzIa;;;;;;;ADTb;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;;;"} -------------------------------------------------------------------------------- /public/js/addons/terminado/terminado.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.terminado = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o\n\nimport { Terminal } from 'xterm';\nimport { ITerminadoAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\nexport function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = term;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n addonTerminal.__getMessage = (ev: MessageEvent) => {\n const data = JSON.parse(ev.data);\n if (data[0] === 'stdout') {\n if (buffered) {\n addonTerminal.__pushToBuffer(data[1]);\n } else {\n addonTerminal.write(data[1]);\n }\n }\n };\n\n addonTerminal.__sendData = (data: string) => {\n socket.send(JSON.stringify(['stdin', data]));\n };\n\n addonTerminal.__setSize = (size: {rows: number, cols: number}) => {\n socket.send(JSON.stringify(['set_size', size.rows, size.cols]));\n };\n\n socket.addEventListener('message', addonTerminal.__getMessage);\n\n if (bidirectional) {\n addonTerminal.on('data', addonTerminal.__sendData);\n }\n addonTerminal.on('resize', addonTerminal.__setSize);\n\n socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));\n socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function terminadoDetach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = term;\n addonTerminal.off('data', addonTerminal.__sendData);\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket - The socket to attach the current terminal.\n * @param bidirectional - Whether the terminal should send data to the socket as well.\n * @param buffered - Whether the rendering of incoming data should happen instantly or at a\n * maximum frequency of 1 rendering per 10ms.\n */\n (terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n return terminadoAttach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {\n return terminadoDetach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADsBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA/CA;AAuDA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAXA;AAaA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;;;"} -------------------------------------------------------------------------------- /public/js/addons/webLinks/webLinks.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.webLinks = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o\n\nimport { Terminal, ILinkMatcherOptions } from 'xterm';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~]*)*';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\nfunction handleLink(event: MouseEvent, uri: string): void {\n window.open(uri, '_blank');\n}\n\n/**\n * Initialize the web links addon, registering the link matcher.\n * @param term The terminal to use web links within.\n * @param handler A custom handler to use.\n * @param options Custom options to use, matchIndex will always be ignored.\n */\nexport function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void {\n options.matchIndex = 1;\n term.registerLinkMatcher(strictUrlRegex, handler, options);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {\n webLinksInit(this, handler, options);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADSA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AAAA;AAAA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAJA;;;"} -------------------------------------------------------------------------------- /public/js/addons/winptyCompat/winptyCompat.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.winptyCompat = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0; 7 | if (!isWindows) { 8 | return; 9 | } 10 | addonTerminal.on('linefeed', function () { 11 | var line = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y - 1); 12 | var lastChar = line[addonTerminal.cols - 1]; 13 | if (lastChar[3] !== 32) { 14 | var nextLine = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y); 15 | nextLine.isWrapped = true; 16 | } 17 | }); 18 | } 19 | exports.winptyCompatInit = winptyCompatInit; 20 | function apply(terminalConstructor) { 21 | terminalConstructor.prototype.winptyCompatInit = function () { 22 | winptyCompatInit(this); 23 | }; 24 | } 25 | exports.apply = apply; 26 | 27 | 28 | 29 | },{}]},{},[1])(1) 30 | }); 31 | //# sourceMappingURL=winptyCompat.js.map 32 | -------------------------------------------------------------------------------- /public/js/addons/winptyCompat/winptyCompat.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"winptyCompat.js","sources":["../../../src/addons/winptyCompat/winptyCompat.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/// \n\nimport { Terminal } from 'xterm';\nimport { IWinptyCompatAddonTerminal } from './Interfaces';\n\nexport function winptyCompatInit(terminal: Terminal): void {\n const addonTerminal = terminal;\n\n // Don't do anything when the platform is not Windows\n const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;\n if (!isWindows) {\n return;\n }\n\n // Winpty does not support wraparound mode which means that lines will never\n // be marked as wrapped. This causes issues for things like copying a line\n // retaining the wrapped new line characters or if consumers are listening\n // in on the data stream.\n //\n // The workaround for this is to listen to every incoming line feed and mark\n // the line as wrapped if the last character in the previous line is not a\n // space. This is certainly not without its problems, but generally on\n // Windows when text reaches the end of the terminal it's likely going to be\n // wrapped.\n addonTerminal.on('linefeed', () => {\n const line = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y - 1);\n const lastChar = line[addonTerminal.cols - 1];\n\n if (lastChar[3] !== 32 /* ' ' */) {\n const nextLine = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y);\n (nextLine).isWrapped = true;\n }\n });\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).winptyCompatInit = function (): void {\n winptyCompatInit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADUA;AACA;AAGA;AACA;AACA;AACA;AAYA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AA5BA;AA8BA;AACA;AACA;AACA;AACA;AAJA;;;"} -------------------------------------------------------------------------------- /public/js/addons/zmodem/zmodem.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.zmodem = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o\n\nimport { Terminal } from 'xterm';\n\n/**\n *\n * Allow xterm.js to handle ZMODEM uploads and downloads.\n *\n * This addon is a wrapper around zmodem.js. It adds the following to the\n * Terminal class:\n *\n * - function `zmodemAttach(, )` - creates a Zmodem.Sentry\n * on the passed WebSocket object. The Object passed is optional and\n * can contain:\n * - noTerminalWriteOutsideSession: Suppress writes from the Sentry\n * object to the Terminal while there is no active Session. This\n * is necessary for compatibility with, for example, the\n * `attach.js` addon.\n *\n * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.\n * Passes the zmodem.js Detection object.\n *\n * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.\n *\n * You’ll need to provide logic to handle uploads and downloads.\n * See zmodem.js’s documentation for more details.\n *\n * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have\n * used the `attach` or `terminado` addons, you’ll need to suspend their\n * operation for the duration of the ZMODEM session. (The demo does this\n * via `detach()` and a re-`attach()`.)\n */\n\nlet zmodem;\n\nexport interface IZModemOptions {\n noTerminalWriteOutsideSession?: boolean;\n}\n\nexport function zmodemAttach(term: Terminal, ws: WebSocket, opts: IZModemOptions = {}): void {\n const senderFunc = (octets: ArrayLike) => ws.send(new Uint8Array(octets));\n\n let zsentry;\n\n function _shouldWrite(): boolean {\n return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;\n }\n\n zsentry = new zmodem.Sentry({\n to_terminal: (octets: ArrayLike) => {\n if (_shouldWrite()) {\n term.write(\n String.fromCharCode.apply(String, octets)\n );\n }\n },\n sender: senderFunc,\n on_retract: () => (term).emit('zmodemRetract'),\n on_detect: (detection: any) => (term).emit('zmodemDetect', detection)\n });\n\n function handleWSMessage(evt: MessageEvent): void {\n\n // In testing with xterm.js’s demo the first message was\n // always text even if the rest were binary. While that\n // may be specific to xterm.js’s demo, ultimately we\n // should reject anything that isn’t binary.\n if (typeof evt.data === 'string') {\n if (_shouldWrite()) {\n term.write(evt.data);\n }\n }\n else {\n zsentry.consume(evt.data);\n }\n }\n\n ws.binaryType = 'arraybuffer';\n ws.addEventListener('message', handleWSMessage);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n zmodem = (typeof window === 'object') ? (window).ZModem : {Browser: null}; // Nullify browser for tests\n\n (terminalConstructor.prototype).zmodemAttach = zmodemAttach.bind(this, this);\n (terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;\n}\n",null],"names":[],"mappings":"ACAA;;;ADsCA;AAMA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAxCA;AA0CA;AACA;AAEA;AACA;AACA;AALA;;;"} -------------------------------------------------------------------------------- /public/js/app.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | var codePageCourante = $("[data-page]").attr("data-page"); 3 | $('#' + codePageCourante + 'Nav').addClass('active'); 4 | loading(); 5 | if (codePageCourante == 'overview') { 6 | } 7 | 8 | if (codePageCourante == 'containers') { 9 | getContainersCPU(); 10 | getContainersRAM(); 11 | } 12 | if (codePageCourante == 'terminal') { 13 | terminal(); 14 | } 15 | if (codePageCourante == 'logs') { 16 | logs(); 17 | } 18 | if (codePageCourante == 'images') { 19 | $('#pullImage').on('click', function () { 20 | pullIamges(); 21 | }); 22 | 23 | $('#imageName').typeahead({ 24 | limit: 10, 25 | source: function (query, process) { 26 | return $.get("/images/search/" + $('#imageName').val(), function (data) { 27 | return process(data); 28 | }); 29 | } 30 | }); 31 | } 32 | }); 33 | 34 | function terminal() { 35 | Terminal.applyAddon(attach); 36 | Terminal.applyAddon(fit); 37 | var term = new Terminal({ 38 | useStyle: true, 39 | convertEol: true, 40 | screenKeys: true, 41 | cursorBlink: false, 42 | visualBell: true, 43 | colors: Terminal.xtermColors 44 | }); 45 | 46 | term.open(document.getElementById('terminal')); 47 | term.fit(); 48 | var id = window.location.pathname.split('/')[3]; 49 | var host = window.location.origin; 50 | var socket = io.connect(host); 51 | socket.emit('exec', id, $('#terminal').width(), $('#terminal').height()); 52 | term.on('data', (data) => { 53 | socket.emit('cmd', data); 54 | }); 55 | 56 | socket.on('show', (data) => { 57 | term.write(data); 58 | }); 59 | 60 | socket.on('end', (status) => { 61 | $('#terminal').empty(); 62 | socket.disconnect(); 63 | }); 64 | } 65 | 66 | function getContainersCPU() { 67 | var containers = $('.container-cpu'); 68 | for (var i = 0; i < containers.length; i++) { 69 | var containerId = $('.container-cpu').eq(i).attr('container-id') 70 | getContainerCPUInfoById(containerId); 71 | } 72 | } 73 | 74 | function getContainersRAM() { 75 | var containers = $('.container-cpu'); 76 | for (var i = 0; i < containers.length; i++) { 77 | var containerId = $('.container-ram').eq(i).attr('container-id') 78 | getContainerRAMInfoById(containerId); 79 | } 80 | } 81 | 82 | function getContainerCPUInfoById(id) { 83 | var host = window.location.origin; 84 | var socket = io.connect(host); 85 | socket.emit('getSysInfo', id); 86 | socket.on(id, (data) => { 87 | var json = JSON.parse(data); 88 | var res = calculateCPUPercentUnix(json); 89 | if (json.precpu_stats.system_cpu_usage) { 90 | $('.container-cpu[container-id=' + id + ']').text(res + ' %'); 91 | } 92 | }); 93 | socket.on('end', (status) => { 94 | console.log("[END] getContainerCPUInfoById"); 95 | }); 96 | } 97 | 98 | function getContainerRAMInfoById(id) { 99 | var host = window.location.origin; 100 | var socket = io.connect(host); 101 | socket.emit('getSysInfo', id); 102 | socket.on(id, (data) => { 103 | var json = JSON.parse(data); 104 | if (json.memory_stats.usage) { 105 | var tmp = ((json.memory_stats.usage / json.memory_stats.limit) * 100).toFixed(2) 106 | $('.container-ram[container-id=' + id + ']').text(tmp + ' %'); 107 | } 108 | }); 109 | socket.on('end', (status) => { 110 | console.log("[END] getContainerRAMInfoById"); 111 | }); 112 | } 113 | 114 | function logs() { 115 | Terminal.applyAddon(attach); 116 | Terminal.applyAddon(fit); 117 | var term = new Terminal({ 118 | useStyle: true, 119 | convertEol: true, 120 | screenKeys: false, 121 | cursorBlink: false, 122 | visualBell: false, 123 | colors: Terminal.xtermColors 124 | }); 125 | 126 | term.open(document.getElementById('terminal')); 127 | term.fit(); 128 | var id = window.location.pathname.split('/')[3]; 129 | var host = window.location.origin; 130 | var socket = io.connect(host); 131 | socket.emit('attach', id, $('#terminal').width(), $('#terminal').height()); 132 | 133 | socket.on('show', (data) => { 134 | term.write(data); 135 | }); 136 | 137 | socket.on('end', (status) => { 138 | socket.disconnect(); 139 | }); 140 | } 141 | 142 | function pullIamges() { 143 | Terminal.applyAddon(attach); 144 | Terminal.applyAddon(fit); 145 | var term = new Terminal({ 146 | useStyle: true, 147 | convertEol: true, 148 | screenKeys: false, 149 | cursorBlink: false, 150 | visualBell: false, 151 | colors: Terminal.xtermColors 152 | }); 153 | 154 | term.open(document.getElementById('terminal')); 155 | term.fit(); 156 | var imagesName = $('#imageName').val(); 157 | var version = $('#imageVersionName').val(); 158 | if (version) { 159 | imagesName = imagesName + ':' + version; 160 | } else { 161 | imagesName = imagesName + ':latest'; 162 | } 163 | var host = window.location.origin; 164 | var socket = io.connect(host); 165 | socket.emit('pull', imagesName, $('#terminal').width(), $('#terminal').height()); 166 | socket.on('show', (data) => { 167 | term.write(data); 168 | }); 169 | 170 | socket.on('end', (status) => { 171 | socket.disconnect(); 172 | location.reload(); 173 | }); 174 | } 175 | 176 | // ref https://github.com/moby/moby/issues/29306 177 | function calculateCPUPercentUnix(json) { 178 | var previousCPU = json.precpu_stats.cpu_usage.total_usage; 179 | var previousSystem = json.precpu_stats.system_cpu_usage; 180 | var cpuPercent = 0.0 181 | var cpuDelta = parseInt(json.cpu_stats.cpu_usage.total_usage) - parseInt(previousCPU) 182 | var systemDelta = parseInt(json.cpu_stats.system_cpu_usage) - parseInt(previousSystem) 183 | if (systemDelta > 0.0 && cpuDelta > 0.0) { 184 | cpuPercent = (cpuDelta / systemDelta) * parseInt(json.cpu_stats.cpu_usage.percpu_usage.length) * 100.0 185 | } 186 | return new Number(cpuPercent).toFixed(2); 187 | } 188 | 189 | function loading() { 190 | $('a.btn').on('click', function () { 191 | var $btn = $(this).button('loading'); 192 | }); 193 | $('#create').on('click', function () { 194 | var $btn = $(this).button('loading'); 195 | }) 196 | $('#pullImage').on('click', function () { 197 | var $btn = $(this).button('loading'); 198 | }) 199 | } -------------------------------------------------------------------------------- /public/js/bootstrap-select.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap-select v1.12.4 (http://silviomoreto.github.io/bootstrap-select) 3 | * 4 | * Copyright 2013-2017 bootstrap-select 5 | * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) 6 | */ 7 | !function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function(a){!function(a){"use strict";function b(b){var c=[{re:/[\xC0-\xC6]/g,ch:"A"},{re:/[\xE0-\xE6]/g,ch:"a"},{re:/[\xC8-\xCB]/g,ch:"E"},{re:/[\xE8-\xEB]/g,ch:"e"},{re:/[\xCC-\xCF]/g,ch:"I"},{re:/[\xEC-\xEF]/g,ch:"i"},{re:/[\xD2-\xD6]/g,ch:"O"},{re:/[\xF2-\xF6]/g,ch:"o"},{re:/[\xD9-\xDC]/g,ch:"U"},{re:/[\xF9-\xFC]/g,ch:"u"},{re:/[\xC7-\xE7]/g,ch:"c"},{re:/[\xD1]/g,ch:"N"},{re:/[\xF1]/g,ch:"n"}];return a.each(c,function(){b=b?b.replace(this.re,this.ch):""}),b}function c(b){var c=arguments,d=b;[].shift.apply(c);var e,f=this.each(function(){var b=a(this);if(b.is("select")){var f=b.data("selectpicker"),g="object"==typeof d&&d;if(f){if(g)for(var h in g)g.hasOwnProperty(h)&&(f.options[h]=g[h])}else{var i=a.extend({},l.DEFAULTS,a.fn.selectpicker.defaults||{},b.data(),g);i.template=a.extend({},l.DEFAULTS.template,a.fn.selectpicker.defaults?a.fn.selectpicker.defaults.template:{},b.data().template,g.template),b.data("selectpicker",f=new l(this,i))}"string"==typeof d&&(e=f[d]instanceof Function?f[d].apply(f,c):f.options[d])}});return"undefined"!=typeof e?e:f}String.prototype.includes||!function(){var a={}.toString,b=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),c="".indexOf,d=function(b){if(null==this)throw new TypeError;var d=String(this);if(b&&"[object RegExp]"==a.call(b))throw new TypeError;var e=d.length,f=String(b),g=f.length,h=arguments.length>1?arguments[1]:void 0,i=h?Number(h):0;i!=i&&(i=0);var j=Math.min(Math.max(i,0),e);return!(g+j>e)&&c.call(d,f,i)!=-1};b?b(String.prototype,"includes",{value:d,configurable:!0,writable:!0}):String.prototype.includes=d}(),String.prototype.startsWith||!function(){var a=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),b={}.toString,c=function(a){if(null==this)throw new TypeError;var c=String(this);if(a&&"[object RegExp]"==b.call(a))throw new TypeError;var d=c.length,e=String(a),f=e.length,g=arguments.length>1?arguments[1]:void 0,h=g?Number(g):0;h!=h&&(h=0);var i=Math.min(Math.max(h,0),d);if(f+i>d)return!1;for(var j=-1;++j":">",'"':""","'":"'","`":"`"},h={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},i=function(a){var b=function(b){return a[b]},c="(?:"+Object.keys(a).join("|")+")",d=RegExp(c),e=RegExp(c,"g");return function(a){return a=null==a?"":""+a,d.test(a)?a.replace(e,b):a}},j=i(g),k=i(h),l=function(b,c){d.useDefault||(a.valHooks.select.set=d._set,d.useDefault=!0),this.$element=a(b),this.$newElement=null,this.$button=null,this.$menu=null,this.$lis=null,this.options=c,null===this.options.title&&(this.options.title=this.$element.attr("title"));var e=this.options.windowPadding;"number"==typeof e&&(this.options.windowPadding=[e,e,e,e]),this.val=l.prototype.val,this.render=l.prototype.render,this.refresh=l.prototype.refresh,this.setStyle=l.prototype.setStyle,this.selectAll=l.prototype.selectAll,this.deselectAll=l.prototype.deselectAll,this.destroy=l.prototype.destroy,this.remove=l.prototype.remove,this.show=l.prototype.show,this.hide=l.prototype.hide,this.init()};l.VERSION="1.12.4",l.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(a,b){return 1==a?"{0} item selected":"{0} items selected"},maxOptionsText:function(a,b){return[1==a?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==b?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:"btn-default",size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:"glyphicon",tickIcon:"glyphicon-ok",showTick:!1,template:{caret:''},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0},l.prototype={constructor:l,init:function(){var b=this,c=this.$element.attr("id");this.$element.addClass("bs-select-hidden"),this.liObj={},this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$newElement=this.createView(),this.$element.after(this.$newElement).appendTo(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(".dropdown-menu"),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.$element.removeClass("bs-select-hidden"),this.options.dropdownAlignRight===!0&&this.$menu.addClass("dropdown-menu-right"),"undefined"!=typeof c&&(this.$button.attr("data-id",c),a('label[for="'+c+'"]').click(function(a){a.preventDefault(),b.$button.focus()})),this.checkDisabled(),this.clickListener(),this.options.liveSearch&&this.liveSearchListener(),this.render(),this.setStyle(),this.setWidth(),this.options.container&&this.selectPosition(),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(a){b.$menuInner.attr("aria-expanded",!1),b.$element.trigger("hide.bs.select",a)},"hidden.bs.dropdown":function(a){b.$element.trigger("hidden.bs.select",a)},"show.bs.dropdown":function(a){b.$menuInner.attr("aria-expanded",!0),b.$element.trigger("show.bs.select",a)},"shown.bs.dropdown":function(a){b.$element.trigger("shown.bs.select",a)}}),b.$element[0].hasAttribute("required")&&this.$element.on("invalid",function(){b.$button.addClass("bs-invalid"),b.$element.on({"focus.bs.select":function(){b.$button.focus(),b.$element.off("focus.bs.select")},"shown.bs.select":function(){b.$element.val(b.$element.val()).off("shown.bs.select")},"rendered.bs.select":function(){this.validity.valid&&b.$button.removeClass("bs-invalid"),b.$element.off("rendered.bs.select")}}),b.$button.on("blur.bs.select",function(){b.$element.focus().blur(),b.$button.off("blur.bs.select")})}),setTimeout(function(){b.$element.trigger("loaded.bs.select")})},createDropdown:function(){var b=this.multiple||this.options.showTick?" show-tick":"",c=this.$element.parent().hasClass("input-group")?" input-group-btn":"",d=this.autofocus?" autofocus":"",e=this.options.header?'×'+this.options.header+"":"",f=this.options.liveSearch?'':"",g=this.multiple&&this.options.actionsBox?''+this.options.selectAllText+''+this.options.deselectAllText+"":"",h=this.multiple&&this.options.doneButton?''+this.options.doneButtonText+"":"",i=' '+this.options.template.caret+''+e+f+g+''+h+"";return a(i)},createView:function(){var a=this.createDropdown(),b=this.createLi();return a.find("ul")[0].innerHTML=b,a},reloadLi:function(){var a=this.createLi();this.$menuInner[0].innerHTML=a},createLi:function(){var c=this,d=[],e=0,f=document.createElement("option"),g=-1,h=function(a,b,c,d){return""+a+""},i=function(d,e,f,g){return''+d+''};if(this.options.title&&!this.multiple&&(g--,!this.$element.find(".bs-title-option").length)){var k=this.$element[0];f.className="bs-title-option",f.innerHTML=this.options.title,f.value="",k.insertBefore(f,k.firstChild);var l=a(k.options[k.selectedIndex]);void 0===l.attr("selected")&&void 0===this.$element.data("selected")&&(f.selected=!0)}var m=this.$element.find("option");return m.each(function(b){var f=a(this);if(g++,!f.hasClass("bs-title-option")){var k,l=this.className||"",n=j(this.style.cssText),o=f.data("content")?f.data("content"):f.html(),p=f.data("tokens")?f.data("tokens"):null,q="undefined"!=typeof f.data("subtext")?''+f.data("subtext")+"":"",r="undefined"!=typeof f.data("icon")?' ':"",s=f.parent(),t="OPTGROUP"===s[0].tagName,u=t&&s[0].disabled,v=this.disabled||u;if(""!==r&&v&&(r=""+r+""),c.options.hideDisabled&&(v&&!t||u))return k=f.data("prevHiddenIndex"),f.next().data("prevHiddenIndex",void 0!==k?k:b),void g--;if(f.data("content")||(o=r+''+o+q+""),t&&f.data("divider")!==!0){if(c.options.hideDisabled&&v){if(void 0===s.data("allOptionsDisabled")){var w=s.children();s.data("allOptionsDisabled",w.filter(":disabled").length===w.length)}if(s.data("allOptionsDisabled"))return void g--}var x=" "+s[0].className||"";if(0===f.index()){e+=1;var y=s[0].label,z="undefined"!=typeof s.data("subtext")?''+s.data("subtext")+"":"",A=s.data("icon")?' ':"";y=A+''+j(y)+z+"",0!==b&&d.length>0&&(g++,d.push(h("",null,"divider",e+"div"))),g++,d.push(h(y,null,"dropdown-header"+x,e))}if(c.options.hideDisabled&&v)return void g--;d.push(h(i(o,"opt "+l+x,n,p),b,"",e))}else if(f.data("divider")===!0)d.push(h("",b,"divider"));else if(f.data("hidden")===!0)k=f.data("prevHiddenIndex"),f.next().data("prevHiddenIndex",void 0!==k?k:b),d.push(h(i(o,l,n,p),b,"hidden is-hidden"));else{var B=this.previousElementSibling&&"OPTGROUP"===this.previousElementSibling.tagName;if(!B&&c.options.hideDisabled&&(k=f.data("prevHiddenIndex"),void 0!==k)){var C=m.eq(k)[0].previousElementSibling;C&&"OPTGROUP"===C.tagName&&!C.disabled&&(B=!0)}B&&(g++,d.push(h("",null,"divider",e+"div"))),d.push(h(i(o,l,n,p),b))}c.liObj[b]=g}}),this.multiple||0!==this.$element.find("option:selected").length||this.options.title||this.$element.find("option").eq(0).prop("selected",!0).attr("selected","selected"),d.join("")},findLis:function(){return null==this.$lis&&(this.$lis=this.$menu.find("li")),this.$lis},render:function(b){var c,d=this,e=this.$element.find("option");b!==!1&&e.each(function(a){var b=d.findLis().eq(d.liObj[a]);d.setDisabled(a,this.disabled||"OPTGROUP"===this.parentNode.tagName&&this.parentNode.disabled,b),d.setSelected(a,this.selected,b)}),this.togglePlaceholder(),this.tabIndex();var f=e.map(function(){if(this.selected){if(d.options.hideDisabled&&(this.disabled||"OPTGROUP"===this.parentNode.tagName&&this.parentNode.disabled))return;var b,c=a(this),e=c.data("icon")&&d.options.showIcon?' ':"";return b=d.options.showSubtext&&c.data("subtext")&&!d.multiple?' '+c.data("subtext")+"":"","undefined"!=typeof c.attr("title")?c.attr("title"):c.data("content")&&d.options.showContent?c.data("content").toString():e+c.html()+b}}).toArray(),g=this.multiple?f.join(this.options.multipleSeparator):f[0];if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var h=this.options.selectedTextFormat.split(">");if(h.length>1&&f.length>h[1]||1==h.length&&f.length>=2){c=this.options.hideDisabled?", [disabled]":"";var i=e.not('[data-divider="true"], [data-hidden="true"]'+c).length,j="function"==typeof this.options.countSelectedText?this.options.countSelectedText(f.length,i):this.options.countSelectedText;g=j.replace("{0}",f.length.toString()).replace("{1}",i.toString())}}void 0==this.options.title&&(this.options.title=this.$element.attr("title")),"static"==this.options.selectedTextFormat&&(g=this.options.title),g||(g="undefined"!=typeof this.options.title?this.options.title:this.options.noneSelectedText),this.$button.attr("title",k(a.trim(g.replace(/<[^>]*>?/g,"")))),this.$button.children(".filter-option").html(g),this.$element.trigger("rendered.bs.select")},setStyle:function(a,b){this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,""));var c=a?a:this.options.style;"add"==b?this.$button.addClass(c):"remove"==b?this.$button.removeClass(c):(this.$button.removeClass(this.options.style),this.$button.addClass(c))},liHeight:function(b){if(b||this.options.size!==!1&&!this.sizeInfo){var c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),f=document.createElement("li"),g=document.createElement("li"),h=document.createElement("a"),i=document.createElement("span"),j=this.options.header&&this.$menu.find(".popover-title").length>0?this.$menu.find(".popover-title")[0].cloneNode(!0):null,k=this.options.liveSearch?document.createElement("div"):null,l=this.options.actionsBox&&this.multiple&&this.$menu.find(".bs-actionsbox").length>0?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,m=this.options.doneButton&&this.multiple&&this.$menu.find(".bs-donebutton").length>0?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null;if(i.className="text",c.className=this.$menu[0].parentNode.className+" open",d.className="dropdown-menu open",e.className="dropdown-menu inner",f.className="divider",i.appendChild(document.createTextNode("Inner text")),h.appendChild(i),g.appendChild(h),e.appendChild(g),e.appendChild(f),j&&d.appendChild(j),k){var n=document.createElement("input");k.className="bs-searchbox",n.className="form-control",k.appendChild(n),d.appendChild(k)}l&&d.appendChild(l),d.appendChild(e),m&&d.appendChild(m),c.appendChild(d),document.body.appendChild(c);var o=h.offsetHeight,p=j?j.offsetHeight:0,q=k?k.offsetHeight:0,r=l?l.offsetHeight:0,s=m?m.offsetHeight:0,t=a(f).outerHeight(!0),u="function"==typeof getComputedStyle&&getComputedStyle(d),v=u?null:a(d),w={vert:parseInt(u?u.paddingTop:v.css("paddingTop"))+parseInt(u?u.paddingBottom:v.css("paddingBottom"))+parseInt(u?u.borderTopWidth:v.css("borderTopWidth"))+parseInt(u?u.borderBottomWidth:v.css("borderBottomWidth")),horiz:parseInt(u?u.paddingLeft:v.css("paddingLeft"))+parseInt(u?u.paddingRight:v.css("paddingRight"))+parseInt(u?u.borderLeftWidth:v.css("borderLeftWidth"))+parseInt(u?u.borderRightWidth:v.css("borderRightWidth"))},x={vert:w.vert+parseInt(u?u.marginTop:v.css("marginTop"))+parseInt(u?u.marginBottom:v.css("marginBottom"))+2,horiz:w.horiz+parseInt(u?u.marginLeft:v.css("marginLeft"))+parseInt(u?u.marginRight:v.css("marginRight"))+2};document.body.removeChild(c),this.sizeInfo={liHeight:o,headerHeight:p,searchHeight:q,actionsHeight:r,doneButtonHeight:s,dividerHeight:t,menuPadding:w,menuExtras:x}}},setSize:function(){if(this.findLis(),this.liHeight(),this.options.header&&this.$menu.css("padding-top",0),this.options.size!==!1){var b,c,d,e,f,g,h,i,j=this,k=this.$menu,l=this.$menuInner,m=a(window),n=this.$newElement[0].offsetHeight,o=this.$newElement[0].offsetWidth,p=this.sizeInfo.liHeight,q=this.sizeInfo.headerHeight,r=this.sizeInfo.searchHeight,s=this.sizeInfo.actionsHeight,t=this.sizeInfo.doneButtonHeight,u=this.sizeInfo.dividerHeight,v=this.sizeInfo.menuPadding,w=this.sizeInfo.menuExtras,x=this.options.hideDisabled?".disabled":"",y=function(){var b,c=j.$newElement.offset(),d=a(j.options.container);j.options.container&&!d.is("body")?(b=d.offset(),b.top+=parseInt(d.css("borderTopWidth")),b.left+=parseInt(d.css("borderLeftWidth"))):b={top:0,left:0};var e=j.options.windowPadding;f=c.top-b.top-m.scrollTop(),g=m.height()-f-n-b.top-e[2],h=c.left-b.left-m.scrollLeft(),i=m.width()-h-o-b.left-e[1],f-=e[0],h-=e[3]};if(y(),"auto"===this.options.size){var z=function(){var m,n=function(b,c){return function(d){return c?d.classList?d.classList.contains(b):a(d).hasClass(b):!(d.classList?d.classList.contains(b):a(d).hasClass(b))}},u=j.$menuInner[0].getElementsByTagName("li"),x=Array.prototype.filter?Array.prototype.filter.call(u,n("hidden",!1)):j.$lis.not(".hidden"),z=Array.prototype.filter?Array.prototype.filter.call(x,n("dropdown-header",!0)):x.filter(".dropdown-header");y(),b=g-w.vert,c=i-w.horiz,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height"),k.data("width")||k.data("width",k.width()),e=k.data("width")):(d=k.height(),e=k.width()),j.options.dropupAuto&&j.$newElement.toggleClass("dropup",f>g&&b-w.verti&&c-w.horiz3?3*p+w.vert-2:0,k.css({"max-height":b+"px",overflow:"hidden","min-height":m+q+r+s+t+"px"}),l.css({"max-height":b-q-r-s-t-v.vert+"px","overflow-y":"auto","min-height":Math.max(m-v.vert,0)+"px"})};z(),this.$searchbox.off("input.getSize propertychange.getSize").on("input.getSize propertychange.getSize",z),m.off("resize.getSize scroll.getSize").on("resize.getSize scroll.getSize",z)}else if(this.options.size&&"auto"!=this.options.size&&this.$lis.not(x).length>this.options.size){var A=this.$lis.not(".divider").not(x).children().slice(0,this.options.size).last().parent().index(),B=this.$lis.slice(0,A+1).filter(".divider").length;b=p*this.options.size+B*u+v.vert,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height")):d=k.height(),j.options.dropupAuto&&this.$newElement.toggleClass("dropup",f>g&&b-w.vert');var b,c,d,e=this,f=a(this.options.container),g=function(a){e.$bsContainer.addClass(a.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass("dropup",a.hasClass("dropup")),b=a.offset(),f.is("body")?c={top:0,left:0}:(c=f.offset(),c.top+=parseInt(f.css("borderTopWidth"))-f.scrollTop(),c.left+=parseInt(f.css("borderLeftWidth"))-f.scrollLeft()),d=a.hasClass("dropup")?0:a[0].offsetHeight,e.$bsContainer.css({top:b.top-c.top+d,left:b.left-c.left,width:a[0].offsetWidth})};this.$button.on("click",function(){var b=a(this);e.isDisabled()||(g(e.$newElement),e.$bsContainer.appendTo(e.options.container).toggleClass("open",!b.hasClass("open")).append(e.$menu))}),a(window).on("resize scroll",function(){g(e.$newElement)}),this.$element.on("hide.bs.select",function(){e.$menu.data("height",e.$menu.height()),e.$bsContainer.detach()})},setSelected:function(a,b,c){c||(this.togglePlaceholder(),c=this.findLis().eq(this.liObj[a])),c.toggleClass("selected",b).find("a").attr("aria-selected",b)},setDisabled:function(a,b,c){c||(c=this.findLis().eq(this.liObj[a])),b?c.addClass("disabled").children("a").attr("href","#").attr("tabindex",-1).attr("aria-disabled",!0):c.removeClass("disabled").children("a").removeAttr("href").attr("tabindex",0).attr("aria-disabled",!1)},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){var a=this;this.isDisabled()?(this.$newElement.addClass("disabled"),this.$button.addClass("disabled").attr("tabindex",-1).attr("aria-disabled",!0)):(this.$button.hasClass("disabled")&&(this.$newElement.removeClass("disabled"),this.$button.removeClass("disabled").attr("aria-disabled",!1)),this.$button.attr("tabindex")!=-1||this.$element.data("tabindex")||this.$button.removeAttr("tabindex")),this.$button.click(function(){return!a.isDisabled()})},togglePlaceholder:function(){var a=this.$element.val();this.$button.toggleClass("bs-placeholder",null===a||""===a||a.constructor===Array&&0===a.length)},tabIndex:function(){this.$element.data("tabindex")!==this.$element.attr("tabindex")&&this.$element.attr("tabindex")!==-98&&"-98"!==this.$element.attr("tabindex")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex"))),this.$element.attr("tabindex",-98)},clickListener:function(){var b=this,c=a(document);c.data("spaceSelect",!1),this.$button.on("keyup",function(a){/(32)/.test(a.keyCode.toString(10))&&c.data("spaceSelect")&&(a.preventDefault(),c.data("spaceSelect",!1))}),this.$button.on("click",function(){b.setSize()}),this.$element.on("shown.bs.select",function(){if(b.options.liveSearch||b.multiple){if(!b.multiple){var a=b.liObj[b.$element[0].selectedIndex];if("number"!=typeof a||b.options.size===!1)return;var c=b.$lis.eq(a)[0].offsetTop-b.$menuInner[0].offsetTop;c=c-b.$menuInner[0].offsetHeight/2+b.sizeInfo.liHeight/2,b.$menuInner[0].scrollTop=c}}else b.$menuInner.find(".selected a").focus()}),this.$menuInner.on("click","li a",function(c){var d=a(this),f=d.parent().data("originalIndex"),g=b.$element.val(),h=b.$element.prop("selectedIndex"),i=!0;if(b.multiple&&1!==b.options.maxOptions&&c.stopPropagation(),c.preventDefault(),!b.isDisabled()&&!d.parent().hasClass("disabled")){var j=b.$element.find("option"),k=j.eq(f),l=k.prop("selected"),m=k.parent("optgroup"),n=b.options.maxOptions,o=m.data("maxOptions")||!1;if(b.multiple){if(k.prop("selected",!l),b.setSelected(f,!l),d.blur(),n!==!1||o!==!1){var p=n');t[2]&&(u=u.replace("{var}",t[2][n>1?0:1]),v=v.replace("{var}",t[2][o>1?0:1])),k.prop("selected",!1),b.$menu.append(w),n&&p&&(w.append(a(""+u+"")),i=!1,b.$element.trigger("maxReached.bs.select")),o&&q&&(w.append(a(""+v+"")),i=!1,b.$element.trigger("maxReachedGrp.bs.select")),setTimeout(function(){b.setSelected(f,!1)},10),w.delay(750).fadeOut(300,function(){a(this).remove()})}}}else j.prop("selected",!1),k.prop("selected",!0),b.$menuInner.find(".selected").removeClass("selected").find("a").attr("aria-selected",!1),b.setSelected(f,!0);!b.multiple||b.multiple&&1===b.options.maxOptions?b.$button.focus():b.options.liveSearch&&b.$searchbox.focus(),i&&(g!=b.$element.val()&&b.multiple||h!=b.$element.prop("selectedIndex")&&!b.multiple)&&(e=[f,k.prop("selected"),l],b.$element.triggerNative("change"))}}),this.$menu.on("click","li.disabled a, .popover-title, .popover-title :not(.close)",function(c){c.currentTarget==this&&(c.preventDefault(),c.stopPropagation(),b.options.liveSearch&&!a(c.target).hasClass("close")?b.$searchbox.focus():b.$button.focus())}),this.$menuInner.on("click",".divider, .dropdown-header",function(a){a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus()}),this.$menu.on("click",".popover-title .close",function(){b.$button.click()}),this.$searchbox.on("click",function(a){a.stopPropagation()}),this.$menu.on("click",".actions-btn",function(c){b.options.liveSearch?b.$searchbox.focus():b.$button.focus(),c.preventDefault(),c.stopPropagation(),a(this).hasClass("bs-select-all")?b.selectAll():b.deselectAll()}),this.$element.change(function(){b.render(!1),b.$element.trigger("changed.bs.select",e),e=null})},liveSearchListener:function(){var c=this,d=a('');this.$button.on("click.dropdown.data-api",function(){c.$menuInner.find(".active").removeClass("active"),c.$searchbox.val()&&(c.$searchbox.val(""),c.$lis.not(".is-hidden").removeClass("hidden"),d.parent().length&&d.remove()),c.multiple||c.$menuInner.find(".selected").addClass("active"),setTimeout(function(){c.$searchbox.focus()},10)}),this.$searchbox.on("click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api",function(a){a.stopPropagation()}),this.$searchbox.on("input propertychange",function(){if(c.$lis.not(".is-hidden").removeClass("hidden"),c.$lis.filter(".active").removeClass("active"),d.remove(),c.$searchbox.val()){var e,f=c.$lis.not(".is-hidden, .divider, .dropdown-header");if(e=c.options.liveSearchNormalize?f.not(":a"+c._searchStyle()+'("'+b(c.$searchbox.val())+'")'):f.not(":"+c._searchStyle()+'("'+c.$searchbox.val()+'")'),e.length===f.length)d.html(c.options.noneResultsText.replace("{0}",'"'+j(c.$searchbox.val())+'"')),c.$menuInner.append(d),c.$lis.addClass("hidden");else{e.addClass("hidden");var g,h=c.$lis.not(".hidden");h.each(function(b){var c=a(this);c.hasClass("divider")?void 0===g?c.addClass("hidden"):(g&&g.addClass("hidden"),g=c):c.hasClass("dropdown-header")&&h.eq(b+1).data("optgroup")!==c.data("optgroup")?c.addClass("hidden"):g=null}),g&&g.addClass("hidden"),f.not(".hidden").first().addClass("active"),c.$menuInner.scrollTop(0)}}})},_searchStyle:function(){var a={begins:"ibegins",startsWith:"ibegins"};return a[this.options.liveSearchStyle]||"icontains"},val:function(a){return"undefined"!=typeof a?(this.$element.val(a),this.render(),this.$element):this.$element.val()},changeAll:function(b){if(this.multiple){"undefined"==typeof b&&(b=!0),this.findLis();var c=this.$element.find("option"),d=this.$lis.not(".divider, .dropdown-header, .disabled, .hidden"),e=d.length,f=[];if(b){if(d.filter(".selected").length===d.length)return}else if(0===d.filter(".selected").length)return;d.toggleClass("selected",b);for(var g=0;g=48&&b.keyCode<=57||b.keyCode>=96&&b.keyCode<=105||b.keyCode>=65&&b.keyCode<=90))return i.options.container?i.$button.trigger("click"):(i.setSize(),i.$menu.parent().addClass("open"),f=!0),void i.$searchbox.focus();if(i.options.liveSearch&&/(^9$|27)/.test(b.keyCode.toString(10))&&f&&(b.preventDefault(),b.stopPropagation(),i.$menuInner.click(),i.$button.focus()),/(38|40)/.test(b.keyCode.toString(10))){if(c=i.$lis.filter(j),!c.length)return;d=i.options.liveSearch?c.index(c.filter(".active")):c.index(c.find("a").filter(":focus").parent()),e=i.$menuInner.data("prevIndex"),38==b.keyCode?(!i.options.liveSearch&&d!=e||d==-1||d--,d<0&&(d+=c.length)):40==b.keyCode&&((i.options.liveSearch||d==e)&&d++,d%=c.length),i.$menuInner.data("prevIndex",d),i.options.liveSearch?(b.preventDefault(),g.hasClass("dropdown-toggle")||(c.removeClass("active").eq(d).addClass("active").children("a").focus(),g.focus())):c.eq(d).children("a").focus()}else if(!g.is("input")){var l,m,n=[];c=i.$lis.filter(j),c.each(function(c){a.trim(a(this).children("a").text().toLowerCase()).substring(0,1)==k[b.keyCode]&&n.push(c)}),l=a(document).data("keycount"),l++,a(document).data("keycount",l),m=a.trim(a(":focus").text().toLowerCase()).substring(0,1),m!=k[b.keyCode]?(l=1,a(document).data("keycount",l)):l>=n.length&&(a(document).data("keycount",0),l>n.length&&(l=1)),c.eq(n[l-1]).children("a").focus()}if((/(13|32)/.test(b.keyCode.toString(10))||/(^9$)/.test(b.keyCode.toString(10))&&i.options.selectOnTab)&&f){if(/(32)/.test(b.keyCode.toString(10))||b.preventDefault(),i.options.liveSearch)/(32)/.test(b.keyCode.toString(10))||(i.$menuInner.find(".active a").click(),g.focus());else{var o=a(":focus");o.click(),o.focus(),b.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(b.keyCode.toString(10))&&f&&(i.multiple||i.options.liveSearch)||/(27)/.test(b.keyCode.toString(10))&&!f)&&(i.$menu.parent().removeClass("open"),i.options.container&&i.$newElement.removeClass("open"),i.$button.focus())},mobile:function(){this.$element.addClass("mobile-device")},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(), 8 | this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(".bs.select").removeData("selectpicker").removeClass("bs-select-hidden selectpicker")}};var m=a.fn.selectpicker;a.fn.selectpicker=c,a.fn.selectpicker.Constructor=l,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=m,this},a(document).data("keycount",0).on("keydown.bs.select",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',l.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);c.call(b,b.data())})})}(a)}); 9 | //# sourceMappingURL=bootstrap-select.js.map -------------------------------------------------------------------------------- /public/js/bootstrap3-typeahead.min.js: -------------------------------------------------------------------------------- 1 | (function(root,factory){"use strict";if(typeof module!=="undefined"&&module.exports){module.exports=factory(require("jquery"))}else if(typeof define==="function"&&define.amd){define(["jquery"],function($){return factory($)})}else{factory(root.jQuery)}})(this,function($){"use strict";var Typeahead=function(element,options){this.$element=$(element);this.options=$.extend({},$.fn.typeahead.defaults,options);this.matcher=this.options.matcher||this.matcher;this.sorter=this.options.sorter||this.sorter;this.select=this.options.select||this.select;this.autoSelect=typeof this.options.autoSelect=="boolean"?this.options.autoSelect:true;this.highlighter=this.options.highlighter||this.highlighter;this.render=this.options.render||this.render;this.updater=this.options.updater||this.updater;this.displayText=this.options.displayText||this.displayText;this.source=this.options.source;this.delay=this.options.delay;this.$menu=$(this.options.menu);this.$appendTo=this.options.appendTo?$(this.options.appendTo):null;this.fitToElement=typeof this.options.fitToElement=="boolean"?this.options.fitToElement:false;this.shown=false;this.listen();this.showHintOnFocus=typeof this.options.showHintOnFocus=="boolean"||this.options.showHintOnFocus==="all"?this.options.showHintOnFocus:false;this.afterSelect=this.options.afterSelect;this.addItem=false;this.value=this.$element.val()||this.$element.text()};Typeahead.prototype={constructor:Typeahead,select:function(){var val=this.$menu.find(".active").data("value");this.$element.data("active",val);if(this.autoSelect||val){var newVal=this.updater(val);if(!newVal){newVal=""}this.$element.val(this.displayText(newVal)||newVal).text(this.displayText(newVal)||newVal).change();this.afterSelect(newVal)}return this.hide()},updater:function(item){return item},setSource:function(source){this.source=source},show:function(){var pos=$.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});var scrollHeight=typeof this.options.scrollHeight=="function"?this.options.scrollHeight.call():this.options.scrollHeight;var element;if(this.shown){element=this.$menu}else if(this.$appendTo){element=this.$menu.appendTo(this.$appendTo);this.hasSameParent=this.$appendTo.is(this.$element.parent())}else{element=this.$menu.insertAfter(this.$element);this.hasSameParent=true}if(!this.hasSameParent){element.css("position","fixed");var offset=this.$element.offset();pos.top=offset.top;pos.left=offset.left}var dropup=$(element).parent().hasClass("dropup");var newTop=dropup?"auto":pos.top+pos.height+scrollHeight;var right=$(element).hasClass("dropdown-menu-right");var newLeft=right?"auto":pos.left;element.css({top:newTop,left:newLeft}).show();if(this.options.fitToElement===true){element.css("width",this.$element.outerWidth()+"px")}this.shown=true;return this},hide:function(){this.$menu.hide();this.shown=false;return this},lookup:function(query){var items;if(typeof query!="undefined"&&query!==null){this.query=query}else{this.query=this.$element.val()||this.$element.text()||""}if(this.query.length0){this.$element.data("active",items[0])}else{this.$element.data("active",null)}if(this.options.addItem){items.push(this.options.addItem)}if(this.options.items=="all"){return this.render(items).show()}else{return this.render(items.slice(0,this.options.items)).show()}},matcher:function(item){var it=this.displayText(item);return~it.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(items){var beginswith=[];var caseSensitive=[];var caseInsensitive=[];var item;while(item=items.shift()){var it=this.displayText(item);if(!it.toLowerCase().indexOf(this.query.toLowerCase()))beginswith.push(item);else if(~it.indexOf(this.query))caseSensitive.push(item);else caseInsensitive.push(item)}return beginswith.concat(caseSensitive,caseInsensitive)},highlighter:function(item){var html=$("");var query=this.query;var i=item.toLowerCase().indexOf(query.toLowerCase());var len=query.length;var leftPart;var middlePart;var rightPart;var strong;if(len===0){return html.text(item).html()}while(i>-1){leftPart=item.substr(0,i);middlePart=item.substr(i,len);rightPart=item.substr(i+len);strong=$("").text(middlePart);html.append(document.createTextNode(leftPart)).append(strong);item=rightPart;i=item.toLowerCase().indexOf(query.toLowerCase())}return html.append(document.createTextNode(item)).html()},render:function(items){var that=this;var self=this;var activeFound=false;var data=[];var _category=that.options.separator;$.each(items,function(key,value){if(key>0&&value[_category]!==items[key-1][_category]){data.push({__type:"divider"})}if(value[_category]&&(key===0||value[_category]!==items[key-1][_category])){data.push({__type:"category",name:value[_category]})}data.push(value)});items=$(data).map(function(i,item){if((item.__type||false)=="category"){return $(that.options.headerHtml).text(item.name)[0]}if((item.__type||false)=="divider"){return $(that.options.headerDivider)[0]}var text=self.displayText(item);i=$(that.options.item).data("value",item);i.find("a").html(that.highlighter(text,item));if(text==self.$element.val()){i.addClass("active");self.$element.data("active",item);activeFound=true}return i[0]});if(this.autoSelect&&!activeFound){items.filter(":not(.dropdown-header)").first().addClass("active");this.$element.data("active",items.first().data("value"))}this.$menu.html(items);return this},displayText:function(item){return typeof item!=="undefined"&&typeof item.name!="undefined"&&item.name||item},next:function(event){var active=this.$menu.find(".active").removeClass("active");var next=active.next();if(!next.length){next=$(this.$menu.find("li")[0])}next.addClass("active")},prev:function(event){var active=this.$menu.find(".active").removeClass("active");var prev=active.prev();if(!prev.length){prev=this.$menu.find("li").last()}prev.addClass("active")},listen:function(){this.$element.on("focus",$.proxy(this.focus,this)).on("blur",$.proxy(this.blur,this)).on("keypress",$.proxy(this.keypress,this)).on("input",$.proxy(this.input,this)).on("keyup",$.proxy(this.keyup,this));if(this.eventSupported("keydown")){this.$element.on("keydown",$.proxy(this.keydown,this))}this.$menu.on("click",$.proxy(this.click,this)).on("mouseenter","li",$.proxy(this.mouseenter,this)).on("mouseleave","li",$.proxy(this.mouseleave,this)).on("mousedown",$.proxy(this.mousedown,this))},destroy:function(){this.$element.data("typeahead",null);this.$element.data("active",null);this.$element.off("focus").off("blur").off("keypress").off("input").off("keyup");if(this.eventSupported("keydown")){this.$element.off("keydown")}this.$menu.remove();this.destroyed=true},eventSupported:function(eventName){var isSupported=eventName in this.$element;if(!isSupported){this.$element.setAttribute(eventName,"return;");isSupported=typeof this.$element[eventName]==="function"}return isSupported},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:if(e.shiftKey)return;e.preventDefault();this.prev();break;case 40:if(e.shiftKey)return;e.preventDefault();this.next();break}},keydown:function(e){this.suppressKeyPressRepeat=~$.inArray(e.keyCode,[40,38,9,13,27]);if(!this.shown&&e.keyCode==40){this.lookup()}else{this.move(e)}},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},input:function(e){var currentValue=this.$element.val()||this.$element.text();if(this.value!==currentValue){this.value=currentValue;this.lookup()}},keyup:function(e){if(this.destroyed){return}switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break}},focus:function(e){if(!this.focused){this.focused=true;if(this.options.showHintOnFocus&&this.skipShowHintOnFocus!==true){if(this.options.showHintOnFocus==="all"){this.lookup("")}else{this.lookup()}}}if(this.skipShowHintOnFocus){this.skipShowHintOnFocus=false}},blur:function(e){if(!this.mousedover&&!this.mouseddown&&this.shown){this.hide();this.focused=false}else if(this.mouseddown){this.skipShowHintOnFocus=true;this.$element.focus();this.mouseddown=false}},click:function(e){e.preventDefault();this.skipShowHintOnFocus=true;this.select();this.$element.focus();this.hide()},mouseenter:function(e){this.mousedover=true;this.$menu.find(".active").removeClass("active");$(e.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=false;if(!this.focused&&this.shown)this.hide()},mousedown:function(e){this.mouseddown=true;this.$menu.one("mouseup",function(e){this.mouseddown=false}.bind(this))}};var old=$.fn.typeahead;$.fn.typeahead=function(option){var arg=arguments;if(typeof option=="string"&&option=="getActive"){return this.data("active")}return this.each(function(){var $this=$(this);var data=$this.data("typeahead");var options=typeof option=="object"&&option;if(!data)$this.data("typeahead",data=new Typeahead(this,options));if(typeof option=="string"&&data[option]){if(arg.length>1){data[option].apply(data,Array.prototype.slice.call(arg,1))}else{data[option]()}}})};$.fn.typeahead.defaults={source:[],items:8,menu:'',item:'',minLength:1,scrollHeight:0,autoSelect:true,afterSelect:$.noop,addItem:false,delay:0,separator:"category",headerHtml:'',headerDivider:''};$.fn.typeahead.Constructor=Typeahead;$.fn.typeahead.noConflict=function(){$.fn.typeahead=old;return this};$(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var $this=$(this);if($this.data("typeahead"))return;$this.typeahead($this.data())})}); 2 | -------------------------------------------------------------------------------- /routes/api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const Docker = require('dockerode'); 4 | const docker = new Docker(); 5 | 6 | /* GET overview. */ 7 | router.get('/overview', (req, res, next) => { 8 | docker.info((err, info) => { 9 | if (err) { 10 | res.json({ 11 | msg: 'error', 12 | message: 'Docker is running ?', 13 | }); 14 | } else { 15 | res.json(info); 16 | } 17 | }); 18 | }); 19 | 20 | /** 21 | * containers list 22 | */ 23 | router.get('/containers', (req, res, next) => { 24 | docker.listContainers({all: true}, (err, containers) => { 25 | res.json(containers); 26 | }); 27 | }); 28 | 29 | router.get('/containers/start/:id', (req, res, next) => { 30 | const container = docker.getContainer(req.params.id); 31 | container.start((err, data) => { 32 | if (!err) { 33 | res.json({ 34 | code: 200, 35 | msg: 'OK', 36 | }); 37 | } else { 38 | res.json({ 39 | code: 400, 40 | msg: err.toString(), 41 | }); 42 | } 43 | }); 44 | }); 45 | 46 | router.get('/containers/stop/:id', (req, res, next) => { 47 | const container = docker.getContainer(req.params.id); 48 | container.stop((err, data) => { 49 | if (!err) { 50 | res.json({ 51 | code: 200, 52 | msg: 'OK', 53 | }); 54 | } else { 55 | res.json({ 56 | code: 400, 57 | msg: err.toString(), 58 | }); 59 | } 60 | }); 61 | }); 62 | 63 | router.get('/containers/remove/:id', (req, res, next) => { 64 | const container = docker.getContainer(req.params.id); 65 | container.remove({force: true}, (err, data) => { 66 | if (!err) { 67 | res.json({ 68 | code: 200, 69 | msg: 'OK', 70 | }); 71 | } else { 72 | res.json({ 73 | code: 400, 74 | msg: err.toString(), 75 | }); 76 | } 77 | }); 78 | }); 79 | 80 | router.get('/images', (req, res, next) => { 81 | docker.listImages(null, (err, listImages) => { 82 | if (err) { 83 | res.json(err); 84 | } else { 85 | res.json(listImages); 86 | } 87 | }); 88 | }); 89 | 90 | router.get('/images/remove/:id', (req, res, next) => { 91 | let imageId = req.params.id; 92 | if (imageId.indexOf(':') > 0) { 93 | imageId = imageId.split(':')[1]; 94 | } 95 | const image = docker.getImage(imageId); 96 | image.remove({force: true}, (err, data) => { 97 | if (err) { 98 | res.json(err); 99 | } else { 100 | res.json(data); 101 | } 102 | }); 103 | }); 104 | 105 | router.get('/search/:name', (req, res, next) => { 106 | const name = req.params.name; 107 | docker.searchImages({term: name}, (err, data) => { 108 | if (err) throw err; 109 | res.json(data); 110 | }); 111 | }); 112 | 113 | module.exports = router; 114 | -------------------------------------------------------------------------------- /routes/containers.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const Docker = require('dockerode'); 4 | const stream = require('stream'); 5 | 6 | const docker = new Docker(); 7 | const returnContainersRouter = (io) => { 8 | /* GET containers. */ 9 | router.get('/', (req, res, next) => { 10 | docker.listContainers({all: true}, (err, containers) => { 11 | res.locals.formatName = (str) => { 12 | return str[0].split('/')[1]; 13 | }; 14 | docker.listImages(null, (err, listImages) => { 15 | res.render('containers', 16 | { 17 | containers: containers, 18 | images: listImages, 19 | }); 20 | }); 21 | }); 22 | }); 23 | 24 | router.get('/start/:id', (req, res, next) => { 25 | const container = docker.getContainer(req.params.id); 26 | container.start(null, (err, data) => { 27 | res.redirect('/containers'); 28 | }); 29 | }); 30 | 31 | router.get('/stop/:id', (req, res, next) => { 32 | const container = docker.getContainer(req.params.id); 33 | container.stop(null, (err, data) => { 34 | res.redirect('/containers'); 35 | }); 36 | }); 37 | 38 | router.get('/remove/:id', (req, res, next) => { 39 | const container = docker.getContainer(req.params.id); 40 | container.remove({force: true}, (err, data) => { 41 | if (err) { 42 | res.render('error', {error: err, message: err.json.message}); 43 | } else { 44 | res.redirect('/containers'); 45 | } 46 | }); 47 | }); 48 | 49 | router.post('/create', (req, res, next) => { 50 | let options = { 51 | Image: req.body.containerImage, 52 | AttachStdin: false, 53 | AttachStdout: true, 54 | AttachStderr: true, 55 | Tty: false, 56 | HostConfig: { 57 | PortBindings: {}, 58 | }, 59 | }; 60 | 61 | // name 62 | if (req.body.containerName !== '') { 63 | options = { 64 | ...options, 65 | name: req.body.containerName, 66 | }; 67 | } 68 | 69 | // volume 70 | if (req.body.containerVolumeSource !== '' && 71 | req.body.containerVolumeDistination !== '') { 72 | const src = req.body.containerVolumeSource; 73 | const dis = req.body.containerVolumeDistination; 74 | options['Volumes'] = JSON.parse('{"' + dis + '": {}}'); 75 | options.HostConfig = { 76 | 'Binds': [src + ':' + dis], 77 | 'RestartPolicy': { 78 | 'Name': req.body.isAlways === 'on' ? 'always' : '', 79 | 'MaximumRetryCount': 5, 80 | }, 81 | }; 82 | } 83 | 84 | // restart policy 85 | options.HostConfig = { 86 | 'RestartPolicy': { 87 | 'Name': req.body.isAlways === 'on' ? 'on-failure' : '', 88 | 'MaximumRetryCount': 5, 89 | } 90 | }; 91 | 92 | // default ssh 93 | options.HostConfig.PortBindings = {'22/tcp': [{HostPort: ''}]} 94 | options.ExposedPorts = {'22/tcp': {}}; 95 | options.HostConfig.PortBindings['22/udp'] = [{HostPort: ""}] 96 | options.ExposedPorts['22/udp'] = {}; 97 | 98 | let srcPortString = req.body.containerPortSource 99 | if (srcPortString.indexOf("-") !== -1) { 100 | let startEnd = srcPortString.split("-") 101 | let start = startEnd[0] 102 | let end = startEnd[1] 103 | let portRange = Array(end - start + 1).fill().map((element, index) => index + parseInt(start)) 104 | portRange.forEach(function (port) { 105 | options.HostConfig.PortBindings[port + '/tcp'] = [{HostPort: ""}] 106 | options.ExposedPorts[port + '/tcp'] = {}; 107 | options.HostConfig.PortBindings[port + '/udp'] = [{HostPort: ""}] 108 | options.ExposedPorts[port + '/udp'] = {}; 109 | }) 110 | } else if (req.body.containerPortSource !== '' && 111 | req.body.containerPortDistination !== '') { 112 | const src = req.body.containerPortSource; 113 | const dis = req.body.containerPortDistination; 114 | 115 | options.HostConfig.PortBindings[src + '/tcp'] = [{HostPort: dis}] 116 | options.ExposedPorts[src + '/tcp'] = {}; 117 | options.HostConfig.PortBindings[src + '/udp'] = [{HostPort: dis}] 118 | options.ExposedPorts[src + '/udp'] = {}; 119 | } 120 | // setting ram limit 121 | // So if --memory="300m" and --memory-swap="1g", the container can use 300m of memory and 700m (1g - 300m) swap. 122 | if (req.body.containerMem!== '') { 123 | options.HostConfig.Memory = req.body.containerMem * 1024 * 1024; 124 | } 125 | // limit cpu 126 | if (req.body.containerCPU !== '') { 127 | // 1 core=1000000000 10**9 128 | options.HostConfig.NanoCpus = parseFloat(req.body.containerCPU) * 10 ** 9; 129 | } 130 | console.log(req.body); 131 | console.log(JSON.stringify(options, null, 4)); 132 | // options.HostConfig.PortBindings={ '22/tcp': [ { HostPort: '' } ],'22/udp': [ { HostPort: '' } ] } 133 | // options.ExposedPorts = { '22/tcp': {} , '22/udp': {} }; 134 | 135 | if (req.body.containerCmd !== '') { 136 | options.Cmd = ['/bin/sh', '-c', req.body.containerCmd]; 137 | // console.log(options) 138 | docker.createContainer(options, (err, container) => { 139 | if (err) throw err; 140 | container.start((err, data) => { 141 | res.redirect('/containers/logs/' + container.id); 142 | }); 143 | }); 144 | } else { 145 | const runOpt = { 146 | Image: req.body.containerImage, 147 | AttachStdin: true, 148 | AttachStdout: true, 149 | AttachStderr: true, 150 | Tty: true, 151 | //Cmd: ['/bin/sh'], 152 | OpenStdin: false, 153 | StdinOnce: false, 154 | ...options, 155 | }; 156 | docker.createContainer(runOpt).then(function (container) { 157 | return container.start(); 158 | }).then((container) => { 159 | res.redirect('/containers'); 160 | }); 161 | } 162 | 163 | }); 164 | 165 | router.get('/console/:id', (req, res, next) => { 166 | res.render('terminal'); 167 | }); 168 | 169 | router.get('/logs/:id', (req, res, next) => { 170 | res.render('logs'); 171 | }); 172 | router.get('/inspect/:id', (req, res, next) => { 173 | const container = docker.getContainer(req.params.id); 174 | 175 | container.inspect(function (err, data) { 176 | res.json(data); 177 | // res.json({"data":"v"}); 178 | }); 179 | }); 180 | 181 | io.on('connection', (socket) => { 182 | socket.on('exec', (id, w, h) => { 183 | const container = docker.getContainer(id); 184 | let cmd = { 185 | 'AttachStdout': true, 186 | 'AttachStderr': true, 187 | 'AttachStdin': true, 188 | 'Tty': true, 189 | Cmd: ['/bin/sh'], 190 | }; 191 | container.exec(cmd, (err, exec) => { 192 | let options = { 193 | 'Tty': true, 194 | stream: true, 195 | stdin: true, 196 | stdout: true, 197 | stderr: true, 198 | // fix vim 199 | hijack: true, 200 | }; 201 | 202 | container.wait((err, data) => { 203 | socket.emit('end', 'ended'); 204 | }); 205 | 206 | if (err) { 207 | return; 208 | } 209 | 210 | exec.start(options, (err, stream) => { 211 | const dimensions = {h, w}; 212 | if (dimensions.h != 0 && dimensions.w != 0) { 213 | exec.resize(dimensions, () => { 214 | }); 215 | } 216 | 217 | stream.on('data', (chunk) => { 218 | socket.emit('show', chunk.toString()); 219 | }); 220 | 221 | socket.on('cmd', (data) => { 222 | stream.write(data); 223 | }); 224 | 225 | }); 226 | }); 227 | }); 228 | 229 | socket.on('attach', (id, w, h) => { 230 | const container = docker.getContainer(id); 231 | 232 | const logStream = new stream.PassThrough(); 233 | logStream.on('data', (chunk) => { 234 | socket.emit('show', chunk.toString('utf8')); 235 | }); 236 | 237 | const logs_opts = { 238 | follow: true, 239 | stdout: true, 240 | stderr: true, 241 | timestamps: false, 242 | }; 243 | 244 | const handler = (err, stream) => { 245 | container.modem.demuxStream(stream, logStream, logStream); 246 | if (!err && stream) { 247 | stream.on('end', () => { 248 | logStream.end('===Logs stream finished==='); 249 | socket.emit('end', 'ended'); 250 | stream.destroy(); 251 | }); 252 | } 253 | }; 254 | 255 | container.logs(logs_opts, handler); 256 | }); 257 | 258 | socket.on('getSysInfo', (id) => { 259 | const container = docker.getContainer(id); 260 | container.stats((err, stream) => { 261 | if (!err && stream != null) { 262 | stream.on('data', (data) => { 263 | socket.emit(id, data.toString('utf8')); 264 | }); 265 | stream.on('end', () => { 266 | socket.emit('end', 'ended'); 267 | stream.destroy(); 268 | }); 269 | } 270 | }); 271 | }); 272 | 273 | socket.on('end', () => { 274 | array = []; 275 | streams.map((stream) => { 276 | stream.destroy(); 277 | }); 278 | console.log('--------end---------'); 279 | }); 280 | 281 | let array = []; 282 | let streams = []; 283 | // for react web ui 284 | socket.on('getContainersInfo', (id) => { 285 | if (array.indexOf(id) === -1) { 286 | array.push(id); 287 | console.log('socket.io => getContainersInfo ' + id); 288 | const container = docker.getContainer(id); 289 | container.stats((err, stream) => { 290 | streams.push(stream); 291 | if (!err && stream != null) { 292 | stream.on('data', (data) => { 293 | const toSend = JSON.parse(data.toString('utf8')); 294 | socket.emit('containerInfo', toSend); 295 | }); 296 | stream.on('end', () => { 297 | socket.emit('end', 'ended'); 298 | stream.destroy(); 299 | }); 300 | } 301 | }); 302 | } 303 | }); 304 | 305 | }); 306 | 307 | return router; 308 | }; 309 | 310 | module.exports = returnContainersRouter; 311 | -------------------------------------------------------------------------------- /routes/images.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const Docker = require('dockerode'); 4 | const docker = new Docker(); 5 | 6 | const returnImagesRouter = (io) => { 7 | /* GET users listing. */ 8 | router.get('/', (req, res, next) => { 9 | docker.listImages((err, listImages) => { 10 | res.locals.imageName = (str) => { 11 | if (str) { 12 | if (str.length != 0) { 13 | return str[0].split(':')[0]; 14 | } 15 | } 16 | return str; 17 | }; 18 | // image Tag 19 | res.locals.imageTag = (str) => { 20 | if (str) { 21 | if (str.length != 0) { 22 | return str[0].split(':')[1]; 23 | } 24 | } 25 | return str; 26 | }; 27 | // imageSize 28 | res.locals.imageSize = (str) => { 29 | const newSiez = parseInt(str, 10); 30 | str = (newSiez / 1000 / 1000).toFixed(2). 31 | toString(). 32 | substring(0, 4); 33 | if (str.indexOf('.') == 3) { 34 | return str.split('.')[0]; 35 | } 36 | return str; 37 | }; 38 | res.render('images', { 39 | images: listImages, 40 | }); 41 | }); 42 | }); 43 | 44 | router.get('/remove/:id', (req, res, next) => { 45 | let imageId = req.params.id; 46 | if (imageId.indexOf(':') > 0) { 47 | imageId = imageId.split(':')[1]; 48 | } 49 | let image = docker.getImage(imageId); 50 | image.remove({force: true}, (err, data) => { 51 | if (err) { 52 | res.render('error', {error: err, message: err.json.message}); 53 | } else { 54 | res.redirect('/images'); 55 | } 56 | }); 57 | }); 58 | 59 | router.get('/search/:name', (req, res, next) => { 60 | let name = req.params.name; 61 | docker.searchImages({term: name}, (err, data) => { 62 | if (err) throw err; 63 | res.json(data); 64 | }); 65 | }); 66 | io.on('connection', (socket) => { 67 | socket.on('pull', (imageName, w, h) => { 68 | docker.pull(imageName, (err, stream) => { 69 | if (err) { 70 | const tmp = err.toString(); 71 | socket.emit('show', tmp); 72 | setTimeout(() => { 73 | socket.emit('end'); 74 | }, 10000); 75 | } else { 76 | 77 | const onFinished = (err, output) => { 78 | if (err) { 79 | console.log(err); 80 | } 81 | socket.emit('end'); 82 | }; 83 | 84 | const onProgress = (event) => { 85 | if (event.id) { 86 | socket.emit('show', 87 | event.status + ':' + event.id + '\n'); 88 | } else { 89 | socket.emit('show', event.status + '\n'); 90 | } 91 | if (event.progress) { 92 | socket.emit('show', event.progress + '\n'); 93 | } 94 | }; 95 | 96 | docker.modem.followProgress(stream, onFinished, onProgress); 97 | } 98 | 99 | }); 100 | }); 101 | }); 102 | return router; 103 | }; 104 | module.exports = returnImagesRouter; 105 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', (req, res, next) => { 6 | res.redirect('/overview'); 7 | }); 8 | 9 | router.get('/logout', (req, res, next) => { 10 | req.session.isLogin = false; 11 | res.locals.isLogin = false; 12 | res.redirect('/'); 13 | }); 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /routes/overview.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const Docker = require('dockerode'); 4 | const docker = new Docker(); 5 | /* GET home page. */ 6 | const returnOverviewRouter = (io) => { 7 | router.get('/', (req, res, next) => { 8 | docker.info((err, info) => { 9 | // console.log(info) 10 | if (err) { 11 | res.render('error', { 12 | message: "Docker is running ?" 13 | }); 14 | } else { 15 | res.render('overview', { 16 | info: info 17 | }); 18 | } 19 | }); 20 | }); 21 | 22 | return router; 23 | }; 24 | 25 | module.exports = returnOverviewRouter; 26 | -------------------------------------------------------------------------------- /views/containers.html: -------------------------------------------------------------------------------- 1 | <% include include/header.html %> 2 | 3 | 4 | New container 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Names 14 | Image 15 | Command 16 | Port 17 | State 18 | CPU 19 | RAM 20 | Action 21 | 22 | 23 | 24 | <% if(containers){ %> 25 | <% containers.forEach(function(container,index){ %> 26 | 27 | 28 | <%= formatName(container.Names) %> 29 | 30 | 31 | <%= container.Image %> 32 | 33 | 34 | <%= container.Command %> 35 | 36 | 37 | <% container.Ports.forEach(function(item) { %> 38 | <%-`${item["IP"]}:${item["PublicPort"]}->${item["PrivatePort"]}/${item["Type"]}`;%> 39 | <% });%> 40 | 41 | 42 | 43 | 44 | <% if(container.State=='running'){ %> 45 | <%= container.State %> 49 | <% } %> 50 | <% if(container.State=='exited'){ %> 51 | <%= container.State %> 55 | <% } %> 56 | <% if(container.State=='created'){ %> 57 | <%= container.State %> 61 | <% } %> 62 | 63 | No data 64 | No data 65 | 66 | 67 | <% if(container.State=='running'){ %> 68 | 69 | <% if(!(container.Image.includes("qfdk")||container.Image.includes("easy-docker-web"))){ %> 70 | 73 | Stop 74 | 75 | <% } %> 76 | 79 | Console 80 | 81 | <% } %> 82 | <% if(container.State=='exited'){ %> 83 | 86 | Start 87 | 88 | 91 | Remove 92 | 93 | <% } %> 94 | <% if(container.State=='created'){ %> 95 | 98 | Remove 99 | 100 | <% } %> 101 | 104 | Logs 105 | 106 | 107 | 109 | Inspect 110 | 111 | 112 | 113 | 114 | 115 | <% }) %> 116 | 117 | <% } %> 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | × 129 | 130 | New container 131 | 132 | 133 | 134 | 135 | 136 | Image: 137 | 139 | Please select an image ... 140 | <% if(images){ %> 141 | <% images.forEach(function(image,index){ %> 142 | <% if (index===0) {%> 143 | <%= image.RepoTags %> 144 | <%} else {%> 145 | <%= image.RepoTags %> 146 | <% }}) %> 147 | <% } %> 148 | 149 | 150 | 151 | 152 | 153 | 155 | 156 | 157 | 158 | 159 | Always restart(on-failure) 161 | 162 | 163 | 164 | 165 | 166 | Volume: 167 | 168 | 169 | 172 | 173 | 174 | 176 | 177 | 178 | 179 | 180 | 181 | Expose port (both TCP and UDP): 182 | 183 | 184 | 187 | 188 | 189 | 191 | 192 | 193 | 194 | 195 | Cmd: 196 | 197 | 198 | 199 | memory(include swap and RAM, in megabytes): 200 | 201 | 202 | 203 | CPU limit(1 refers to one core while 0.5 refers to half a 204 | core): 205 | 206 | 207 | 208 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 223 | 224 | 225 | 226 | 227 | × 228 | 229 | 230 | 容器状态 231 | 232 | 233 | 234 | abcd 235 | 236 | 240 | 241 | 242 | 243 | 244 | <% include include/footer.html %> 245 | -------------------------------------------------------------------------------- /views/error.html: -------------------------------------------------------------------------------- 1 | <% include include/header.html %> 2 | 3 | 4 | 5 | 6 | <%= error.status %> <%= message %> 7 | <%= error.stack %> 8 | 9 | Return 10 | 11 | 12 | 13 | <% include include/footer.html %> 14 | -------------------------------------------------------------------------------- /views/images.html: -------------------------------------------------------------------------------- 1 | <% include include/header.html %> 2 | 3 | 4 | Pull image 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | REPOSITORY 13 | TAG 14 | Size 15 | Action 16 | 17 | 18 | 19 | <% if(images){ %> 20 | <% images.forEach(function(image,index){ %> 21 | 22 | 23 | <%= imageName(image.RepoTags) %> 24 | 25 | 26 | <%= imageTag(image.RepoTags) %> 27 | 28 | 29 | <%= imageSize(image.Size) %> MB 30 | 31 | 32 | 34 | Remove 35 | 36 | 37 | 38 | <% }) %> 39 | 40 | <% } %> 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | × 50 | 51 | Pull image 52 | 53 | 54 | 55 | 56 | 57 | 58 | Image name: 59 | 61 | 62 | 63 | Version: 64 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 79 | 80 | 81 | 82 | <% include include/footer.html %> 83 | -------------------------------------------------------------------------------- /views/include/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 43 |
abcd
<%= error.stack %>