mirror of
https://github.com/semaphoreui/semaphore.git
synced 2024-11-23 12:30:41 +01:00
Barebones angular app
This commit is contained in:
parent
82a40395d9
commit
5465fb2e11
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
||||
lib/credentials.js
|
||||
lib/credentials.json
|
||||
public/vendor
|
||||
dist/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
156
Gruntfile.js
Normal file
156
Gruntfile.js
Normal file
@ -0,0 +1,156 @@
|
||||
module.exports = function(grunt) {
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
// Configuration
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
bump: {
|
||||
options: {
|
||||
files: ['package.json'],
|
||||
pushTo: 'origin',
|
||||
commitFiles: ['package.json']
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
js: {
|
||||
files: ['public/js/*.js', 'public/js/**/*.js'],
|
||||
tasks: ['newer:copy:js']
|
||||
},
|
||||
styles: {
|
||||
files: ['public/css/{,**/}*.less'],
|
||||
tasks: ['newer:less:development']
|
||||
},
|
||||
livereload: {
|
||||
files: [
|
||||
'dist/{,**/}*.{css,js,png,jpg,jpeg,gif,webp,svg,html}'
|
||||
],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
less: {
|
||||
development: {
|
||||
expand: true,
|
||||
cwd: 'public/css',
|
||||
dest: 'dist/css',
|
||||
src: '**/*',
|
||||
ext: '.css'
|
||||
},
|
||||
production: {
|
||||
expand: true,
|
||||
cwd: 'public/css',
|
||||
dest: 'dist/css',
|
||||
src: '**/*',
|
||||
ext: '.css',
|
||||
options: {
|
||||
cleancss: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
clean: {
|
||||
clean: {
|
||||
files: [{
|
||||
dot: true,
|
||||
src: [
|
||||
'dist'
|
||||
]
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Copies remaining files to places other tasks can use
|
||||
copy: {
|
||||
js: {
|
||||
expand: true,
|
||||
cwd: 'public/js',
|
||||
dest: 'dist/js/',
|
||||
src: '{,**/}*.js'
|
||||
},
|
||||
img: {
|
||||
expand: true,
|
||||
cwd: 'public/img',
|
||||
dest: 'dist/img/',
|
||||
src: '{,**/}*.{png,jpg,jpeg,gif}'
|
||||
},
|
||||
vendor: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'public/vendor',
|
||||
dest: 'dist/vendor',
|
||||
src: ['**/*.js', '**/*.css', '**/*.png', '**/*.jpg', '**/*.jpeg', '**/*.woff', '**/*.ttf', '**/*.svg', '**/*.eot']
|
||||
}]
|
||||
},
|
||||
fonts: {
|
||||
expand: true,
|
||||
cwd: 'public/fonts',
|
||||
dest: 'dist/fonts/',
|
||||
src: '{,**/}*.{woff,ttf,svg,eot}'
|
||||
}
|
||||
},
|
||||
|
||||
// Run some tasks in parallel to speed up the build process
|
||||
concurrent: {
|
||||
options: {
|
||||
limit: 6
|
||||
},
|
||||
server: [
|
||||
'copy:js',
|
||||
'copy:vendor',
|
||||
'copy:img',
|
||||
'copy:fonts'
|
||||
],
|
||||
watch: {
|
||||
tasks: [
|
||||
'nodemon:dev',
|
||||
'watch'
|
||||
],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uglify: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'public/js',
|
||||
src: ['**/*.js', '*.js'],
|
||||
dest: 'dist/js'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
nodemon: {
|
||||
dev: {
|
||||
script: 'lib/app.js',
|
||||
logConcurrentOutput: true,
|
||||
options: {
|
||||
cwd: __dirname,
|
||||
watch: ['lib/']
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('serve', [
|
||||
'clean:clean',
|
||||
'concurrent:server',
|
||||
'less:development',
|
||||
'concurrent:watch'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build', [
|
||||
'clean:clean',
|
||||
'concurrent:server',
|
||||
'less:production'
|
||||
]);
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'build'
|
||||
]);
|
||||
};
|
16
bower.json
Normal file
16
bower.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "semaphore",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"angular": "latest",
|
||||
"requirejs": "latest",
|
||||
"fontawesome": "latest",
|
||||
"jquery": "latest",
|
||||
"bootstrap": "latest",
|
||||
"moment": "latest",
|
||||
"d3": "latest",
|
||||
"async": "latest",
|
||||
"angular-couch-potato": "~0.1.1",
|
||||
"angular-ui-router": "~0.2.10"
|
||||
}
|
||||
}
|
126
lib/app.js
Normal file
126
lib/app.js
Normal file
@ -0,0 +1,126 @@
|
||||
var config = require('./config');
|
||||
|
||||
var newrelic = {
|
||||
getBrowserTimingHeader: function () {}
|
||||
};
|
||||
if (config.production && config.credentials.use_analytics) {
|
||||
newrelic = require('newrelic');
|
||||
}
|
||||
|
||||
var express = require('express')
|
||||
, routes = require('./routes')
|
||||
, http = require('http')
|
||||
, path = require('path')
|
||||
, mongoose = require('mongoose')
|
||||
, util = require('./util')
|
||||
, session = require('express-session')
|
||||
, RedisStore = require('connect-redis')(session)
|
||||
, passport = require('passport')
|
||||
, auth = require('./auth')
|
||||
, bugsnag = require('bugsnag')
|
||||
, socketPassport = require('passport.socketio')
|
||||
, bodyParser = require('body-parser')
|
||||
|
||||
var app = exports.app = express();
|
||||
|
||||
if (config.production) {
|
||||
require('newrelic');
|
||||
}
|
||||
|
||||
var releaseStage = config.production ? "production" : "development";
|
||||
|
||||
bugsnag.register("c0c7568710bb46d4bf14b3dad719dbbe", {
|
||||
notifyReleaseStages: ["production"],
|
||||
releaseStage: releaseStage
|
||||
});
|
||||
|
||||
mongoose.connect(config.credentials.db, config.credentials.db_options);
|
||||
|
||||
var sessionStore = new RedisStore({
|
||||
host: config.credentials.redis_host,
|
||||
port: config.credentials.redis_port,
|
||||
ttl: 604800000,
|
||||
pass: config.credentials.redis_key
|
||||
});
|
||||
|
||||
var db = mongoose.connection
|
||||
db.on('error', console.error.bind(console, 'Mongodb Connection Error:'));
|
||||
db.once('open', function callback () {
|
||||
if (!config.is_testing) console.log("Mongodb connection established")
|
||||
});
|
||||
|
||||
// all environments
|
||||
app.enable('trust proxy');
|
||||
app.set('port', process.env.PORT || 3000); // Port
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'jade'); // Templating engine
|
||||
app.set('app version', config.version); // App version
|
||||
app.set('x-powered-by', false);
|
||||
|
||||
app.set('view cache', config.production);
|
||||
|
||||
app.locals.newrelic = newrelic;
|
||||
config.configure(app);
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
res.set('x-frame-options', 'SAMEORIGIN');
|
||||
res.set('x-xss-protection', '1; mode=block');
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(require('serve-static')(path.join(__dirname, '..', 'dist')));
|
||||
app.use(require('morgan')(config.production ? 'combined' : 'dev'));
|
||||
|
||||
app.use(bugsnag.requestHandler);
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
app.use(bodyParser.json());
|
||||
app.use(require('cookie-parser')());
|
||||
app.use(session({
|
||||
secret: "#semaphore",
|
||||
name: 'semaphore',
|
||||
store: sessionStore,
|
||||
proxy: true,
|
||||
saveUninitialized: false,
|
||||
resave: false,
|
||||
cookie: {
|
||||
secure: config.credentials.is_ssl,
|
||||
maxAge: 604800000
|
||||
}
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// Custom middleware
|
||||
app.use(function(req, res, next) {
|
||||
res.locals.user = req.user;
|
||||
res.locals.loggedIn = res.locals.user != null;
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// routes
|
||||
routes.router(app);
|
||||
|
||||
app.use(bugsnag.errorHandler);
|
||||
|
||||
var server = http.createServer(app)
|
||||
server.listen(app.get('port'), function(){
|
||||
console.log('Semaphore listening on port ' + app.get('port'));
|
||||
});
|
||||
exports.io = io = require('socket.io').listen(server)
|
||||
|
||||
config.init();
|
||||
|
||||
io.use(socketPassport.authorize({
|
||||
cookieParser: require('cookie-parser'),
|
||||
secret: "#semaphore",
|
||||
key: 'semaphore',
|
||||
store: sessionStore,
|
||||
passport: passport,
|
||||
fail: function(data, message, error, accept) {
|
||||
accept(false);
|
||||
}
|
||||
}))
|
13
lib/auth.js
Normal file
13
lib/auth.js
Normal file
@ -0,0 +1,13 @@
|
||||
var passport = require('passport')
|
||||
, models = require('./models')
|
||||
, bugsnag = require('bugsnag')
|
||||
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user._id);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(id, done) {
|
||||
models.User.findOne({
|
||||
_id: id
|
||||
}, done);
|
||||
})
|
75
lib/config.js
Normal file
75
lib/config.js
Normal file
@ -0,0 +1,75 @@
|
||||
var fs = require('fs')
|
||||
, mailer = require('nodemailer')
|
||||
|
||||
try {
|
||||
var credentials = require('./credentials.json')
|
||||
} catch (e) {
|
||||
console.log("\nNo credentials.json File!\n")
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
exports.credentials = credentials;
|
||||
|
||||
exports.version = require('../package.json').version;
|
||||
exports.hash = 'dirty';
|
||||
exports.production = process.env.NODE_ENV == "production";
|
||||
exports.port = process.env.PORT || credentials.port;
|
||||
exports.path = __dirname;
|
||||
|
||||
if (process.platform.match(/^win/) == null) {
|
||||
try {
|
||||
var spawn_process = require('child_process').spawn
|
||||
var readHash = spawn_process('git', ['rev-parse', '--short', 'HEAD']);
|
||||
readHash.stdout.on('data', function (data) {
|
||||
exports.hash = data.toString().trim();
|
||||
require('./app').app.locals.versionHash = exports.hash;
|
||||
})
|
||||
} catch (e) {
|
||||
console.log("\n~= Unable to obtain git commit hash =~\n")
|
||||
}
|
||||
}
|
||||
|
||||
exports.configure = function (app) {
|
||||
app.locals.pretty = exports.production // Pretty HTML outside production mode
|
||||
app.locals.version = exports.version;
|
||||
app.locals.versionHash = exports.hash;
|
||||
app.locals.production = exports.production;
|
||||
app.locals.use_analytics = credentials.use_analytics;
|
||||
}
|
||||
|
||||
// Create SMTP transport method
|
||||
exports.transport_enabled = credentials.smtp.user.length > 0;
|
||||
exports.transport = null;
|
||||
|
||||
if (exports.transport_enabled) {
|
||||
var smtp = require('nodemailer-smtp-transport');
|
||||
|
||||
exports.transport = mailer.createTransport(smtp({
|
||||
service: "Mandrill",
|
||||
auth: credentials.smtp,
|
||||
port: 2525 // should bypass any port restrictions
|
||||
}));
|
||||
}
|
||||
|
||||
exports.init = function () {
|
||||
var models = require('./models');
|
||||
|
||||
models.User.findOne({
|
||||
email: 'admin@semaphore.local'
|
||||
}).exec(function (err, admin) {
|
||||
if (!admin) {
|
||||
console.log("Creating Admin user admin@semaphore.local!");
|
||||
|
||||
admin = new models.User({
|
||||
email: 'admin@semaphore.local',
|
||||
username: 'semaphore',
|
||||
name: 'Administrator'
|
||||
});
|
||||
models.User.hashPassword('CastawayLabs', function (hash) {
|
||||
admin.password = hash;
|
||||
|
||||
admin.save();
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
22
lib/credentials.example.json
Normal file
22
lib/credentials.example.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"redis_port": 6379,
|
||||
"redis_host": "127.0.0.1",
|
||||
"redis_key": "",
|
||||
"use_analytics": false,
|
||||
"is_ssl": false,
|
||||
"newrelic_key": "",
|
||||
"bugsnag_key": "",
|
||||
"smtp": {
|
||||
"user": "",
|
||||
"pass": ""
|
||||
},
|
||||
"db": "mongodb://127.0.0.1/semaphore",
|
||||
"db_options": {
|
||||
"auto_reconnect": true,
|
||||
"native_parser": true,
|
||||
"server": {
|
||||
"auto_reconnect": true
|
||||
}
|
||||
},
|
||||
"port": 3000
|
||||
}
|
34
lib/models/User.js
Normal file
34
lib/models/User.js
Normal file
@ -0,0 +1,34 @@
|
||||
var bcrypt = require('bcrypt')
|
||||
|
||||
var mongoose = require('mongoose')
|
||||
var ObjectId = mongoose.Schema.ObjectId;
|
||||
|
||||
var schema = mongoose.Schema({
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
username: String,
|
||||
name: String,
|
||||
email: String,
|
||||
password: String
|
||||
});
|
||||
|
||||
schema.index({
|
||||
email: 1
|
||||
});
|
||||
|
||||
schema.statics.hashPassword = function(password, cb) {
|
||||
bcrypt.hash(password, 10, function(err, hash) {
|
||||
cb(hash);
|
||||
});
|
||||
}
|
||||
|
||||
schema.methods.comparePassword = function (password, cb) {
|
||||
bcrypt.compare(password, this.password, function(err, res) {
|
||||
// res is boolean
|
||||
cb(res);
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = mongoose.model('User', schema);
|
7
lib/models/index.js
Normal file
7
lib/models/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
var manifest = [
|
||||
'User'
|
||||
];
|
||||
|
||||
manifest.forEach(function (model) {
|
||||
module.exports[model] = require('./'+model);
|
||||
});
|
9
lib/routes/apps.js
Normal file
9
lib/routes/apps.js
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
exports.httpRouter = function () {
|
||||
|
||||
}
|
||||
|
||||
exports.router = function () {
|
||||
|
||||
}
|
171
lib/routes/auth.js
Normal file
171
lib/routes/auth.js
Normal file
@ -0,0 +1,171 @@
|
||||
var passport = require('passport')
|
||||
, models = require('../models')
|
||||
, validator = require('validator')
|
||||
, util = require('../util')
|
||||
, config = require('../config')
|
||||
, async = require('async')
|
||||
, express = require('express')
|
||||
, mongoose = require('mongoose')
|
||||
|
||||
exports.unauthorized = function (app, template) {
|
||||
// Unrestricted -- non-authorized people can access!
|
||||
template([
|
||||
'login'
|
||||
], {
|
||||
prefix: 'auth'
|
||||
});
|
||||
|
||||
var auth = express.Router();
|
||||
|
||||
auth.post('/password', doLogin)
|
||||
.get('/loggedin', isLoggedIn)
|
||||
.get('/logout', doLogout)
|
||||
|
||||
app.use('/auth', auth);
|
||||
}
|
||||
|
||||
exports.router = function (app) {
|
||||
// Restricted -- only authorized people can access!
|
||||
app.post('/auth/register', doRegister)
|
||||
}
|
||||
|
||||
function isLoggedIn (req, res) {
|
||||
res.send({
|
||||
hasSession: req.user != null,
|
||||
isLoggedIn: res.locals.loggedIn
|
||||
});
|
||||
}
|
||||
|
||||
function doLogin (req, res) {
|
||||
var auth = req.body.auth;
|
||||
var isValid = true;
|
||||
|
||||
if (!validator.isLength(auth, 4)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// validate password
|
||||
if (!validator.isLength(req.body.password, 6)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
return authCallback(false, null, req, res);
|
||||
}
|
||||
|
||||
var query = {
|
||||
email: auth.toLowerCase()
|
||||
};
|
||||
|
||||
models.User.findOne(query, function(err, user) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
user.comparePassword(req.body.password, function (matches) {
|
||||
if (!matches) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
authCallback(isValid, user, req, res);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function authCallback (isValid, user, req, res) {
|
||||
if (!isValid) {
|
||||
res.send(400, {
|
||||
message: "Nope. Incorrect Credentials!"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
req.login(user, function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
res.send(201)
|
||||
})
|
||||
}
|
||||
|
||||
function doRegister (req, res) {
|
||||
var errs = {
|
||||
name: false,
|
||||
email: false,
|
||||
password: false,
|
||||
username: false
|
||||
};
|
||||
|
||||
var userObject = req.body.user;
|
||||
if (!(userObject && typeof userObject === 'object')) {
|
||||
return res.send(400, {
|
||||
message: 'Invalid Request'
|
||||
});
|
||||
}
|
||||
|
||||
var email = userObject.email;
|
||||
if (email) {
|
||||
email = email.toLowerCase();
|
||||
}
|
||||
var password = userObject.password;
|
||||
var username = userObject.username;
|
||||
var name = userObject.name;
|
||||
|
||||
errs.email = !validator.isEmail(email);
|
||||
errs.username = !validator.isLength(username, 3, 15);
|
||||
errs.name = !validator.isLength(name, 4, 50);
|
||||
|
||||
if (!(username && username.match(/^[a-zA-Z0-9_-]{3,15}$/) && validator.isAscii(username))) {
|
||||
// Errornous username
|
||||
errs.username = true;
|
||||
}
|
||||
|
||||
// validate password
|
||||
errs.password = !validator.isLength(password, 8, 100);
|
||||
|
||||
if (!(errs.username == false && errs.password == false && errs.name == false && errs.email == false)) {
|
||||
res.send(400, {
|
||||
fields: errs,
|
||||
message: ''
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Register
|
||||
var user = new models.User({
|
||||
email: email,
|
||||
username: username,
|
||||
name: name
|
||||
});
|
||||
|
||||
models.User.hashPassword(password, function (hash) {
|
||||
user.password = hash;
|
||||
|
||||
user.save();
|
||||
|
||||
// log in now
|
||||
req.login(user, function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
res.send({
|
||||
message: "Registration Successful",
|
||||
user_id: user._id
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doLogout (req, res) {
|
||||
req.logout();
|
||||
req.session.destroy();
|
||||
|
||||
res.format({
|
||||
json: function() {
|
||||
res.send(201)
|
||||
},
|
||||
html: function() {
|
||||
res.redirect('/')
|
||||
}
|
||||
})
|
||||
}
|
43
lib/routes/index.js
Normal file
43
lib/routes/index.js
Normal file
@ -0,0 +1,43 @@
|
||||
var util = require('../util')
|
||||
, auth = require('./auth')
|
||||
, apps = require('./apps')
|
||||
|
||||
exports.router = function(app) {
|
||||
var templates = require('../templates')(app);
|
||||
templates.route([
|
||||
auth,
|
||||
// apps
|
||||
]);
|
||||
|
||||
templates.add('homepage')
|
||||
templates.setup();
|
||||
|
||||
app.get('/', layout);
|
||||
app.all('*', util.authorized);
|
||||
|
||||
// Handle HTTP reqs
|
||||
apps.httpRouter(app);
|
||||
|
||||
// only json beyond this point
|
||||
app.get('*', function(req, res, next) {
|
||||
res.format({
|
||||
json: function() {
|
||||
next()
|
||||
},
|
||||
html: function() {
|
||||
layout(req, res);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
auth.router(app);
|
||||
apps.router(app);
|
||||
}
|
||||
|
||||
function layout (req, res) {
|
||||
if (res.locals.loggedIn) {
|
||||
res.render('layout')
|
||||
} else {
|
||||
res.render('auth');
|
||||
}
|
||||
}
|
67
lib/templates.js
Normal file
67
lib/templates.js
Normal file
@ -0,0 +1,67 @@
|
||||
// By Matej Kramny <matej@matej.me>
|
||||
// Please leave this comment here.
|
||||
|
||||
module.exports = function(app) {
|
||||
var self = this;
|
||||
self.routes = [];
|
||||
self.app = app
|
||||
|
||||
self.route = function (controller) {
|
||||
if (!(controller instanceof Array)) {
|
||||
controller = [controller];
|
||||
}
|
||||
|
||||
for (c in controller) {
|
||||
controller[c].unauthorized(self.app, self.add.bind(self));
|
||||
}
|
||||
}
|
||||
|
||||
self.makeRoute = function(route, view) {
|
||||
return {
|
||||
route: route,
|
||||
view: view
|
||||
}
|
||||
}
|
||||
|
||||
self.add = function (routes, opts) {
|
||||
var args = arguments;
|
||||
|
||||
var prefix = opts ? opts.prefix : null;
|
||||
if (!prefix) prefix = '';
|
||||
else prefix += '/';
|
||||
|
||||
if (typeof routes === 'string') {
|
||||
self.routes.push(self.makeRoute(prefix+routes, prefix+routes));
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.toString.call(routes) == '[object Object]') {
|
||||
self.routes.push(routes);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < routes.length; i++) {
|
||||
var r;
|
||||
if (typeof routes[i] == 'string') {
|
||||
r = self.makeRoute(prefix+routes[i], prefix+routes[i]);
|
||||
} else if (routes[i] instanceof Array) {
|
||||
r = self.makeRoute(prefix+routes[i][0], routes[i][1]);
|
||||
} else {
|
||||
r = routes[i]
|
||||
}
|
||||
|
||||
self.routes.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
self.setup = function () {
|
||||
for (var i = 0; i < routes.length; i++) {
|
||||
app.get('/view/'+routes[i].route, self.getView.bind(routes[i]));
|
||||
}
|
||||
}
|
||||
|
||||
self.getView = function (req, res) {
|
||||
res.render(this.view);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
16
lib/util.js
Normal file
16
lib/util.js
Normal file
@ -0,0 +1,16 @@
|
||||
exports.authorized = function (req, res, next) {
|
||||
if (res.locals.loggedIn) {
|
||||
next()
|
||||
} else {
|
||||
res.format({
|
||||
html: function() {
|
||||
res.render('auth');
|
||||
},
|
||||
json: function() {
|
||||
res.send(403, {
|
||||
message: "Unauthorized"
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
32
lib/views/auth.jade
Normal file
32
lib/views/auth.jade
Normal file
@ -0,0 +1,32 @@
|
||||
doctype
|
||||
html
|
||||
head
|
||||
meta(http-equiv="Content-Type", content="text/html; charset=utf-8;")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
block meta
|
||||
title(ng-bind-template="{{ pageTitle }} - Semaphore") Semaphore
|
||||
link(href="/favicon.ico" type="image/x-icon" rel="icon")
|
||||
link(href="/favicon.ico" type="image/x-icon" rel="shortcut icon")
|
||||
|
||||
//- all css goes here
|
||||
block css
|
||||
|
||||
link(rel="stylesheet" href="/css/semaphore.css")
|
||||
|
||||
body
|
||||
.container-fluid(style="margin-top: 100px;")
|
||||
.row
|
||||
.col-sm-6.col-sm-offset-3
|
||||
block content
|
||||
ui-view(autoscroll="false")
|
||||
p.lead.text-center
|
||||
i.fa.fa-spin.fa-cog
|
||||
| Loading...
|
||||
|
||||
block js
|
||||
script(src="/vendor/requirejs/require.js" data-main="/js/semaphore_auth.js")
|
||||
if use_analytics
|
||||
!= newrelic.getBrowserTimingHeader()
|
||||
|
||||
//- page-specific js
|
||||
block addonjs
|
21
lib/views/auth/login.jade
Normal file
21
lib/views/auth/login.jade
Normal file
@ -0,0 +1,21 @@
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
h3.panel-title Semaphore Log In
|
||||
.panel-body
|
||||
form.form-horizontal
|
||||
.form-group(ng-if="status.length > 0")
|
||||
.col-sm-7.col-sm-offset-4
|
||||
span(ng-bind="status")
|
||||
|
||||
.form-group
|
||||
label.control-label.col-sm-4 Email
|
||||
.col-sm-7
|
||||
input.form-control(type="email" ng-model="user.auth" placeholder="Email Address")
|
||||
.form-group
|
||||
label.control-label.col-sm-4 Password
|
||||
.col-sm-7
|
||||
input.form-control(type="password" ng-model="user.password" placeholder="Password")
|
||||
|
||||
.form-group
|
||||
.col-sm-7.col-sm-offset-4
|
||||
button.btn.btn-default(ng-click="authenticate(user)") Log in
|
1
lib/views/homepage.jade
Normal file
1
lib/views/homepage.jade
Normal file
@ -0,0 +1 @@
|
||||
h1 Hello there!
|
39
lib/views/layout.jade
Normal file
39
lib/views/layout.jade
Normal file
@ -0,0 +1,39 @@
|
||||
doctype
|
||||
html
|
||||
head
|
||||
meta(http-equiv="Content-Type", content="text/html; charset=utf-8;")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
block meta
|
||||
title(ng-bind-template="{{ pageTitle }} - Semaphore") Semaphore
|
||||
link(href="/favicon.ico" type="image/x-icon" rel="icon")
|
||||
link(href="/favicon.ico" type="image/x-icon" rel="shortcut icon")
|
||||
|
||||
//- all css goes here
|
||||
block css
|
||||
|
||||
link(rel="stylesheet" href="/css/semaphore.css")
|
||||
|
||||
body
|
||||
.container-fluid
|
||||
.row
|
||||
.col-sm-3.col-lg-2
|
||||
ul.nav
|
||||
h2.text-center: a(ui-sref="homepage") Semaphore
|
||||
|
||||
li(ng-repeat="playbook in playbooks")
|
||||
a(ui-sref="playbook({ pid: playbook._id })") {{ playbook.name }}
|
||||
|
||||
.col-sm-9.col-lg-10
|
||||
block content
|
||||
ui-view(autoscroll="false")
|
||||
p.lead.text-center
|
||||
i.fa.fa-spin.fa-cog
|
||||
| Loading...
|
||||
|
||||
block js
|
||||
script(src="/vendor/requirejs/require.js" data-main="/js/semaphore.js")
|
||||
if use_analytics
|
||||
!= newrelic.getBrowserTimingHeader()
|
||||
|
||||
//- page-specific js
|
||||
block addonjs
|
15
newrelic.js
Normal file
15
newrelic.js
Normal file
@ -0,0 +1,15 @@
|
||||
var config = require('./lib/config')
|
||||
|
||||
exports.config = {
|
||||
app_name : ['Semaphore'],
|
||||
license_key : config.credentials.newrelic_key,
|
||||
logging : {
|
||||
level : 'trace'
|
||||
},
|
||||
rules: {
|
||||
ignore: [
|
||||
'^/socket.io/.*/*-polling',
|
||||
'^/ping$'
|
||||
]
|
||||
}
|
||||
};
|
76
package.json
Normal file
76
package.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "semaphore",
|
||||
"version": "0.0.0",
|
||||
"description": "Open Source Alternative to Ansible Tower",
|
||||
"main": "bin/semaphore.js",
|
||||
"scripts": {
|
||||
"test": "mocha -R spec",
|
||||
"start": "node bin/semaphore.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/CastawayLabs/semaphore.git"
|
||||
},
|
||||
"keywords": [
|
||||
"ansible"
|
||||
],
|
||||
"author": "Matej Kramny <matej@matej.me>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/CastawayLabs/semaphore/issues"
|
||||
},
|
||||
"homepage": "https://github.com/CastawayLabs/semaphore",
|
||||
"dependencies": {
|
||||
"async": "latest",
|
||||
"bcrypt": "^0.8.0",
|
||||
"body-parser": "1.4.3",
|
||||
"bower": "latest",
|
||||
"bugsnag": "latest",
|
||||
"connect-mongo": "latest",
|
||||
"connect-redis": "latest",
|
||||
"cookie-parser": "latest",
|
||||
"express": "latest",
|
||||
"express-session": "latest",
|
||||
"grunt": "latest",
|
||||
"grunt-bump": "latest",
|
||||
"grunt-cli": "latest",
|
||||
"grunt-concurrent": "latest",
|
||||
"grunt-contrib-clean": "latest",
|
||||
"grunt-contrib-concat": "latest",
|
||||
"grunt-contrib-connect": "latest",
|
||||
"grunt-contrib-copy": "latest",
|
||||
"grunt-contrib-cssmin": "latest",
|
||||
"grunt-contrib-jade": "latest",
|
||||
"grunt-contrib-jshint": "latest",
|
||||
"grunt-contrib-less": "latest",
|
||||
"grunt-contrib-uglify": "latest",
|
||||
"grunt-contrib-watch": "latest",
|
||||
"grunt-newer": "latest",
|
||||
"grunt-nodemon": "latest",
|
||||
"hiredis": "latest",
|
||||
"jade": "latest",
|
||||
"load-grunt-tasks": "latest",
|
||||
"moment": "latest",
|
||||
"mongodb": "latest",
|
||||
"mongoose": "latest",
|
||||
"morgan": "^1.2.2",
|
||||
"mysql": "latest",
|
||||
"newrelic": "latest",
|
||||
"nodemailer": "latest",
|
||||
"nodemailer-smtp-transport": "latest",
|
||||
"passport": "latest",
|
||||
"passport.socketio": "latest",
|
||||
"ratelimiter": "^1.0.1",
|
||||
"redis": "latest",
|
||||
"request": "^2.40.0",
|
||||
"serve-static": "latest",
|
||||
"socket.io": "latest",
|
||||
"socket.io-client": "latest",
|
||||
"speakeasy": "latest",
|
||||
"validator": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"should": "latest",
|
||||
"supertest": "latest"
|
||||
}
|
||||
}
|
13
public/css/semaphore.less
Normal file
13
public/css/semaphore.less
Normal file
@ -0,0 +1,13 @@
|
||||
@import '../vendor/bootstrap/less/variables.less';
|
||||
@import '../vendor/fontawesome/less/variables.less';
|
||||
@import '//fonts.googleapis.com/css?family=Roboto:300,400,400italic,700,500,700italic';
|
||||
@import '//fonts.googleapis.com/css?family=Source+Code+Pro:300,400,500,700:latin';
|
||||
|
||||
@fa-font-path: "../vendor/fontawesome/fonts";
|
||||
|
||||
@font-family-sans-serif: Roboto, Arial, sans-serif;
|
||||
@font-family-monospace: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
@font-family-base: @font-family-sans-serif;
|
||||
|
||||
@import '../vendor/bootstrap/less/bootstrap.less';
|
||||
@import '../vendor/fontawesome/less/font-awesome.less';
|
36
public/js/app.js
Normal file
36
public/js/app.js
Normal file
@ -0,0 +1,36 @@
|
||||
define([
|
||||
'angular',
|
||||
'couchPotato'
|
||||
], function(angular, couchPotato, ga) {
|
||||
var app = angular.module('semaphore', ['scs.couch-potato', 'ui.router']);
|
||||
|
||||
couchPotato.configureApp(app);
|
||||
|
||||
app.run(function($rootScope, $window, $couchPotato) {
|
||||
app.lazy = $couchPotato;
|
||||
|
||||
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl){
|
||||
if (newUrl.match(/\&no_router/)) {
|
||||
event.preventDefault();
|
||||
$window.location.href = newUrl.replace(/\&no_router/, '');
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
|
||||
if (toState.pageTitle) {
|
||||
$rootScope.pageTitle = "Loading " + toState.pageTitle;
|
||||
} else {
|
||||
$rootScope.pageTitle = "Loading..";
|
||||
}
|
||||
});
|
||||
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams){
|
||||
if (toState.pageTitle) {
|
||||
$rootScope.pageTitle = toState.pageTitle;
|
||||
} else {
|
||||
$rootScope.pageTitle = "Semaphore Page";
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return app;
|
||||
})
|
35
public/js/controllers/auth/login.js
Normal file
35
public/js/controllers/auth/login.js
Normal file
@ -0,0 +1,35 @@
|
||||
define([
|
||||
'app'
|
||||
], function(app) {
|
||||
app.registerController('SignInCtrl', ['$scope', '$rootScope', '$http', '$state', function($scope, $rootScope, $http, $state) {
|
||||
$scope.status = "";
|
||||
$scope.user = {
|
||||
auth: "",
|
||||
password: ""
|
||||
};
|
||||
|
||||
$scope.authenticate = function(user) {
|
||||
$scope.status = "Authenticating..";
|
||||
|
||||
var pwd = user.password;
|
||||
user.password = "";
|
||||
|
||||
$http.post('/auth/password', {
|
||||
auth: user.auth,
|
||||
password: pwd
|
||||
}).success(function(data, status) {
|
||||
$scope.status = "Login Successful";
|
||||
window.location = "/";
|
||||
}).error(function (data, status) {
|
||||
if (status == 400) {
|
||||
// Login Failed
|
||||
$scope.status = data.message;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.status = status + ' Request Failed. Try again later.';
|
||||
});
|
||||
}
|
||||
}]);
|
||||
});
|
19
public/js/routes/auth.js
Normal file
19
public/js/routes/auth.js
Normal file
@ -0,0 +1,19 @@
|
||||
define([
|
||||
'app'
|
||||
], function(app) {
|
||||
app.config(function($stateProvider, $urlRouterProvider, $locationProvider, $couchPotatoProvider) {
|
||||
$locationProvider.html5Mode(true);
|
||||
|
||||
$urlRouterProvider.otherwise('/');
|
||||
|
||||
$stateProvider.state('login', {
|
||||
url: '/',
|
||||
pageTitle: "Sign In",
|
||||
templateUrl: "/view/auth/login",
|
||||
controller: "SignInCtrl",
|
||||
resolve: {
|
||||
dummy: $couchPotatoProvider.resolveDependencies(['controllers/auth/login'])
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
35
public/js/routes/routes.js
Normal file
35
public/js/routes/routes.js
Normal file
@ -0,0 +1,35 @@
|
||||
define([
|
||||
'app',
|
||||
'services/user'
|
||||
], function(app) {
|
||||
app.config(function($stateProvider, $urlRouterProvider, $locationProvider, $couchPotatoProvider) {
|
||||
$locationProvider.html5Mode(true);
|
||||
|
||||
$urlRouterProvider.otherwise('');
|
||||
|
||||
$stateProvider
|
||||
.state('homepage', {
|
||||
url: '/',
|
||||
pageTitle: 'Homepage',
|
||||
templateUrl: "/view/homepage"
|
||||
})
|
||||
|
||||
.state('logout', {
|
||||
url: '/logout',
|
||||
pageTitle: 'Log Out',
|
||||
controller: function($scope) {
|
||||
window.location = "/logout";
|
||||
}
|
||||
})
|
||||
})
|
||||
.run(function($rootScope, $state, $stateParams, $http, user) {
|
||||
$rootScope.$state = $state
|
||||
$rootScope.$stateParams = $stateParams
|
||||
|
||||
user.getUser(function() {})
|
||||
|
||||
$http.get('/playbooks').success(function(data, status) {
|
||||
$rootScope.playbooks = data.playbooks;
|
||||
})
|
||||
})
|
||||
})
|
34
public/js/semaphore.js
Normal file
34
public/js/semaphore.js
Normal file
@ -0,0 +1,34 @@
|
||||
require.config({
|
||||
paths: {
|
||||
angular: '../vendor/angular/angular.min',
|
||||
uiRouter: '../vendor/angular-ui-router/release/angular-ui-router.min',
|
||||
jquery: '../vendor/jquery/dist/jquery.min',
|
||||
moment: '../vendor/moment/moment',
|
||||
bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min',
|
||||
couchPotato: '../vendor/angular-couch-potato/dist/angular-couch-potato'
|
||||
},
|
||||
shim: {
|
||||
angular: {
|
||||
exports: 'angular'
|
||||
},
|
||||
uiRouter: {
|
||||
deps: ['angular']
|
||||
},
|
||||
bootstrap: ['jquery']
|
||||
}
|
||||
});
|
||||
|
||||
require([
|
||||
'jquery',
|
||||
'angular',
|
||||
'couchPotato',
|
||||
'uiRouter',
|
||||
'app',
|
||||
'routes/routes'
|
||||
], function($, angular) {
|
||||
var $html = angular.element(document.getElementsByTagName('html')[0]);
|
||||
|
||||
angular.element().ready(function() {
|
||||
angular.bootstrap($html, ['semaphore'])
|
||||
});
|
||||
});
|
34
public/js/semaphore_auth.js
Normal file
34
public/js/semaphore_auth.js
Normal file
@ -0,0 +1,34 @@
|
||||
require.config({
|
||||
paths: {
|
||||
angular: '../vendor/angular/angular.min',
|
||||
uiRouter: '../vendor/angular-ui-router/release/angular-ui-router.min',
|
||||
jquery: '../vendor/jquery/dist/jquery.min',
|
||||
moment: '../vendor/moment/moment',
|
||||
bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min',
|
||||
couchPotato: '../vendor/angular-couch-potato/dist/angular-couch-potato'
|
||||
},
|
||||
shim: {
|
||||
angular: {
|
||||
exports: 'angular'
|
||||
},
|
||||
uiRouter: {
|
||||
deps: ['angular']
|
||||
},
|
||||
bootstrap: ['jquery']
|
||||
}
|
||||
});
|
||||
|
||||
require([
|
||||
'jquery',
|
||||
'angular',
|
||||
'couchPotato',
|
||||
'uiRouter',
|
||||
'app',
|
||||
'routes/auth'
|
||||
], function($, angular) {
|
||||
var $html = angular.element(document.getElementsByTagName('html')[0]);
|
||||
|
||||
angular.element().ready(function() {
|
||||
angular.bootstrap($html, ['semaphore'])
|
||||
});
|
||||
});
|
15
public/js/services/user.js
Normal file
15
public/js/services/user.js
Normal file
@ -0,0 +1,15 @@
|
||||
define([
|
||||
'app'
|
||||
], function(app) {
|
||||
app.service('user', function($http, $rootScope) {
|
||||
var self = this;
|
||||
|
||||
self.getUser = function(cb) {
|
||||
$http.get('/profile').success(function(data) {
|
||||
$rootScope.user = self.user = data.user;
|
||||
|
||||
cb();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user