Initial commit
This commit is contained in:
		
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | Example Voting App | ||||||
|  | ================== | ||||||
|  |  | ||||||
|  | This is an example Docker app with multiple services. It is run with Docker Compose and uses Docker Networking to connect containers together. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Running | ||||||
|  | ------- | ||||||
|  |  | ||||||
|  | Since this app makes use of Compose's experimental networking support, it must be started with: | ||||||
|  |  | ||||||
|  |     $ cd vote-apps/ | ||||||
|  |     $ docker-compose --x-networking up -d | ||||||
|  |  | ||||||
|  | The app will be running on port 5000 on your Docker host, and the results will be on port 5001. | ||||||
							
								
								
									
										25
									
								
								vote-apps/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vote-apps/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | voting-app: | ||||||
|  |   build: ./voting-app/. | ||||||
|  |   volumes: | ||||||
|  |     - ./voting-app:/app | ||||||
|  |   ports: | ||||||
|  |     - "5000:80" | ||||||
|  |  | ||||||
|  | redis: | ||||||
|  |   image: redis | ||||||
|  |   ports: ["6379"] | ||||||
|  |  | ||||||
|  | worker: | ||||||
|  |   build: ./worker | ||||||
|  |  | ||||||
|  | db: | ||||||
|  |   image: postgres:9.4 | ||||||
|  |   volumes: | ||||||
|  |     - "myvolume:/var/lib/postgresql/data" | ||||||
|  |  | ||||||
|  | result-app: | ||||||
|  |   build: ./result-app/. | ||||||
|  |   volumes: | ||||||
|  |     - ./result-app:/app | ||||||
|  |   ports: | ||||||
|  |     - "5001:80" | ||||||
							
								
								
									
										15
									
								
								vote-apps/result-app/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vote-apps/result-app/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | FROM node:0.10 | ||||||
|  |  | ||||||
|  | RUN mkdir /app | ||||||
|  | WORKDIR /app | ||||||
|  |  | ||||||
|  | ADD package.json /app/package.json | ||||||
|  | RUN npm install && npm ls | ||||||
|  | RUN mv /app/node_modules /node_modules | ||||||
|  |  | ||||||
|  | ADD . /app | ||||||
|  |  | ||||||
|  | ENV PORT 80 | ||||||
|  | EXPOSE 80 | ||||||
|  |  | ||||||
|  | CMD ["node", "server.js"] | ||||||
							
								
								
									
										20
									
								
								vote-apps/result-app/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vote-apps/result-app/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | { | ||||||
|  |   "name": "result-app", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "description": "", | ||||||
|  |   "main": "server.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "test": "echo \"Error: no test specified\" && exit 1" | ||||||
|  |   }, | ||||||
|  |   "author": "", | ||||||
|  |   "license": "MIT", | ||||||
|  |   "dependencies": { | ||||||
|  |     "body-parser": "^1.14.1", | ||||||
|  |     "cookie-parser": "^1.4.0", | ||||||
|  |     "express": "^4.13.3", | ||||||
|  |     "method-override": "^2.3.5", | ||||||
|  |     "async": "^1.5.0", | ||||||
|  |     "pg": "^4.4.3", | ||||||
|  |     "socket.io": "^1.3.7" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								vote-apps/result-app/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								vote-apps/result-app/server.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | var express = require('express'), | ||||||
|  |     async = require('async'), | ||||||
|  |     pg = require("pg"), | ||||||
|  |     cookieParser = require('cookie-parser'), | ||||||
|  |     bodyParser = require('body-parser'), | ||||||
|  |     methodOverride = require('method-override'), | ||||||
|  |     app = express(), | ||||||
|  |     server = require('http').Server(app), | ||||||
|  |     io = require('socket.io')(server); | ||||||
|  |  | ||||||
|  | io.set('transports', ['polling']); | ||||||
|  |  | ||||||
|  | var port = process.env.PORT || 4000; | ||||||
|  |  | ||||||
|  | io.sockets.on('connection', function (socket) { | ||||||
|  |  | ||||||
|  |   socket.emit('message', { text : 'Welcome!' }); | ||||||
|  |  | ||||||
|  |   socket.on('subscribe', function (data) { | ||||||
|  |     socket.join(data.channel); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | async.retry( | ||||||
|  |   {times: 1000, interval: 1000}, | ||||||
|  |   function(callback) { | ||||||
|  |     pg.connect('postgres://postgres@voteapps_db_1/postgres', function(err, client, done) { | ||||||
|  |       if (err) { | ||||||
|  |         console.error("Failed to connect to db"); | ||||||
|  |       } | ||||||
|  |       callback(err, client); | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  |   function(err, client) { | ||||||
|  |     if (err) { | ||||||
|  |       return console.err("Giving up"); | ||||||
|  |     } | ||||||
|  |     console.log("Connected to db"); | ||||||
|  |     getVotes(client); | ||||||
|  |   } | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | function getVotes(client) { | ||||||
|  |   client.query('SELECT vote, COUNT(id) AS count FROM votes GROUP BY vote', [], function(err, result) { | ||||||
|  |     if (err) { | ||||||
|  |       console.error("Error performing query: " + err); | ||||||
|  |     } else { | ||||||
|  |       var data = result.rows.reduce(function(obj, row) { | ||||||
|  |         obj[row.vote] = row.count; | ||||||
|  |         return obj; | ||||||
|  |       }, {}); | ||||||
|  |       io.sockets.emit("scores", JSON.stringify(data)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     setTimeout(function() {getVotes(client) }, 1000); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | app.use(cookieParser()); | ||||||
|  | app.use(bodyParser()); | ||||||
|  | app.use(methodOverride('X-HTTP-Method-Override')); | ||||||
|  | app.use(function(req, res, next) { | ||||||
|  |   res.header("Access-Control-Allow-Origin", "*"); | ||||||
|  |   res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); | ||||||
|  |   res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS"); | ||||||
|  |   next(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | app.use(express.static(__dirname + '/views')); | ||||||
|  |  | ||||||
|  | app.get('/', function (req, res) { | ||||||
|  |   res.sendFile(path.resolve(__dirname + '/views/index.html')); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | server.listen(port, function () { | ||||||
|  |   var port = server.address().port; | ||||||
|  |   console.log('App running on port ' + port); | ||||||
|  | }); | ||||||
							
								
								
									
										45
									
								
								vote-apps/result-app/views/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								vote-apps/result-app/views/app.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | var app = angular.module('catsvsdogs', []); | ||||||
|  | var socket = io.connect({transports:['polling']}); | ||||||
|  |  | ||||||
|  | var bg1 = document.getElementById('background-stats-1'); | ||||||
|  | var bg2 = document.getElementById('background-stats-2'); | ||||||
|  |  | ||||||
|  | app.controller('statsCtrl', function($scope){ | ||||||
|  |   var animateStats = function(a,b){ | ||||||
|  |     if(a+b>0){ | ||||||
|  |       var percentA = a/(a+b)*100; | ||||||
|  |       var percentB = 100-percentA; | ||||||
|  |       bg1.style.width= percentA+"%"; | ||||||
|  |       bg2.style.width = percentB+"%"; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   $scope.aPercent = 50; | ||||||
|  |   $scope.bPercent = 50; | ||||||
|  |  | ||||||
|  |   var updateScores = function(){ | ||||||
|  |     socket.on('scores', function (json) { | ||||||
|  |        data = JSON.parse(json); | ||||||
|  |        var a = parseInt(data.a || 0); | ||||||
|  |        var b = parseInt(data.b || 0); | ||||||
|  |  | ||||||
|  |        animateStats(a, b); | ||||||
|  |  | ||||||
|  |        $scope.$apply(function() { | ||||||
|  |          if(a + b > 0){ | ||||||
|  |            $scope.aPercent = a/(a+b) * 100; | ||||||
|  |            $scope.bPercent = b/(a+b) * 100; | ||||||
|  |            $scope.total = a + b | ||||||
|  |          } | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   var init = function(){ | ||||||
|  |     document.body.style.opacity=1; | ||||||
|  |     updateScores(); | ||||||
|  |   }; | ||||||
|  |   socket.on('message',function(data){ | ||||||
|  |     init(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										41
									
								
								vote-apps/result-app/views/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								vote-apps/result-app/views/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html ng-app="catsvsdogs"> | ||||||
|  |   <head> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <title>Cats vs Dogs -- Result</title> | ||||||
|  |     <base href="/index.html"> | ||||||
|  |     <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> | ||||||
|  |     <meta name="keywords" content="docker-compose, docker, stack"> | ||||||
|  |     <meta name="author" content="Tutum dev team"> | ||||||
|  |     <link rel='stylesheet' href='/stylesheets/style.css' /> | ||||||
|  |   </head> | ||||||
|  |   <body ng-controller="statsCtrl" > | ||||||
|  |      <div id="background-stats"> | ||||||
|  |        <div id="background-stats-1"> | ||||||
|  |        </div><!-- | ||||||
|  |       --><div id="background-stats-2"> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div id="content-container"> | ||||||
|  |       <div id="content-container-center"> | ||||||
|  |         <div id="choice"> | ||||||
|  |           <div class="choice cats"> | ||||||
|  |             <div class="label">Cats</div> | ||||||
|  |             <div class="stat">{{aPercent | number:1}}%</div> | ||||||
|  |           </div> | ||||||
|  |           <div class="divider"></div> | ||||||
|  |           <div class="choice dogs"> | ||||||
|  |             <div class="label">Dogs</div> | ||||||
|  |             <div class="stat">{{bPercent | number:1}}%</div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div id="result" ng-if="total > 100"> | ||||||
|  |       {{total}} votes | ||||||
|  |     </div> | ||||||
|  |     <script src="socket.io.js"></script> | ||||||
|  |     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script> | ||||||
|  |     <script src="app.js"></script> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										6988
									
								
								vote-apps/result-app/views/socket.io.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6988
									
								
								vote-apps/result-app/views/socket.io.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										111
									
								
								vote-apps/result-app/views/stylesheets/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								vote-apps/result-app/views/stylesheets/style.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,600); | ||||||
|  |  | ||||||
|  | *{ | ||||||
|  |   box-sizing:border-box; | ||||||
|  | } | ||||||
|  | html,body{ | ||||||
|  |   margin:0; | ||||||
|  |   padding:0; | ||||||
|  |   height:100%; | ||||||
|  |   font-family: 'Open Sans'; | ||||||
|  | } | ||||||
|  | body{ | ||||||
|  |   opacity:0; | ||||||
|  |   transition: all 1s linear; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .divider{ | ||||||
|  |   height: 150px; | ||||||
|  |   width:2px; | ||||||
|  |   background-color: #C0C9CE; | ||||||
|  |   position: relative; | ||||||
|  |   top: 50%; | ||||||
|  |   float: left; | ||||||
|  |   transform: translateY(-50%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #background-stats-1{ | ||||||
|  |   background-color: #2196f3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #background-stats-2{ | ||||||
|  |   background-color: #00cbca; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #content-container{ | ||||||
|  |   z-index:2; | ||||||
|  |   position:relative; | ||||||
|  |   margin:0 auto; | ||||||
|  |   display:table; | ||||||
|  |   padding:10px; | ||||||
|  |   max-width:940px; | ||||||
|  |   height:100%; | ||||||
|  | } | ||||||
|  | #content-container-center{ | ||||||
|  |   display:table-cell; | ||||||
|  |   text-align:center; | ||||||
|  |   vertical-align:middle; | ||||||
|  | } | ||||||
|  | #result{ | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 40px; | ||||||
|  |   right: 20px; | ||||||
|  |   color: #fff; | ||||||
|  |   opacity: 0.5; | ||||||
|  |   font-size: 45px; | ||||||
|  |   font-weight: 600; | ||||||
|  | } | ||||||
|  | #choice{ | ||||||
|  |   transition: all 300ms linear; | ||||||
|  |   line-height:1.3em; | ||||||
|  |   background:#fff; | ||||||
|  |   box-shadow: 10px 0 0 #fff, -10px 0 0 #fff; | ||||||
|  |   vertical-align:middle; | ||||||
|  |   font-size:40px; | ||||||
|  |   font-weight: 600; | ||||||
|  |   width: 450px; | ||||||
|  |   height: 200px; | ||||||
|  | } | ||||||
|  | #choice a{ | ||||||
|  |   text-decoration:none; | ||||||
|  | } | ||||||
|  | #choice a:hover, #choice a:focus{ | ||||||
|  |   outline:0; | ||||||
|  |   text-decoration:underline; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice .choice{ | ||||||
|  |   width: 49%; | ||||||
|  |   position: relative; | ||||||
|  |   top: 50%; | ||||||
|  |   transform: translateY(-50%); | ||||||
|  |   text-align: left; | ||||||
|  |   padding-left: 50px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice .choice .label{ | ||||||
|  |   text-transform: uppercase; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice .choice.dogs{ | ||||||
|  |   color: #00cbca; | ||||||
|  |   float: right; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice .choice.cats{ | ||||||
|  |   color: #2196f3; | ||||||
|  |   float: left; | ||||||
|  | } | ||||||
|  | #background-stats{ | ||||||
|  |   z-index:1; | ||||||
|  |   height:100%; | ||||||
|  |   width:100%; | ||||||
|  |   position:absolute; | ||||||
|  | } | ||||||
|  | #background-stats div{ | ||||||
|  |   transition: width 400ms ease-in-out; | ||||||
|  |   display:inline-block; | ||||||
|  |   margin-bottom:-4px; | ||||||
|  |   width:50%; | ||||||
|  |   height:100%; | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								vote-apps/voting-app/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vote-apps/voting-app/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # Using official python runtime base image | ||||||
|  | FROM python:2.7 | ||||||
|  |  | ||||||
|  | # Set the application directory | ||||||
|  | WORKDIR /app | ||||||
|  |  | ||||||
|  | # Install our requirements.txt | ||||||
|  | ADD requirements.txt /app/requirements.txt | ||||||
|  | RUN pip install -r requirements.txt | ||||||
|  |  | ||||||
|  | # Copy our code from the current folder to /app inside the container | ||||||
|  | ADD . /app | ||||||
|  |  | ||||||
|  | # Make port 5000 available for links and/or publish | ||||||
|  | EXPOSE 80  | ||||||
|  |  | ||||||
|  | # Define our command to be run when launching the container | ||||||
|  | CMD ["python", "app.py"] | ||||||
							
								
								
									
										44
									
								
								vote-apps/voting-app/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								vote-apps/voting-app/app.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | from flask import Flask | ||||||
|  | from flask import render_template | ||||||
|  | from flask import request | ||||||
|  | from flask import make_response | ||||||
|  | from utils import connect_to_redis | ||||||
|  | import os | ||||||
|  | import socket | ||||||
|  | import random | ||||||
|  | import json | ||||||
|  |  | ||||||
|  | option_a = os.getenv('OPTION_A', "Cats") | ||||||
|  | option_b = os.getenv('OPTION_B', "Dogs") | ||||||
|  | hostname = socket.gethostname() | ||||||
|  |  | ||||||
|  | redis = connect_to_redis("voteapps_redis_1") | ||||||
|  | app = Flask(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.route("/", methods=['POST','GET']) | ||||||
|  | def hello(): | ||||||
|  |     voter_id = request.cookies.get('voter_id') | ||||||
|  |     if not voter_id: | ||||||
|  |         voter_id = hex(random.getrandbits(64))[2:-1] | ||||||
|  |  | ||||||
|  |     vote = None | ||||||
|  |  | ||||||
|  |     if request.method == 'POST': | ||||||
|  |         vote = request.form['vote'] | ||||||
|  |         data = json.dumps({'voter_id': voter_id, 'vote': vote}) | ||||||
|  |         redis.rpush('votes', data) | ||||||
|  |  | ||||||
|  |     resp = make_response(render_template( | ||||||
|  |         'index.html', | ||||||
|  |         option_a=option_a, | ||||||
|  |         option_b=option_b, | ||||||
|  |         hostname=hostname, | ||||||
|  |         vote=vote, | ||||||
|  |     )) | ||||||
|  |     resp.set_cookie('voter_id', voter_id) | ||||||
|  |     return resp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  | 	app.run(host='0.0.0.0', port=80, debug=True) | ||||||
							
								
								
									
										2
									
								
								vote-apps/voting-app/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								vote-apps/voting-app/requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | Flask | ||||||
|  | Redis | ||||||
							
								
								
									
										129
									
								
								vote-apps/voting-app/static/stylesheets/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								vote-apps/voting-app/static/stylesheets/style.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | |||||||
|  | @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,600); | ||||||
|  |  | ||||||
|  | *{ | ||||||
|  |   box-sizing:border-box; | ||||||
|  | } | ||||||
|  | html,body{ | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 0; | ||||||
|  |   background-color: #F7F8F9; | ||||||
|  |   height: 100vh; | ||||||
|  |   font-family: 'Open Sans'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button{ | ||||||
|  |   border-radius: 0; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 50%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button[type="submit"] { | ||||||
|  |   -webkit-appearance:none; -webkit-border-radius:0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button i{ | ||||||
|  |   float: right; | ||||||
|  |   padding-right: 30px; | ||||||
|  |   margin-top: 3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button.a{ | ||||||
|  |   background-color: #1aaaf8; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button.b{ | ||||||
|  |   background-color: #00cbca; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #tip{ | ||||||
|  |   text-align: left; | ||||||
|  |   color: #c0c9ce; | ||||||
|  |   font-size: 14px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #hostname{ | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 100px; | ||||||
|  |   right: 0; | ||||||
|  |   left: 0; | ||||||
|  |   color: #8f9ea8; | ||||||
|  |   font-size: 14px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #content-container{ | ||||||
|  |   z-index: 2; | ||||||
|  |   position: relative; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   display: table; | ||||||
|  |   padding: 10px; | ||||||
|  |   max-width: 940px; | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  | #content-container-center{ | ||||||
|  |   display: table-cell; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #content-container-center h3{ | ||||||
|  |   color: #254356; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice{ | ||||||
|  |   transition: all 300ms linear; | ||||||
|  |   line-height: 1.3em; | ||||||
|  |   display: inline; | ||||||
|  |   vertical-align: middle; | ||||||
|  |   font-size: 3em; | ||||||
|  | } | ||||||
|  | #choice a{ | ||||||
|  |   text-decoration:none; | ||||||
|  | } | ||||||
|  | #choice a:hover, #choice a:focus{ | ||||||
|  |   outline:0; | ||||||
|  |   text-decoration:underline; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice button{ | ||||||
|  |   display: block; | ||||||
|  |   height: 80px; | ||||||
|  |   width: 330px; | ||||||
|  |   border: none; | ||||||
|  |   color: white; | ||||||
|  |   text-transform: uppercase; | ||||||
|  |   font-size:18px; | ||||||
|  |   font-weight: 700; | ||||||
|  |   margin-top: 10px; | ||||||
|  |   margin-bottom: 10px; | ||||||
|  |   text-align: left; | ||||||
|  |   padding-left: 50px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice button.a:hover{ | ||||||
|  |   background-color: #1488c6; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice button.b:hover{ | ||||||
|  |   background-color: #00a2a1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice button.a:focus{ | ||||||
|  |   background-color: #1488c6; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #choice button.b:focus{ | ||||||
|  |   background-color: #00a2a1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #background-stats{ | ||||||
|  |   z-index:1; | ||||||
|  |   height:100%; | ||||||
|  |   width:100%; | ||||||
|  |   position:absolute; | ||||||
|  | } | ||||||
|  | #background-stats div{ | ||||||
|  |   transition: width 400ms ease-in-out; | ||||||
|  |   display:inline-block; | ||||||
|  |   margin-bottom:-4px; | ||||||
|  |   width:50%; | ||||||
|  |   height:100%; | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								vote-apps/voting-app/templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vote-apps/voting-app/templates/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <title>{{option_a}} vs {{option_b}}!</title> | ||||||
|  |     <base href="/index.html"> | ||||||
|  |     <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> | ||||||
|  |     <meta name="keywords" content="docker-compose, docker, stack"> | ||||||
|  |     <meta name="author" content="Tutum dev team"> | ||||||
|  |     <link rel='stylesheet' href="{{ url_for('static',filename='stylesheets/style.css') }}" /> | ||||||
|  |     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |     <div id="content-container"> | ||||||
|  |       <div id="content-container-center"> | ||||||
|  |         <h3>{{option_a}} vs {{option_b}}!</h3> | ||||||
|  |         <form id="choice" name='form' method="POST" action="/"> | ||||||
|  |           <button id="a" type="submit" name="vote" class="a" value="a">{{option_a}}</button> | ||||||
|  |           <button id="b" type="submit" name="vote" class="b" value="b">{{option_b}}</button> | ||||||
|  |         </form> | ||||||
|  |         <div id="tip"> | ||||||
|  |           (Tip: you can change your vote) | ||||||
|  |         </div> | ||||||
|  |         <div id="hostname"> | ||||||
|  |           Vote processed by {{hostname}} | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script> | ||||||
|  |     <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script> | ||||||
|  |  | ||||||
|  |     {% if vote %} | ||||||
|  |     <script> | ||||||
|  |       var vote = "{{vote}}"; | ||||||
|  |  | ||||||
|  |       if(vote == "a"){ | ||||||
|  |         $(".a").prop('disabled', true); | ||||||
|  |         $(".a").html('{{option_a}} <i class="fa fa-check-circle"></i>'); | ||||||
|  |         $(".b").css('opacity','0.5'); | ||||||
|  |       } | ||||||
|  |       if(vote == "b"){ | ||||||
|  |         $(".b").prop('disabled', true); | ||||||
|  |         $(".b").html('{{option_b}} <i class="fa fa-check-circle"></i>'); | ||||||
|  |         $(".a").css('opacity','0.5'); | ||||||
|  |       } | ||||||
|  |     </script> | ||||||
|  |     {% endif %} | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										17
									
								
								vote-apps/voting-app/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vote-apps/voting-app/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | import time | ||||||
|  | from redis import Redis, ConnectionError | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def connect_to_redis(host): | ||||||
|  |     time.sleep(2) | ||||||
|  |     print "Connecting to redis" | ||||||
|  |  | ||||||
|  |     while True: | ||||||
|  |         try: | ||||||
|  |             redis = Redis(host=host, db=0) | ||||||
|  |             redis.ping() | ||||||
|  |             print "Connected to redis" | ||||||
|  |             return redis | ||||||
|  |         except ConnectionError: | ||||||
|  |             print "Failed to connect to redis - retrying" | ||||||
|  |             time.sleep(1) | ||||||
							
								
								
									
										15
									
								
								vote-apps/worker/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vote-apps/worker/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | FROM java:7 | ||||||
|  |  | ||||||
|  | RUN apt-get update -qq && apt-get install -y maven && apt-get clean | ||||||
|  |  | ||||||
|  | WORKDIR /code | ||||||
|  |  | ||||||
|  | ADD pom.xml /code/pom.xml | ||||||
|  | RUN ["mvn", "dependency:resolve"] | ||||||
|  | RUN ["mvn", "verify"] | ||||||
|  |  | ||||||
|  | # Adding source, compile and package into a fat jar | ||||||
|  | ADD src /code/src | ||||||
|  | RUN ["mvn", "package"] | ||||||
|  |  | ||||||
|  | CMD ["/usr/lib/jvm/java-7-openjdk-amd64/bin/java", "-jar", "target/worker-jar-with-dependencies.jar"] | ||||||
							
								
								
									
										84
									
								
								vote-apps/worker/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								vote-apps/worker/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||||
|  |   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||||
|  |   <modelVersion>4.0.0</modelVersion> | ||||||
|  |  | ||||||
|  |   <groupId>worker</groupId> | ||||||
|  |   <artifactId>worker</artifactId> | ||||||
|  |   <version>1.0-SNAPSHOT</version> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   <dependencies> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.json</groupId> | ||||||
|  |       <artifactId>json</artifactId> | ||||||
|  |       <version>20140107</version> | ||||||
|  |     </dependency> | ||||||
|  |  | ||||||
|  |     <dependency> | ||||||
|  |         <groupId>redis.clients</groupId> | ||||||
|  |         <artifactId>jedis</artifactId> | ||||||
|  |         <version>2.7.2</version> | ||||||
|  |         <type>jar</type> | ||||||
|  |         <scope>compile</scope> | ||||||
|  |     </dependency> | ||||||
|  |  | ||||||
|  |     <dependency> | ||||||
|  |         <groupId>org.postgresql</groupId> | ||||||
|  |         <artifactId>postgresql</artifactId> | ||||||
|  |         <version>9.4-1200-jdbc41</version> | ||||||
|  |     </dependency> | ||||||
|  |   </dependencies> | ||||||
|  |  | ||||||
|  |   <build> | ||||||
|  |     <plugins> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-jar-plugin</artifactId> | ||||||
|  |         <version>2.4</version> | ||||||
|  |         <configuration> | ||||||
|  |           <finalName>worker</finalName> | ||||||
|  |           <archive> | ||||||
|  |             <manifest> | ||||||
|  |               <addClasspath>true</addClasspath> | ||||||
|  |               <mainClass>worker.Worker</mainClass> | ||||||
|  |               <classpathPrefix>dependency-jars/</classpathPrefix> | ||||||
|  |             </manifest> | ||||||
|  |           </archive> | ||||||
|  |         </configuration> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-compiler-plugin</artifactId> | ||||||
|  |         <version>3.1</version> | ||||||
|  |         <configuration> | ||||||
|  |           <source>1.7</source> | ||||||
|  |           <target>1.7</target> | ||||||
|  |         </configuration> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-assembly-plugin</artifactId> | ||||||
|  |         <executions> | ||||||
|  |           <execution> | ||||||
|  |             <goals> | ||||||
|  |               <goal>attached</goal> | ||||||
|  |             </goals> | ||||||
|  |             <phase>package</phase> | ||||||
|  |             <configuration> | ||||||
|  |               <finalName>worker</finalName> | ||||||
|  |               <descriptorRefs> | ||||||
|  |                 <descriptorRef>jar-with-dependencies</descriptorRef> | ||||||
|  |               </descriptorRefs> | ||||||
|  |               <archive> | ||||||
|  |                 <manifest> | ||||||
|  |                   <mainClass>worker.Worker</mainClass> | ||||||
|  |                 </manifest> | ||||||
|  |               </archive> | ||||||
|  |             </configuration> | ||||||
|  |           </execution> | ||||||
|  |         </executions> | ||||||
|  |       </plugin> | ||||||
|  |     </plugins> | ||||||
|  |   </build> | ||||||
|  | </project> | ||||||
							
								
								
									
										101
									
								
								vote-apps/worker/src/main/java/worker/Worker.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								vote-apps/worker/src/main/java/worker/Worker.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | package worker; | ||||||
|  |  | ||||||
|  | import redis.clients.jedis.Jedis; | ||||||
|  | import redis.clients.jedis.exceptions.JedisConnectionException; | ||||||
|  | import java.sql.*; | ||||||
|  | import org.json.JSONObject; | ||||||
|  |  | ||||||
|  | class Worker { | ||||||
|  |   public static void main(String[] args) { | ||||||
|  |     try { | ||||||
|  |       Jedis redis = connectToRedis("voteapps_redis_1"); | ||||||
|  |       Connection dbConn = connectToDB("voteapps_db_1"); | ||||||
|  |  | ||||||
|  |       System.err.println("Watching vote queue"); | ||||||
|  |  | ||||||
|  |       while (true) { | ||||||
|  |         String voteJSON = redis.blpop(0, "votes").get(1); | ||||||
|  |         JSONObject voteData = new JSONObject(voteJSON); | ||||||
|  |         String voterID = voteData.getString("voter_id"); | ||||||
|  |         String vote = voteData.getString("vote"); | ||||||
|  |  | ||||||
|  |         System.err.printf("Processing vote for '%s' by '%s'\n", vote, voterID); | ||||||
|  |         updateVote(dbConn, voterID, vote); | ||||||
|  |       } | ||||||
|  |     } catch (SQLException e) { | ||||||
|  |       e.printStackTrace(); | ||||||
|  |       System.exit(1); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static void updateVote(Connection dbConn, String voterID, String vote) throws SQLException { | ||||||
|  |     PreparedStatement insert = dbConn.prepareStatement( | ||||||
|  |       "INSERT INTO votes (id, vote) VALUES (?, ?)"); | ||||||
|  |     insert.setString(1, voterID); | ||||||
|  |     insert.setString(2, vote); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       insert.executeUpdate(); | ||||||
|  |     } catch (SQLException e) { | ||||||
|  |       PreparedStatement update = dbConn.prepareStatement( | ||||||
|  |         "UPDATE votes SET vote = ? WHERE id = ?"); | ||||||
|  |       update.setString(1, vote); | ||||||
|  |       update.setString(2, voterID); | ||||||
|  |       update.executeUpdate(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Jedis connectToRedis(String host) { | ||||||
|  |     Jedis conn = new Jedis(host); | ||||||
|  |  | ||||||
|  |     while (true) { | ||||||
|  |       try { | ||||||
|  |         conn.keys("*"); | ||||||
|  |         break; | ||||||
|  |       } catch (JedisConnectionException e) { | ||||||
|  |         System.err.println("Failed to connect to redis - retrying"); | ||||||
|  |         sleep(1000); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     System.err.println("Connected to redis"); | ||||||
|  |     return conn; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Connection connectToDB(String host) throws SQLException { | ||||||
|  |     Connection conn = null; | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |  | ||||||
|  |       Class.forName("org.postgresql.Driver"); | ||||||
|  |       String url = "jdbc:postgresql://" + host + "/postgres"; | ||||||
|  |  | ||||||
|  |       while (conn == null) { | ||||||
|  |         try { | ||||||
|  |           conn = DriverManager.getConnection(url, "postgres", ""); | ||||||
|  |         } catch (SQLException e) { | ||||||
|  |           System.err.println("Failed to connect to db - retrying"); | ||||||
|  |           sleep(1000); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       PreparedStatement st = conn.prepareStatement( | ||||||
|  |         "CREATE TABLE IF NOT EXISTS votes (id VARCHAR(255) NOT NULL UNIQUE, vote VARCHAR(255) NOT NULL)"); | ||||||
|  |       st.executeUpdate(); | ||||||
|  |  | ||||||
|  |     } catch (ClassNotFoundException e) { | ||||||
|  |       e.printStackTrace(); | ||||||
|  |       System.exit(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return conn; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static void sleep(long duration) { | ||||||
|  |     try { | ||||||
|  |       Thread.sleep(duration); | ||||||
|  |     } catch (InterruptedException e) { | ||||||
|  |       System.exit(1); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Ben Firshman
					Ben Firshman