├── .gitignore ├── credentials.json ├── app ├── .routes.js.swp ├── models │ ├── apps.js │ └── user.js └── routes.js ├── public ├── img │ ├── back.jpg │ ├── HarborJS.png │ ├── HarborJS2.png │ └── HarborJS3.png ├── css │ └── main.css └── js │ └── bootstrap.min.js ├── config ├── database.js ├── application.js ├── sockets.js └── passport.js ├── package.json ├── LICENSE ├── views ├── index.ejs ├── signup.ejs ├── login.ejs ├── containers │ ├── show.ejs │ └── new.ejs ├── profile.ejs └── dashboard.ejs ├── server.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config/database.js 3 | credentials.json 4 | .codio -------------------------------------------------------------------------------- /credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "root", 3 | "host": "127.0.0.1" 4 | } 5 | -------------------------------------------------------------------------------- /app/.routes.js.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaximeHeckel/HarborJS/HEAD/app/.routes.js.swp -------------------------------------------------------------------------------- /public/img/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaximeHeckel/HarborJS/HEAD/public/img/back.jpg -------------------------------------------------------------------------------- /public/img/HarborJS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaximeHeckel/HarborJS/HEAD/public/img/HarborJS.png -------------------------------------------------------------------------------- /public/img/HarborJS2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaximeHeckel/HarborJS/HEAD/public/img/HarborJS2.png -------------------------------------------------------------------------------- /public/img/HarborJS3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaximeHeckel/HarborJS/HEAD/public/img/HarborJS3.png -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | // config/database.js 2 | module.exports = { 3 | 4 | 'url' : 'mongodb://localhost' //like mongodb://:.ds000000.mongolab.com:27017/mydb 5 | 6 | }; 7 | -------------------------------------------------------------------------------- /app/models/apps.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | //define the schema for our apps model 4 | var Schema = mongoose.Schema; 5 | 6 | var appSchema = new Schema({ 7 | name : String, 8 | user : String 9 | }); 10 | 11 | // create the model for apps and expose it to our app 12 | module.exports = mongoose.model('App', appSchema); 13 | 14 | -------------------------------------------------------------------------------- /public/css/main.css: -------------------------------------------------------------------------------- 1 | body.home { 2 | background: url(../img/back.jpg) no-repeat center center fixed; 3 | -webkit-background-size: cover; 4 | -moz-background-size: cover; 5 | -o-background-size: cover; 6 | background-size: cover; 7 | padding-top: 200px; 8 | } 9 | 10 | .btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .open .dropdown-toggle.btn-warning{ 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HarborJS", 3 | "main": "server.js", 4 | "dependencies": { 5 | "bcrypt-nodejs": "latest", 6 | "body-parser": "^1.13.0", 7 | "connect-flash": "~0.1.1", 8 | "connect-mongo": "^0.8.1", 9 | "cookie-parser": "^1.3.5", 10 | "docker.io": "latest", 11 | "ejs": "latest", 12 | "express": "^4.12.4", 13 | "express-session": "^1.11.3", 14 | "mongoose": "~3.8.1", 15 | "morgan": "^1.6.0", 16 | "passport": "~0.1.17", 17 | "passport-local": "~0.1.6", 18 | "socket.io": "latest", 19 | "ssh-exec": "latest" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/models/user.js: -------------------------------------------------------------------------------- 1 | // load the things we need 2 | var mongoose = require('mongoose'); 3 | var bcrypt = require('bcrypt-nodejs'); 4 | 5 | // define the schema for our user model 6 | var userSchema = mongoose.Schema({ 7 | 8 | local : { 9 | username : String, 10 | password : String, 11 | } 12 | 13 | }); 14 | 15 | // generating a hash 16 | userSchema.methods.generateHash = function(password) { 17 | return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); 18 | }; 19 | 20 | // checking if password is valid 21 | userSchema.methods.validPassword = function(password) { 22 | return bcrypt.compareSync(password, this.local.password); 23 | }; 24 | 25 | // create the model for users and expose it to our app 26 | module.exports = mongoose.model('User', userSchema); 27 | -------------------------------------------------------------------------------- /config/application.js: -------------------------------------------------------------------------------- 1 | // load up the user model 2 | var App = require('../app/models/apps'); 3 | 4 | exports.create = function(req, res){ 5 | new App({ 6 | name : 'app/'+req.body.name+':latest', 7 | user : req.body.user 8 | }).save(function(err, app, count){ 9 | res.redirect('/new'); 10 | }); 11 | }; 12 | 13 | exports.createdb = function(req,res){ 14 | new App({ 15 | name : req.body.type + '/' + req.body.name + ':latest', 16 | user : req.body.user 17 | }).save(function(err, app, count){ 18 | res.redirect('/new'); 19 | }); 20 | }; 21 | 22 | exports.destroy = function(req, res){ 23 | App.findById( req.params.id, function(err, app){ 24 | app.remove(function(err, app){ 25 | res.redirect('/dashboard'); 26 | }); 27 | }) 28 | }; 29 | 30 | /*exports.show = function(req,res){ 31 | App.find( function(err, apps, count){ 32 | 33 | };*/ 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Maxime Heckel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HarborJS 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |

HarborJS

17 |

Deploy and manage your apps on dokku

18 | Login 19 | Signup 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /views/signup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HarborJS 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 |

Signup

15 | <% if (message.length > 0) { %> 16 |
<%= message %>
17 | <% } %> 18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 | 28 |
29 |
30 |

Already have an account? Login

31 |
32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /views/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HarborJS 5 | 6 | 7 | 8 | 11 | 12 | 13 |
14 |
15 |

Login

16 | <% if (message.length > 0) { %> 17 |
<%= message %>
18 | <% } %> 19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 | 29 |
30 |
31 |

Need an account? Signup

32 |
33 |
34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /config/sockets.js: -------------------------------------------------------------------------------- 1 | var exec = require('ssh-exec'); 2 | var Writable = require('stream').Writable; 3 | //socket functions 4 | module.exports = function(io, credentials, docker){ 5 | 6 | function streamToWebsocket(stream, socket){ 7 | stream._write = function(chunk, enc, next){ 8 | socket.emit('logs', {data: chunk.toString()}); 9 | next(); 10 | } 11 | }; 12 | 13 | io.sockets.on('connection', function(socket){ 14 | socket.on('thisContainerId', function(data){ 15 | docker.containers.attach(data, {stream: true, stderr: true, stdout:true, logs:true}, function(err,attach){ 16 | var out = Writable(); 17 | var err = Writable(); 18 | streamToWebsocket(out, socket); 19 | streamToWebsocket(err, socket); 20 | docker.demuxStream(attach, out, err); 21 | }); 22 | }); 23 | 24 | socket.on('sshkey', function(data){ 25 | exec('touch ssh.pub ; echo '+data.mysshkey+' > ssh.pub; cat ~/ssh.pub | sudo sshcommand acl-add dokku '+ data.name, credentials).pipe(process.stdout); 26 | }); 27 | 28 | socket.on('containerId', function(data){ 29 | exec('docker kill '+ data.idcont +'; sudo rm -r /home/dokku/'+ data.namecont, credentials).pipe(process.stdout); 30 | }); 31 | 32 | socket.on('dbCreate',function(data){ 33 | var name=data.name; 34 | var type=data.type; 35 | exec('dokku '+type+':create '+name, credentials).pipe(process.stdout); 36 | }); 37 | 38 | socket.on('cmd', function(data){ 39 | var cmd=data.cmd; 40 | var name=data.name; 41 | exec('dokku run ' + name + ' ' + cmd, credentials).pipe(process.stdout); 42 | }); 43 | 44 | socket.on('dbLink', function(data){ 45 | var app=data.appName; 46 | var db=data.dbName; 47 | exec('dokku postgresql:link '+ app +' '+ db +'; dokku mysql:link '+ app +' '+ db +'; dokku redis:link '+ app +' '+ db, credentials).pipe(process.stdout); 48 | }); 49 | }); 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // server.js 2 | 3 | // set up ====================================================================== 4 | 5 | var express = require('express'); 6 | var app = express(); 7 | var logger = require('morgan'); 8 | var session = require('express-session'); 9 | var cookieParser = require('cookie-parser'); 10 | var bodyParser = require('body-parser'); 11 | var port = process.env.PORT || 3000; 12 | var fs = require('fs'); 13 | var MongoStore = require('connect-mongo')(session); 14 | var mongoose = require('mongoose'); 15 | var passport = require('passport'); 16 | var http = require('http'); 17 | var path = require('path'); 18 | var flash = require('connect-flash'); 19 | var server = http.createServer(app); 20 | var io = require('socket.io').listen(server); 21 | 22 | //config files ================================================================ 23 | var configDB = require('./config/database.js'); 24 | var credentials = require('./credentials.json'); 25 | 26 | var docker = require('docker.io')({ 27 | socketPath: false, 28 | host: 'http://'+credentials.host, port: '4243'}); 29 | 30 | 31 | // configuration =============================================================== 32 | mongoose.connect(configDB.url); // connect to our database 33 | 34 | require('./config/passport')(passport); // pass passport for configuration 35 | 36 | // set up our express application 37 | app.use(logger("combined")); 38 | app.use(cookieParser()); 39 | app.use(bodyParser.urlencoded({ 40 | extended: true 41 | })); 42 | app.set('view engine', 'ejs'); // set up ejs for templating 43 | app.use(express.static(path.join(__dirname, 'public'))); 44 | 45 | // required for passport 46 | app.use(session({ 47 | secret:'iloveharborjsiloveharborjs', 48 | maxAge: new Date(Date.now() + 3600000), 49 | store: new MongoStore( 50 | {db:configDB.url}, 51 | function(err){ 52 | console.log(err || 'connect-mongodb setup ok'); 53 | }) 54 | })); 55 | 56 | 57 | app.use(passport.initialize()); 58 | app.use(passport.session()); 59 | app.use(flash()); // use connect-flash for flash messages stored in session 60 | 61 | // routes ====================================================================== 62 | require('./app/routes.js')(app, passport, docker); // load our routes and pass in our app and fully configured passport 63 | 64 | // socket function ============================================================= 65 | require('./config/sockets.js')(io, credentials, docker); 66 | 67 | // launch ====================================================================== 68 | server.listen(port); 69 | console.log('HarborJS is running on port ' + port); 70 | -------------------------------------------------------------------------------- /views/containers/show.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HarborJS 6 | 7 | 8 | 9 | 10 | 11 | 30 |
31 |
32 |
33 |
34 |

<%=name%>

35 |
36 |
37 |
38 |
39 |

Created: <%=container.Created%>

40 |

Running: <%=container.State.Running%>

41 |

Command: <%=container.Path%>

42 |

Arguments: <%=container.Args[0]%>

43 |
44 |
45 |
46 | 	    
47 | 	  
48 |
49 |

Send a command

50 |
51 | 52 |
53 |
54 |

Your app is running on :

55 | http://<%=name%>.yourhostname 56 |
57 |
58 |
59 | 60 | 61 | 62 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /views/profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HarborJS 5 | 6 | 7 | 8 | 9 | 10 | 11 | 30 |
31 |
32 |
33 |
34 |

Your Profile

35 |
36 |
37 |

Account information

38 | <% if (user.local.username) { %> 39 |

40 | id: <%= user._id %>
41 | username: <%= user.local.username %>
42 |

43 | Unlink Account 44 | <% } %> 45 |
46 |

SSH-KEY

47 |
48 | 49 |
50 | 51 |
52 | 53 |
54 |
55 |
56 | 57 | 58 | 59 | 60 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #HarborJS 2 | 3 | This project aims to provide a full web interface for [dokku](https://github.com/progrium/dokku) and session management. 4 | 5 | ##Requirements 6 | 7 | In order to run HarborJS you will need : 8 | 9 | - Ubuntu 13 or 12.04 x64 10 | 11 | - Dokku and obviously Docker 12 | 13 | - Dokku plugins (in order to use databases) 14 | 15 | - Node.JS 16 | 17 | - A MongoDB database 18 | 19 | 20 | ##Installing and configuring 21 | 22 | 23 | First clone the repository 24 | 25 | ``` 26 | git clone git@github.com:MaximeHeckel/HarborJS.git 27 | ``` 28 | 29 | Before launching the server you will need to do some setup on your machine. 30 | 31 | 1. Add your public key to ~/.ssh/authorized_key of your root and sudo service ssh restart 32 | 33 | 2. Fill the credentials.json file with your root user and host. ( nothing needs to be change here as you will run the server locally ) 34 | 35 | 3. Fill the ./config/database.js file with your MongoDB database address 36 | 37 | 38 | Then run inside the HarborJS repository 39 | 40 | ``` 41 | npm install 42 | 43 | ``` 44 | 45 | 46 | Launch the server with : 47 | 48 | ``` 49 | sudo node server.js 50 | ``` 51 | 52 | ##Features 53 | 54 | 55 | HarborJS has been build to provide user sessions and web interface for managing apps and databases for dokku. 56 | 57 | ####0.2 58 | 59 | - Containers have logs 60 | 61 | - Check if app or db already exists 62 | 63 | ####0.1.2 64 | 65 | - Security : No need of root password anymore 66 | 67 | ####0.1.0 68 | 69 | - Ability to create/delete apps and databases 70 | 71 | - Link apps with databases 72 | 73 | - Send command to a specific app 74 | 75 | - List all your apps 76 | 77 | - Add a ssh-key to the server 78 | 79 | - Create/Unlink accounts 80 | 81 | - Each apps has its own dedicated page 82 | 83 | 84 | ##How to use it 85 | 86 | 87 | Let's deploy an app with HarborJS 88 | 89 | - First Login or Signup. 90 | 91 | - Once you're logged in add your ssh-key and your computer's name into the **SSH-KEY section**. 92 | 93 | 94 | 95 | - Then go to the Dashboard section and click on the **Create new app/db** 96 | 97 | - Type the name of your application in the App Name input 98 | and push the register button. This action will add a new application into the database which will contain your username and the name of your app. 99 | 100 | 101 | 102 | - Now you just need to push your application on the server (just look at the [dokku](https://github.com/progrium/dokku) documentation to do so). 103 | 104 | - Your app is deployed ! 105 | 106 | 107 | 108 | 109 | If you want to create a database just click on the **Create new app/db** button, enter a new name for your database in the database section, choose which type of database you want, hit the **Create DB** button and your done. 110 | 111 | 112 | ######Note: 113 | HarborJS comes with PostgreSQL, MySQL, and Redis built in. You will need to install the corresponding dokku plugins in order to make them work. In order to add your own database type you need ( at least for the moment ) to it directly into the code of the project 114 | 115 | ##TODO: 116 | 117 | - Config File : JSON with hostname, plugin list, etc. 118 | 119 | - Registering new app : check if the name of the app already exists ( almost done ) 120 | 121 | - Error handling : when creating apps or databases 122 | 123 | - Vagrantfile : For fast deployment 124 | 125 | - Chef Recipe : That would be great 126 | 127 | - Documentation : Doc and Tutorials, or even a wiki with step by step guides in order to explain how to deploy all sorts of apps ( with all kinds of databases ) 128 | 129 | 130 | ##License 131 | 132 | MIT 133 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | var config = require('../config/application.js'); 2 | var App = require('../app/models/apps'); 3 | module.exports = function(app, passport, docker) { 4 | 5 | // normal routes =============================================================== 6 | 7 | // show the home page 8 | app.get('/', function(req, res) { 9 | res.render('index.ejs'); 10 | }); 11 | 12 | // PROFILE SECTION ========================= 13 | app.get('/profile', isLoggedIn, function(req, res) { 14 | res.render('profile.ejs', { 15 | user : req.user 16 | }); 17 | }); 18 | 19 | app.get('/dashboard', isLoggedIn, function (req,res) { 20 | docker.containers.list(function(err,cont){ 21 | App.find(function (warn, apps, count){ 22 | if(!apps) apps = []; 23 | if(!cont) cont = []; 24 | 25 | res.render('dashboard.ejs',{apps: apps, containers: cont, user : req.user}); 26 | }); 27 | }); 28 | }); 29 | 30 | app.get('/ssh', isLoggedIn ,function (req,res) { 31 | res.render('ssh.ejs'); 32 | }); 33 | 34 | app.get('/containers/:id', isLoggedIn,function(req,res){ 35 | console.log('INSPECT CONTAINER WITH ID '+req.params.id); 36 | docker.containers.inspect(req.params.id,function(err,requ){ 37 | if (err) throw err; 38 | var reqname = requ.Config.Image; 39 | var name = reqname.replace('app/','').replace('postgresql/','').replace('mysql/',''); 40 | docker.containers.attach(req.params.id, {stream: true, stdout: true, stderr:false, tty:false}, function(err,stream) { 41 | res.render('containers/show.ejs',{container: requ, name: name, stream: stream}); 42 | }); 43 | }); 44 | }); 45 | 46 | 47 | // LOGOUT ============================== 48 | app.get('/logout', function(req, res) { 49 | req.logout(); 50 | res.redirect('/'); 51 | }); 52 | 53 | // app routes =============================================================== 54 | app.get('/new', isLoggedIn, function(req,res){ 55 | res.render('containers/new.ejs' ,{user : req.user}); 56 | }); 57 | 58 | app.post( '/create', config.create ); 59 | 60 | app.post('/createdb', config.createdb); 61 | 62 | app.get('/destroy/:id', config.destroy); 63 | 64 | // ============================================================================= 65 | // AUTHENTICATE (FIRST LOGIN) ================================================== 66 | // ============================================================================= 67 | 68 | // locally -------------------------------- 69 | // LOGIN =============================== 70 | // show the login form 71 | app.get('/login', function(req, res) { 72 | res.render('login.ejs', { message: req.flash('loginMessage') }); 73 | }); 74 | 75 | // process the login form 76 | app.post('/login', passport.authenticate('local-login', { 77 | successRedirect : '/profile', // redirect to the secure profile section 78 | failureRedirect : '/login', // redirect back to the signup page if there is an error 79 | failureFlash : true // allow flash messages 80 | })); 81 | 82 | // SIGNUP ================================= 83 | // show the signup form 84 | app.get('/signup', function(req, res) { 85 | res.render('signup.ejs', { message: req.flash('signupMessage') }); 86 | }); 87 | 88 | // process the signup form 89 | app.post('/signup', passport.authenticate('local-signup', { 90 | successRedirect : '/profile', // redirect to the secure profile section 91 | failureRedirect : '/signup', // redirect back to the signup page if there is an error 92 | failureFlash : true // allow flash messages 93 | })); 94 | 95 | 96 | 97 | // locally -------------------------------- 98 | app.get('/connect/local', function(req, res) { 99 | res.render('connect-local.ejs', { message: req.flash('loginMessage') }); 100 | }); 101 | app.post('/connect/local', passport.authenticate('local-signup', { 102 | successRedirect : '/profile', // redirect to the secure profile section 103 | failureRedirect : '/connect/local', // redirect back to the signup page if there is an error 104 | failureFlash : true // allow flash messages 105 | })); 106 | 107 | // unlink ----------------------------------- 108 | app.get('/unlink/local', function(req, res) { 109 | var user = req.user; 110 | user.local.username = undefined; 111 | user.local.password = undefined; 112 | user.save(function(err) { 113 | res.redirect('/'); 114 | }); 115 | }); 116 | }; 117 | 118 | // route middleware to ensure user is logged in 119 | function isLoggedIn(req, res, next) { 120 | 121 | if (req.user){ 122 | return next(); 123 | } 124 | res.redirect('/'); 125 | } 126 | -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | // load all the things we need 2 | var LocalStrategy = require('passport-local').Strategy; 3 | // load up the user model 4 | var User = require('../app/models/user'); 5 | 6 | module.exports = function(passport) { 7 | 8 | // ========================================================================= 9 | // passport session setup ================================================== 10 | // ========================================================================= 11 | // required for persistent login sessions 12 | // passport needs ability to serialize and unserialize users out of session 13 | 14 | // used to serialize the user for the session 15 | passport.serializeUser(function(user, done) { 16 | done(null, user.id); 17 | }); 18 | 19 | // used to deserialize the user 20 | passport.deserializeUser(function(id, done) { 21 | User.findById(id, function(err, user) { 22 | done(err, user); 23 | }); 24 | }); 25 | 26 | // ========================================================================= 27 | // LOCAL LOGIN ============================================================= 28 | // ========================================================================= 29 | passport.use('local-login', new LocalStrategy({ 30 | // by default, local strategy uses username and password, we will override with email 31 | usernameField : 'username', 32 | passwordField : 'password', 33 | passReqToCallback : true // allows us to pass in the req from our route (lets us check if a user is logged in or not) 34 | }, 35 | function(req, username, password, done) { 36 | 37 | // asynchronous 38 | process.nextTick(function() { 39 | User.findOne({ 'local.username' : username }, function(err, user) { 40 | // if there are any errors, return the error 41 | if (err) 42 | return done(err); 43 | 44 | // if no user is found, return the message 45 | if (!user) 46 | return done(null, false, req.flash('loginMessage', 'No user found.')); 47 | 48 | if (!user.validPassword(password)) 49 | return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); 50 | 51 | // all is well, return user 52 | else 53 | return done(null, user); 54 | }); 55 | }); 56 | 57 | })); 58 | 59 | // ========================================================================= 60 | // LOCAL SIGNUP ============================================================ 61 | // ========================================================================= 62 | passport.use('local-signup', new LocalStrategy({ 63 | // by default, local strategy uses username and password, we will override with email 64 | usernameField : 'username', 65 | passwordField : 'password', 66 | passReqToCallback : true // allows us to pass in the req from our route (lets us check if a user is logged in or not) 67 | }, 68 | function(req, username, password, done) { 69 | 70 | // asynchronous 71 | process.nextTick(function() { 72 | // check if the user is already logged ina 73 | if (!req.user) { 74 | User.findOne({ 'local.username' : username }, function(err, user) { 75 | // if there are any errors, return the error 76 | if (err) 77 | return done(err); 78 | 79 | // check to see if theres already a user with that email 80 | if (user) { 81 | return done(null, false, req.flash('signupMessage', 'That username is already taken.')); 82 | } else { 83 | 84 | // create the user 85 | var newUser = new User(); 86 | newUser.local.username = username; 87 | newUser.local.password = newUser.generateHash(password); 88 | 89 | newUser.save(function(err) { 90 | if (err) 91 | throw err; 92 | 93 | return done(null, newUser); 94 | }); 95 | } 96 | 97 | }); 98 | } else { 99 | 100 | var user = req.user; 101 | user.local.username = username; 102 | user.local.password = user.generateHash(password); 103 | user.save(function(err) { 104 | if (err) 105 | throw err; 106 | return done(null, user); 107 | }); 108 | 109 | } 110 | 111 | }); 112 | 113 | })); 114 | }; 115 | -------------------------------------------------------------------------------- /views/containers/new.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HarborJS 6 | 7 | 8 | 9 | 10 | 29 |
30 |
31 |
32 |
33 |

Register App

34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 |

Create DB

48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 | 56 | 61 |
62 |
63 | 64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 75 | 76 | 77 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /views/dashboard.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HarborJS 6 | 7 | 8 | 9 | 10 | 11 | 30 |
31 |
32 |
33 |
34 |

Your Apps

35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | <%for(var i=0; i< containers.length; i++){%> 51 | <% if(user.local.username == "admin"){%> 52 | 53 | 54 | 57 | 60 | 65 | 66 | 67 | <%}%> 68 | <% apps.forEach(function(app){%> 69 | 70 | 71 | <%if(app.user == user.local.username && app.name == containers[i].Image){%> 72 | 75 | 78 | 83 | <%}%> 84 | 85 | 86 | <%})%> 87 | <%}%> 88 | 89 |
NameStateAction
55 | <%=containers[i].Image%> 56 | 58 | <%=containers[i].Status%> 59 | 61 |
62 | 63 |
64 |
73 | <%=containers[i].Image%> 74 | 76 | <%=containers[i].Status%> 77 | 79 |
80 | 81 |
82 |
90 |
91 |
92 |
93 |

Link DB-App

94 |
95 |
96 |
97 |
98 | 99 | 100 |
101 |
102 | 103 | 104 |
105 |
106 | 107 |
108 |
109 |
110 |
111 |
112 | 113 | 114 | 115 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /public/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.3 (http://getbootstrap.com) 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | 7 | if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]'),b=!0;if(a.length){var c=this.$element.find("input");"radio"===c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?b=!1:a.find(".active").removeClass("active")),b&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}b&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); --------------------------------------------------------------------------------