├── .gitignore ├── Deploy ├── config.js ├── docker-compose.yml ├── prepare.sh └── wait-for-it.sh ├── Dockerfile.old ├── Dockerfile.ui ├── Makefile ├── README.md ├── app.conf.json ├── bower.json ├── config.rb ├── dist ├── apple-touch-icon.png ├── assets │ └── images │ │ ├── applist.svg │ │ ├── avatar.png │ │ ├── blue-mountain.jpg │ │ ├── container.svg │ │ ├── dashboard.jpg │ │ ├── docker.svg │ │ ├── server.svg │ │ ├── shop.svg │ │ ├── svg-sprite.svg │ │ ├── volume-normal.svg │ │ └── volume.svg ├── favicon.ico ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── index.html ├── scripts │ ├── app-daf7d8c32e.js │ ├── socket-07bd7a4a60.js │ └── vendor-bf340c1cb6.js └── styles │ ├── app-7a5f66ff19.css │ ├── app-7ce6eff00a.css │ └── vendor-f583e7128e.css ├── gulp ├── base.js ├── build.js ├── config.js └── server.js ├── gulpfile.js ├── monitor ├── Dockerfile ├── README.md ├── config.js ├── index.js ├── package.json └── request.js ├── nodemon.json ├── package.json ├── server ├── api │ ├── build │ │ ├── build.controller.js │ │ └── index.js │ ├── cluster │ │ └── index.js │ ├── containers │ │ ├── containers.controller.js │ │ └── index.js │ ├── endpoint │ │ └── index.js │ ├── images │ │ ├── images.controller.js │ │ └── index.js │ ├── user │ │ ├── index.js │ │ └── user.controller.js │ └── volumes │ │ ├── index.js │ │ └── volumes.controller.js ├── app.js ├── auth │ ├── auth.service.js │ ├── index.js │ └── local │ │ ├── index.js │ │ └── passport.js ├── components │ ├── logs │ │ └── index.js │ ├── mysql │ │ └── index.js │ ├── qiniu │ │ └── index.js │ ├── request │ │ └── index.js │ ├── superagent │ │ └── index.js │ └── tools │ │ └── index.js ├── config │ ├── env │ │ ├── development.js │ │ ├── index.js │ │ ├── production.js │ │ └── test.js │ ├── express.js │ └── seed.js ├── logs │ ├── development-error.log │ ├── development-fatal.log │ └── development-warn.log ├── model │ ├── logs.model.js │ └── user.model.js ├── routes.js └── socket.js ├── src ├── app │ ├── app.conf.js │ ├── base.scss │ ├── cluster │ │ ├── cluster.controller.js │ │ ├── cluster.jade │ │ ├── cluster.js │ │ └── cluster.scss │ ├── common.scss │ ├── components │ │ ├── directive │ │ │ ├── chart.directive.js │ │ │ ├── cmchart.directive.js │ │ │ ├── containerLogs.directive.js │ │ │ ├── index.js │ │ │ ├── mytab.directive.js │ │ │ └── nav.directive.js │ │ ├── header │ │ │ ├── header.controller.js │ │ │ ├── header.jade │ │ │ └── header.scss │ │ ├── resource │ │ │ ├── cluster.js │ │ │ ├── container.js │ │ │ ├── image.js │ │ │ ├── index.js │ │ │ ├── user.js │ │ │ └── volume.js │ │ ├── service │ │ │ ├── auto.service.js │ │ │ ├── autoInterceptor.service.js │ │ │ ├── createContainer.service.js │ │ │ ├── dataFormat.service.js │ │ │ ├── index.js │ │ │ └── modal.service.js │ │ └── slidebar │ │ │ ├── slidebar.jade │ │ │ ├── slidebar.scss │ │ │ └── sliderbar.controller.js │ ├── containerCreate │ │ ├── containerCreate.controller.js │ │ ├── containerCreate.jade │ │ └── containerCreate.scss │ ├── containerDetail │ │ ├── containerDetail.controller.js │ │ ├── containerDetail.jade │ │ └── containerDetail.scss │ ├── containerList │ │ ├── containerList.controller.js │ │ ├── containerList.jade │ │ └── containerList.scss │ ├── containers │ │ └── containers.js │ ├── echarts.min.js │ ├── imageList │ │ ├── imageList.controller.js │ │ ├── imageList.jade │ │ ├── imageList.js │ │ └── imageList.scss │ ├── index.js │ ├── index.scss │ ├── main │ │ ├── dashboard.jade │ │ ├── dashboard.scss │ │ ├── main.controller.js │ │ ├── main.jade │ │ ├── main.js │ │ └── main.scss │ ├── manage │ │ ├── manage.js │ │ └── manage.scss │ ├── manage_user │ │ ├── userAdd.jade │ │ ├── userEdit.jade │ │ ├── userList.controller.js │ │ └── userList.jade │ ├── markdown.scss │ ├── settings │ │ ├── settings.js │ │ ├── settings.scss │ │ ├── signin.jade │ │ ├── singin.controller.js │ │ ├── user.setting.js │ │ └── userSetting.jade │ └── volume │ │ ├── newVolume.jade │ │ ├── volume.controller.js │ │ ├── volume.jade │ │ ├── volume.js │ │ └── volume.scss ├── apple-touch-icon.png ├── assets │ └── images │ │ ├── applist.svg │ │ ├── avatar.png │ │ ├── blue-mountain.jpg │ │ ├── container.svg │ │ ├── dashboard.jpg │ │ ├── docker.svg │ │ ├── server.svg │ │ ├── shop.svg │ │ ├── svg-sprite.svg │ │ ├── volume-normal.svg │ │ └── volume.svg ├── favicon.ico ├── index.jade └── socket.io.js └── start_mongo.sh /.gitignore: -------------------------------------------------------------------------------- 1 | \ 2 | \\ 3 | node_modules 4 | *.pyc 5 | .idea/ 6 | docker_visual/docker_visual/*.pyc 7 | server/session 8 | bower_components/ 9 | src/app/**/*.html 10 | src/*.html 11 | .tmp/ 12 | monitor/log/ 13 | .sass-cache/ 14 | logs/* 15 | server/logs/* 16 | mongo/ 17 | -------------------------------------------------------------------------------- /Deploy/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | swarm:{ 3 | address: 'http://10.103.242.167:2377' //docker swarm master 地址 4 | }, 5 | host:{ 6 | ip:'10.103.242.167' //host ip for deploy project 7 | } 8 | }; 9 | module.exports = config; 10 | -------------------------------------------------------------------------------- /Deploy/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | ui: 4 | build: 5 | context: ../ 6 | dockerfile: Dockerfile.ui 7 | ports: 8 | - "8100:8100" 9 | - "9090:9090" 10 | links: 11 | - db:db 12 | depends_on: 13 | - db 14 | - monitor 15 | - mongo 16 | command: nodemon 17 | db: 18 | image: mysql 19 | volumes: 20 | - /data/database:/var/lib/mysql 21 | environment: 22 | - MYSQL_ROOT_PASSWORD=root 23 | monitor: 24 | build: 25 | context: ../monitor/ 26 | command: npm start 27 | # volumes: 28 | # - ../monitor:/monitor # for developer 29 | links: 30 | - db:db 31 | depends_on: 32 | - db 33 | mongo: 34 | image: mongo 35 | ports: 36 | - "27000:27017" 37 | volumes: 38 | - /data/mongo:/data/db 39 | -------------------------------------------------------------------------------- /Deploy/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | SCRIPTSDIR="../dist/scripts" 4 | PRODUCNTIONJS="../server/config/env" 5 | PAT="^app.*\.js$" 6 | IP=`ip route get 8.8.8.8 | awk '{ print $7; }'` # get host ip 7 | 8 | `sed -ie s/localhost/$IP/g $PRODUCNTIONJS/production.js` > /dev/null #replace localhost in production.js to current host ip 9 | `rm $PRODUCNTIONJS/*.jse > /dev/null` 10 | 11 | for FILE in `ls $SCRIPTSDIR` # loop file 12 | do 13 | echo $FILE | grep -e $PAT > /dev/null 14 | if [ $? -eq 0 ];then 15 | sed -ie s/localhost/$IP/g $SCRIPTSDIR/$FILE > /dev/null #replace localhost to IP 16 | JSEFILE=`echo $FILE | sed -e s/\.js$/\.jse/` 17 | if [ -e $SCRIPTSDIR/$JSEFILE ];then # del jse file created by sed command 18 | rm $SCRIPTSDIR/*.jse > /dev/null 19 | fi 20 | fi 21 | done 22 | -------------------------------------------------------------------------------- /Deploy/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | cmdname=$(basename $0) 5 | 6 | echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | wait_for() 26 | { 27 | if [[ $TIMEOUT -gt 0 ]]; then 28 | echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" 29 | else 30 | echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" 31 | fi 32 | start_ts=$(date +%s) 33 | while : 34 | do 35 | (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 36 | result=$? 37 | if [[ $result -eq 0 ]]; then 38 | end_ts=$(date +%s) 39 | echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" 40 | break 41 | fi 42 | sleep 1 43 | done 44 | return $result 45 | } 46 | 47 | wait_for_wrapper() 48 | { 49 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 50 | if [[ $QUIET -eq 1 ]]; then 51 | timeout $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 52 | else 53 | timeout $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 54 | fi 55 | PID=$! 56 | trap "kill -INT -$PID" INT 57 | wait $PID 58 | RESULT=$? 59 | if [[ $RESULT -ne 0 ]]; then 60 | echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" 61 | fi 62 | return $RESULT 63 | } 64 | 65 | # process arguments 66 | while [[ $# -gt 0 ]] 67 | do 68 | case "$1" in 69 | *:* ) 70 | hostport=(${1//:/ }) 71 | HOST=${hostport[0]} 72 | PORT=${hostport[1]} 73 | shift 1 74 | ;; 75 | --child) 76 | CHILD=1 77 | shift 1 78 | ;; 79 | -q | --quiet) 80 | QUIET=1 81 | shift 1 82 | ;; 83 | -s | --strict) 84 | STRICT=1 85 | shift 1 86 | ;; 87 | -h) 88 | HOST="$2" 89 | if [[ $HOST == "" ]]; then break; fi 90 | shift 2 91 | ;; 92 | --host=*) 93 | HOST="${1#*=}" 94 | shift 1 95 | ;; 96 | -p) 97 | PORT="$2" 98 | if [[ $PORT == "" ]]; then break; fi 99 | shift 2 100 | ;; 101 | --port=*) 102 | PORT="${1#*=}" 103 | shift 1 104 | ;; 105 | -t) 106 | TIMEOUT="$2" 107 | if [[ $TIMEOUT == "" ]]; then break; fi 108 | shift 2 109 | ;; 110 | --timeout=*) 111 | TIMEOUT="${1#*=}" 112 | shift 1 113 | ;; 114 | --) 115 | shift 116 | CLI="$@" 117 | break 118 | ;; 119 | --help) 120 | usage 121 | ;; 122 | *) 123 | echoerr "Unknown argument: $1" 124 | usage 125 | ;; 126 | esac 127 | done 128 | 129 | if [[ "$HOST" == "" || "$PORT" == "" ]]; then 130 | echoerr "Error: you need to provide a host and port to test." 131 | usage 132 | fi 133 | 134 | TIMEOUT=${TIMEOUT:-15} 135 | STRICT=${STRICT:-0} 136 | CHILD=${CHILD:-0} 137 | QUIET=${QUIET:-0} 138 | 139 | if [[ $CHILD -gt 0 ]]; then 140 | wait_for 141 | RESULT=$? 142 | exit $RESULT 143 | else 144 | if [[ $TIMEOUT -gt 0 ]]; then 145 | wait_for_wrapper 146 | RESULT=$? 147 | else 148 | wait_for 149 | RESULT=$? 150 | fi 151 | fi 152 | 153 | if [[ $CLI != "" ]]; then 154 | if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then 155 | echoerr "$cmdname: strict mode, refusing to execute subprocess" 156 | exit $RESULT 157 | fi 158 | exec $CLI 159 | else 160 | exit $RESULT 161 | fi 162 | -------------------------------------------------------------------------------- /Dockerfile.old: -------------------------------------------------------------------------------- 1 | FROM node:5.11.1 2 | 3 | MAINTAINER from BUPT by gaoyangyang (gyycoder@gmai.com) 4 | 5 | # skip installing gem documentation 6 | RUN mkdir -p /usr/local/etc \ 7 | && { \ 8 | echo 'install: --no-document'; \ 9 | echo 'update: --no-document'; \ 10 | } >> /usr/local/etc/gemrc 11 | 12 | ENV RUBY_MAJOR 2.1 13 | ENV RUBY_VERSION 2.1.9 14 | ENV RUBY_DOWNLOAD_SHA256 034cb9c50676d2c09b3b6cf5c8003585acea05008d9a29fa737c54d52c1eb70c 15 | ENV RUBYGEMS_VERSION 2.6.6 16 | 17 | # some of ruby's build scripts are written in ruby 18 | # we purge this later to make sure our final image uses what we just built 19 | RUN set -ex \ 20 | && buildDeps=' \ 21 | bison \ 22 | libgdbm-dev \ 23 | ruby \ 24 | ' \ 25 | && apt-get update \ 26 | && apt-get install -y --no-install-recommends $buildDeps \ 27 | && rm -rf /var/lib/apt/lists/* \ 28 | && curl -fSL -o ruby.tar.gz "http://cache.ruby-lang.org/pub/ruby/$RUBY_MAJOR/ruby-$RUBY_VERSION.tar.gz" \ 29 | && echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.gz" | sha256sum -c - \ 30 | && mkdir -p /usr/src/ruby \ 31 | && tar -xzf ruby.tar.gz -C /usr/src/ruby --strip-components=1 \ 32 | && rm ruby.tar.gz \ 33 | && cd /usr/src/ruby \ 34 | && { echo '#define ENABLE_PATH_CHECK 0'; echo; cat file.c; } > file.c.new && mv file.c.new file.c \ 35 | && autoconf \ 36 | && ./configure --disable-install-doc \ 37 | && make -j"$(nproc)" \ 38 | && make install \ 39 | && apt-get purge -y --auto-remove $buildDeps \ 40 | && gem update --system $RUBYGEMS_VERSION \ 41 | && rm -r /usr/src/ruby 42 | 43 | ENV BUNDLER_VERSION 1.12.5 44 | 45 | RUN gem install bundler --version "$BUNDLER_VERSION" 46 | 47 | # install things globally, for great justice 48 | # and don't create ".bundle" in all our apps 49 | ENV GEM_HOME /usr/local/bundle 50 | ENV BUNDLE_PATH="$GEM_HOME" \ 51 | BUNDLE_BIN="$GEM_HOME/bin" \ 52 | BUNDLE_SILENCE_ROOT_WARNING=1 \ 53 | BUNDLE_APP_CONFIG="$GEM_HOME" 54 | ENV PATH $BUNDLE_BIN:$PATH 55 | RUN mkdir -p "$GEM_HOME" "$BUNDLE_BIN" \ 56 | && chmod 777 "$GEM_HOME" "$BUNDLE_BIN" 57 | 58 | 59 | # Install Gem Sass Compass 60 | RUN gem update --system \ 61 | && gem install sass \ 62 | && gem install compass 63 | 64 | COPY . /gyyzyp/docker_swarm/ui 65 | WORKDIR /gyyzyp/docker_swarm/ui 66 | 67 | #install node-gyp 68 | RUN npm i -g node-gyp 69 | 70 | 71 | #install gulp & bower 72 | RUN npm install -g bower@1.7.9 \ 73 | && npm install -g gulp 74 | 75 | # environment variables 76 | ENV MYSQL_USR root \ 77 | MYSQL_PWD root 78 | 79 | #install gulp dependency using package.json 80 | RUN npm install 81 | 82 | #install js library dependency using bower.json 83 | RUN bower --allow-root install 84 | 85 | #run gulp build 86 | RUN gulp build 87 | 88 | EXPOSE 8100 89 | -------------------------------------------------------------------------------- /Dockerfile.ui: -------------------------------------------------------------------------------- 1 | FROM node:5.12.0 2 | 3 | MAINTAINER from BUPT by gaoyangyang (gyycoder@gmail.com) 4 | 5 | COPY . /gyyzyp/docker_swarm/ui 6 | WORKDIR /gyyzyp/docker_swarm/ui 7 | 8 | #install node-gyp 9 | RUN npm i -g node-gyp 10 | 11 | #install nodemon 12 | RUN npm install -g nodemon 13 | 14 | #install app dependency using package.json 15 | RUN npm install --production 16 | 17 | EXPOSE 8100 18 | EXPOSE 9090 19 | 20 | CMD ["nodemon"] 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build -t gaoyangyang/docker_visual . 3 | run: 4 | docker run -t -i -d -p 8000:8000 --name docker_visual gaoyangyang/docker_visual 5 | clean: 6 | docker rm -f docker_visual 7 | push: 8 | docker build -t localhost:5000/videosynopsis . 9 | docker push localhost:5000/videosynopsis 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DockerVI 2 | # *Important: This Project only supports docker v1.10 - v1.11 version and now is deprecated* 3 | 4 | 标签(空格分隔): Docker 5 | 6 | --- 7 | ![docker][1] 8 | > Project DockerVI is inspired by the idea of realize the Visible Management for **[Docker Swarm][2]** by using Docker Swarm Remote API . The goal is to provide a beauty and power pure client side implementation, make it is effortless to connect and manage docker swarm. 9 | 10 | 11 | ### Current implementation features 12 | * **Role Based Access Control(RBAC)** 13 | * **Display the overall survey of cluster**. 14 | * **Container:** 15 | * show Container list in swarm cluste. 16 | * Create, Running, Stop, Delete Container. 17 | * Look up the low-level information on the container. 18 | * View the real-time resource usage in Container. 19 | * View the stdout and stderr logs from the Container. 20 | * **Image:** 21 | * show Image list in swarm cluste. 22 | * Search, Pull, Delete Image. 23 | * Create Container using Image. 24 | * **Volume:** 25 | * show Volume list in swarm cluste. 26 | * Create, Delete Volume. 27 | * Search volume by using volume name or node name in docker swarm cluster; 28 | * Look up the low-level detail information about volume. 29 | 30 | ### Some pictures for Demo 31 | + **Demo for show searching image using name** 32 | - ![cluster](http://o9dop9y2w.bkt.clouddn.com/searchImage.png) 33 | 34 | + **Demo for show the real-time resource usages in Container** 35 | - ![real-time resource state](http://o9dop9y2w.bkt.clouddn.com/realTimeResourceUsage.png) 36 | 37 | + **Demo for show low-information in container** 38 | - ![information](http://o9dop9y2w.bkt.clouddn.com/detail.png) 39 | 40 | + **Demo for show the create container using image** 41 | - ![createContainer](http://o9dop9y2w.bkt.clouddn.com/containerCreate.png) 42 | 43 | + **Demo for show deleting container** 44 | - ![deleteContainer](http://o9dop9y2w.bkt.clouddn.com/deleteContainer.png) 45 | + **Demo for show Role Based Access Control** 46 | - ![user-manage](http://o9dop9y2w.bkt.clouddn.com/users.png) 47 | + 48 | - ![login](http://o9dop9y2w.bkt.clouddn.com/login.png) 49 | 50 | 51 | 52 | ### Getting Started 53 | DockerVI is self-contained and can be easily deployed via [docker-compose][7](Quick-Start steps below). 54 | It mainly consists of four Container Services: 55 | 56 | 1. **ui:** The core part of this project , used for realizing the visual operation of the docker swarm. 57 | 2. **monitor:** Real-time access the usage of resources in the Container in docker swarm cluster. 58 | 3. **mysql:** store the resource-usage data for every Container in docker swarm cluster. 59 | 4. **mongo** store users related information for authentication and authorization. 60 | **System requirements:** 61 | DockerVI need works with docker 1.10+ and docker-compose 1.6.0+ and 8100,9090 port can be available. 62 | 63 | 1. Get the source code: 64 | 65 | ```sh 66 | $ git clone https://github.com/gaoyangxiaozhu/DockerVI.git 67 | ``` 68 | 2. Edit the file **Deploy/config.js** and **monitor/config.js**, make necessary configuration changes such as hostname for deploy project and swarm address. 69 | 70 | 3. Install DockerVI with the following commands(Need networking for installing the dependencies). Note that the docker-compose process can take a while. 71 | ```sh 72 | $ cd Deploy 73 | $ ./prepare.sh #important 74 | 75 | $ docker-compose up 76 | ``` 77 | 78 | If everything worked properly, you should be able to open a browser to manage your docker swarm using 79 | `http::8100` 80 | 81 | **Notice:** Program does not support the low version of the IE browser, If you use IE to access, please upgrade to IE10+. 82 | 83 | ### Stack 84 | * [Angular](https://github.com/angular/angular.js) 85 | * [nodeJs](https://nodejs.org/en/) 86 | * [Express](https://github.com/expressjs/express/) 87 | * [socket.io](https://github.com/socketio/socket.io/) 88 | * [Bootstrap](http://getbootstrap.com/) 89 | * [Jade](http://jade-lang.com/) 90 | * [Compass](http://compass-style.org/) 91 | * [Docker compose](https://docs.docker.com/compose/overview/) 92 | 93 | 94 | ### Todo: 95 | * Authority management 96 | * Full remote swarm api support 97 | * using websocket technology for entering the container to carry out the command operation in browser-end 98 | * Unit tests 99 | 100 | 101 | ### License 102 | The DockerVI code is licensed under the MIT license. 103 | 104 | 105 | [1]: http://o9dop9y2w.bkt.clouddn.com/docker.png 106 | [2]: https://docs.docker.com/engine/swarm/ 107 | [3]: http://o9dop9y2w.bkt.clouddn.com/searchImage.png 108 | [4]: http://o9dop9y2w.bkt.clouddn.com/realTimeResourceUsage.png 109 | [5]: http://o9dop9y2w.bkt.clouddn.com/containerCreate.png 110 | [6]: http://o9dop9y2w.bkt.clouddn.com/deleteContainer.png 111 | [7]: https://docs.docker.com/compose/overview/ 112 | [8]: http://o9dop9y2w.bkt.clouddn.com/detail.png 113 | [9]: http://o9dop9y2w.bkt.clouddn.com/users.png 114 | [10]:http://o9dop9y2w.bkt.clouddn.com/login.png 115 | -------------------------------------------------------------------------------- /app.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "IsDebug":true 4 | }, 5 | "production": { 6 | "IsDebug":false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dockerApp", 3 | "version": "0.0.2", 4 | "dependencies": { 5 | "angular": "1.3.13", 6 | "angular-animate": "~1.4.0", 7 | "angular-bootstrap": "~0.13.3", 8 | "angular-cookies": "~1.4.0", 9 | "angular-resource": "~1.4.3", 10 | "angular-sanitize": "~1.4.0", 11 | "angular-touch": "~1.4.0", 12 | "angular-ui-router": "~0.2.15", 13 | "AngularJS-Toaster": "angularjs-toaster#~0.4.15", 14 | "animate.css": "~3.3.0", 15 | "bootstrap-markdown": "~2.9.0", 16 | "bootstrap-sass": "~3.3.5", 17 | "bower": "*", 18 | "font-awesome": "~4.3.0", 19 | "install": "~1.0.4", 20 | "jquery": "~1.11.3", 21 | "raphael": "~2.0.0", 22 | "morrisjs": "~0.5.1", 23 | "marked": "~0.3.4", 24 | "ng-file-upload": "~5.0.9", 25 | "ng-lodash": "~0.2.3", 26 | "ngprogress": "~1.1.1", 27 | "angular-paging": "~2.0.0", 28 | "angular-sweetalert": "latest", 29 | "angular-ui-bootstrap": "ui-bootstrap#^1.3.2", 30 | "echarts": "^3.1.10" 31 | }, 32 | "devDependencies": { 33 | "angular-mocks": "~1.4.0" 34 | }, 35 | "resolutions": { 36 | "jquery": "~1.11.3", 37 | "angular": "~1.4.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | require 'compass/import-once/activate' 2 | # Require any additional compass plugins here. 3 | # bootstrap 已经包含normalize 4 | # require 'compass-normalize' 5 | # Set this to the root of your project when deployed: 6 | http_path = "/" 7 | # project_path = "" 8 | css_dir = ".tmp/serve/app" 9 | sass_dir = "src/app" 10 | images_dir = "src/assets/images" 11 | javascripts_dir = "src/app/" 12 | sprite_load_path = ["src/assets/images/sprite"] 13 | # You can select your preferred output style here (can be overridden via the command line): 14 | # output_style = :expanded or :nested or :compact or :compressed 15 | # output_style = :expanded 16 | # To enable relative paths to assets via compass helper functions. Uncomment: 17 | # relative_assets = true 18 | 19 | # To disable debugging comments that display the original location of your selectors. Uncomment: 20 | # line_comments = false 21 | 22 | 23 | # If you prefer the indented syntax, you might want to regenerate this 24 | # project again passing --syntax sass, or you can uncomment this: 25 | # preferred_syntax = :sass 26 | # and then run: 27 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass 28 | sourcemap = true 29 | # 使用绝对路径方便调试http://sass-lang.com/documentation/file.SASS_REFERENCE.html#options 30 | sass_options = {:sourcemap => :file} -------------------------------------------------------------------------------- /dist/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/apple-touch-icon.png -------------------------------------------------------------------------------- /dist/assets/images/applist.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/assets/images/avatar.png -------------------------------------------------------------------------------- /dist/assets/images/blue-mountain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/assets/images/blue-mountain.jpg -------------------------------------------------------------------------------- /dist/assets/images/container.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/images/dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/assets/images/dashboard.jpg -------------------------------------------------------------------------------- /dist/assets/images/docker.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/images/server.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/images/shop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/images/svg-sprite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/images/volume-normal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/images/volume.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/favicon.ico -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/dist/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /gulp/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var path = require('path'); 5 | var config = require('./config'); 6 | var _ = require('lodash'); 7 | var wiredep = require('wiredep').stream; 8 | 9 | var $ = require('gulp-load-plugins')({ 10 | pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del','imagemin-pngquant'] 11 | }); 12 | 13 | /*****************angular模板合成JS start*********************************************/ 14 | //只有在build的时候才需要 15 | gulp.task('partials', function () { 16 | return gulp.src([ 17 | path.join(config.paths.src, '/app/**/*.html') 18 | ]) 19 | .pipe($.minifyHtml({ 20 | empty: true, 21 | spare: true, 22 | quotes: true 23 | })) 24 | .pipe($.angularTemplatecache('templateCacheHtml.js', { 25 | module: 'dockerApp', 26 | root: 'app' 27 | })) 28 | .pipe(gulp.dest(config.paths.tmp + '/partials/')); 29 | }); 30 | /*****************angular模板合成JS end*********************************************/ 31 | 32 | /*****************html(压缩合并js,css,html) start*********************/ 33 | gulp.task('html',['inject','partials'],function () { 34 | var partialsInjectFile = gulp.src(path.join(config.paths.tmp, '/partials/templateCacheHtml.js'), { read: false }); 35 | var partialsInjectOptions = { 36 | starttag: '', 37 | ignorePath: path.join(config.paths.tmp, '/partials'), 38 | addRootSlash: false 39 | }; 40 | 41 | var htmlFilter = $.filter('*.html',{restore: true}); 42 | var jsFilter = $.filter('**/*.js',{restore: true}); 43 | var cssFilter = $.filter('**/*.css',{restore: true}); 44 | var assets = $.useref.assets(); 45 | 46 | return gulp.src(path.join(config.paths.tmp, '/serve/*.html')) 47 | //自动处理全部错误信息防止因为错误而导致 watch 不正常工作 48 | .pipe($.plumber(config.errorHandler())) 49 | //注入angular模板文件 50 | .pipe($.inject(partialsInjectFile, partialsInjectOptions)) 51 | //获取index.html中的文件 52 | .pipe(assets) 53 | //js处理 54 | .pipe(jsFilter) 55 | .pipe($.ngAnnotate()) 56 | .pipe($.uglify()) 57 | .pipe(jsFilter.restore) 58 | //css处理 59 | .pipe(cssFilter) 60 | .pipe($.replace('../../bower_components/bootstrap-sass/assets/fonts/bootstrap/', '../fonts/')) 61 | .pipe($.csso()) 62 | .pipe(cssFilter.restore) 63 | //md5后缀 64 | .pipe($.rev()) 65 | .pipe(assets.restore()) 66 | .pipe($.useref()) 67 | //替换md5后缀的文件名 68 | .pipe($.revReplace()) 69 | //html处理 70 | .pipe(htmlFilter) 71 | .pipe($.minifyHtml({ 72 | empty: true, 73 | spare: true, 74 | quotes: true, 75 | conditionals: true 76 | })) 77 | .pipe(htmlFilter.restore) 78 | .pipe(gulp.dest(path.join(config.paths.dist, '/'))) 79 | .pipe($.size({ title: path.join(config.paths.dist, '/'), showFiles: true })); 80 | 81 | }); 82 | /*****************html end*********************/ 83 | 84 | /*****************fonts start*********************/ 85 | gulp.task('fonts',function () { 86 | return gulp.src($.mainBowerFiles()) 87 | .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}')) 88 | .pipe($.flatten()) 89 | .pipe(gulp.dest(path.join(config.paths.dist,'/fonts/'))); 90 | }); 91 | /*****************fonts end*********************/ 92 | 93 | /*****************图片压缩 start*********************/ 94 | gulp.task('images',function () { 95 | return gulp.src([ 96 | path.join(config.paths.src, '/assets/images/**/*'), 97 | path.join('!' + config.paths.src, '/assets/images/sprite/**/*') 98 | ]) 99 | .pipe($.imagemin({ 100 | progressive: true, 101 | svgoPlugins: [{removeViewBox: false}], 102 | use: [$.imageminPngquant()] 103 | })) 104 | .pipe(gulp.dest(path.join(config.paths.dist,'/assets/images'))); 105 | }); 106 | /*****************图片压缩 end*********************/ 107 | 108 | /*****************复制其它文件 start*********************/ 109 | gulp.task('other',function () { 110 | return gulp.src([ 111 | path.join(config.paths.src, '/**/*'), 112 | path.join('!' + config.paths.src, '/assets/images/**/*'), 113 | path.join('!' + config.paths.src, '/**/*.{html,js,css,scss,jade}') 114 | ]) 115 | .pipe($.filter(function (file) { 116 | return file.stat.isFile(); 117 | })) 118 | .pipe(gulp.dest(path.join(config.paths.dist,'/'))); 119 | }); 120 | /*****************复制其它文件 end*********************/ 121 | 122 | //按顺序执行任务,images需要在html之后执行 123 | gulp.task('build',$.sequence('prod-config',['html'],['fonts','images'],'other')); 124 | -------------------------------------------------------------------------------- /gulp/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gutil = require('gulp-util'); 4 | 5 | exports.paths = { 6 | src: 'src', 7 | dist: 'dist', 8 | server:'server', 9 | tmp: '.tmp' 10 | }; 11 | 12 | //用于wiredep获取bower依赖主要JS文件列表的options 13 | exports.wiredep = { 14 | exclude: [/bootstrap.js$/, /bootstrap-sass-official\/.*\.js/, /bootstrap\.css/], 15 | directory: 'bower_components' 16 | }; 17 | 18 | /** 19 | * 错误处理 20 | */ 21 | exports.errorHandler = function() { 22 | return function (err) { 23 | gutil.beep(); 24 | gutil.log(err.toString()); 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /gulp/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var config = require('./config'); 5 | var path = require('path'); 6 | var browserSync = require('browser-sync'); 7 | var proxyMiddleware = require('http-proxy-middleware'); 8 | var browserSyncSpa = require('browser-sync-spa'); 9 | var nodemon = require('gulp-nodemon'); 10 | var gulpSequence = require('gulp-sequence'); 11 | 12 | 13 | 14 | //开始之前先将必要文件注入 15 | gulp.task('watch', function () { 16 | 17 | gulpSequence('jade', ['inject'], function() { 18 | 19 | //监控jade文件(除index.jade之外) 20 | gulp.watch([ 21 | path.join(config.paths.src, '/**/*.jade') 22 | ], ['jade']); 23 | 24 | //监控index.jade, 和bower.json文件 25 | gulp.watch([ 26 | path.join(config.paths.src, '/index.jade'), 27 | path.join(config.paths.src, '/app/**/*.jade'), 28 | 'bower.json'],['inject']); 29 | //监控CSS文件 30 | gulp.watch([ 31 | path.join(config.paths.src, '/app/**/*.scss'), 32 | path.join(config.paths.src, '/app/*.scss')], 33 | function (event) { 34 | gulp.start('inject'); 35 | }); 36 | //监控JS文件 37 | gulp.watch([path.join(config.paths.src,'/app/**/*.js')],function (event) { 38 | if(event.type === 'changed'){ 39 | gulp.start('scripts'); 40 | }else{ 41 | gulp.start('inject'); 42 | } 43 | }); 44 | //监控html文件 45 | gulp.watch([ 46 | path.join(config.paths.src,'/app/**/*.html') 47 | ],function (event) { 48 | browserSync.reload(event.path); 49 | }); 50 | }); 51 | 52 | 53 | }); 54 | 55 | gulp.task('nodemon',function () { 56 | nodemon({ 57 | script: path.join(config.paths.server,'/app.js'), 58 | ext: 'js json', 59 | watch: [ 60 | path.join(config.paths.server,'/') 61 | ], 62 | env: { 'NODE_ENV': 'development' } 63 | }); 64 | }); 65 | 66 | gulp.task('nodemon:dist',function () { 67 | nodemon({ 68 | script: path.join(config.paths.server,'/app.js'), 69 | ext: 'js json', 70 | watch: [ 71 | path.join(config.paths.server,'/') 72 | ] 73 | }); 74 | }); 75 | 76 | gulp.task('nodemon:production',function () { 77 | nodemon({ 78 | script: path.join(config.paths.server,'/app.js'), 79 | ext: 'js json', 80 | watch: [ 81 | path.join(config.paths.server,'/') 82 | ], 83 | env: { 'NODE_ENV': 'production' } 84 | }); 85 | }); 86 | 87 | function browserSyncInit (baseDir) { 88 | // Only needed for angular apps,angular 正确路由需要 89 | browserSync.use(browserSyncSpa({ 90 | selector: '[ng-app]' 91 | })); 92 | //起动browserSync 93 | browserSync.init({ 94 | startPath:'/', 95 | server:{ 96 | baseDir: baseDir, 97 | routes: { 98 | "/bower_components": "bower_components" 99 | }, 100 | //使用代理 101 | middleware:[ 102 | proxyMiddleware(['/api/**','/auth/**'], {target: 'http://localhost:9000', changeOrigin:true}) 103 | ] 104 | }, 105 | socket: { 106 | "clients.heartbeatTimeout" : 500000 107 | } 108 | }); 109 | } 110 | gulp.task('serve',function () { 111 | gulpSequence('nodemon',['dev-config','watch'],function () { 112 | browserSyncInit([path.join(config.paths.tmp, '/serve'), config.paths.src]); 113 | }); 114 | }); 115 | gulp.task('serve:dist',function () { 116 | gulpSequence('nodemon','build',function () { 117 | browserSyncInit(config.paths.dist); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var fs = require('fs'); 5 | 6 | fs.readdirSync('./gulp').forEach(function (file) { 7 | if((/\.(js|coffee)$/i).test(file)){ 8 | require('./gulp/' + file); 9 | } 10 | }); 11 | 12 | gulp.task('default', ['serve']); 13 | -------------------------------------------------------------------------------- /monitor/Dockerfile: -------------------------------------------------------------------------------- 1 | #设置集成镜像 2 | FROM node:5.11.1 3 | 4 | #作者信息 5 | MAINTAINER from BUPT by gaoyangyang (gyycoder@gmail.com) 6 | 7 | RUN mkdir -p /monitor 8 | 9 | ADD package.json /monitor/ 10 | 11 | WORKDIR /monitor 12 | RUN npm install --production 13 | 14 | ADD . /monitor/ 15 | -------------------------------------------------------------------------------- /monitor/README.md: -------------------------------------------------------------------------------- 1 | # docker_monitor 2 | swarm集群资源的实时监控(后端部分) 3 | 4 | 配合DockerVI项目使用,实现前端容器内部资源使用情况的实时监控 5 | -------------------------------------------------------------------------------- /monitor/config.js: -------------------------------------------------------------------------------- 1 | exports.mysql = { 2 | host : 'db', 3 | port : '3306', 4 | user : 'root', 5 | password : 'root' 6 | }; 7 | exports.dbname = 'docker'; 8 | exports.logDir = './log'; 9 | exports.swarm_add = "http://10.103.242.167:2377/" //swarm address 10 | -------------------------------------------------------------------------------- /monitor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker_monitor", 3 | "version": "1.0.0", 4 | "description": "using for docker stats monitor", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/gaoyangxiaozhu/docker_monitor.git" 12 | }, 13 | "keywords": [ 14 | "monitor", 15 | "docker", 16 | "mysql" 17 | ], 18 | "author": "gaoyangyang", 19 | "maintainers": [ 20 | { 21 | "email": "gyycoder@gmail.com", 22 | "name": "gyyzyp" 23 | } 24 | ], 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/gaoyangxiaozhu/docker_monitor/issues" 28 | }, 29 | "homepage": "https://github.com/gaoyangxiaozhu/docker_monitor#readme", 30 | "dependencies": { 31 | "async": "^2.0.0-rc.4", 32 | "bunyan": "^1.8.1", 33 | "mysql": "^2.10.2", 34 | "q": "^1.4.1", 35 | "superagent": "^1.8.3", 36 | "tracer": "^0.8.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /monitor/request.js: -------------------------------------------------------------------------------- 1 | var agent = require('superagent'); 2 | var Q = require('q'); 3 | function Request(){} 4 | //get function 5 | Request.prototype.get = function(url){ 6 | var defer = Q.defer(); 7 | agent 8 | .get(url) 9 | .end(defer.makeNodeResolver()); 10 | 11 | return defer.promise; 12 | }; 13 | //post function 14 | Request.prototype.post = function(url, data){ 15 | var defer = Q.defer(); 16 | if(data.length){ 17 | agent 18 | .post(url) 19 | .send(data) 20 | .end(defer.makeNodeResolver()); 21 | }else{ 22 | agent 23 | .post(url) 24 | .end(defer.makeNodeResolver()); 25 | } 26 | 27 | return defer.promise; 28 | }; 29 | //del function 30 | Request.prototype.del = function(url){ 31 | var defer = Q.defer(); 32 | agent 33 | .del(url) 34 | .end(defer.makeNodeResolver()); 35 | return defer.promise; 36 | 37 | }; 38 | module.exports = new Request(); 39 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "script": "server/app.js", 3 | "ext": "js json", 4 | "watch": [ 5 | "server/" 6 | ], 7 | "env": { 8 | "NODE_ENV": "production" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker-visual", 3 | "version": "1.0.1", 4 | "description": "docker web", 5 | "repository": "https://github.com/gaoyangxiaozhu/DockerVI", 6 | "license": "MIT", 7 | "author": "gyy", 8 | "main": "./server/app.js", 9 | "dependencies": { 10 | "bluebird": "^2.10.1", 11 | "body-parser": "^1.14.0", 12 | "bunyan": "^1.5.1", 13 | "ccap": "^0.6.0", 14 | "composable-middleware": "^0.3.0", 15 | "compression": "^1.5.2", 16 | "connect-mongo": "^0.8.2", 17 | "connect-timeout": "^1.7.0", 18 | "cookie-parser": "^1.4.0", 19 | "errorhandler": "^1.4.2", 20 | "express": "^4.13.3", 21 | "express-jwt": "^3.1.0", 22 | "express-session": "^1.11.3", 23 | "jsonwebtoken": "^5.0.5", 24 | "lodash": "^3.10.1", 25 | "method-override": "^2.3.5", 26 | "moment": "^2.10.6", 27 | "mongoose": "^4.1.9", 28 | "morgan": "^1.6.1", 29 | "multer": "^0.1.8", 30 | "mysql": "^2.10.2", 31 | "passport": "^0.2.2", 32 | "passport-local": "^1.0.0", 33 | "pm2": "^0.14.7", 34 | "q": "^1.4.1", 35 | "qiniu": "^6.1.8", 36 | "serve-favicon": "^2.3.0", 37 | "socket.io": "^1.4.5", 38 | "superagent": "^1.8.3" 39 | }, 40 | "devDependencies": { 41 | "browser-sync": "^2.9.4", 42 | "browser-sync-spa": "^1.0.3", 43 | "del": "^2.0.2", 44 | "event-stream": "^3.3.1", 45 | "gulp": "^3.9.0", 46 | "gulp-angular-filesort": "^1.1.1", 47 | "gulp-angular-templatecache": "^1.7.0", 48 | "gulp-autoprefixer": "^3.0.1", 49 | "gulp-compass": "^2.1.0", 50 | "gulp-concat": "^2.4.3", 51 | "gulp-coveralls": "^0.1.4", 52 | "gulp-csso": "^1.0.0", 53 | "gulp-env": "^0.2.0", 54 | "gulp-filter": "^3.0.1", 55 | "gulp-flatten": "^0.2.0", 56 | "gulp-imagemin": "^2.3.0", 57 | "gulp-inject": "^2.2.0", 58 | "gulp-istanbul": "^0.10.1", 59 | "gulp-jade": "^0.10.0", 60 | "gulp-jshint": "^1.11.2", 61 | "gulp-load-plugins": "^0.10.0", 62 | "gulp-minify-css": "^1.2.0", 63 | "gulp-minify-html": "^1.0.4", 64 | "gulp-mocha": "^2.1.3", 65 | "gulp-ng-annotate": "^1.1.0", 66 | "gulp-ng-config": "^1.2.1", 67 | "gulp-nodemon": "^2.0.4", 68 | "gulp-plumber": "^1.0.1", 69 | "gulp-protractor": "^1.0.0", 70 | "gulp-replace": "^0.5.4", 71 | "gulp-rev": "^6.0.1", 72 | "gulp-rev-replace": "^0.4.2", 73 | "gulp-sass": "^2.0.4", 74 | "gulp-sequence": "^0.4.1", 75 | "gulp-size": "^2.0.0", 76 | "gulp-sourcemaps": "^1.5.2", 77 | "gulp-stylus": "^2.0.4", 78 | "gulp-uglify": "^1.4.1", 79 | "gulp-useref": "^1.3.0", 80 | "gulp-util": "^3.0.6", 81 | "gulp-watch": "^4.3.5", 82 | "http-proxy-middleware": "^0.8.1", 83 | "imagemin-pngquant": "^4.2.0", 84 | "istanbul": "^0.3.20", 85 | "jasmine-core": "^2.3.4", 86 | "jshint-stylish": "^2.0.1", 87 | "nock": "^2.13.0", 88 | "wiredep": "^2.2.2", 89 | "main-bower-files": "^2.13.1" 90 | }, 91 | "engines": { 92 | "node": ">=0.10.0" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /server/api/build/build.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var mongoose = require('mongoose'); 5 | -------------------------------------------------------------------------------- /server/api/build/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./build.controller'); 5 | 6 | 7 | var router = express.Router(); 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /server/api/cluster/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@app docker-visual 3 | *@author gaoyangyang 4 | *@description 5 | * for get swarm cluster resource info 6 | * equal curl http:///info 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var request = require('superagent'); 12 | var express = require('express'); 13 | var endpoint = require('../endpoint').SWARMADDRESS; 14 | var router = express.Router(); 15 | 16 | /** 17 | * @param {string} data 18 | * @param {int} 19 | */ 20 | function getUsedMemByByte(mem){ 21 | var usedMem = parseInt(mem); 22 | var unit = mem.replace(/[0-9\s]*/, ''); 23 | switch (unit.trim()){ 24 | case 'B': 25 | return usedMem; 26 | case 'K': 27 | case 'KB': 28 | case 'Kb': 29 | return usedMem * 1000; 30 | case 'M': 31 | case 'MB': 32 | case 'MiB': 33 | case 'Mb': 34 | return usedMem * 1000 * 1000; 35 | case 'G': 36 | case 'GB': 37 | case 'Gb': 38 | return usedMem * 1000 * 1000 * 1000; 39 | 40 | } 41 | } 42 | 43 | /** 44 | *格式化数据 45 | *@param {Array} data 46 | *@return {Object} ret 47 | */ 48 | function formatData(data){ 49 | var containerNums = parseInt(data.Containers); //容器总个数 50 | var containerRunningNums = parseInt(data.ContainersRunning); //运行容器个数 51 | var containerPauseNums = parseInt(data.ContainersPaused); //暂停容器个数 52 | var containerStopNums = parseInt(data.ContainersStopped);// 停止容器个数 53 | var imageNums = parseInt(data.Images); //镜像总个数 54 | var opSystem = data.OperatingSystem; //操作系统 55 | 56 | var systemData = data.SystemStatus || data.DriverStatus; //兼容低版本docker engine 57 | var nodeArray = []; 58 | var nodes = parseInt(systemData[3][1]) + 1; //集群节点个数 59 | var healtynodes = nodes; //健康节点的个数 60 | 61 | var totalMemByGB = (parseInt(data.MemTotal) / 1000 / 1000 /1000).toFixed(2);//(GB) 62 | var totalMem = parseInt(data.MemTotal); 63 | var totalCpu = parseInt(data.NCPU); 64 | 65 | var totalUsedMem = 0; 66 | var totalUsedCpu = 0; 67 | var memUsedUnit = 'B'; 68 | 69 | var _data = systemData.slice(4); 70 | 71 | var strategy = systemData[1][1]; //strategy 72 | 73 | for(var i = 0; i <= _data.length / 9 - 1; i++){ 74 | var name = _data[i * 9][0]; 75 | var ip = _data[i * 9][1]; 76 | var status = _data[i * 9 + 1][1]; 77 | var cNums = _data[i * 9 + 2][1]; //部署的容器个数 78 | var cpu = _data[i * 9 + 3][1].split('/'); 79 | var mem = _data[i * 9 + 4][1].split('/'); 80 | 81 | var node = { 82 | name : name, 83 | ip : ip, 84 | status : status, 85 | cNums : cNums, 86 | cpu_use : parseInt(cpu[0]), 87 | cpu_has : parseInt(cpu[1]), 88 | mem_use : mem[0], 89 | mem_has : mem[1] 90 | }; 91 | 92 | nodeArray.push(node); 93 | 94 | if(status.trim() != 'Healthy'){ 95 | healtynodes = healtynodes - 1; 96 | } 97 | 98 | totalUsedMem += getUsedMemByByte(mem[0]); 99 | totalUsedCpu += parseInt(cpu[0]); 100 | 101 | } 102 | var ret = { 103 | opSystem : opSystem, 104 | strategy : strategy, 105 | nodes : nodes, 106 | healtynodes : healtynodes, 107 | totalCpu : totalCpu, 108 | totalMem : totalMem, 109 | totalUsedCpu : totalUsedCpu, 110 | totalUsedMem : totalUsedMem, 111 | totalMemByGB : totalMemByGB, 112 | memUsedUnit : memUsedUnit, //计算使用的内存的计量标志 113 | nodeArray : nodeArray, 114 | imageNums : imageNums, 115 | containerNums : containerNums, 116 | containerStopNums : containerStopNums, 117 | containerPauseNums : containerPauseNums, 118 | containerRunningNums : containerRunningNums, 119 | }; 120 | return ret; 121 | } 122 | /** 123 | * [cluster description] 124 | * docker remote api http:/info 接口 125 | * @param {request Objcet} req 126 | * @param {response Object} res 127 | * @return None 128 | */ 129 | function cluster(req, res){ 130 | var url = endpoint + '/info'; 131 | request 132 | .get(url) 133 | .end(function(err, response){ 134 | if(err || !response.ok){ 135 | res.send({'error_msg': 'do you has connect internet?'}); 136 | } 137 | else{ 138 | res.send(formatData(response.body)); 139 | } 140 | }); 141 | } 142 | module.exports = cluster; 143 | -------------------------------------------------------------------------------- /server/api/containers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./containers.controller'); 5 | 6 | var timeout = require('connect-timeout'); 7 | 8 | var router = express.Router(); 9 | 10 | router.get('/getContainerCount', controller.getContainerCount); 11 | router.get('/getContainerList',controller.getContainerList); 12 | router.get('/:id/getContainer', controller.getContainer); 13 | router.get('/:id/getContainerStats', controller.getContainerStats); 14 | //stop or start 15 | router.post('/deleteContainer', controller.deleteContainer); //删除容器 16 | router.post('/:id/updateContainer', controller.updateContainer); 17 | // router.post('/createContainer', timeout('6000s'), controller.createContainer); 18 | 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /server/api/endpoint/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var config = require('../../../Deploy/config');//加载swarm相关配置 3 | exports.SWARMADDRESS=config.swarm.address; 4 | -------------------------------------------------------------------------------- /server/api/images/index.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require('express'); 3 | var controller = require('./images.controller'); 4 | 5 | 6 | var router = express.Router(); 7 | 8 | router.get('/getImagesList', controller.getImagesList); 9 | router.get('/getImagesCount', controller.getImagesCount); 10 | router.get('/:id/getImageDetail', controller.getImageDetail); 11 | router.get('/:id/searchImage', controller.searchImage); 12 | router.delete('/:id', controller.deleteImage); 13 | 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /server/api/user/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./user.controller'); 5 | var auth = require('../../auth/auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router.get('/getUserList', auth.hasRole('admin'), controller.getUserList); 10 | router.post('/addUser', auth.hasRole('admin'), controller.addUser); 11 | router.delete('/:id', auth.hasRole('admin'), controller.destroy); 12 | router.put('/:id/updateUser', auth.hasRole('admin'), controller.updateUser); 13 | 14 | //前台用户更新信息 15 | router.put('/mdUser', auth.isAuthenticated(), controller.mdUser); 16 | router.get('/getCaptcha', controller.getCaptcha); 17 | 18 | router.get('/me', auth.isAuthenticated(), controller.getMe); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /server/api/volumes/index.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require('express'); 3 | var controller = require('./volumes.controller'); 4 | 5 | 6 | var router = express.Router(); 7 | 8 | router.get('/getVolumesList', controller.getVolumesList); 9 | router.get('/searchVolume', controller.getVolumesList); 10 | router.get('/getVolumesCount', controller.getVolumesCount); 11 | router.get('/:id/getVolumesDetail', controller.getVolumesDetail); 12 | router.post('/:id', controller.createNewVolume); 13 | router.delete('/:id', controller.deleteVolume); 14 | 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /server/api/volumes/volumes.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author gaoyangyang 3 | * @description 4 | * 镜像相关的操作 5 | */ 6 | 'use strict'; 7 | 8 | var _ = require('lodash'); 9 | var request = require('../../components/superagent'); 10 | var endpoint = require('../endpoint').SWARMADDRESS; 11 | 12 | 13 | 14 | var _volumesList = []; //[{Name: node1/xxx}, ..., {Name:nodename/yyy}] 15 | 16 | 17 | function _formtData(volumes){ 18 | //volumes参数可能是_filterVList 也可能是获取的最原始的volumes列表 19 | return volumes.map(function(item ,index){ 20 | if('Labels' in item){ 21 | delete item.Labels; 22 | } 23 | if('W' in item){ 24 | delete item.W; 25 | } 26 | var nodeName = item.Name.split('/'); 27 | item.fullName = nodeName[1]; 28 | item.name = nodeName[1].slice(0, 15); 29 | item.node = nodeName[0]; 30 | delete item.Name; 31 | return item; 32 | }); 33 | } 34 | 35 | function _getFilterVList(filterVList, node, name){ 36 | var re; 37 | function _compare(a, b){ 38 | return a.W - b.W; 39 | }; 40 | _volumesList.forEach(function(item, index){ 41 | var Name = item.Name; 42 | if(node){ 43 | re = new RegExp(['.*', node, '.*/.*', name].join('')); 44 | if(Name.match(re)){ 45 | var noRe = new RegExp(node); 46 | var naRe = new RegExp(name); 47 | var _indexForNo = Name.match(noRe).index; 48 | var _indexForNa = Name.match(naRe).index; 49 | var data = { 50 | Name: Name, 51 | W: _indexForNa + _indexForNo 52 | } 53 | filterVList.push(data); 54 | } 55 | }else{ 56 | re = new RegExp(['.*', name].join('')); 57 | if(Name.match(re)){ 58 | var _index = Name.match(re).index; 59 | var data = { 60 | Name: Name, 61 | W: _indexForNa 62 | } 63 | filterVList.push(data); 64 | } 65 | } 66 | }); 67 | filterVList.sort(_compare); 68 | 69 | } 70 | exports.getVolumesList = function(req, res){ 71 | 72 | 73 | var itemsPerPage = (parseInt(req.query.itemsPerPage) > 0) ? parseInt(req.query.itemsPerPage) : 10; 74 | var currentPage = (parseInt(req.query.currentPage) > 0) ? parseInt(req.query.currentPage) : 1; 75 | var name = req.query.name ? req.query.name.trim() : undefined; 76 | var node = req.query.node ? req.query.node.trim() : undefined; 77 | var url = endpoint + '/volumes'; 78 | 79 | request.get(url) 80 | .then(function(response){ 81 | if(!response.ok){ 82 | throw new Error("error"); 83 | }else{ 84 | 85 | var volumes; 86 | var total; 87 | var _data = response.body; 88 | 89 | _volumesList = _data.Volumes.map(function(item, index){ 90 | return {Name: item.Name}; 91 | }); 92 | 93 | var _filterVList = []; 94 | //返回当前需要的数据 95 | try { 96 | if(node || name){ 97 | _getFilterVList(_filterVList, node, name); 98 | }else{ 99 | _filterVList = _volumesList; 100 | } 101 | 102 | total = _filterVList.length; 103 | volumes = _filterVList.slice((currentPage - 1) * 10, (currentPage - 1) * 10 + itemsPerPage); 104 | volumes = _formtData(_filterVList); 105 | 106 | res.send({ volumes: volumes, total: total, msg: 'success'}); 107 | } catch (e) { 108 | throw new Error(e); 109 | } 110 | } 111 | 112 | 113 | }).fail(function(err){ 114 | res.status(404).send({'error_msg': err.message}); 115 | }).done(); 116 | }; 117 | 118 | exports.getVolumesCount = function(req, res){ 119 | 120 | var url = endpoint + '/volumes'; 121 | request.get(url) 122 | .then(function(response){ 123 | var _data = response.body; 124 | if(!response.ok){ 125 | throw new Error("error"); 126 | } 127 | //返回当前需要的数据 128 | try { 129 | res.send({count: _data.Volumes.length}); 130 | } catch (e) { 131 | throw new Error(e); 132 | } 133 | }).fail(function(err){ 134 | res.send({'error_msg': err.message}); 135 | }); 136 | }; 137 | exports.getVolumesDetail = function(req, res){ 138 | 139 | var id = req.params.id; 140 | var node = req.query.node; 141 | 142 | var url = endpoint + '/volumes/' + node + '/' + id; 143 | 144 | request.get(url) 145 | .then(function(response){ 146 | if(!response.ok){ 147 | throw new Error("error"); 148 | } 149 | var _data = response.body; 150 | 151 | /*****format data*******/ 152 | _data.Engine.Memory = _data.Engine.Memory / 1000 / 1000 / 1000; 153 | _data.Engine.Memory = _data.Engine.Memory.toFixed(2) + 'G'; 154 | if(!_data.Engine.Labels.executiondriver){ 155 | delete _data.Engine.Labels.executiondriver; 156 | } 157 | if(!_data.Labels){ 158 | delete _data.Labels; 159 | } 160 | /*******end******/ 161 | try { 162 | res.send(_data); 163 | } catch (e) { 164 | throw new Error(e); 165 | } 166 | 167 | }).fail(function(err){ 168 | res.send({'error_msg': err.message}); 169 | }); 170 | 171 | }; 172 | exports.createNewVolume = function(req, res){ 173 | 174 | var id = req.body.id; 175 | var node = req.body.node; 176 | var url = endpoint + '/volumes/create'; 177 | var postData = {}; 178 | postData.Name = node ? node + '/' + id : id; 179 | 180 | 181 | request.post(url, postData) 182 | .then(function(response){ 183 | if(!response.ok){ 184 | throw new Error("error"); 185 | } 186 | var _data = response.body; 187 | try { 188 | res.send({volume: _data, msg: 'ok'}); 189 | } catch (e) { 190 | throw new Error(e); 191 | } 192 | }).fail(function(err){ 193 | res.send({ error_msg : err.message , status : err.status }); 194 | }); 195 | }; 196 | exports.deleteVolume = function(req ,res){ 197 | 198 | var id = req.params.id; 199 | var node = req.query.node; 200 | var url = endpoint + '/volumes/' + node + '/' + id; 201 | 202 | request.del(url) 203 | .then(function(response){ 204 | if(!response.ok){ 205 | throw new Error('error'); 206 | } 207 | res.send({ msg : 'ok'}); 208 | }).fail(function(err){ 209 | res.send({error_msg: err.message, status: err.status}); 210 | }); 211 | }; 212 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application file 3 | */ 4 | 5 | 'use strict'; 6 | 7 | // 设置默认环境变量 8 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 9 | var express = require('express'); 10 | var host = require('../Deploy/config').host; 11 | var config = require('./config/env'); 12 | var path = require('path'); 13 | var fs = require('fs'); 14 | var mysql = require('mysql'); 15 | var mongoose = require('mongoose'); 16 | 17 | //连接mongoose数据库 18 | mongoose.connect(config.mongo.uri, config.mongo.options); 19 | 20 | var modelsPath = path.join(__dirname, 'model'); 21 | fs.readdirSync(modelsPath).forEach(function (file) { 22 | if (/(.*)\.(js$|coffee$)/.test(file)) { 23 | require(modelsPath + '/' + file); 24 | } 25 | }); 26 | 27 | // 初始化数据 28 | if(config.seedDB) { require('./config/seed'); } 29 | 30 | var app = express(); 31 | var server = require('http').createServer(app); 32 | 33 | require('./config/express')(app); 34 | 35 | require('./routes')(app); 36 | require('./socket')(9090); 37 | //Start server 38 | server.listen(config.port, config.ip, function () { 39 | console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); 40 | }); 41 | 42 | exports = module.exports = app; 43 | -------------------------------------------------------------------------------- /server/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var passport = require('passport'); 5 | var config = require('../config/env'); 6 | var jwt = require('jsonwebtoken'); 7 | var expressJwt = require('express-jwt'); 8 | var compose = require('composable-middleware'); 9 | var User = mongoose.model('User'); 10 | 11 | /** 12 | * 验证token 13 | */ 14 | function authToken(credentialsRequired) { 15 | return compose() 16 | .use(function(req, res, next) { 17 | if(req.query && req.query.hasOwnProperty('access_token')) { 18 | req.headers.authorization = 'Bearer ' + req.query.access_token; 19 | } 20 | next(); 21 | }) 22 | .use(expressJwt({ 23 | secret: config.secrets.session, 24 | credentialsRequired: credentialsRequired //是否抛出错误 25 | })); 26 | } 27 | /** 28 | * 验证用户是否登录 29 | */ 30 | function isAuthenticated() { 31 | return compose() 32 | .use(authToken(true)) 33 | .use(function (err, req, res, next) { 34 | //expressJwt 错误处理中间件 35 | if (err.name === 'UnauthorizedError') { 36 | return res.status(401).send(); 37 | } 38 | next(); 39 | }) 40 | .use(function(req, res, next) { 41 | User.findById(req.user._id, function (err, user) { 42 | if (err) return res.status(500).send(); 43 | if (!user) return res.status(401).send(); 44 | req.user = user; 45 | next(); 46 | }); 47 | }); 48 | } 49 | 50 | /** 51 | * 验证用户权限 52 | */ 53 | function hasRole(roleRequired) { 54 | if (!roleRequired) throw new Error('Required role needs to be set'); 55 | 56 | return compose() 57 | .use(isAuthenticated()) 58 | .use(function meetsRequirements(req, res, next) { 59 | if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) { 60 | next(); 61 | } 62 | else { 63 | return res.status(403).send(); 64 | } 65 | }); 66 | } 67 | 68 | /** 69 | * 生成token 70 | */ 71 | function signToken(id) { 72 | return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*24*7 }); 73 | } 74 | 75 | 76 | exports.isAuthenticated = isAuthenticated; 77 | exports.hasRole = hasRole; 78 | exports.signToken = signToken; 79 | -------------------------------------------------------------------------------- /server/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var config = require('../config/env'); 6 | var mongoose = require('mongoose'); 7 | var User = mongoose.model('User'); 8 | var auth = require('./auth.service'); 9 | 10 | // Passport Configuration 11 | require('./local/passport').setup(User, config); 12 | 13 | var router = express.Router(); 14 | 15 | router.use('/local', require('./local')); 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /server/auth/local/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var express = require('express'); 5 | var passport = require('passport'); 6 | var auth = require('../auth.service'); 7 | var router = express.Router(); 8 | var User = mongoose.model('User'); 9 | 10 | router.post('/', function (req, res, next) { 11 | //测试环境不用验证码 12 | if(process.env.NODE_ENV !== 'test'){ 13 | var error_msg; 14 | if(!req.body.captcha){ 15 | error_msg = "验证码不能为空."; 16 | }else if(req.session.captcha !== req.body.captcha.toUpperCase()){ 17 | error_msg = "验证码错误."; 18 | }else if(req.body.email === '' || req.body.password === ''){ 19 | error_msg = "用户名和密码不能为空."; 20 | } 21 | if(error_msg){ 22 | return res.status(400).send({error_msg:error_msg}); 23 | }else{ 24 | next(); 25 | } 26 | }else{ 27 | next(); 28 | } 29 | 30 | },function(req, res, next) { 31 | passport.authenticate('local', function (err, user, info) { 32 | if (err){ 33 | return res.status(401).send(); 34 | } 35 | if(info){ 36 | return res.status(403).send(info); 37 | } 38 | var token = auth.signToken(user._id); 39 | return res.json({token: token}); 40 | })(req, res, next); 41 | }); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /server/auth/local/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var LocalStrategy = require('passport-local').Strategy; 3 | var logger = require('../../components/logs'); 4 | 5 | exports.setup = function (User, config) { 6 | passport.use(new LocalStrategy({ 7 | usernameField: 'email', 8 | passwordField: 'password' // this is the virtual field on the model 9 | }, 10 | function(email, password, done) { 11 | User.findOne({ 12 | email: email.toLowerCase() 13 | }, function(err, user) { 14 | if (err) return done(err); 15 | if (!user) { 16 | //logger.error({'登录用户名错误!':{'username':email}}); 17 | return done(null, false, { error_msg: '用户名或密码错误.' }); 18 | } 19 | if (!user.authenticate(password)) { 20 | //logger.error({'登录密码错误!':{'username':email}}); 21 | return done(null, false, { error_msg: '用户名或密码错误.' }); 22 | } 23 | 24 | if(user.status === 2){ 25 | //logger.error({'被阻止登录!':{'username':email}}); 26 | return done(null, false, { error_msg: '用户被阻止登录.' }); 27 | } 28 | if(user.status === 0){ 29 | //logger.error({'未验证用户登录!':{'username':email}}); 30 | return done(null, false, { error_msg: '用户未验证.' }); 31 | } 32 | return done(null, user); 33 | }); 34 | } 35 | )); 36 | }; 37 | -------------------------------------------------------------------------------- /server/components/logs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var bunyan = require('bunyan'); 5 | 6 | 7 | var logger = bunyan.createLogger({ 8 | name: 'docker-visual', 9 | serializers: { 10 | req: bunyan.stdSerializers.req, 11 | res: bunyan.stdSerializers.res, 12 | err: bunyan.stdSerializers.err 13 | }, 14 | streams: [ 15 | { 16 | level: 'info', 17 | stream: process.stdout 18 | },{ 19 | level: 'trace', 20 | stream: process.stdout 21 | }, 22 | { 23 | level: 'debug', 24 | stream: process.stderr 25 | },{ 26 | level: 'error', 27 | path: path.join(__dirname,'../../logs/' + process.env.NODE_ENV + '-' +'error.log') 28 | },{ 29 | level:'fatal', 30 | path: path.join(__dirname,'../../logs/' + process.env.NODE_ENV + '-' +'fatal.log') 31 | },{ 32 | level: 'warn', 33 | path: path.join(__dirname,'../../logs/' + process.env.NODE_ENV + '-' +'warn.log') 34 | } 35 | ] 36 | }); 37 | 38 | module.exports = logger; 39 | -------------------------------------------------------------------------------- /server/components/mysql/index.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'); 2 | var async = require('async'); 3 | var mysql = require('mysql'); 4 | var config = require('../../config/env'); 5 | 6 | 7 | function PromiseDB(){ 8 | this.connection = undefined; 9 | this.tryCount = 0; //尝试连接数据库的次数  10 | this.maxTry = 30; //默认最多尝试连接30次 11 | } 12 | 13 | PromiseDB.prototype = Object.create(null); 14 | 15 | 16 | PromiseDB.prototype.connect = function(cb){ 17 | console.log(cb); 18 | if(!cb || Object.prototype.toString.call(cb) != '[object Function]'){ 19 | throw Error("cb show be function."); 20 | } 21 | var that = this; 22 | var data; 23 | that.connection = mysql.createConnection(config.mysql); 24 | that.connection.connect(function(err){ 25 | if(err){ 26 | console.log('error when connecting to db: ', err); 27 | //We introduce a delay before attempting to reconnect, 28 | //to avoid a hot loop, and to allow our node script to 29 | //process asynchronous requests in the meantime. 30 | // If you're also serving http, display a 503 error. 31 | that.tryCount++; 32 | if(that.tryCount > that.maxTry){ 33 | //说明可能服务器没有开启或者deploy_db服务出错 34 | cb(err); 35 | that.tryCount = 0; 36 | }else{ 37 | //重新尝试连接数据库 38 | setTimeout(function(){ 39 | that.connect(cb); 40 | }, 2000); 41 | } 42 | }else{ 43 | //connect success 44 | cb(); 45 | //set error handler 46 | // Connection to the MySQL server is usually 47 | // lost due to either server restart, or a 48 | // connection idle timeout (the wait_timeout 49 | // server variable configures this) 50 | that.connection.on('error', function(err) { 51 | console.log('db error', err); 52 | if(err.code === 'PROTOCOL_CONNECTION_LOST') { 53 | console.log('try reconnect mysql..'); 54 | if(that.onError && Object.prototype.toString.call(that.onError) == '[object Function]'){ 55 | that.connect(function(err){ 56 | if(err){ 57 | that.onError(); 58 | } 59 | }); 60 | } 61 | } else { 62 | console.log('read mysql error: ' + err.message); //以后增加到log日志文件中 63 | } 64 | }); 65 | } 66 | }); 67 | }; 68 | 69 | PromiseDB.prototype.use = function(db){ 70 | var that = this; 71 | var defer = Q.defer(); 72 | 73 | that.connection.query('USE ' + db, defer.makeNodeResolver()); 74 | return defer.promise; 75 | }; 76 | 77 | PromiseDB.prototype.query = function(sql){ 78 | var that =this; 79 | var defer = Q.defer(); 80 | that.connection.query(sql, defer.makeNodeResolver()); 81 | return defer.promise; 82 | }; 83 | PromiseDB.prototype.end = function(){ 84 | var that = this; 85 | if(that.connection){ 86 | that.connection.end(); 87 | } 88 | }; 89 | PromiseDB.prototype.onError = function(){}; 90 | exports.PromiseDB = PromiseDB; 91 | -------------------------------------------------------------------------------- /server/components/qiniu/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var config = require('../../config/env'); 5 | var mongoose = require('mongoose'); 6 | var Promise = require("bluebird"); 7 | var qiniu = require('qiniu'); 8 | 9 | qiniu.conf.ACCESS_KEY = config.qiniu.app_key; 10 | qiniu.conf.SECRET_KEY = config.qiniu.app_secret; 11 | var client = new qiniu.rs.Client(); 12 | 13 | //对一般操作进行promise封装 14 | var uploadFile = Promise.promisify(qiniu.io.putFile, qiniu.io); 15 | var moveFile = Promise.promisify(client.move, client); 16 | var copyFile = Promise.promisify(client.copy, client); 17 | var removeFile = Promise.promisify(client.remove, client); 18 | var statFile = Promise.promisify(client.stat, client); 19 | var fetchFile = Promise.promisify(client.fetch, client); 20 | var allList = Promise.promisify(qiniu.rsf.listPrefix, qiniu.ref); 21 | 22 | exports.uploadFile = uploadFile; 23 | exports.moveFile = moveFile; 24 | exports.copyFile = copyFile; 25 | exports.removeFile = removeFile; 26 | exports.statFile = statFile; 27 | exports.fetchFile = fetchFile; 28 | exports.allList = allList; 29 | 30 | //获取上传凭证 31 | function getUptoken(bucketname) { 32 | var putPolicy = new qiniu.rs.PutPolicy(bucketname); 33 | return putPolicy.token(); 34 | } 35 | //不同空间可以相互操作,在这里只在一个空间下操作 36 | var bucket = config.qiniu.bucket; 37 | exports.bucket = bucket; 38 | //将网络图片上传到七牛服务器 39 | exports.fetch = function (url,key) { 40 | return this.fetchFile(url,bucket,key).spread(function (result,response) { 41 | result.url = config.qiniu.domain + result.key; 42 | return result; 43 | }); 44 | } 45 | 46 | //上传文件 47 | exports.upload = function (path,key) { 48 | var extra = new qiniu.io.PutExtra(); 49 | var uptoken = getUptoken(config.qiniu.bucket); 50 | return this.uploadFile(uptoken, key, path, extra).spread(function(result,response){ 51 | result.url = config.qiniu.domain + result.key; 52 | return result; 53 | }); 54 | } 55 | 56 | //将源空间的指定资源移动到目标空间,或在同一空间内对资源重命名。 57 | exports.move = function(keySrc,keyDest){ 58 | var bucketSrc,bucketDest; 59 | bucketSrc = bucketDest = bucket; 60 | return this.moveFile(bucketSrc, keySrc, bucketDest, keyDest).spread(function (result,response) { 61 | return result; 62 | }); 63 | }; 64 | //复制文件 65 | exports.copy = function(keySrc,keyDest){ 66 | var bucketSrc,bucketDest; 67 | bucketSrc = bucketDest = bucket; 68 | return this.copyFile(bucketSrc, keySrc, bucketDest, keyDest).spread(function (result,response) { 69 | return result; 70 | }); 71 | }; 72 | 73 | exports.remove = function(key){ 74 | return this.removeFile(bucket,key).spread(function (result,response) { 75 | return result; 76 | }) 77 | }; 78 | /* 79 | 列出所有资源, 80 | prefix 想要查询的资源前缀缺省值为空字符串,limit 限制条数缺省值为1000 81 | marker 上一次列举返回的位置标记,作为本次列举的起点信息。缺省值为空字符串 82 | */ 83 | exports.list = function(prefix, marker, limit){ 84 | return this.allList(bucket, prefix, marker, limit).spread(function(result,response){ 85 | return result; 86 | }) 87 | }; 88 | 89 | 90 | -------------------------------------------------------------------------------- /server/components/request/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 实现一个简单的clientHttpRequest对象 主要是实现了data方法 3 | * 用于获取docker的实时日志流数据 4 | * @author gaoyangyang 5 | * @module using for request 6 | */ 7 | "use strict"; 8 | var http = require('http'); 9 | var util = require('util'); 10 | var EventEmitter = require('events'); 11 | 12 | 13 | function parseURL(url){ 14 | var tmp = url.match(/(\w{2}\.\w{3}\.\w{3}\.\w{3}):(\w*)(.*)/); 15 | var hostname = tmp[1]; 16 | var port = parseInt(tmp[2]); 17 | var path = tmp[3]; 18 | return { 19 | hostname : hostname, 20 | port : port, 21 | path : path 22 | }; 23 | } 24 | 25 | /********Docker request Objce Define for get Log ********/ 26 | function GRequest(){ 27 | 28 | this.options={}; 29 | //循环监听是否有新的数据到来的事件监听器的句柄 30 | this.timeHander = null; 31 | this.response = {}; //存储获取的数据 32 | this.exit = false;  //是否退出监听当前的数据流 33 | this.hasError = false ; //是否发生错误 34 | return this; 35 | } 36 | GRequest.prototype = Object.create(null); 37 | 38 | GRequest.prototype.get = function(url){ 39 | this.options = parseURL(url); 40 | this.options.method = 'GET'; 41 | //解析url以后返回this用于链式调用 42 | return this; 43 | }; 44 | 45 | GRequest.prototype.data = function(cb){ 46 | 47 | var that = this; 48 | 49 | /** 50 | * private function 51 | * 52 | * @return { function } 作为res data事件处理器 53 | */ 54 | function _makeDataFunc(){ 55 | var count = 0; 56 | function sendData(){ 57 | //如果exit为True 说明当前http请求已经结束或者出错 58 | if(that.exit){ 59 | that.exit = false; 60 | if(that.timeHander){ 61 | clearInterval(that.timeHander); 62 | that.timeHander = null; 63 | } 64 | if(that.hasError){ 65 | that.send(that.hasError, cb); 66 | }else{ 67 | that.send(null, cb); 68 | } 69 | }else{ 70 | //如果count >=2说明当前这一部分的数据流已经完全获取 71 | if(count >= 2){ 72 | if(!that.response.text){ 73 | if(count >= 10){ 74 | count = 0; 75 | that.send(null, cb); //如果连续10次为空 直接发送空内容 76 | } 77 | }else{ 78 | count = 0; 79 | that.send(null, cb); 80 | } 81 | } 82 | count++; 83 | } 84 | 85 | } 86 | //轮询监听函数 如果连续两次data获取的数据为空 87 | that.timeHander = setInterval(sendData, 500); 88 | 89 | //返回的用于绑定在res.data的事件处理函数 用于实时获取数据流 90 | return function(chunk){ 91 | //获取新的数据并追加到response.text 92 | that.response.text = that.response.text ? that.response.text += chunk : chunk.toString(); 93 | count = 0; 94 | 95 | return true; 96 | }; 97 | } 98 | // http.request方法 返回一个clientRequest对象 99 | that.request = http.request(this.options, function(res){ 100 | var err = false; // http请求是否出错 101 | that.timeHander = null; 102 | 103 | res.on('data', that.dataFunc); 104 | res.on('end', function(){ 105 | //当前数据发送以后 当前请求就结束 106 | that.exit = true; 107 | return; 108 | }); 109 | res.on('error', function(err){ 110 | that.exit = true; 111 | that.hasError = err; 112 | return; 113 | }); 114 | }); 115 | 116 | that.request.on('error', function(err){ 117 | that.exit = true; 118 | that.hasError = err; 119 | return; 120 | }); 121 | that.request.on('socket', function(socket){ 122 | that.dataFunc = _makeDataFunc(); 123 | }); 124 | 125 | that.request.end(); 126 | //返回this 127 | return this; 128 | }; 129 | //send函数 只有在需要数据发送时 或者当前数据流结束时(end事件触发) 或者发生err时调用 130 | GRequest.prototype.send = function(err, cb){ 131 | if(err){//如果发生err 132 | cb(err, this.response); 133 | }else{ 134 | cb(null, this.response); 135 | } 136 | this.response = {}; 137 | return; 138 | }; 139 | GRequest.prototype.abort = function(){ 140 | //TODO 141 | this.exit = true; 142 | this.response = {}; 143 | 144 | if(this.globalReq){ 145 | this.globalReq.abort(); //中断当前socket 146 | delete this.globalReq; 147 | } 148 | }; 149 | 150 | module.exports = GRequest; 151 | -------------------------------------------------------------------------------- /server/components/superagent/index.js: -------------------------------------------------------------------------------- 1 | var agent = require('superagent'); 2 | var Q = require('q'); 3 | function Request(){} 4 | //get function 5 | Request.prototype.get = function(url){ 6 | var defer = Q.defer(); 7 | agent 8 | .get(url) 9 | .end(defer.makeNodeResolver()); 10 | 11 | return defer.promise; 12 | }; 13 | //post function 14 | Request.prototype.post = function(url, data){ 15 | var defer = Q.defer(); 16 | if(data){ 17 | agent 18 | .post(url) 19 | .send(data) 20 | .end(defer.makeNodeResolver()); 21 | }else{ 22 | agent 23 | .post(url) 24 | .end(defer.makeNodeResolver()); 25 | } 26 | 27 | return defer.promise; 28 | }; 29 | //del function 30 | Request.prototype.del = function(url){ 31 | var defer = Q.defer(); 32 | agent 33 | .del(url) 34 | .end(defer.makeNodeResolver()); 35 | return defer.promise; 36 | 37 | }; 38 | module.exports = new Request(); 39 | -------------------------------------------------------------------------------- /server/components/tools/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var _ = require('lodash'); 3 | //生成随机字符串 4 | exports.randomString = function (len) { 5 |   len = len || 12; 6 |   var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/ 7 |   var maxPos = $chars.length; 8 |   var pwd = ''; 9 |   for (var i = 0; i < len; i++) { 10 |     pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); 11 |   } 12 |   return pwd; 13 | }; 14 | //从markdown中提取图片 15 | exports.extractImage = function (content) { 16 | var results = []; 17 | var images = content.match(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g); 18 | if(_.isArray(images) && images.length > 0){ 19 | for(var i = 0,j = images.length;i?[ \t]*((['"])(.*?)\6[ \t]*)?\))/,function ($1,m1,m2,m3,m4) { 21 | return m4 || ''; 22 | }); 23 | if(url !== ''){ 24 | results.push({url:url}); 25 | } 26 | } 27 | } 28 | return results; 29 | }; 30 | //判断对象是否为空对象 31 | exports.isNullObj = function (obj){ 32 | 33 | if(!(obj && Object.prototype.toString.call(obj) === '[object Object]')){ 34 | return false; 35 | } 36 | for(var i in obj){ 37 | if(obj.hasOwnProperty(i)){ 38 | return false; 39 | } 40 | } 41 | return true; 42 | }; 43 | -------------------------------------------------------------------------------- /server/config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 开发环境配置 4 | // ================================== 5 | module.exports = { 6 | mysql: { 7 | host: '0.0.0.0', 8 | port: '3333', 9 | user: 'root', 10 | password: '123123', 11 | database:'docker' 12 | }, 13 | mongo: { 14 | uri: 'mongodb://localhost:27017/gyyzyp-dev' 15 | }, 16 | seedDB: true 17 | }; 18 | -------------------------------------------------------------------------------- /server/config/env/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var _ = require('lodash'); 5 | var fs = require('fs'); 6 | 7 | var all = { 8 | env: process.env.NODE_ENV, 9 | root: path.normalize(__dirname + '/../../..'), 10 | port: process.env.PORT || 9000, 11 | mongo: { 12 | options: { 13 | db: { 14 | safe: true 15 | } 16 | } 17 | }, 18 | secrets: { 19 | session: 'docker-secret' 20 | }, 21 | userRoles: ['user', 'admin'], 22 | qiniu:{ 23 | app_key:"app_key", 24 | app_secret:"app_secret", 25 | domain:"domain", //七牛配置域名 26 | bucket:"bucket" //七牛空间名称 27 | }, 28 | github:{ 29 | clientID:"github", 30 | clientSecret:"clientSecret", 31 | callback:"/auth/github/callback" 32 | }, 33 | }; 34 | 35 | var config = _.merge(all, require('./' + process.env.NODE_ENV + '.js') || {}); 36 | //加载私有配置 37 | if (fs.existsSync(path.join(__dirname, 'private/index.js'))) { 38 | config = _.merge(config, require(path.join(__dirname, 'private/index.js')) || {}); 39 | } 40 | module.exports = config; 41 | -------------------------------------------------------------------------------- /server/config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 生产环境配置 4 | // ================================= 5 | module.exports = { 6 | port: process.env.PORT || 8100, 7 | mysql: { 8 | host: 'db', 9 | port: '3306', 10 | user: 'root', 11 | password: 'root', 12 | database:'docker' 13 | }, 14 | mongo: { 15 | uri: 'mongodb://localhost:27017/docker-pro' 16 | }, 17 | seedDB: true 18 | }; 19 | -------------------------------------------------------------------------------- /server/config/env/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 测试环境配置 4 | // =========================== 5 | module.exports = { 6 | mysql: { 7 | host: 'db', 8 | port: '3306', 9 | user: 'root', 10 | password: 'root', 11 | database:'docker' 12 | }, 13 | port: process.env.PORT || 8080 14 | }; 15 | -------------------------------------------------------------------------------- /server/config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Express configuration 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var express = require('express'); 8 | var favicon = require('serve-favicon'); 9 | var morgan = require('morgan'); 10 | var compression = require('compression'); 11 | var bodyParser = require('body-parser'); 12 | var multer = require('multer'); 13 | var methodOverride = require('method-override'); 14 | var cookieParser = require('cookie-parser'); 15 | var errorHandler = require('errorhandler'); 16 | var path = require('path'); 17 | var config = require('./env'); 18 | var passport = require('passport'); 19 | var session = require('express-session'); 20 | var MongoStore = require('connect-mongo')(session); 21 | var mongoose = require('mongoose'); 22 | 23 | module.exports = function(app) { 24 | var env = app.get('env'); 25 | 26 | app.use(compression()); 27 | app.use(bodyParser.urlencoded({ extended: false })); 28 | app.use(bodyParser.json()); 29 | app.use(multer()); 30 | app.use(methodOverride()); 31 | app.use(cookieParser()); 32 | app.use(session({ 33 | secret: config.secrets.session, 34 | resave: false, 35 | saveUninitialized: true, 36 | store: new MongoStore({ mongooseConnection: mongoose.connection }), 37 | cookie: { maxAge: 60000 } 38 | })); 39 | app.use(passport.initialize()); 40 | if ('development' === env) { 41 | app.use(errorHandler()); 42 | }else if ('test' === env) { 43 | app.set('appPath', 'dist'); 44 | app.use(express.static(path.join(config.root, 'dist'))); 45 | }else{ 46 | app.set('appPath', 'dist'); 47 | app.use(express.static(path.join(config.root, 'dist'))); 48 | app.use(favicon(path.join(config.root, 'dist', 'favicon.ico'))); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /server/config/seed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 初始化数据 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var mongoose = require('mongoose'), 8 | User = mongoose.model('User'); 9 | var Promise = require('bluebird'); 10 | 11 | //初始化用户(seedDB=true) 默认只有一个admin用户 12 | 13 | User.countAsync().then(function (count) { 14 | if(count === 0){ 15 | User.removeAsync().then(function () { 16 | User.createAsync({ 17 | nickname:'admin', 18 | email:'gyycoder@gmail.com', 19 | role:'admin', 20 | password:'admin', 21 | status:1 22 | }); 23 | }); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /server/logs/development-error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/server/logs/development-error.log -------------------------------------------------------------------------------- /server/logs/development-fatal.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/server/logs/development-fatal.log -------------------------------------------------------------------------------- /server/logs/development-warn.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/server/logs/development-warn.log -------------------------------------------------------------------------------- /server/model/logs.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | Schema = mongoose.Schema; 5 | 6 | var LogsSchema = new Schema({ 7 | uid: { 8 | type:Schema.Types.ObjectId, 9 | ref:'User' 10 | }, 11 | content: { 12 | type:String, 13 | trim: true 14 | }, 15 | type: String, 16 | created: { 17 | type: Date, 18 | default: Date.now 19 | } 20 | }); 21 | 22 | var Logs = mongoose.model('Logs', LogsSchema); 23 | 24 | var Promise = require('bluebird'); 25 | Promise.promisifyAll(Logs); 26 | Promise.promisifyAll(Logs.prototype); 27 | 28 | module.exports = Logs; 29 | -------------------------------------------------------------------------------- /server/model/user.model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户表 3 | */ 4 | 'use strict'; 5 | 6 | var mongoose = require('mongoose'); 7 | var Schema = mongoose.Schema; 8 | var crypto = require('crypto'); 9 | 10 | 11 | var UserSchema = new Schema({ 12 | nickname : String, 13 | email: { 14 | type : String, 15 | lowercase : true 16 | }, 17 | hashedPassword: String, 18 | salt: String, 19 | role: { 20 | type : String , 21 | default : 'user' 22 | }, 23 | avatar: String, 24 | status:{ 25 | type:Number, 26 | default:0 27 | }, 28 | created: { 29 | type: Date, 30 | default: Date.now 31 | }, 32 | updated: { 33 | type: Date, 34 | default: Date.now 35 | } 36 | }); 37 | 38 | /** 39 | * Virtuals 40 | * 可以通过user.password获取 密码 41 | * 可以通过user.password='xxx' 设置密码  42 | */ 43 | UserSchema 44 | .virtual('password') 45 | .set(function(password) { 46 | this._password = password; 47 | this.salt = this.makeSalt(); 48 | this.hashedPassword = this.encryptPassword(password); 49 | }) 50 | .get(function() { 51 | return this._password; 52 | }); 53 | 54 | 55 | UserSchema 56 | .virtual('userInfo') 57 | .get(function() { 58 | return { 59 | 'nickname': this.nickname, 60 | 'role': this.role, 61 | 'email': this.email, 62 | 'avatar': this.avatar 63 | }; 64 | }); 65 | 66 | // Non-sensitive info we'll be putting in the token 67 | UserSchema 68 | .virtual('token') 69 | .get(function() { 70 | return { 71 | '_id': this._id, 72 | 'role': this.role 73 | }; 74 | }); 75 | 76 | UserSchema 77 | .path('nickname') 78 | .validate(function(value, respond) { 79 | var self = this; 80 | this.constructor.findOne({nickname: value}, function(err, user) { 81 | if(err) throw err; 82 | if(user) { 83 | if(self.id === user.id) return respond(true); 84 | return respond(false); 85 | } 86 | respond(true); 87 | }); 88 | }, '这个呢称已经被使用.'); 89 | /** 90 | * methods 91 | */ 92 | UserSchema.methods = { 93 | //检查用户权限 94 | hasRole: function(role) { 95 | var selfRoles = this.role; 96 | return (selfRoles.indexOf('admin') !== -1 || selfRoles.indexOf(role) !== -1); 97 | }, 98 | //验证用户密码 99 | authenticate: function(plainText) { 100 | return this.encryptPassword(plainText) === this.hashedPassword; 101 | }, 102 | //生成盐 103 | makeSalt: function() { 104 | return crypto.randomBytes(16).toString('base64'); 105 | }, 106 | //生成密码 107 | encryptPassword: function(password) { 108 | if (!password || !this.salt) return ''; 109 | var salt = new Buffer(this.salt, 'base64'); 110 | return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); 111 | } 112 | }; 113 | 114 | UserSchema.set('toObject', { virtuals: true }); 115 | 116 | var User = mongoose.model('User', UserSchema); 117 | 118 | var Promise = require('bluebird'); 119 | Promise.promisifyAll(User); 120 | Promise.promisifyAll(User.prototype); 121 | 122 | module.exports = User; 123 | -------------------------------------------------------------------------------- /server/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application routes 3 | */ 4 | 'use strict'; 5 | 6 | var path = require('path'); 7 | 8 | var auth = require('./auth/auth.service') 9 | 10 | module.exports = function(app){ 11 | 12 | //重定向 /xxx/ to /xxx 13 | app.use(function (req, res, next) { 14 | if(req.originalUrl && req.originalUrl.lastIndexOf('/') == req.originalUrl.length - 1){ 15 | res.redirect(req.originalUrl.slice(0, -1)); 16 | }else{ 17 | next(); 18 | } 19 | }); 20 | 21 | app.use('/auth', require('./auth')); 22 | app.use('/api/users', require('./api/user')); 23 | 24 | app.use('/api/cluster', auth.isAuthenticated(), require('./api/cluster')); 25 | app.use('/api/containers', auth.isAuthenticated(), require('./api/containers')); 26 | app.use('/api/images', auth.isAuthenticated(), require('./api/images')); 27 | app.use('/api/volumes', auth.isAuthenticated(), require('./api/volumes')); 28 | 29 | var env = app.get('env'); 30 | if ('development' !== env) { 31 | app.route('/*').get(function(req, res) { 32 | res.sendFile(path.resolve(__dirname, '../' + app.get('appPath') + '/index.html')); 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/app/app.conf.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module("dockerApp") 4 | .constant("IsDebug", false); 5 | 6 | })(); -------------------------------------------------------------------------------- /src/app/base.scss: -------------------------------------------------------------------------------- 1 | @import "compass/css3"; 2 | @import "compass/css3/flexbox"; 3 | @import "compass/utilities/sprites"; 4 | // $icons: sprite-map("sort/*.png",$new-position: 100%, $new-spacing: 55px, $new-repeat: no-repeat); 5 | // .icon-sort-desc { background: sprite($icons, desc); } 6 | 7 | $baseFontColor : #aab2bd; //默认段落字体颜色 8 | $titleFontColor : #656D78; //默认标题字体颜色 9 | $textMutedColor: #777; //默认柔色字体 10 | $dangerColor: #e74c3c; //text-danger 字体 11 | 12 | $baseBorderColor : #d9d9d9; //边框颜色 13 | $baseBgColor : #ecf0f1; //默认网站背景颜色 14 | $basePanelBgColor : white; //默认面板背景颜色 15 | $linkBaseColor : #337ab7; //链接颜色 16 | $linkBaseHoverColor : #717171; //链接悬浮颜色 17 | $linkTitleColor : #555; //标题链接颜色 18 | 19 | $linkTitleHoverColor : black; //标题链接悬浮颜色 20 | $linkLightColor : #4094c7; //高亮链接颜色 21 | $linkLightHoverColor : #075b8d;//高亮链接悬浮颜色 22 | 23 | //msg 24 | $defaultMsgBgColor: #F5BBBB; //默认的消息提示span的背景色 25 | $defaultMsgColor: #895810; //默认的msg消息提醒的span的字体颜色 26 | //navbar 27 | $navBgColor: #3e474e; 28 | $navBdColor: #f49484; 29 | $navBarLinkColor: #337ab7; 30 | $navHoverColor: $linkLightHoverColor; 31 | $navHoverBgColor: darken($navBgColor, 3%); 32 | $navActiveBgColor: darken($navBgColor, 7%); 33 | 34 | 35 | //btn 36 | $defaultBtnColor: #ecf0f1; 37 | $defaultBtnBdColor: #ccd1d9; //默认的btn的边框颜色 38 | $primaryBtnColor: #5faee3; 39 | $greenBtnColor: #2ecc71; 40 | $blueBtnColor: #4E85F3; 41 | 42 | 43 | //input 44 | $defaultInputHeight : 33px; //默认input大小 45 | 46 | //border 47 | $defaultBorderColor : #EAE9E9;// #e6e9ed; 48 | 49 | 50 | //form relate 51 | $formColor: #555; 52 | $formBdColor:#ddd; 53 | $formBgColor: #fff; 54 | 55 | //size 56 | $navbarSize:85px; 57 | $defaultFontSize : 16px; //默认字体大小 58 | 59 | 60 | //default table 61 | $table-title-color: #9ba3af; 62 | $table-bg: white; 63 | $table-selected-bg: #fffced; 64 | $table-bg-accent : #fff; 65 | $table-bg-hover : rgba(245,247,250, .8); 66 | $table-bg-active : $table-bg-hover; 67 | $table-border-color : #e4e7ed; 68 | $table-font-color : #595f69; 69 | $pagination-bg : #fff; 70 | $pagination-border : #2f2f2f; 71 | $pagination-active-bg : #337ab7; 72 | $pagination-active-border : #337ab7; 73 | 74 | //共用内容容器 75 | @mixin public-box($mediaWidth:700px){ 76 | width:100%; 77 | margin: 0 auto; 78 | padding: 20px 10px 10px $navbarSize + 12px; 79 | @media (min-width: $screen-sm-min){ 80 | width:$mediaWidth; 81 | padding: 0 0 10px $navbarSize + 12px; 82 | } 83 | } 84 | 85 | //垂直居中容器 86 | .flex-vertical-box{ 87 | @include display-flex; 88 | @include flex-wrap(wrap); 89 | @include justify-content(center); 90 | @include align-items(center); 91 | } 92 | -------------------------------------------------------------------------------- /src/app/cluster/cluster.jade: -------------------------------------------------------------------------------- 1 | .content-body.cluster(ng-if="load.loaded") 2 | .container-fluid 3 | .row 4 | .col-md-6 5 | .panel.container-msg 6 | section.panel-body 7 | h3 8 | i.fa.fa-cubes(aria-hidden="true") 9 | | 集群容器信息概要 10 | .chart 11 | cmchart(chart-option="swarm.container.option" style="width:600px; height:400px; position:relative; display:block") 12 | .description.container 13 | div(style="margin-left:35%") 14 | span.has 共计 {{ swarm.container.nums }}个容器应用 15 | div(style="margin-left:5%") 16 | span.running.used {{ swarm.container.running }}个主机处于运行状态 17 | span.paused {{ swarm.container.paused }}个处于暂停状态 18 | span.stop {{ swarm.container.stop }}个处于停止状态 19 | 20 | .col-md-5 21 | .row 22 | .col-md-12 23 | .panel.image-msg 24 | section.panel-body 25 | h3 26 | i.fa.fa-file(aria-hidden="true") 27 | |镜像 28 | p 29 | strong {{ swarm.imageNums }} 30 | | 个本地镜像 31 | .row 32 | .col-md-12 33 | .panel.volume-msg 34 | section.panel-body 35 | h3 36 | i.fa.fa-database(aria-hidden="true") 37 | | 存储卷 38 | p 39 | strong {{ swarm.volumeNums }} 40 | | 个本地存储卷 41 | .row 42 | .col-md-12 43 | .panel.host-msg 44 | section.panel-body 45 | h3 46 | i.fa.fa-server(aria-hidden="true") 47 | | 主机概况 48 | div 49 | .chart 50 | cmchart(chart-option="swarm.host.option" style="width:400px; height:400px; position:relative; display:block") 51 | .description 52 | span.has 共计 {{ swarm.nodes }}个主机 53 | br 54 | span 其中 {{ swarm.healtynodes}} 个主机处于健康状态 55 | div 56 | .chart 57 | cmchart(chart-option="swarm.cpu.option" style="width:400px; height:400px; position:relative; display:block") 58 | .description 59 | span.has 可分配 {{ swarm.cpu.has }} core 60 | br 61 | span.used 已使用 {{ swarm.cpu.use }} core 62 | div 63 | .chart 64 | cmchart(chart-option="swarm.mem.option" style="width:400px; height:400px; position:relative; display:block") 65 | .description 66 | span.has 可分配 {{ swarm.mem.has }} GB 67 | br 68 | span.used 已使用 {{ swarm.mem.use }} GB 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/app/cluster/cluster.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.cluster',[]) 5 | .config(function ($stateProvider) { 6 | $stateProvider 7 | .state('cluster', { 8 | url: '/cluster', 9 | templateUrl: 'app/cluster/cluster.html', 10 | controller: 'clusterCtrl' 11 | }); 12 | }); 13 | })(); 14 | -------------------------------------------------------------------------------- /src/app/cluster/cluster.scss: -------------------------------------------------------------------------------- 1 | .content-body.cluster{ 2 | .panel{ 3 | .panel-body{ 4 | h3{ 5 | color : #454242; 6 | font-weight: bolder; 7 | i{ 8 | font-size: inherit; 9 | padding-right: 5px; 10 | } 11 | } 12 | p{ 13 | strong{ 14 | font-size: 2em; 15 | } 16 | } 17 | .chart{ 18 | .description{ 19 | width: 60%; 20 | margin-left: 40%; 21 | span{ 22 | display: inline-block; 23 | &:before{ 24 | content: ''; 25 | display: inline-block; 26 | width: 16px; 27 | height: 16px; 28 | background-color: transparent; 29 | 30 | position: relative; 31 | top: 2px; 32 | margin-right: 5px; 33 | } 34 | &.has::before{ 35 | background-color: #12b668; 36 | } 37 | &.used::before{ 38 | background-color: #60e5a6; 39 | } 40 | } 41 | &.container{ 42 | width: 100%; 43 | margin-left: 0; 44 | span{ 45 | &::before{ 46 | margin-left: 10px; 47 | } 48 | &.running::before{ 49 | background-color: #c23531; 50 | } 51 | &.stop::before{ 52 | background-color: #61a0a8; 53 | } 54 | &.paused::before{ 55 | background-color: #2f4554; 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | &.host-msg{ 63 | .panel-body{ 64 | > div{ 65 | float: left; 66 | } 67 | } 68 | 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app/components/directive/cmchart.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.directives') 5 | .directive('cmchart', [function(){ 6 | 7 | return { 8 | // Restrict to elements and attributes 9 | restrict:'EA', 10 | scope: { 11 | option: '=chartOption' 12 | }, 13 | // Assign the angular link function 14 | link: filedLink 15 | }; 16 | 17 | /** 18 | * Link the directive to enable our scope watch values 19 | * 20 | * @param {object} scope - Angular link scope 21 | * @param {object} el - Angular link element 22 | * @param {object} attrs - Angular link attribute 23 | */ 24 | function filedLink(scope, el, attr){ 25 | scope.$watchCollection('[option]', function(){ 26 | if(scope.option){ 27 | build(scope, el, attr); 28 | } 29 | }); 30 | } 31 | /* 32 | * The main build function used to determine the logic 33 | * 34 | * @param {Object} scope - The local directive scope object 35 | * @param {Object} attrs - The local directive attribute object 36 | */ 37 | function build(scope, el, attr){ 38 | var that = echarts.init(el[0]); 39 | if(scope.option){ 40 | that.setOption(scope.option); 41 | } 42 | } 43 | }]); 44 | })(); 45 | -------------------------------------------------------------------------------- /src/app/components/directive/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('dockerApp.directives', ['dockerApp.resources']); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/app/components/directive/mytab.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.directives') 5 | .directive('mytab', ['Container', function (Container) { 6 | 7 | return { 8 | // Restrict to elements and attributes 9 | restrict:'EA', 10 | // Assign the angular link function 11 | link: filedLink 12 | }; 13 | 14 | /** 15 | * Link the directive to enable our scope watch values 16 | * 17 | * @param {object} scope - Angular link scope 18 | * @param {object} el - Angular link element 19 | * @param {object} attrs - Angular link attribute 20 | */ 21 | function filedLink(scope, el, attr){ 22 | build(scope, el, attr); 23 | } 24 | 25 | /** 26 | * The main build function used to determine the logic 27 | * 28 | * @param {Object} scope - The local directive scope object 29 | * @param {Object} attrs - The local directive attribute object 30 | */ 31 | function build(scope, el, attr){ 32 | el.on('click', function(){ 33 | var that = $(this); 34 | var $parent = that.parent('li'); 35 | 36 | var targetId = that.attr('href'); 37 | var $target = $(targetId); 38 | 39 | if(!$parent.hasClass('active')){ 40 | $parent.addClass('active').siblings().removeClass('active'); 41 | $target.addClass('active').siblings().removeClass('active'); 42 | } 43 | }); 44 | 45 | } 46 | }]); 47 | })(); 48 | -------------------------------------------------------------------------------- /src/app/components/directive/nav.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.directives') 5 | .directive('nav', [function (){ 6 | 7 | return { 8 | // Restrict to elements and attributes 9 | restrict:'C', 10 | // Assign the angular link function 11 | link: filedLink 12 | }; 13 | 14 | /** 15 | * Link the directive to enable our scope watch values 16 | * 17 | * @param {object} scope - Angular link scope 18 | * @param {object} el - Angular link element 19 | * @param {object} attrs - Angular link attribute 20 | */ 21 | function filedLink(scope, el, attr){ 22 | build(scope, el, attr); 23 | } 24 | 25 | /** 26 | * The main build function used to determine the logic 27 | * 28 | * @param {Object} scope - The local directive scope object 29 | * @param {Object} attrs - The local directive attribute object 30 | */ 31 | function build(scope, el, attr){ 32 | el.find('li').on('click', function(){ 33 | var that = $(this); 34 | if(!that.hasClass('active')){ 35 | that.addClass('active').siblings().removeClass('active'); 36 | } 37 | }); 38 | 39 | } 40 | }]); 41 | })(); 42 | -------------------------------------------------------------------------------- /src/app/components/header/header.controller.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | angular.module("dockerApp") 3 | .controller('headerCtrl', ['$scope','$rootScope', '$state', '$stateParams', '$location', 'Auth', function($scope, $rootScope, $state, $stateParams, $location, Auth){ 4 | 5 | $scope.user = Auth.getCurrentUser(); 6 | 7 | $scope.manageUser = function(){ 8 | $state.go('userList'); 9 | }; 10 | $scope.logout = function(){ 11 | Auth.logout(); 12 | $state.go('home'); 13 | }; 14 | 15 | }]); 16 | })(); 17 | -------------------------------------------------------------------------------- /src/app/components/header/header.jade: -------------------------------------------------------------------------------- 1 | 2 | nav.navbar(ng-controller="headerCtrl") 3 | div(ng-class="{ 'vi-container' : pageClass != 'dashboard', 'container': pageClass == 'dashboard' }") 4 | .navbar-text.navbar-right.userbar 5 | div.btn-group(ng-class="{'open' : dropdown.hover}" ng-mouseover="dropdown.hover=true;" ng-mouseleave="dropdown.hover=false;" dropdown) 6 | button.dropdown-toggle(type="button" dropdown-toggle aria-haspopup="true" aria-expanded="false") 7 | img.userbar-img(ng-src="{{ default_avatar }}") 8 | | {{ user.nickname }} 9 | i.fa.fa-angle-down 10 | ul.dropdown-menu(role="menu") 11 | li(ng-if="user.role === 'admin'") 12 | a(href ng-click="manageUser()" tabindex="0") 管理用户 13 | li 14 | a(href="#" ng-click="logout()" tabindex="0") 登出 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/components/header/header.scss: -------------------------------------------------------------------------------- 1 | .container, 2 | .vi-container{ 3 | .userbar{ 4 | .btn-group{ 5 | .dropdown-toggle{ 6 | background-color: transparent; 7 | border: none; 8 | @include box-shadow(0 0 0 #fff); 9 | color: #98A0AA; 10 | @include border-radius(3px); 11 | margin-right: 25px; 12 | img{ 13 | width: 22px; 14 | height: 22px; 15 | line-height: 1; 16 | margin-right: 5px; 17 | border-radius: 100%; 18 | } 19 | } 20 | } 21 | .dropdown{ 22 | .dropdown-menu{ 23 | @include scale(1); 24 | } 25 | } 26 | .open{ 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/resource/cluster.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.resources') 5 | .factory('Cluster', function($resource){ 6 | var clustersResource = $resource('/api/cluster/'); 7 | return { 8 | getClusterList : function(data, callback){ 9 | var cb = callback || angular.noop; 10 | return clustersResource.get(function(result) { 11 | return cb(result); 12 | }, function(err) { 13 | return cb(err); 14 | }).$promise; 15 | } 16 | }; 17 | }); 18 | })(); 19 | -------------------------------------------------------------------------------- /src/app/components/resource/container.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.resources') 5 | .factory('Container', function($resource){ 6 | var containerResource = $resource('/api/containers/:id/:controller', { 7 | id: '@_id' 8 | }, 9 | { 10 | //添加标签分类 11 | getContainerList: { 12 | method: 'GET', 13 | params: { 14 | controller:'getContainerList' 15 | }, 16 | isArray: true 17 | }, 18 | getContainerCount:{ 19 | method: 'GET', 20 | params:{ 21 | controller: 'getContainerCount' 22 | } 23 | }, 24 | getContainer:{ 25 | method: 'GET', 26 | params: { 27 | controller:'getContainer' 28 | } 29 | }, 30 | createContainer:{ 31 | method:'POST', 32 | params:{ 33 | controller:'createContainer' 34 | } 35 | }, 36 | updateContainer:{ 37 | method: 'POST', 38 | params: { 39 | controller: 'updateContainer' 40 | } 41 | }, 42 | deleteContainer:{ 43 | method: 'POST', 44 | params:{ 45 | controller: 'deleteContainer' 46 | } 47 | }, 48 | getContainerStats:{ 49 | method:'GET', 50 | params:{ 51 | controller: 'getContainerStats' 52 | } 53 | } 54 | }); 55 | return { 56 | getContainerList : function(data, callback){ 57 | var cb = callback || angular.noop; 58 | return containerResource.getContainerList(data, function(result) { 59 | return cb(result); 60 | }, function(err) { 61 | return cb(err); 62 | }).$promise; 63 | }, 64 | getContainerCount : function(data, callback){ 65 | var cb = callback || angular.noop; 66 | return containerResource.getContainerCount(data, function(result) { 67 | return cb(result); 68 | }, function(err) { 69 | return cb(err); 70 | }).$promise; 71 | }, 72 | getContainer : function (data, callback) { 73 | var cb = callback || angular.noop; 74 | return containerResource.getContainer(data, function(result) { 75 | return cb(result); 76 | }, function(err) { 77 | return cb(err); 78 | }).$promise; 79 | }, 80 | createContainer : function (data, callback) { 81 | var cb = callback || angular.noop; 82 | return containerResource.createContainer(data, function(result) { 83 | return cb(result); 84 | }, function(err) { 85 | return cb(err); 86 | }).$promise; 87 | }, 88 | deleteContainer:function(data,callback){ 89 | var cb = callback || angular.noop; 90 | return containerResource.deleteContainer(data, function(result) { 91 | return cb(result); 92 | }, function(err) { 93 | return cb(err); 94 | }).$promise; 95 | }, 96 | //停止或运行容器 97 | updateContainer: function(data, callback){ 98 | var cb = callback || angular.noop; 99 | return containerResource.updateContainer(data, function(result) { 100 | return cb(result); 101 | }, function(err) { 102 | return cb(err); 103 | }).$promise; 104 | }, 105 | getContainerStats : function (data, callback) { 106 | var cb = callback || angular.noop; 107 | return containerResource.getContainerStats(data, function(result) { 108 | return cb(result); 109 | }, function(err) { 110 | return cb(err); 111 | }).$promise; 112 | }, 113 | }; 114 | 115 | }); 116 | })(); 117 | -------------------------------------------------------------------------------- /src/app/components/resource/image.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.resources') 5 | .factory('Image', function($resource){ 6 | var imagesResource = $resource('/api/images/:id/:controller', { 7 | id: '@_id' 8 | }, 9 | { 10 | //添加标签分类 11 | getImagesList: { 12 | method: 'GET', 13 | params: { 14 | controller: 'getImagesList' 15 | } 16 | }, 17 | getImagesCount:{ 18 | method: 'GET', 19 | params: { 20 | controller: 'getImagesCount' 21 | } 22 | }, 23 | getImageDetail:{ 24 | method:'GET', 25 | params:{ 26 | controller: 'getImageDetail' 27 | } 28 | }, 29 | searchDockerImage:{ 30 | method:'GET', 31 | params:{ 32 | id: 'dockerhub', 33 | controller: 'searchImage' 34 | } 35 | } 36 | }); 37 | return { 38 | getImagesList : function(data, callback){ 39 | var cb = callback || angular.noop; 40 | return imagesResource. getImagesList(data ,function(result) { 41 | return cb(result); 42 | }, function(err) { 43 | return cb(err); 44 | }).$promise; 45 | }, 46 | getImagesCount : function(data, callback){ 47 | var cb = callback || angular.noop; 48 | return imagesResource.getImagesCount(function(result) { 49 | return cb(result); 50 | }, function(err) { 51 | return cb(err); 52 | }).$promise; 53 | }, 54 | getImageDetail : function(data, callback){ 55 | var cb = callback || angular.noop; 56 | return imagesResource.getImageDetail(data, function(result){ 57 | return cb(result); 58 | }, function(err){ 59 | return cb(err); 60 | }).$promise; 61 | }, 62 | searchDockerImage: function(data, callback){ 63 | var cb = callback || angular.noop; 64 | return imagesResource.searchDockerImage(data, function(result){ 65 | return cb(result); 66 | }, function(err){ 67 | return cb(err); 68 | }).$promise; 69 | }, 70 | deleteImage:function(data,callback){ 71 | var cb = callback || angular.noop; 72 | return imagesResource.remove(data, function(result) { 73 | return cb(result); 74 | }, function(err) { 75 | return cb(err); 76 | }).$promise; 77 | } 78 | }; 79 | }); 80 | })(); 81 | -------------------------------------------------------------------------------- /src/app/components/resource/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.resources',[ 5 | 'ngResource' 6 | ]); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/app/components/resource/user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.resources') 5 | .factory('User', function($resource){ 6 | var userResource = $resource('/api/users/:id/:controller', { 7 | id: '@_id' 8 | }, 9 | { 10 | getCaptcha:{ 11 | method: 'GET', 12 | params: { 13 | id:'getCaptcha' 14 | } 15 | }, 16 | get: { 17 | method: 'GET', 18 | params: { 19 | id:'me' 20 | } 21 | }, 22 | getUserList:{ 23 | method:'GET', 24 | params:{ 25 | id:'getUserList' 26 | } 27 | }, 28 | addUser:{ 29 | method:'POST', 30 | params:{ 31 | id:'addUser' 32 | } 33 | }, 34 | updateUser:{ 35 | method:'PUT', 36 | params:{ 37 | controller:'updateUser' 38 | } 39 | }, 40 | mdUser:{ 41 | method:'PUT', 42 | params:{ 43 | id:'mdUser' 44 | } 45 | } 46 | }); 47 | 48 | return { 49 | get:userResource.get, 50 | getCaptcha: function(callback){ 51 | var cb = callback || angular.noop; 52 | return userResource.getCaptcha(function(result) { 53 | return cb(result); 54 | }, function(err) { 55 | return cb(err); 56 | }).$promise; 57 | }, 58 | getUserList:function (data,callback) { 59 | var cb = callback || angular.noop; 60 | return userResource.getUserList(function(result) { 61 | return cb(result); 62 | }, function(err) { 63 | return cb(err); 64 | }).$promise; 65 | }, 66 | addUser:function (data, callback) { 67 | var cb = callback || angular.noop; 68 | return userResource.addUser(data, function(result) { 69 | return cb(result); 70 | }, function(err) { 71 | return cb(err); 72 | }).$promise; 73 | }, 74 | deleteUser:function(data,callback){ 75 | var cb = callback || angular.noop; 76 | return userResource.remove(data,function(result) { 77 | return cb(result); 78 | }, function(err) { 79 | return cb(err); 80 | }).$promise; 81 | }, 82 | updateUser:function (id,data,callback) { 83 | var cb = callback || angular.noop; 84 | return userResource.updateUser({id:id}, data, function(result) { 85 | return cb(result); 86 | }, function(err) { 87 | return cb(err); 88 | }).$promise; 89 | }, 90 | //前台更新用户信息 91 | mdUser:function (data,callback) { 92 | var cb = callback || angular.noop; 93 | return userResource.mdUser(data,function(result) { 94 | return cb(result); 95 | }, function(err) { 96 | return cb(err); 97 | }).$promise; 98 | } 99 | }; 100 | }); 101 | })(); 102 | -------------------------------------------------------------------------------- /src/app/components/resource/volume.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.resources') 5 | .factory('Volume', function($resource){ 6 | var volumesResource = $resource('/api/volumes/:id/:controller', { 7 | id: '@id' 8 | }, 9 | { 10 | //添加标签分类 11 | getVolumesList: { 12 | method: 'GET', 13 | params: { 14 | controller:'getVolumesList' 15 | } 16 | }, 17 | getVolumesCount:{ 18 | method: 'GET', 19 | params: { 20 | controller: 'getVolumesCount' 21 | } 22 | }, 23 | getVolumesDetail:{ 24 | method:'GET', 25 | params:{ 26 | controller: 'getVolumesDetail' 27 | } 28 | }, 29 | searchVolume:{ 30 | method:'GET', 31 | params:{ 32 | controller: 'searchVolume' 33 | } 34 | } 35 | }); 36 | return { 37 | getVolumesList : function(data, callback){ 38 | var cb = callback || angular.noop; 39 | return volumesResource.getVolumesList(function(result) { 40 | return cb(result); 41 | }, function(err) { 42 | return cb(err); 43 | }).$promise; 44 | }, 45 | getVolumesCount : function(data, callback){ 46 | var cb = callback || angular.noop; 47 | return volumesResource.getVolumesCount(function(result) { 48 | return cb(result); 49 | }, function(err) { 50 | return cb(err); 51 | }).$promise; 52 | }, 53 | getVolumesDetail : function(data, callback){ 54 | var cb = callback || angular.noop; 55 | return volumesResource.getVolumesDetail(data, function(result){ 56 | return cb(result); 57 | }, function(err){ 58 | return cb(err); 59 | }).$promise; 60 | }, 61 | createNewVolume : function(data, callback){ 62 | var cb = callback || angular.noop; 63 | return volumesResource.save(data, function(result){ 64 | return cb(result); 65 | }, function(err){ 66 | return cb(err); 67 | }).$promise; 68 | }, 69 | searchVolume: function(data, callback){ 70 | var cb = callback || angular.noop; 71 | return volumesResource.searchVolume(data, function(result){ 72 | return cb(result); 73 | }, function(err){ 74 | return cb(err); 75 | }).$promise; 76 | }, 77 | deleteVolume: function(data, callback){ 78 | var cb = callback || angular.noop; 79 | return volumesResource.remove(data, function(result) { 80 | return cb(result); 81 | }, function(err) { 82 | return cb(err); 83 | }).$promise; 84 | } 85 | }; 86 | }); 87 | })(); 88 | -------------------------------------------------------------------------------- /src/app/components/service/auto.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.service') 5 | .factory('Auth', function Auth($location, $rootScope, $http, User, $cookies, $q, lodash, $window) { 6 | var currentUser = {}; 7 | if($cookies.get('token')) { 8 | currentUser = User.get(); 9 | } 10 | 11 | return { 12 | login: function(user, callback) { 13 | var cb = callback || angular.noop; 14 | var deferred = $q.defer(); 15 | 16 | $http.post('/auth/local', { 17 | email: user.email, 18 | password: user.password, 19 | captcha: user.captcha 20 | }). 21 | success(function(data) { 22 | $cookies.put('token', data.token); 23 | currentUser = User.get(); 24 | deferred.resolve(data); 25 | return cb(); 26 | }). 27 | error(function(err) { 28 | this.logout(); 29 | deferred.reject(err); 30 | return cb(err); 31 | }.bind(this)); 32 | 33 | return deferred.promise; 34 | }, 35 | 36 | logout: function() { 37 | $cookies.remove('token'); 38 | currentUser = {}; 39 | }, 40 | 41 | getCurrentUser: function() { 42 | return currentUser; 43 | }, 44 | 45 | isLoggedIn: function() { 46 | return currentUser.hasOwnProperty('role'); 47 | }, 48 | 49 | /** 50 | * 检测用户是否登录 51 | */ 52 | isLoggedInAsync: function(cb) { 53 | if(currentUser.hasOwnProperty('$promise')) { 54 | currentUser.$promise.then(function() { 55 | cb(true); 56 | }).catch(function() { 57 | cb(false); 58 | }); 59 | } else if(currentUser.hasOwnProperty('role')) { 60 | cb(true); 61 | } else { 62 | cb(false); 63 | } 64 | }, 65 | 66 | isLike: function (aid) { 67 | var index = lodash.findIndex(currentUser.likes,function (item) { 68 | return item.toString() === aid; 69 | }); 70 | return (index !== -1)?true:false; 71 | }, 72 | 73 | isAdmin: function() { 74 | return currentUser.role === 'admin'; 75 | }, 76 | 77 | getToken: function() { 78 | return $cookies.get('token'); 79 | } 80 | }; 81 | }); 82 | })(); 83 | -------------------------------------------------------------------------------- /src/app/components/service/autoInterceptor.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.service') 5 | .factory('AutoInterceptor', function ($rootScope, $q, $cookies, $location, $injector) { 6 | var Auth; 7 | return { 8 | request: function (config) { 9 | config.headers = config.headers || {}; 10 | if ($cookies.get('token')) { 11 | config.headers.Authorization = 'Bearer ' + $cookies.get('token').replace(/(^\")|(\"$)/g, ""); 12 | } 13 | return config; 14 | }, 15 | response: function (response) { 16 | return response; 17 | }, 18 | responseError:function(rejection){ 19 | if (rejection.status === 401) { 20 | Auth = $injector.get('Auth'); 21 | Auth.logout(); 22 | $location.path('/signin'); 23 | return $q.reject(rejection); 24 | }else { 25 | return $q.reject(rejection); 26 | } 27 | } 28 | }; 29 | }); 30 | })(); 31 | -------------------------------------------------------------------------------- /src/app/components/service/createContainer.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('dockerApp.service') 4 | .factory('CreateCon', function(){ 5 | 6 | var createNewCon = function(postData, cb){ 7 | cb = cb || angular.noop; 8 | 9 | var socket = io.connect('http://localhost:9090/newCon'); 10 | socket.on('message', function(data){ 11 | if(data){ 12 | //code 为通知码  0表示获取内容为空 1表示出错 2表示连接接连 3表示当前请求动作成功完成 13 | switch (data.code) { 14 | case 0 : 15 | break; 16 | case 1 : 17 | cb(data); 18 | break; 19 | case 2: 20 | socket.emit('createContainer', postData); 21 | break; 22 | case 3: 23 | cb(null, data); 24 | break; 25 | default: 26 | 27 | } 28 | } 29 | }); 30 | }; 31 | return { 32 | createContainer : createNewCon 33 | }; 34 | }); 35 | })(); 36 | -------------------------------------------------------------------------------- /src/app/components/service/dataFormat.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('dockerApp.service') 4 | .factory('formatData', function(){ 5 | var getPostDataFormat = function(image, containerName, containerSize, portList, envList, linkList, volumeList, cmd){ 6 | function getVolumeFormat(volumeList){ 7 | var bindsArray = []; 8 | for(var index in volumeList){ 9 | var volume = volumeList[index]; 10 | var item = [volume.volumeHost, volume.volumeDest].join(':'); 11 | bindsArray.push(item); 12 | } 13 | return bindsArray; 14 | } 15 | function getEnvFormat(envList){ 16 | var env = []; 17 | for(var index in envList){ 18 | var item = [envList[index].envKey, envList[index].envValue].join('='); 19 | env.push(item); 20 | } 21 | return env; 22 | } 23 | function getLinkFormat(linkList){ 24 | var links = []; 25 | for(var index in linkList){ 26 | linkList[index].alias = linkList[index].alias ? linkList[index].alias : linkList[index].name; 27 | var item = [linkList[index].name, linkList[index].alias].join(':'); 28 | links.push(item); 29 | } 30 | return links; 31 | } 32 | function getPortFormat(portList){ 33 | var ports = {}; 34 | for(var index in portList){ 35 | if(ports[portList[index].containerPort]){ 36 | ports[portList[index].containerPort].push({'HostPort': portList[index].hostPort}); 37 | }else{ 38 | ports[portList[index].containerPort] = [{'HostPort': portList[index].hostPort}]; 39 | } 40 | } 41 | return ports; 42 | } 43 | function getExposedPortFormat(portList){ 44 | var ports = {}; 45 | for(var index in portList){ 46 | ports[portList[index].containerPort] = {}; 47 | } 48 | return ports; 49 | } 50 | function getMemoryFormat(size){ 51 | var unit = size.slice(-1); 52 | var num = size.slice(0,-1); 53 | var memory = 0; 54 | switch(unit){ 55 | case 'M': return num*1024*1024; 56 | case 'G': return num*1024*1024*1024; 57 | } 58 | } 59 | var option = {}; 60 | 61 | option.Image = image; 62 | option.Name = containerName; 63 | 64 | option.Env = getEnvFormat(envList); 65 | if(option.Env.length === 0){ 66 | delete option.Env; 67 | } 68 | 69 | option.HostConfig={}; 70 | //CMD 71 | if($.trim(cmd)){ 72 | option.Cmd = cmd.split(' '); 73 | } 74 | //TODO Cpushares含义?? 75 | option.HostConfig.Cpushares = 1; 76 | option.HostConfig.CpusetCpus = parseInt(containerSize.cpu) == 1 ? "0" : "0,1"; 77 | option.HostConfig.Links = getLinkFormat(linkList); 78 | if(option.HostConfig.Links.length === 0){ 79 | delete option.HostConfig.Links; 80 | } 81 | option.HostConfig.PortBindings = getPortFormat(portList); 82 | option.ExposedPorts = getExposedPortFormat(portList); 83 | option.HostConfig.Memory = getMemoryFormat(containerSize.mem); 84 | option.HostConfig.Binds = getVolumeFormat(volumeList); 85 | if(option.HostConfig.Binds.length === 0){ 86 | delete option.HostConfig.Binds; 87 | } 88 | return option; 89 | }; 90 | return { 91 | getPostDataFormat: getPostDataFormat 92 | }; 93 | }); 94 | })(); 95 | -------------------------------------------------------------------------------- /src/app/components/service/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('dockerApp.service',[]); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/app/components/service/modal.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.service') 5 | .factory('CustomModalService', function ($modal) { 6 | return { 7 | open: function (ctrlName,url,size) { 8 | $modal.open({ 9 | templateUrl: url, 10 | controller: ctrlName, 11 | size: size 12 | }); 13 | } 14 | }; 15 | }); 16 | })(); 17 | -------------------------------------------------------------------------------- /src/app/components/slidebar/slidebar.jade: -------------------------------------------------------------------------------- 1 | div.ui.navbar(ng-controller="navBarCtrl") 2 | ul.nav.nav-pills.nav-stacked 3 | li 4 | a.text-center(tooltip-placement="right" tooltip="控制台" tooltip-append-to-body="true" ui-sref="dashboard") 5 | svg(alt="控制台") 6 | use(xlink:href="{{ currentUrl.docker }}") 7 | li(ng-class="{'active': navs[0].active}") 8 | a.text-center( tooltip-placement="right" tooltip="集群状况" tooltip-append-to-body="true" ui-sref="cluster") 9 | svg(alt="集群状况") 10 | use(xlink:href="{{ currentUrl.server }}") 11 | li(ng-class="{'active': navs[1].active}") 12 | a.text-center(tooltip-placement="right" tooltip="镜像列表" tooltip-append-to-body="true" ui-sref="imageList") 13 | svg(alt="镜像列表") 14 | use(xlink:href="{{ currentUrl.shop }}") 15 | li(ng-class="{'active': navs[2].active}") 16 | a.text-center(tooltip-placement="right" tooltip="容器列表" tooltip-append-to-body="true" ui-sref="containerList") 17 | svg(alt="容器列表") 18 | use(xlink:href="{{ currentUrl.applist }}") 19 | li(ng-class="{'active': navs[3].active}") 20 | a.text-center(tooltip-placement="right" tooltip="数据卷" tooltip-append-to-body="true" ui-sref="volume") 21 | svg(alt="数据卷") 22 | use(xlink:href="{{ currentUrl.volume }}") 23 | -------------------------------------------------------------------------------- /src/app/components/slidebar/slidebar.scss: -------------------------------------------------------------------------------- 1 | //定义svg path颜色继承自父辈 2 | svg path{ 3 | fill:inherit; 4 | } 5 | .ui.navbar{ 6 | @include display-flex; 7 | @include justify-content(space-between); 8 | @include flex-direction(column); 9 | 10 | @include border-radius(0px); 11 | overflow: auto; 12 | height: 100%; 13 | width: $navbarSize; 14 | position: fixed; 15 | top: 0; 16 | right: 0; 17 | bottom: 0; 18 | left: 0; 19 | border: 0; 20 | background-color: $navBgColor; 21 | &:before, 22 | &:after{ 23 | display: none!important; 24 | } 25 | .nav{ 26 | $azureHoverColor: #1FE66F; 27 | li{ 28 | a{ 29 | border:1px solid transparent; 30 | height: 65px; 31 | width: 60px; 32 | padding:0; 33 | margin: 0 auto; 34 | text-align: center; 35 | @extend .flex-vertical-box; 36 | @include opacity(.6); 37 | svg{ 38 | height: 32px; 39 | width: 32px; 40 | use{ 41 | fill:#fff; 42 | } 43 | } 44 | &:hover, 45 | &:active, 46 | &:focus{ 47 | background-color: transparent; 48 | @include opacity(1); 49 | } 50 | } 51 | &.active a{ 52 | background-color: #353c43; 53 | @include border-radius(3px); 54 | @include opacity(1) 55 | } 56 | &:nth-child(1){ 57 | margin-top: 10px; 58 | a:hover{ 59 | use{ 60 | fill : $azureHoverColor; 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/components/slidebar/sliderbar.controller.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | angular.module("dockerApp") 3 | .controller('navBarCtrl', ['$scope','$rootScope', '$state', '$stateParams', '$location', function($scope, $rootScope, $state, $stateParams, $location){ 4 | 5 | $scope.currentUrl = {}; 6 | $scope.currentUrl.docker = $location.url() +'#icon-docker'; 7 | $scope.currentUrl.server = $location.url() +'#icon-server'; 8 | $scope.currentUrl.shop = $location.url() + '#icon-shop'; 9 | $scope.currentUrl.applist = $location.url() + '#symbol-icon_applist'; 10 | $scope.currentUrl.volume = $location.url() + '#icon-volume'; 11 | 12 | $scope.navs= []; 13 | for(var i=0; i<4; i++){ 14 | var nav = {}; 15 | nav.active = false; 16 | $scope.navs.push(nav); 17 | } 18 | $scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ 19 | switch ($rootScope.currentState.name) { 20 | case 'cluster': 21 | $scope.navs[0].active = true; 22 | break; 23 | case 'imageList': 24 | $scope.navs[1].active = true; 25 | break; 26 | case 'containerList': 27 | $scope.navs[2].active = true; 28 | break; 29 | case 'volume': 30 | $scope.navs[3].actice = true; 31 | break; 32 | } 33 | }); 34 | 35 | }]); 36 | })(); 37 | -------------------------------------------------------------------------------- /src/app/containerCreate/containerCreate.scss: -------------------------------------------------------------------------------- 1 | .content-body.create-container{ 2 | .image{ 3 | display: inline-block; 4 | min-width: 300px; 5 | width: 400px; 6 | height: 100px; 7 | .img-icon{ 8 | width:100px; 9 | height:100%; 10 | float: left; 11 | margin-right: 10px; 12 | } 13 | .img-msg{ 14 | height:100%; 15 | margin-left: auto; 16 | h3{ 17 | font-size: 21px; 18 | color: #46B79E; 19 | } 20 | span{ 21 | color:$baseFontColor; 22 | &.sm-text{ 23 | font-size: 14px; 24 | } 25 | } 26 | 27 | } 28 | } 29 | h5.head{ 30 | font-weight:bold; 31 | font-size: 14px; 32 | text-align:right; 33 | margin-bottom:0; 34 | margin-top:0; 35 | } 36 | .create-container-btn{ 37 | i.fa-2x{ 38 | font-size: 19px; 39 | } 40 | i.fa-1x{ 41 | font-size: 15px; 42 | margin-left: -6px; 43 | } 44 | &.btn-default{ 45 | background-color: #cfcece; 46 | } 47 | } 48 | .type-list{ 49 | display: inline-block; 50 | li{ 51 | float: left; 52 | display: inline-block; 53 | border: 2px solid lighten($baseBorderColor, 5%); 54 | @include transform(border 200ms); 55 | .panel{ 56 | margin-bottom: 0px; 57 | border-width: 0; 58 | .panel-heading{ 59 | background-color: rgb(97, 149, 143); 60 | color: white; 61 | @include border-radius(0); 62 | } 63 | .panel-body{ 64 | padding: 6px 15px 0px; 65 | } 66 | } 67 | &:not(:first-child){ 68 | margin-left: 10px; 69 | } 70 | &.active{ 71 | border: 2px solid lighten(#000, 30%); 72 | i{ 73 | color:rgb(47, 240, 15); 74 | } 75 | } 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/app/containerDetail/containerDetail.controller.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | angular.module("dockerApp.containers") 3 | .controller('containerDetailCtrl', ['$rootScope', '$scope', 'Container', '$state', '$stateParams', 'toaster', 'SweetAlert', function($rootScope, $scope, Container, $state, $stateParams, toaster, SweetAlert){ 4 | 5 | //其实是name 6 | var containerId = $state.params.id; 7 | console.log(containerId); 8 | 9 | //如果当前有新的容器生成 给出提示框 10 | if($state.params.new){ 11 | 12 | $scope.isFirst = true; //显示日志tab 13 | if($rootScope.load.loaded){ 14 | toaster.pop('success', "", "容器创建成功!"); 15 | 16 | } 17 | } 18 | 19 | $scope.logInit = false;//默认补获取日志 20 | 21 | 22 | 23 | $scope.stopContainer = function(){ 24 | Container.updateContainer({ _id: containerId, action : 'stop'}).then(function(result){ 25 | if(result && 'msg' in result && result.msg === 'ok'){ 26 | $scope.container.status = 'stop'; 27 | } 28 | }); 29 | }; 30 | $scope.startContainer = function(){ 31 | Container.updateContainer({ _id: containerId, action : 'start'}).then(function(result){ 32 | //TODO err的处理 33 | if(result && 'msg' in result && result.msg === 'ok'){ 34 | $scope.container.status = 'running'; 35 | } 36 | }); 37 | }; 38 | Container.getContainer({id: containerId}).then(function(result){ 39 | if('error_msg' in result && result.error_msg && result.status == 404){ 40 | $state.go('containerList'); 41 | return; 42 | } 43 | $scope.container = result.container; 44 | //如果容器是新创建的 就启动容器 动态更新日志 45 | if($state.params.new){ 46 | if($scope.container.status != 'running') { 47 | $scope.startContainer(); 48 | } 49 | } 50 | }); 51 | 52 | $scope.delete = function(){ 53 | SweetAlert.swal({ 54 | title: "你确定?", 55 | text: "你将删除选中的" + $scope.container.name + '容器!', 56 | type: "warning", 57 | showCancelButton: true, 58 | cancelButtonText: "取消", 59 | confirmButtonColor: "#DD6B55", 60 | confirmButtonText: "是的, 我要删除!", 61 | closeOnConfirm: false, 62 | showLoaderOnConfirm: true }, 63 | function(isConfirm){ 64 | if(isConfirm){ 65 | Container.deleteContainer({data: [$scope.container.name ]}).then(function(result){ 66 | if(result && 'msg' in result && result.msg === 'ok'){ 67 | window.swal.close(); //关闭SweetAlert 68 | $state.go('containerList', { 69 | removeContainer : true 70 | }); 71 | } 72 | }); 73 | 74 | } 75 | 76 | }); 77 | }; 78 | 79 | $scope.getMetrics = function(option){ 80 | switch (option) { 81 | case 'day': 82 | //当点击24小时时,显示loading效果 83 | $scope.dayLoading = true; 84 | Container.getContainerStats({id : containerId, node: $scope.container.node }).then(function(resluts){ 85 | $scope.dayResources = resluts; 86 | }); 87 | break; 88 | 89 | } 90 | }; 91 | }]); 92 | })(); 93 | -------------------------------------------------------------------------------- /src/app/containerDetail/containerDetail.scss: -------------------------------------------------------------------------------- 1 | .content-body.container-detail{ 2 | .primary-section{ 3 | .img-msg{ 4 | button{ 5 | position: relative; 6 | margin-top: 15px; 7 | } 8 | } 9 | } 10 | .content-block{ 11 | .section{ 12 | table, 13 | .table{ 14 | > thead{ 15 | background-color: #ececec; 16 | } 17 | > thead, 18 | > tbody{ 19 | > tr{ 20 | > td{ 21 | padding:10px 10px; 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | .log{ 29 | pre{ 30 | $bgColor: #151718; 31 | white-space: normal; 32 | &.darken{ 33 | color: #fff; 34 | background-color: $bgColor; 35 | height: 450px; 36 | overflow: auto; 37 | padding-left: 25px; 38 | text-indent: -14.5px; 39 | .t{ 40 | color: #9D6E35; 41 | } 42 | .c{ 43 | color: #555; 44 | } 45 | } 46 | } 47 | } 48 | //log loading 效果 49 | .loading-log{ 50 | span.circle{ 51 | display: inline-block; 52 | height: 6px; 53 | width: 6px; 54 | @include border-radius(20px); 55 | background-color: white; 56 | @include box-shadow(0 2px 2px rgba(255, 255, 255, 0.5)); 57 | &.one{ 58 | margin-left: 5px; 59 | @include animation(ld .8s -.6s infinite linear); 60 | } 61 | &.two{ 62 | @include animation(ld .8s 0s infinite linear); 63 | } 64 | &.three{ 65 | @include animation(ld .8s .6s infinite linear); 66 | } 67 | } 68 | } 69 | 70 | @include keyframes(ld){ 71 | 0%{ @include transform(scale(1)); } 72 | 25%{ @include transform(scale(0.5)); } 73 | 50%{ @include transform(scale(0.1)); } 74 | 75%{ @include transform(scale(0.5)); } 75 | 80%{ @include transform(scale(0.8)); } 76 | 90%{ @include transform(scale(1));} 77 | 100%{ @include transform(scale(1)); } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/app/containerList/containerList.controller.js: -------------------------------------------------------------------------------- 1 | // container list page controller 2 | (function(){ 3 | angular.module("dockerApp.containers") 4 | .controller('ContainerListCtrl', ['$rootScope', '$scope', '$location', 'Container', 'SweetAlert', '$state', 'toaster', function($rootScope, $scope, $location, Container, SweetAlert, $state, toaster){ 5 | 6 | //现在默认创建成功跳转到detail页面了.. 7 | //如果当前有新的容器生成 给出提示框 8 | if($state.params.newContainer){ 9 | $scope.newContainer ={}; 10 | $scope.newContainer.name = $state.params.newContainer; 11 | if($rootScope.load.loaded){ 12 | toaster.pop('success', "", "容器创建成功!"); 13 | } 14 | } 15 | //如果容器删除成功 16 | if($state.params.removeContainer){ 17 | if($rootScope.load.loaded){ 18 | toaster.pop('success', "", "容器删除成功!"); 19 | } 20 | } 21 | 22 | $scope.containerList = []; 23 | $scope.waitForDeleteContainerIDList = []; 24 | 25 | $scope.options = { 26 | currentPage: 1, 27 | itemsPerPage : 10 28 | }; 29 | 30 | function doPaging(options){ 31 | $scope.isLoading = true; 32 | //数量需要过滤 33 | Container.getContainerCount(options).then(function(result){ 34 | 35 | $scope.containerCount = result.count; 36 | $scope.numPages = Math.ceil($scope.containerCount / $scope.options.itemsPerPage); 37 | 38 | }); 39 | //获取列表 40 | Container.getContainerList(options).then(function(result){ 41 | $scope.isLoading = false; 42 | $scope.containerList = result; 43 | }).catch(function(){ 44 | $scope.isLoading = false; 45 | $scope.containerList = []; 46 | }); 47 | } 48 | //初始化列表 49 | doPaging($scope.option); 50 | 51 | //加载更多 52 | $scope.loadMore = function(page){ 53 | $scope.options.currentPage = page; 54 | doPaging($scope.options, true); 55 | }; 56 | 57 | 58 | //点击delete按钮 删除选中的容器应用 59 | $scope.deleteCheckedApp = function(){ 60 | SweetAlert.swal({ 61 | title: "你确定?", 62 | text: "你将删除选中的" + $scope.waitForDeleteContainerIDList.length + '个容器!', 63 | type: "warning", 64 | showCancelButton: true, 65 | cancelButtonText: "取消", 66 | confirmButtonColor: "#DD6B55", 67 | confirmButtonText: "是的, 我要删除!", 68 | closeOnConfirm: false, 69 | showLoaderOnConfirm: true }, 70 | function(isConfirm){ 71 | if(isConfirm){ 72 | var deleteContainerNameList = []; 73 | $scope.containerList.forEach(function(item, index){ 74 | if(item.check){ 75 | deleteContainerNameList.push(item.name); 76 | } 77 | }); 78 | 79 | if(deleteContainerNameList.length > 0){ 80 | Container.deleteContainer({data: deleteContainerNameList}).then(function(result){ 81 | if(result && 'msg' in result && result.msg === 'ok'){ 82 | window.swal.close(); //关闭SweetAlert 83 | $scope.checkedItem = false; 84 | //获取列表 85 | $scope.options = { 86 | currentPage: 1, 87 | itemsPerPage : 10 88 | }; 89 | $rootScope.progressbar.setColor('green'); 90 | $rootScope.progressbar.reset(); // Required to handle all edge cases. 91 | $rootScope.progressbar.start(); 92 | Container.getContainerList($scope.options).then(function(result){ 93 | 94 | $scope.containerList = result; 95 | toaster.pop('success', "", "删除成功!"); 96 | $rootScope.progressbar.complete(); 97 | }).catch(function(){ 98 | $scope.containerList = $scope.containerList || []; 99 | toaster.pop('success', "", "删除成功!"); 100 | $rootScope.progressbar.complete(); 101 | 102 | 103 | }); 104 | } 105 | }); 106 | } 107 | } 108 | 109 | }); 110 | }; 111 | $scope.checkItem = function(container){ 112 | if(container.check){ 113 | if($scope.waitForDeleteContainerIDList.indexOf(container.Id) < 0){ 114 | $scope.waitForDeleteContainerIDList.push(container.Id); 115 | } 116 | if(!$scope.checkedItem){ 117 | $scope.checkedItem = true; 118 | } 119 | }else{ 120 | if($scope.waitForDeleteContainerIDList.indexOf(container.Id) >= 0){ 121 | var index = $scope.waitForDeleteContainerIDList.indexOf(container.Id); 122 | $scope.waitForDeleteContainerIDList.splice(index, 1); //从待删除的列表中移除取消删除的项 123 | if($scope.waitForDeleteContainerIDList.length === 0){ 124 | if($scope.checkedItem){ 125 | $scope.checkedItem = false; 126 | } 127 | } 128 | } 129 | 130 | } 131 | }; 132 | }]); 133 | })(); 134 | -------------------------------------------------------------------------------- /src/app/containerList/containerList.jade: -------------------------------------------------------------------------------- 1 | 2 | //- page contnt begins 3 | .content-body.container-list(ng-if="load.loaded") 4 | .panel.panel-default.panel-page-header 5 | .panel-body 6 | .primary-section 7 | h2 应用管理 8 | p 这里通过表格的形式显示出SWARM集群中已经部署的所有的容器应用 9 | .secondary-section 10 | .row 11 | .col-xs-12.col-sm-12 12 | h4 如何在SRARM集群中部署您的容器应用? 13 | p 您可以从镜像仓库中挑选镜像(目前只支持swarm集群中已有的镜像)并生成容器应用 14 | a(ui-sref="imageList") 镜像列表 15 | .panel 16 | section.panel-body 17 | div.table-container 18 | div.table-toolbar 19 | div.btn-group 20 | button.btn.btn-danger(ng-if="checkedItem", ng-click="deleteCheckedApp()" tabindex="0") 删除 21 | div.table.main 22 | table.table.table-bordered 23 | thead 24 | tr 25 | th 26 | th.left 名称 27 | th.text-center 运行节点 28 | th.text-center 状态 29 | th.text-center 所属镜像 30 | th.text-center 创建时间 31 | th.text-center 动作 32 | tbody 33 | tr(ng-repeat="container in containerList | orderBy: '-Created'" ng-class="{'selected' : container.check, 'new' : newContainer && newContainer.name == container.name }") 34 | td.checkbox 35 | div 36 | input(type="checkbox" ng-model="container.check" ng-change="checkItem(container)" tabindex="0") 37 | td.left 38 | span {{ container.name }} 39 | span(ng-if="newContainer && newContainer.name == container.name" style="color:#F6566B") (New) 40 | td.text-center 41 | span {{ container.node }} 42 | td.text-center 43 | span.state-label.play.item-success(ng-if="container.status == 'running'") 44 | | 运行中 45 | span.state-label(ng-if="container.status =='stop'") 46 | | 停止 47 | span.state-label.error(ng-if="container.status== 'error'") 应用异常 48 | td.text-center 49 | span {{ container.Image }} 50 | br 51 | span.item-tag 52 | i.fa.fa-tag 53 | |  {{container.imageTag }} 54 | 55 | td.text-center {{ container.Created*1000 | date:'yyyy-MM-dd HH:mm' }} 56 | td.text-center 57 | a(ui-sref="containerDetail({id: container.name })") 查看详情 58 | paging(page="options.currentPage" page-size="options.itemsPerPage" total="containerCount" dots="......" hide-if-empty="true" show-prev-next="true" paging-action="loadMore(page)") 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/app/containerList/containerList.scss: -------------------------------------------------------------------------------- 1 | .content-body.container-list{ 2 | $newBorderColor: #17D217; 3 | .panel{ 4 | table{ 5 | > tbody{ 6 | > tr{ 7 | &.new{ 8 | border-left: 4px solid $newBorderColor; 9 | } 10 | } 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/containers/containers.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.containers', []) 5 | .config(function ($stateProvider) { 6 | $stateProvider 7 | .state('containerList', { 8 | url : '/containers/list', 9 | templateUrl : 'app/containerList/containerList.html', 10 | controller : 'ContainerListCtrl', 11 | params:{ 12 | newContainer: undefined, 13 | removeContainer : undefined 14 | } 15 | }) 16 | .state('containerDetail', { 17 | url : '/containers/:id/Detail', 18 | templateUrl : 'app/containerDetail/containerDetail.html', 19 | controller : 'containerDetailCtrl', 20 | params:{ 21 | new: undefined 22 | } 23 | }) 24 | .state('containerCreate', { 25 | url: '/create/:id/:source', 26 | templateUrl : 'app/containerCreate/containerCreate.html', 27 | controller : 'containerCreateCtrl', 28 | controllerAs : 'ctrl', 29 | params:{ 30 | source : 'local' 31 | } 32 | }); 33 | }); 34 | })(); 35 | -------------------------------------------------------------------------------- /src/app/imageList/imageList.jade: -------------------------------------------------------------------------------- 1 | 2 | .content-body(ng-if="load.loaded") 3 | .panel.panel-default.panel-page-header 4 | .panel-body 5 | .primary-section 6 | h2 镜像仓库 7 | p 这里是容器镜像的仓库,仓库里目前汇聚了集群已经下载和部署的镜像文件(并支持搜寻dockerhub上的镜像并下载) 8 | .panel 9 | section.panel-body 10 | tabset 11 | tab(index="0" heading="本地镜像") 12 | div.table-container 13 | table.table.table-bordered 14 | thead 15 | tr 16 | th.text-center 镜像仓库 17 | th 镜像用户 18 | th 镜像名称 19 | th 标签 20 | th 创建时间 21 | th 动作 22 | tbody 23 | tr(ng-repeat="image in imagePackages.results | orderBy: '-time'") 24 | td.text-center {{ image.repo }} 25 | td {{ image.username }} 26 | td {{ image.name }} 27 | td {{ image.tag }} 28 | td {{ image.time | date:'yyyy-MM-dd HH:mm' }} 29 | td 30 | button.btn.btn-primary(ng-click='createContainerInstance(image)') 创建应用 31 | span.btn.delete-image-btn(ng-click="delImage(image)") 32 | i.fa.fa-trash.fa-2x 33 | button.btn-more.btn(ng-click="loadMore()" ng-if="options.currentPage < imagePackages.numPages" ng-disabled="imagePackages.isLoading" tabindex="0") 34 | span(ng-if="!imagePackages.isLoading") 加载更多 35 | span(ng-if="imagePackages.isLoading") 36 | i.fa.fa-spinner.fa-pulse 37 | tab(index="1" heading="DockerHub镜像") 38 | div.row.dockerhub 39 | h3.text-center Docker镜像 40 | div.input-group.col-xs-6.col-xs-offset-3 41 | input.form-control(placeholder="在这里搜索镜像", ng-model="search.dockerhub.content", ng-keypress="keypressEvent($event)", tabindex="0") 42 | span.input-group-addon(ng-click="searchDockerHub()" role="button" tabindex="0") 搜索 43 | div.well(ng-if="loading.dockerhub") 44 | span 45 | i.fa.fa-spinner.fa-pulse 46 | |   Loading... 47 | span.sr-only | Loading... 48 | div.well(ng-if="!loading.dockerhub && dockerhubPackages && dockerhubPackages.results.length === 0 ") 没有匹配的 DockerHub 镜像 49 | div(ng-if="!loading.dockerhub && showNeedNetworkMsg") 50 | span.msg 在线搜索需要确保swarm集群处于联网状态 51 | div(ng-if="!loading.dockerhub && dockerhubPackages.results.length > 0") 52 | div.package-list.container-fluid 53 | div.row.package(ng-repeat="package in dockerhubPackages.results") 54 | div.col-sm-4 55 | img.package-icon(ng-src="/assets/images/container.svg") 56 | div.package-heading 57 | h3 {{ package.name }} 58 | div.meta 59 | span 60 | i.fa.fa-star-o 61 | | {{ package.star_count }} 62 | div.col-sm-4 63 | span.text.text-muted {{ package.description }} 64 | div.col-sm-4.text-right 65 | button.btn.btn-primary(ng-click='createContainerInstance(package.name, true)' ng-disabled="package.loading") 创建应用 66 | 67 | button.btn-more.btn(ng-click="loadMoreDockerhub()" ng-if="options.currentPageForDockerhub < dockerhubPackages.pagesNum" ng-disabled="loading.dockerhubMore" tabindex="0") 68 | span(ng-if="!loading.dockerhubMore") 加载更多 69 | span(ng-if="loading.dockerhubMore") 70 | i.fa.fa-spinner.fa-pulse 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/app/imageList/imageList.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.imageList',[]) 5 | .config(function ($stateProvider) { 6 | $stateProvider 7 | .state('imageList', { 8 | url: '/images/list', 9 | templateUrl: 'app/imageList/imageList.html', 10 | controller: 'imageListCtrl' 11 | }); 12 | }); 13 | })(); 14 | -------------------------------------------------------------------------------- /src/app/imageList/imageList.scss: -------------------------------------------------------------------------------- 1 | $color : #5d9cec; 2 | .dockerhub{ 3 | .input-group{ 4 | margin-top:15px; 5 | margin-bottom: 15px; 6 | .input-group-addon{ 7 | background-color: $color; 8 | color: #fff; 9 | border: 1px solid $color; 10 | line-height: 1; 11 | padding: 9px 17px 8px; 12 | cursor: pointer; 13 | @include single-transition(all, 200ms); 14 | &:hover{ 15 | background-color: darken($color, 5%); 16 | border: 1px solid darken($color, 5%); 17 | } 18 | } 19 | } 20 | } 21 | .package-list { 22 | .package { 23 | $packageBgColor: #f2f4f7; 24 | margin-bottom: 10px; 25 | padding: 20px 30px; 26 | background-color: $packageBgColor; 27 | border: none; 28 | .package-icon { 29 | float: left; 30 | height: 58px; 31 | width: 58px; 32 | border-radius: 8px; 33 | } 34 | .package-heading{ 35 | margin-top: 5px; 36 | padding-left: 80px; 37 | word-break: break-all; 38 | h3{ 39 | font-size: 20px; 40 | font-weight: 300; 41 | margin: 0 0 5px; 42 | } 43 | .meta{ 44 | color: #aab2bd; 45 | } 46 | } 47 | 48 | 49 | } 50 | } 51 | .btn.delete-image-btn{ 52 | padding: 0px 13px; 53 | &:focus, 54 | &:active{ 55 | background-color: transparent; 56 | @include box-shadow(0 0); 57 | } 58 | i{ 59 | color: rgb(201, 84, 84); 60 | } 61 | &:hover{ 62 | i{ 63 | color: darken(rgb(201, 84, 84), 5%) 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dockerApp初始化入口文件 3 | */ 4 | (function () { 5 | 'use strict'; 6 | 7 | angular.module('dockerApp', [ 8 | 'ngAnimate', 9 | 'ngCookies', 10 | 'ngTouch', 11 | 'ngSanitize', 12 | 'ui.router', 13 | 'ui.bootstrap', 14 | 'ngLodash', 15 | 'ngProgress', 16 | 'toaster', 17 | 'ngFileUpload', 18 | 'bw.paging', 19 | 'oitozero.ngSweetAlert', 20 | 'dockerApp.manage', 21 | 'dockerApp.settings', 22 | 'dockerApp.resources', 23 | 'dockerApp.service', 24 | 'dockerApp.directives', 25 | 'dockerApp.containers', 26 | 'dockerApp.imageList', 27 | 'dockerApp.volume', 28 | 'dockerApp.cluster' 29 | ]) 30 | .config(function ($logProvider, $stateProvider, $urlRouterProvider, $locationProvider, $httpProvider, IsDebug) { 31 | 32 | $locationProvider.html5Mode(true); 33 | $httpProvider.defaults.timeout = 600000; 34 | $httpProvider.interceptors.push('AutoInterceptor'); 35 | 36 | // Enable log 37 | $logProvider.debugEnabled(IsDebug); 38 | $urlRouterProvider.otherwise('/'); 39 | }) 40 | .run(function ($rootScope, ngProgressFactory, $state, lodash, Auth, $cookies, toaster) { 41 | 42 | //默认头像 43 | $rootScope.default_avatar = '/assets/images/avatar.png' 44 | 45 | //加载进度 46 | $rootScope.progressbar = ngProgressFactory.createInstance(); 47 | 48 | //登录之后不可进入页面. 49 | var routesThatForLogins = ['/signin']; 50 | 51 | var routeLogin = function (route) { 52 | return lodash.find(routesThatForLogins, 53 | function (noAuthRoute) { 54 | return lodash.startsWith(route, noAuthRoute); 55 | }); 56 | }; 57 | // 页面权限判断 58 | $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams){ 59 | $rootScope.progressbar.setColor('green'); 60 | $rootScope.progressbar.reset(); // Required to handle all edge cases. 61 | $rootScope.progressbar.start(); 62 | if(!$rootScope.load || Object.prototype.toString.call($rootScope.load) != '[object Object]'){ 63 | $rootScope.load = {}; 64 | } 65 | 66 | if(toState.name === 'dashboard' || toState.name === 'home'){ 67 | $rootScope.pageClass = "dashboard"; //只有home/main/signin页面不显示侧边栏 68 | if(toState.name === 'home'){ 69 | $rootScope.home = true; 70 | }else{ 71 | $rootScope.home = false; 72 | } 73 | }else{ 74 | if(toState.name === "signin"){ 75 | $rootScope.pageClass = "signin"; 76 | }else{ 77 | $rootScope.pageClass = ""; 78 | } 79 | } 80 | $rootScope.load.loaded = false; 81 | //已登录就需要跳转的页面 82 | Auth.isLoggedInAsync(function(loggedIn) { 83 | if((routeLogin(toState.url) && loggedIn) || (toState.onlyAdmin && !Auth.isAdmin() && loggedIn)){ 84 | event.preventDefault(); 85 | // if(!fromState || fromState.name === 'home'){ //如果本身就在home页面 $state.go('home')将不会实际执行 86 | // $rootScope.home = true; 87 | // $rootScope.load.loaded = true; 88 | // $rootScope.pageClass = "dashboard"; 89 | // $rootScope.progressbar.complete(); 90 | // }else{ 91 | // $state.go('home'); 92 | // } 93 | $state.go('dashboard'); 94 | } 95 | if((toState.onlyUser && !loggedIn) || (toState.onlyAdmin && !Auth.isAdmin() && !loggedIn)){ 96 | event.preventDefault(); 97 | $state.go('signin'); 98 | } 99 | }); 100 | }); 101 | 102 | // When route successfully changed. 103 | $rootScope.$on('$stateChangeSuccess', function(ev, toState, toParams, fromState, fromParams) { 104 | $rootScope.load.loaded = true; 105 | $rootScope.progressbar.complete(); 106 | $rootScope.previousState = fromState; 107 | $rootScope.previousParams = fromParams; 108 | $rootScope.currentState = toState; 109 | }); 110 | 111 | // When some error occured. 112 | $rootScope.$on('$stateChangeError', function() { 113 | $rootScope.progressbar.reset(); 114 | }); 115 | 116 | }); 117 | })(); 118 | -------------------------------------------------------------------------------- /src/app/index.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 可以覆盖变量. 3 | * bootstrap 变量列表: bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/_variables.scss 4 | */ 5 | //$bootstrap-sass-asset-helper: false; 6 | $icon-font-path: "../../bower_components/bootstrap-sass/assets/fonts/bootstrap/"; 7 | /** 8 | * bootstrap 已经包含normalize 9 | * 自动注入 bower.json中的依赖scss. 10 | */ 11 | // bower:scss 12 | @import "../../bower_components/bootstrap-sass/assets/stylesheets/_bootstrap.scss"; 13 | // endbower 14 | 15 | /** 16 | * gulp 中任务inject将自动注入其它scss文件 17 | */ 18 | // injector 19 | @import "base.scss"; 20 | @import "common.scss"; 21 | @import "markdown.scss"; 22 | @import "cluster/cluster.scss"; 23 | @import "containerCreate/containerCreate.scss"; 24 | @import "containerDetail/containerDetail.scss"; 25 | @import "containerList/containerList.scss"; 26 | @import "imageList/imageList.scss"; 27 | @import "main/dashboard.scss"; 28 | @import "main/main.scss"; 29 | @import "manage/manage.scss"; 30 | @import "settings/settings.scss"; 31 | @import "volume/volume.scss"; 32 | @import "components/header/header.scss"; 33 | @import "components/slidebar/slidebar.scss"; 34 | // endinjector 35 | -------------------------------------------------------------------------------- /src/app/main/dashboard.jade: -------------------------------------------------------------------------------- 1 | .main(ng-if="load.loaded") 2 | .container 3 | .app-list 4 | a.icon-view#cluster(ui-sref="cluster" title="集群资源使用情况") 5 | .icon-wrapper 6 | .icon-img 7 | img(ng-src="/assets/images/server.svg" alt="集群状况") 8 | .icon-name 集群状况 9 | 10 | a.icon-view#containerList(ui-sref="containerList" title="集群容器应用列表") 11 | .icon-wrapper 12 | .icon-img 13 | img(ng-src="/assets/images/applist.svg" alt="容器列表") 14 | .icon-name 应用列表 15 | 16 | a.icon-view#imageList(ui-sref="imageList" title="集群容器镜像列表") 17 | .icon-wrapper 18 | .icon-img 19 | img(ng-src="/assets/images/shop.svg" alt="镜像列表") 20 | .icon-name 镜像列表 21 | 22 | a.icon-view#volume(ui-sref="volume" title="数据卷") 23 | .icon-wrapper 24 | .icon-img 25 | img(ng-src="/assets/images/volume.svg" alt="数据卷") 26 | .icon-name 数据卷 27 | .wallpaper 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/app/main/dashboard.scss: -------------------------------------------------------------------------------- 1 | .app-list{ 2 | margin:auto; 3 | width: 70%; 4 | position: relative; 5 | top: 100px; 6 | @include transition(opacity 200ms ease, transform 100ms ease); 7 | @include animation(animate-fade-zoom-in 600ms); 8 | //清楚浮动 9 | &:after{ 10 | clear: both; 11 | content: ""; 12 | display: table; 13 | } 14 | } 15 | .icon-view{ 16 | width: 50%; 17 | padding-right: 20px; 18 | padding-left: 20px; 19 | margin-top: 20px; 20 | margin-bottom: 40px; 21 | float: left; 22 | display: block; 23 | @include box-sizing(border-box); 24 | .icon-wrapper{ 25 | background-color: rgba(116,140,222,.2); 26 | &:hover{ 27 | background-color: rgba(40, 127, 227, 0.4); 28 | } 29 | @include border-radius(25px); 30 | @include box-shadow(0 5px 20px rgba(0,0,0,.1)); 31 | @include transition(background-color 200ms ease-in, box-shadow 200ms ease); 32 | .icon-img{ 33 | height:100px; 34 | @extend .flex-vertical-box; 35 | img{ 36 | height: 85px; 37 | width: 85px; 38 | } 39 | } 40 | .icon-name{ 41 | padding: 8px 5px; 42 | border-top: 1px solid rgba(0,0,0,.055); 43 | border-radius: 0 0 25px 25px; 44 | background-color: rgba(0,0,0,.1); 45 | color: rgba(255,255,255,.85); 46 | font-weight: 700; 47 | letter-spacing: 2px; 48 | text-align: center; 49 | text-shadow: 0 7px 15px rgba(0,0,0,.25); 50 | } 51 | } 52 | } 53 | .wallpaper{ 54 | height: 100%; 55 | width: 100%; 56 | position: fixed; 57 | top: 0; 58 | right: 0; 59 | bottom: 0; 60 | left: 0; 61 | z-index: -1; 62 | overflow: hidden; 63 | &:after{ 64 | content: ""; 65 | display: block; 66 | height: 110%; 67 | width: 110%; 68 | position: absolute; 69 | top: -10px; 70 | right: -10px; 71 | bottom: -10px; 72 | left: -10px; 73 | background-image: url(/assets/images/dashboard.jpg); 74 | background-repeat: no-repeat; 75 | background-size: cover; 76 | -webkit-filter: blur(4px) grayscale(70%); 77 | } 78 | } 79 | 80 | @include keyframes(animate-fade-zoom-in){ 81 | 0%{ @include transform(scale(0)); } 82 | 25%{ @include transform(scale(0.4)); } 83 | 50%{ @include transform(scale(0.75)); } 84 | 100%{ @include transform(scale(1)); } 85 | } 86 | -------------------------------------------------------------------------------- /src/app/main/main.controller.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | angular.module("dockerApp") 3 | .controller('MainCtrl', ['$rootScope', '$scope', '$location', '$state', function($rootScope, $scope, $location, $state){ 4 | $scope.goToDashboard = function(){ 5 | $state.go( 6 | 'dashboard' 7 | ); 8 | }; 9 | }]) 10 | .controller('DashboardCtrl', ['$rootScope', '$scope', '$location', '$state', function($rootScope, $scope, $location, $state){ 11 | 12 | }]); 13 | })(); 14 | -------------------------------------------------------------------------------- /src/app/main/main.jade: -------------------------------------------------------------------------------- 1 | div(ng-if="load.loaded" ng-cloak) 2 | .code 3 | h1 4 | span(style="color:#97C774;") H 5 | span(style="color: #B63E98;") E 6 | span(style="color: #D18E62;") L 7 | span(style="color: #DB3E41;") L 8 | span(style="color: #1BABA5;") O 9 | span , D 10 | span.planet-wraper.planet-shadow 11 | .planet 12 | .crater-1 13 | .crater-2 14 | .crater-3 15 | .crater-4 16 | .crater-5 17 | | cker 18 | h2 /*Docker Swarm 可视化解决方案*/ 19 | .btn-group 20 | a.btn.dashboard-btn.btn-ghost-light(ng-click="goToDashboard()") 进入控制台 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/app/main/main.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp') 5 | .config(function ($stateProvider) { 6 | $stateProvider 7 | .state('home', { 8 | url: '/', 9 | views:{ 10 | 'home':{ 11 | templateUrl: 'app/main/main.html', 12 | controller: 'MainCtrl' 13 | } 14 | } 15 | }) 16 | .state('dashboard', { 17 | url : '/dashboard', 18 | templateUrl : 'app/main/dashboard.html', 19 | controller : 'DashboardCtrl', 20 | onlyUser : true 21 | }); 22 | }); 23 | })(); 24 | -------------------------------------------------------------------------------- /src/app/main/main.scss: -------------------------------------------------------------------------------- 1 | body.ui.main{ 2 | // background-color: #163040; 3 | background-color: #292B36; 4 | background-image: url('data:image/svg+xml;utf8,'); 5 | background-size: 20rem 20rem; 6 | background-position: center 0; 7 | @include animation(bg 20s linear infinite); 8 | } 9 | .home{ 10 | width: 100%; 11 | .code{ 12 | position : absolute; 13 | top : 40%; 14 | left : 50%; 15 | @include transform(translate(-50%,-50%)); 16 | text-align : center; 17 | .planet-wraper { 18 | display: inline-block; 19 | width: 4rem; 20 | height: 4rem; 21 | margin: auto; 22 | @include border-radius(50%); 23 | } 24 | .planet-shadow { 25 | background-image: linear-gradient(top left, #24b88b 20%, #21776f); 26 | background-image: -moz-linear-gradient(top left, #24b88b 20%, #21776f); 27 | @include box-shadow(inset 0 -5px 10px rgba(22, 48, 64, 0.5), 0 0 1.5rem rgba(170, 194, 194, 0.3)); 28 | } 29 | .planet { 30 | position: relative; 31 | width: 100%; 32 | height: 100%; 33 | border-radius: 50%; 34 | @include animation(rotate 15s linear infinite); 35 | overflow: hidden; 36 | > * { 37 | position: relative; 38 | border-radius: 50%; 39 | background-color: #209b85; 40 | box-shadow: inset 1px 1px 5px #163040, 0 0 7px #63eed2 ; 41 | opacity: 0.3; 42 | } 43 | } 44 | .crater-1 { 45 | top: 2rem; 46 | left: 1.5rem; 47 | width: 0.75rem; 48 | height: 0.75rem; 49 | } 50 | .crater-2 { 51 | top: -0.75rem; 52 | left: 0rem; 53 | width: 1.5rem; 54 | height: 1.5rem; 55 | } 56 | .crater-3 { 57 | top: 0.5rem; 58 | left: 2rem; 59 | width: 1.25rem; 60 | height: 1.25rem; 61 | } 62 | .crater-4 { 63 | top: -1.5rem; 64 | left: 3.5rem; 65 | width: 0.5rem; 66 | height: 0.5rem; 67 | } 68 | .crater-5 { 69 | top: -1.7rem; 70 | left: -0.8rem; 71 | width: 2rem; 72 | height: 1.5rem; 73 | } 74 | } 75 | 76 | h1{ 77 | position : relative; 78 | font-size : 3vw; 79 | color : white; 80 | &:after{ 81 | content : ''; 82 | height : 140%; 83 | width : 1px; 84 | background : transparent; 85 | position : absolute; 86 | display : block; 87 | right : 1vw; 88 | top : -20%; 89 | @include animation(blink 1.2s linear infinite); 90 | } 91 | } 92 | 93 | h2{ 94 | margin-top : 10%; 95 | font-size : 2vw; 96 | color : #59B75C; 97 | } 98 | .btn-group{ 99 | margin-top: 30px; 100 | } 101 | .btn-ghost-light, 102 | .btn-ghost-light:focus{ 103 | border: 1px solid #fff; 104 | background-color: rgba(155,163,175,0); 105 | color: #fff; 106 | } 107 | .btn-ghost-light:hover{ 108 | background-color: rgba(255, 255, 255, 0.1); 109 | } 110 | 111 | @include keyframes(bg){ 112 | 100%{ background-position: center 20rem; } 113 | } 114 | @include keyframes(rotate){ 115 | 100% { @include transform(rotate(360deg)); } 116 | } 117 | @include keyframes(blink){ 118 | 49%{background : transparent;} 119 | 50%{background : white;} 120 | 100%{background : white;} 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/app/manage/manage.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.manage',[]) 5 | .config(function ($stateProvider) { 6 | $stateProvider 7 | .state('userList',{ 8 | url:'/userlist', 9 | templateUrl:'app/manage_user/userList.html', 10 | controller:'UserListCtrl', 11 | onlyAdmin:true 12 | }); 13 | }); 14 | })(); 15 | -------------------------------------------------------------------------------- /src/app/manage/manage.scss: -------------------------------------------------------------------------------- 1 | .manage-box{ 2 | @include public-box(100%); 3 | } 4 | .manage-container{ 5 | padding: 0 15px; 6 | 7 | a.edit_user{ 8 | i{ 9 | margin-right: 10px; 10 | } 11 | } 12 | a.delete_user{ 13 | color: rgb(242, 109, 109); 14 | } 15 | table.table{ 16 | th,td{ 17 | text-align: center; 18 | vertical-align: middle; 19 | } 20 | th{ 21 | line-height: 2em; 22 | border-bottom: none; 23 | } 24 | th:first-child,td:first-child{ 25 | text-align: left; 26 | } 27 | } 28 | .portlet-empty { 29 | min-height: 125px; 30 | } 31 | .portlet-body { 32 | clear: both; 33 | padding: 0; 34 | } 35 | .table-toolbar { 36 | margin-bottom: 15px; 37 | } 38 | .dataTables_info { 39 | margin: 30px 0; 40 | } 41 | .sorting{ 42 | cursor: pointer; 43 | *cursor: hand; 44 | span{ 45 | display: inline-block; 46 | width:16px; 47 | height:16px; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/manage_user/userAdd.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | .modal-title 增加用户 3 | .modal-body 4 | .portlet-body.form 5 | form.addUserForm.form-horizontal(name="addUserForm" novalidate) 6 | .form-body 7 | .form-group 8 | label.control-label.col-md-4 nickname 9 | .col-sm-6 10 | input.form-control(type="text" placeholder="昵称" autocomplete="off" ng-model="user.nickname" required) 11 | .form-group 12 | label.control-label.col-md-4 Email 13 | .col-sm-6 14 | input.form-control(type="email" placeholder="邮箱" autocomplete="off" ng-model="user.email" required) 15 | .form-group 16 | label.control-label.col-md-4 Password 17 | .col-sm-6 18 | input.form-control(type="password" placeholder="密码" autocomplete="off" ng-model="user.password" required) 19 | .form-group 20 | label.control-label.col-md-4 状态 21 | .col-sm-8 22 | label.radio-inline 23 | input(type="radio" ng-model="user.status" value="1") 24 | | Authenticated 25 | label.radio-inline 26 | input(type="radio" ng-model="user.status" value="0") 27 | | Unverified 28 | label.radio-inline 29 | input(type="radio" ng-model="user.status" value="2") 30 | | Blocked 31 | .form-group 32 | label.control-label.col-md-4 角色 33 | .col-sm-8 34 | label.radio-inline 35 | input(type="radio" ng-model="user.role" value="user") 36 | | user(普通用户) 37 | label.radio-inline 38 | input(type="radio" ng-model="user.role" value="admin") 39 | | admin(管理员) 40 | .modal-footer 41 | .btn.btn-primary(ng-disabled="addUserForm.$invalid" ng-click="ok(addUserForm)") OK 42 | .btn.btn-warning(ng-click="cancel()") Cancel 43 | -------------------------------------------------------------------------------- /src/app/manage_user/userEdit.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | .modal-title Edit User 3 | .modal-body 4 | .portlet-body.form 5 | //- BEGIN FORM 6 | form.form-horizontal(name="editUserForm" novalidate) 7 | .form-body 8 | .form-group 9 | label.control-label.col-md-4 Nick Name 10 | .col-md-6 11 | input.form-control(type="text" required autocomplete="off" ng-model="currentUser.nickname") 12 | .form-group 13 | label.control-label.col-md-4 Email 14 | .col-md-6 15 | input.form-control(type="email" email required autocomplete="off" ng-model="currentUser.email") 16 | .form-group 17 | label.control-label.col-md-4 是否修改密码(可选) 18 | .col-md-6 19 | .checkbox 20 | input(type="checkbox" autocomplete="off" ng-model="currentUser.changePwd" style="margin-left:0px;") 21 | div.change_pwd(ng-show="currentUser.changePwd" ng-cloak) 22 | .form-group 23 | label.control-label.col-md-4 新密码 24 | .col-md-6 25 | input.form-control(type="password" autocomplete="off" ng-model="currentUser.password") 26 | .form-group 27 | label.control-label.col-md-4 再次输入新密码 28 | .col-md-6 29 | input.form-control(type="password" autocomplete="off" ng-model="currentUser.password2") 30 | div.col-md-offset-4(ng-show="currentUser.password || currentUser.password2" style="width:100%;") 31 | span.text-danger( ng-show="currentUser.password !== currentUser.password2 && currentUser.password && currentUser.password2") 两次密码不匹配 32 | .form-group 33 | .control-label.col-md-4 Status 34 | .col-md-8 35 | label.radio-inline 36 | input(type="radio" ng-model="currentUser.status" value="1") 37 | | Authenticated 38 | label.radio-inline 39 | input(type="radio" ng-model="currentUser.status" value="0") 40 | | Unverified 41 | label.radio-inline 42 | input(type="radio" ng-model="currentUser.status" value="2") 43 | | Blocked 44 | //- END fORM 45 | .modal-footer 46 | button.btn.btn-primary(ng-disabled="editUserForm.$invalid || (currentUser.changePwd && (!currentUser.password || !currentUser.password2)) || (currentUser.changePwd && currentUser.password !== currentUser.password2)" ng-click="ok(editUserForm)") OK 47 | button.btn.btn-warning(ng-click="cancel()") Cancel 48 | -------------------------------------------------------------------------------- /src/app/manage_user/userList.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.manage') 5 | .controller('UserListCtrl', function($scope, $modal, $state, User, toaster, SweetAlert){ 6 | //初始数据 7 | $scope.options = { 8 | currentPage:1, //当前页数 9 | itemsPerPage:10, //每页显示的条数 10 | sortName:'created', //排序项 11 | sortOrder:false //升降 12 | }; 13 | $scope.changeSort = function(sortName){ 14 | if($scope.options.sortName === sortName){ 15 | $scope.options.sortOrder = !$scope.options.sortOrder; 16 | } 17 | $scope.options.sortName = sortName; 18 | }; 19 | $scope.maxSize = 5; //分页条最大显示数 20 | $scope.$watchCollection( 21 | 'options', 22 | function(newValue, oldValue) { 23 | doPaging(newValue); 24 | } 25 | ); 26 | function doPaging(options) { 27 | User.getUserList(options, function(users){ 28 | $scope.userList = users.data; 29 | $scope.bigTotalItems = users.count; 30 | },function(err){ 31 | $scope.userList = []; 32 | }); 33 | } 34 | 35 | 36 | //删除 37 | $scope.del = function (user) { 38 | SweetAlert.swal({ 39 | title: "你确定?", 40 | text: "你将删除选中的"+ user.nickname +"用户", 41 | type: "warning", 42 | showCancelButton: true, 43 | cancelButtonText: "取消", 44 | confirmButtonColor: "#DD6B55", 45 | confirmButtonText: "是的, 我要删除!", 46 | closeOnConfirm: false, 47 | showLoaderOnConfirm: true }, 48 | function(isConfirm){ 49 | if(isConfirm){ 50 | window.swal.close(); //关闭SweetAlert 51 | User.deleteUser({id:user._id}).then(function () { 52 | toaster.pop('success', '', '删除用户' + user.nickname + '成功'); 53 | doPaging($scope.options); 54 | }).catch(function (err) { 55 | err = err.data.error_msg || '删除用户失败'; 56 | toaster.pop('error', '', err); 57 | }); 58 | } 59 | }); 60 | }; 61 | //增加 62 | $scope.add = function (size) { 63 | var modalAdd = $modal.open({ 64 | templateUrl: 'app/manage_user/userAdd.html', 65 | controller: 'ModalAddUserCtrl', 66 | size: size, 67 | }); 68 | 69 | modalAdd.result.then(function (nickname) { 70 | toaster.pop('success', '', '添加用户 '+nickname+' 成功'); 71 | doPaging($scope.options); 72 | }).catch(function (err) { 73 | if(typeof err === 'object'){ 74 | err = err.message || err.data.error_msg; 75 | } 76 | if(err && err !== "cancel"){ 77 | toaster.pop('error', '', err); 78 | } 79 | }); 80 | }; 81 | //编辑 82 | $scope.edit = function (user) { 83 | var modalEdit = $modal.open({ 84 | templateUrl: 'app/manage_user/userEdit.html', 85 | controller: 'ModalEditUserCtrl', 86 | resolve: { 87 | currentUser: function () { 88 | return user; 89 | } 90 | } 91 | }); 92 | 93 | modalEdit.result.then(function (nickname) { 94 | toaster.pop('success', '', '编辑用户 '+nickname+' 成功'); 95 | doPaging($scope.options); 96 | }); 97 | }; 98 | 99 | }) 100 | .controller('ModalAddUserCtrl',function ($scope, $modalInstance, User, toaster) { 101 | $scope.user = {}; 102 | $scope.ok = function (form) { 103 | if(form.$valid){ 104 | User.addUser($scope.user).then(function(){ 105 | $modalInstance.close($scope.user.nickname); 106 | }).catch( function(err) { 107 | err = err.data.error_msg || '添加用户失败'; 108 | toaster.pop('error', '', err); 109 | }); 110 | } 111 | }; 112 | 113 | $scope.cancel = function () { 114 | $modalInstance.dismiss('cancel'); 115 | }; 116 | }) 117 | .controller('ModalEditUserCtrl',function ($scope, $modalInstance, User, toaster, currentUser) { 118 | $scope.currentUser = currentUser; 119 | 120 | $scope.ok = function (form) { 121 | if(form.$valid){ 122 | User.updateUser($scope.currentUser._id, $scope.currentUser).then(function(){ 123 | $modalInstance.close($scope.currentUser.nickname); 124 | }).catch( function(err) { 125 | err = err.data.error_msg || '编辑用户失败'; 126 | toaster.pop('error', '', err); 127 | }); 128 | } 129 | }; 130 | 131 | $scope.cancel = function () { 132 | $modalInstance.dismiss('cancel'); 133 | }; 134 | }); 135 | })(); 136 | -------------------------------------------------------------------------------- /src/app/manage_user/userList.jade: -------------------------------------------------------------------------------- 1 | .manage-box 2 | .manage-container.user-manage 3 | h1.text-center 用户管理-用户列表 4 | .panel 5 | section.panel-body 6 | .table-toolbar 7 | .btn-group 8 | button.btn.btn-primary(ng-click="add()") 9 | | 增加用户 10 | i.fa.fa-plus 11 | .btn-group.pull-right 12 | .dataTables_filter 13 | input.form-control.input-medium(type="text" placeholder="搜索" ng-model="q") 14 | .portlet-body.table-responsive 15 | table.table.table-striped.table-hover.table-condensed 16 | thead 17 | tr 18 | th 昵称 19 | th Email 20 | th 角色 21 | th 状态 22 | th 注册时间 23 | th 更新时间 24 | th.text-center 管理 25 | tbody 26 | tr.odd.gradeX(ng-repeat="user in userList | filter:q") 27 | td 28 | span {{ user.nickname }} 29 | td 30 | span {{ user.email }} 31 | td 32 | span {{ user.role }} 33 | td 34 | span {{ user.status || useStatus }} 35 | td 36 | span {{ user.created | date: 'short' }} 37 | td 38 | span {{ user.updated | date : 'short' }} 39 | td.text-center 40 | a.edit_user(href="" ng-click="edit(user)") 41 | i.fa.fa-pencil-square-o.fa-lg(aria-hidden="true") 42 | a.delete_user(href="" ng-click="del(user)") 43 | i.fa.fa-trash.fa-lg(aria-hidden="true") 44 | .row 45 | .col-sm-5.col-xs-12 46 | .dataTables_info Page: {{ options.currentPage }} / {{ numPages}} of {{ bigTotalItems }} 47 | .col-sm-7.col-xs-12 48 | pagination(ng-change="pageChanged()" total-items="bigTotalItems" ng-model="options.currentPage" max-size="maxSize" items-per-page="options.itemsPerPage" class="pagination-md pull-right" boundary-links="true" rotate="false" num-pages="numPages") 49 | -------------------------------------------------------------------------------- /src/app/markdown.scss: -------------------------------------------------------------------------------- 1 | $markBaseFontColor: #333; 2 | $markDefaultFontSize: 16px; 3 | .markdown-body{ 4 | 5 | font-family: "Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif; 6 | color: $markBaseFontColor; 7 | overflow: hidden; 8 | font-size: $markDefaultFontSize; 9 | line-height: 1.6; 10 | word-wrap: break-word; 11 | 12 | padding: 0 16px; 13 | *{ 14 | @include box-sizing(border-box); 15 | } 16 | blockquote, 17 | dl, 18 | ol, 19 | p, 20 | pre, 21 | table, 22 | ul{ 23 | margin-top: 0px; 24 | margin-bottom: 16px; 25 | } 26 | ul{ 27 | padding-left: 2em; 28 | list-style: circle; 29 | li{ 30 | list-style: initial; 31 | } 32 | } 33 | p{ 34 | margin: 0 0 10px; 35 | color: $markBaseFontColor; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/settings/settings.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.settings',[]) 5 | .config(function ($stateProvider) { 6 | $stateProvider 7 | .state('signin', { 8 | url: '/signin', 9 | templateUrl: 'app/settings/signin.html', 10 | controller: 'LocalSignInCtrl' 11 | }) 12 | .state('setting', { 13 | url: '/setting', 14 | templateUrl: 'app/settings/userSetting.html', 15 | controller: 'SettingCtrl', 16 | onlyUser:true 17 | }); 18 | }); 19 | })(); 20 | -------------------------------------------------------------------------------- /src/app/settings/settings.scss: -------------------------------------------------------------------------------- 1 | .sigin-container{ 2 | font-size: 14px; 3 | position: relative; 4 | top:60px; 5 | width: 400px; 6 | margin: 0 auto; 7 | padding: 27px 20px 30px; 8 | @include border-radius(0); 9 | background-color: #fff; 10 | @include box-shadow(0 15px 30px 0 rgba(0,0,1,.1)); 11 | } 12 | .bg{ 13 | position: fixed; 14 | z-index: -1; 15 | top: 0; 16 | right: 0; 17 | bottom: 0; 18 | left: 0; 19 | background: url(../assets/images/blue-mountain.jpg) no-repeat; 20 | background-size: cover; 21 | &:before{ 22 | position: fixed; 23 | z-index: -1; 24 | bottom: 0; 25 | left: 0; 26 | display: block; 27 | width: 100%; 28 | height: 50%; 29 | content: ''; 30 | opacity: .4; 31 | } 32 | } 33 | .sigin-form{ 34 | $primary: #2196F3; 35 | 36 | .wrapper{ 37 | @extend .flex-vertical-box; 38 | flex-direction: column; 39 | width: 100%; 40 | padding: 20px; 41 | position: relative; 42 | input{ 43 | display: block; 44 | padding: 15px 10px; 45 | margin-bottom: 10px; 46 | width: 100%; 47 | border: 1px solid #ddd; 48 | @include transition(border-width 0.2s ease); 49 | @include border-radius(2px); 50 | color: #ccc; 51 | & + i.fa{ 52 | color: #fff; 53 | font-size: 1em; 54 | position: absolute; 55 | margin-top: -40px; 56 | opacity: 0; 57 | left: 0; 58 | transition: all 0.1s ease-in; 59 | } 60 | &:focus { 61 | & + i.fa { 62 | opacity: 1; 63 | left: 30px; 64 | @include transition(all 0.25s ease-out); 65 | } 66 | outline: none; 67 | color: #444; 68 | border-color: $primary; 69 | border-left-width: 35px; 70 | } 71 | } 72 | 73 | } 74 | .captcha-code{ 75 | padding-left: 0; 76 | } 77 | .captcha-img{ 78 | padding-right: 0; 79 | img{ 80 | width: 100%; 81 | height: 44px; 82 | } 83 | } 84 | } 85 | #siginBtn{ 86 | @include border-radius(0); 87 | } 88 | -------------------------------------------------------------------------------- /src/app/settings/signin.jade: -------------------------------------------------------------------------------- 1 | .bg 2 | .signin-box 3 | .sigin-container 4 | h4.title 登录 DockerVI 5 | form.sigin-form.form-horizontal(name="signinForm" ng-submit="login(signinForm)" ng-keypress="loginPress($event, signinForm)" novalidate) 6 | .wrapper 7 | input.form-control(type="text" name="uEmail" required email ng-minlength="3" ng-maxlength="30" ng-model="user.email" placeholder="邮箱") 8 | i.fa.fa-envelope-o 9 | div(ng-show=" signinForm.uEmail.$touched" style="width:100%;") 10 | span.text-danger( ng-show="signinForm.uEmail.$error.email") 请输入正确的邮箱地址 11 | input.form-control(type="password" required ng-model="user.password" placeholder="密码") 12 | i.fa.fa-unlock-alt 13 | .form-group 14 | input.form-control(required maxlength="6" type="text" name="captcha" ng-model="user.captcha" placeholder="验证码") 15 | a(href="javascript:;" ng-click="changeCaptcha()") 16 | img(ng-src="{{ captchaUrl }}") 17 | .form-group 18 | input#siginBtn.btn.btn-primary.btn-lg.btn-block(type="submit" ng-disabled="signinForm.$invalid" value="登陆") 19 | -------------------------------------------------------------------------------- /src/app/settings/singin.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.settings') 5 | .controller('LocalSignInCtrl', function ($rootScope, $scope, Auth, $state, $log, toaster, $cookies) { 6 | //获取验证码 7 | function getCaptcha() { 8 | $scope.captchaUrl = '/api/users/getCaptcha?' + Math.random(); 9 | } 10 | getCaptcha(); 11 | 12 | //更新验证码 13 | $scope.changeCaptcha = function () { 14 | getCaptcha(); 15 | }; 16 | 17 | $scope.user = {}; 18 | function toLogin(){ 19 | Auth.login({ 20 | email: $scope.user.email, 21 | password: $scope.user.password, 22 | captcha: $scope.user.captcha 23 | }) 24 | .then( function() { 25 | toaster.pop({ 26 | type: 'success', 27 | title: '', 28 | body: '登录成功,欢迎光临!', 29 | timeout: 1000 30 | }); 31 | $state.go('dashboard'); 32 | }) 33 | .catch( function(err) { 34 | $scope.user.captcha = ''; 35 | getCaptcha(); 36 | err = err.error_msg || err.data.error_msg || "登录失败,请重试"; 37 | toaster.pop('error','',err); 38 | $cookies.remove('token'); 39 | }); 40 | } 41 | 42 | $scope.login = function(form) { 43 | if(form.$valid) { 44 | toLogin(); 45 | } 46 | }; 47 | $scope.loginPress = function(ev, form) { 48 | if (ev.which===13 && form.$valid){ 49 | ev.preventDefault(); 50 | toLogin(); 51 | } 52 | }; 53 | 54 | }); 55 | }()); 56 | -------------------------------------------------------------------------------- /src/app/settings/user.setting.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.settings') 5 | .controller('SettingCtrl', function ($scope,User,Auth,toaster,$state) { 6 | $scope.user = User.get(); 7 | 8 | $scope.mdUser = function (form) { 9 | if(form.$valid) { 10 | User.mdUser($scope.user).then(function (result) { 11 | $scope.user = result.data; 12 | toaster.pop('success', '', '修改资料成功'); 13 | }).catch(function (err) { 14 | err = err.data.error_msg || '修改用户失败'; 15 | toaster.pop('error', '', err); 16 | $state.reload(); 17 | }); 18 | } 19 | }; 20 | }); 21 | })(); 22 | -------------------------------------------------------------------------------- /src/app/settings/userSetting.jade: -------------------------------------------------------------------------------- 1 | div(ng-include="app/components/slidebar/slidebar.html") 2 | .setting-box 3 | .settings-container 4 | h2.title 设置 5 | hr 6 | .profile 7 | //- 昵称 8 | .control-group 9 | .settings-form(name="settingForm" ng-submit="mdUser(settingForm)" novalidate) 10 | .form-group 11 | label.control-label 昵称 12 | input.form-control(type="text" required nickname placeholder="2-15字符,中英文、数字和下划线") 13 | button.btn.btn-block.btn-lg.btn-primary(type="submit") 14 | -------------------------------------------------------------------------------- /src/app/volume/newVolume.jade: -------------------------------------------------------------------------------- 1 | .content-body.volume(ng-if="load.loaded") 2 | .panel.panel-default.panel-page-header 3 | .panel-body 4 | .primary-section 5 | h3 创建Volume 6 | p 容器持久化数据、共享数据的存储空间 7 | 8 | .panel 9 | section.panel-body 10 | .setting-section 11 | .row 12 | div.col-md-9.col-lg-9 13 | form.form-horizontal(roel='volumeForm', name='volumeForm' novalidate) 14 | .form-group 15 | label.col-sm-2.control-label.no-padding-right(for="volume-name") Volume名称 16 | span.txt-color-red * 17 | div.col-sm-3 18 | input#volume-name(type="text" name="volume-name" ng-model="volume.name" ng-change="checkVolumeName()" ng-blur="checkVolumeName()" autofocus tabindex="0" required) 19 | br 20 | span.text-danger(ng-if="!available.name") {{ available.nameErrorMsg }} 21 | div.col-sm-5 22 | button.btn.create-volume-btn(ng-click="createVolume()" ng-class="{'btn-success': !waitForCreated, 'btn-default': waitForCreated}" ng-disabled="!volume.name || !available.name || waitForCreated" style="padding-left:60px; padding-right:60px;") 23 | i(ng-if="waitForCreated").fa.fa-cog.fa-spin.fa-2x.fa-fw 24 | i(ng-if="waitForCreated").fa.fa-cog.fa-spin.fa-1x.fa-fw 25 | | 创建 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/app/volume/volume.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('dockerApp.volume',[]) 5 | .config(function ($stateProvider) { 6 | $stateProvider 7 | .state('volume', { 8 | url: '/volumes', 9 | templateUrl: 'app/volume/volume.html', 10 | controller: 'volumeCtrl', 11 | params:{ 12 | newVolume : undefined 13 | } 14 | }) 15 | .state('volumeCreate', { 16 | url: '/volumes/new', 17 | templateUrl: 'app/volume/newVolume.html', 18 | controller: 'volumeCreateCtrl' 19 | }); 20 | }); 21 | })(); 22 | -------------------------------------------------------------------------------- /src/app/volume/volume.scss: -------------------------------------------------------------------------------- 1 | .volume-toolbar{ 2 | margin-bottom: 10px; 3 | } 4 | .volumes-list { 5 | .volume-heading{ 6 | margin-top: 5px; 7 | text-align: center; 8 | word-break: break-all; 9 | h3{ 10 | font-size: 20px; 11 | font-weight: 300; 12 | margin: 0 0 5px; 13 | } 14 | .meta{ 15 | color: #aab2bd; 16 | } 17 | } 18 | .volumes{ 19 | $volumeBgColor: #f2f4f7; 20 | margin-bottom: 10px; 21 | padding: 0px; 22 | .volume{ 23 | text-align: center; 24 | min-height: 60px; 25 | background-color: $volumeBgColor; 26 | @include border-radius(4px 0px); 27 | border: none; 28 | } 29 | .volume-detail{ 30 | padding: 15px 0 20px 40px; 31 | border: 1px solid $baseBorderColor; 32 | border-top:0; 33 | table{ 34 | width: 95%; 35 | tr{ 36 | &:hover{ 37 | background-color: white; 38 | } 39 | td{ 40 | span{ 41 | display: inline-block; 42 | border-width: 1px; 43 | border-color: transparent; 44 | background-color: transparent; 45 | border-style: none none dotted; 46 | 47 | padding: 5px; 48 | margin-bottom: 5px; 49 | 50 | &.value{ 51 | color: #999!important; 52 | border-bottom-color:$defaultBorderColor; 53 | } 54 | &.engineK{ 55 | float: right; 56 | width: 90%; 57 | } 58 | } 59 | ul{ 60 | $evenColor: #f9f9f9; 61 | li{ 62 | overflow: hidden;//BFC 63 | } 64 | li:nth-child(even){ 65 | background-color: $evenColor; 66 | } 67 | } 68 | } 69 | } 70 | 71 | } 72 | } 73 | 74 | } 75 | } 76 | .create-volume-btn{ 77 | i.fa-1x{ 78 | margin-left: -6px; 79 | } 80 | i.fa-2x{ 81 | font-size: 1.3em; 82 | } 83 | 84 | &.btn-default, 85 | &.btn-default:hover{ 86 | background-color: #cfcece; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/src/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/images/applist.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/src/assets/images/avatar.png -------------------------------------------------------------------------------- /src/assets/images/blue-mountain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/src/assets/images/blue-mountain.jpg -------------------------------------------------------------------------------- /src/assets/images/container.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/images/dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/src/assets/images/dashboard.jpg -------------------------------------------------------------------------------- /src/assets/images/server.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/shop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/volume-normal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/assets/images/volume.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoyangxiaozhu/DockerVI/73ae2f5ab9ecb1296e3da42988cf527eb52c82fc/src/favicon.ico -------------------------------------------------------------------------------- /src/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html.loading(ng-app="dockerApp") 3 | head 4 | meta(charset="utf-8") 5 | title docker集群可视化管理 6 | base(href="/") 7 | meta(name="description" content="this is docker swarm manager") 8 | meta(name="keyword" content="docker visual node angular swarm") 9 | meta(name="viewport" content="width=device-width") 10 | link(rel="shortcut icon" href="favicon.ico") 11 | //- Place favicon.ico and apple-touch-icon.png in the root directory 12 | 13 | 14 | // build:css({.tmp/serve,src}) styles/vendor.css 15 | // bower:css 16 | // run `gulp inject` to automatically populate bower styles dependencies 17 | // endbower 18 | // endbuild 19 | 20 | // build:css({.tmp/serve,src}) styles/app.css 21 | // inject:css 22 | // css files will be automatically insert here 23 | // endinject 24 | // endbuild 25 | 26 | 27 | // build:js(src) scripts/vendor.js 28 | // bower:js 29 | // run `gulp inject` to automatically populate bower script dependencies 30 | // endbower 31 | // endbuild 32 | 33 | 34 | body.ui(ng-class="{ 'main': home }") 35 | include ./assets/images/svg-sprite.svg 36 | .row 37 | .col-xs-12 38 | div(ng-if="!home && pageClass != 'signin' " ng-cloak) 39 | include ./app/components/header/header.jade 40 | div(ng-show="pageClass != 'dashboard' && pageClass != 'signin'" ng-cloak) 41 | include ./app/components/slidebar/slidebar.jade 42 | div(ui-view) 43 | div.home(ui-view="home") 44 | 45 | toaster-container 46 | div.loader(style="position: absolute; top:50%; left:50%; transform: translate(-50%, -50%);", ng-hide="load.loaded" aria-hidden="true") 47 | div.loader-inner.pacman 48 | div(ng-style="{'border-color': (pageClass && pageClass==='dashboard')?'#fff transparent #fff #fff':'#ccc transparent #ccc #ccc'}" style="border-color: rgb(204, 204, 204) transparent rgb(204, 204, 204) rgb(204, 204, 204);") 49 | div(ng-style="{'border-color': (pageClass && pageClass==='dashboard')?'#fff transparent #fff #fff':'#ccc transparent #ccc #ccc'}" style="border-color: rgb(204, 204, 204) transparent rgb(204, 204, 204) rgb(204, 204, 204);") 50 | div(ng-style="{'background-color': (pageClass && pageClass==='dashboard')?'#fff':'#ccc'}" style="background-color: rgb(204, 204, 204);") 51 | div(ng-style="{'background-color': (pageClass && pageClass==='dashboard')?'#fff':'#ccc'}" style="background-color: rgb(204, 204, 204);") 52 | div(ng-style="{'background-color': (pageClass && pageClass==='dashboard')?'#fff':'#ccc'}" style="background-color: rgb(204, 204, 204);") 53 | 54 | // build:js(src) scripts/socket.js 55 | script(src="socket.io.js") 56 | // endbuild 57 | // build:js({.tmp/serve,.tmp/partials,src}) scripts/app.js 58 | // inject:js 59 | // js files will be automatically insert here 60 | // endinject 61 | 62 | // inject:partials 63 | // angular templates will be automatically converted in js and inserted here 64 | // endinject 65 | // endbuild 66 | -------------------------------------------------------------------------------- /start_mongo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ ! $1 ]; then 3 | echo "usage: $0 name" 4 | exit 5 | fi 6 | NAME=$1 7 | docker rm -f $NAME 2>/dev/null 8 | docker run --name $NAME --hostname $NAME --restart=always -p 32777:27017 -v `pwd`/session:/data/db -d mongo 9 | --------------------------------------------------------------------------------