├── .gitignore ├── README.markdown ├── lib ├── auth.js ├── encoding.js └── nodester-api.js ├── license.txt ├── package.json ├── public ├── i │ ├── favicon.ico │ ├── loader-small.gif │ ├── loader.gif │ ├── rocket-logo-extra-small.png │ ├── rocket-logo-small.png │ └── rocket-logo.png ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png ├── javascripts │ ├── lib │ │ ├── backbone-min.js │ │ ├── backbone.validation.min.js │ │ ├── bootstrap.min.js │ │ ├── html5.js │ │ ├── jq.modal.js │ │ ├── mustache.min.js │ │ └── underscore-min.js │ ├── main.js │ ├── n.js │ └── registration.js └── stylesheets │ ├── bootstrap-responsive.css │ ├── bootstrap-responsive.min.css │ ├── bootstrap.css │ └── style.css ├── server.js ├── test ├── app.test.js └── nodester-api.test.js └── views ├── bootstrap.jade ├── frontend-templates.jade ├── index.jade ├── layout.jade ├── login.jade ├── nav.jade └── register.jade /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nodester.appconfig 3 | node_modules 4 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | nodester-admin-panel 2 | ================================================== 3 | 4 | A web admin interface to manage your Nodester.com's account. 5 | 6 | Features 7 | ------- 8 | * Create new apps 9 | * Destroy existing apps 10 | * Get Info of each app 11 | * Start|Stop for your nodejs apps 12 | * View Domains aliased with apps 13 | 14 | Running 15 | ------- 16 | Version : 0.1 17 | 18 | Running 19 | ------- 20 | A working demo: [http://admin.nodester.com](http://admin.nodester.com) 21 | 22 | Questions? 23 | ---------- 24 | If you have any questions, please feel free to ask at micheal[at]visionmasterdesigns.com or tweet to [@MichealBenedict](http://twitter.com/michealbenedict) 25 | 26 | Special Thanks 27 | -------------- 28 | to [Nodester.com](http://nodester.com) for creating an amazing node hosting platform ! ~kudos -------------------------------------------------------------------------------- /lib/auth.js: -------------------------------------------------------------------------------- 1 | var nodester = require('./nodester-api'); 2 | 3 | function check_auth(options) { 4 | options= options || {}; 5 | var auth= {}; 6 | // Authentication Lib name 7 | auth.name = options.name || "awesomeauth"; 8 | 9 | // validate_creds 10 | function validate_credentials( executionScope, req, res, callback ) { 11 | // method, api path, data, credentials, callback 12 | // authorize: user 13 | nodester.authorize(req.user.creds, function(bool) { 14 | // if true, logged in 15 | if(bool) { 16 | executionScope.success( {}, callback ) 17 | } else { 18 | executionScope.fail( callback ) 19 | } 20 | }); 21 | }; 22 | 23 | // expose this method 24 | auth.authenticate = function(req, res, callback) { 25 | console.log("authenticate user ==>"); 26 | if( req.user ) { 27 | validate_credentials( this, req, res, callback ); 28 | } 29 | } 30 | return auth; 31 | } 32 | 33 | exports.auth = check_auth; -------------------------------------------------------------------------------- /lib/encoding.js: -------------------------------------------------------------------------------- 1 | var END_OF_INPUT = -1; 2 | 3 | var base64Chars = new Array( 4 | 'A','B','C','D','E','F','G','H', 5 | 'I','J','K','L','M','N','O','P', 6 | 'Q','R','S','T','U','V','W','X', 7 | 'Y','Z','a','b','c','d','e','f', 8 | 'g','h','i','j','k','l','m','n', 9 | 'o','p','q','r','s','t','u','v', 10 | 'w','x','y','z','0','1','2','3', 11 | '4','5','6','7','8','9','+','/' 12 | ); 13 | 14 | var reverseBase64Chars = new Array(); 15 | for (var i=0; i < base64Chars.length; i++){ 16 | reverseBase64Chars[base64Chars[i]] = i; 17 | } 18 | 19 | var base64Str; 20 | var base64Count; 21 | function setBase64Str(str){ 22 | base64Str = str; 23 | base64Count = 0; 24 | } 25 | function readBase64(){ 26 | if (!base64Str) return END_OF_INPUT; 27 | if (base64Count >= base64Str.length) return END_OF_INPUT; 28 | var c = base64Str.charCodeAt(base64Count) & 0xff; 29 | base64Count++; 30 | return c; 31 | } 32 | function encodeBase64(str){ 33 | setBase64Str(str); 34 | var result = ''; 35 | var inBuffer = new Array(3); 36 | var lineCount = 0; 37 | var done = false; 38 | while (!done && (inBuffer[0] = readBase64()) != END_OF_INPUT){ 39 | inBuffer[1] = readBase64(); 40 | inBuffer[2] = readBase64(); 41 | result += (base64Chars[ inBuffer[0] >> 2 ]); 42 | if (inBuffer[1] != END_OF_INPUT){ 43 | result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30) | (inBuffer[1] >> 4) ]); 44 | if (inBuffer[2] != END_OF_INPUT){ 45 | result += (base64Chars [((inBuffer[1] << 2) & 0x3c) | (inBuffer[2] >> 6) ]); 46 | result += (base64Chars [inBuffer[2] & 0x3F]); 47 | } else { 48 | result += (base64Chars [((inBuffer[1] << 2) & 0x3c)]); 49 | result += ('='); 50 | done = true; 51 | } 52 | } else { 53 | result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30)]); 54 | result += ('='); 55 | result += ('='); 56 | done = true; 57 | } 58 | lineCount += 4; 59 | if (lineCount >= 76){ 60 | result += ('\n'); 61 | lineCount = 0; 62 | } 63 | } 64 | return result; 65 | } 66 | function readReverseBase64(){ 67 | if (!base64Str) return END_OF_INPUT; 68 | while (true){ 69 | if (base64Count >= base64Str.length) return END_OF_INPUT; 70 | var nextCharacter = base64Str.charAt(base64Count); 71 | base64Count++; 72 | if (reverseBase64Chars[nextCharacter]){ 73 | return reverseBase64Chars[nextCharacter]; 74 | } 75 | if (nextCharacter == 'A') return 0; 76 | } 77 | return END_OF_INPUT; 78 | } 79 | 80 | function ntos(n){ 81 | n=n.toString(16); 82 | if (n.length == 1) n="0"+n; 83 | n="%"+n; 84 | return unescape(n); 85 | } 86 | 87 | function decodeBase64(str){ 88 | setBase64Str(str); 89 | var result = ""; 90 | var inBuffer = new Array(4); 91 | var done = false; 92 | while (!done && (inBuffer[0] = readReverseBase64()) != END_OF_INPUT 93 | && (inBuffer[1] = readReverseBase64()) != END_OF_INPUT){ 94 | inBuffer[2] = readReverseBase64(); 95 | inBuffer[3] = readReverseBase64(); 96 | result += ntos((((inBuffer[0] << 2) & 0xff)| inBuffer[1] >> 4)); 97 | if (inBuffer[2] != END_OF_INPUT){ 98 | result += ntos((((inBuffer[1] << 4) & 0xff)| inBuffer[2] >> 2)); 99 | if (inBuffer[3] != END_OF_INPUT){ 100 | result += ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[3])); 101 | } else { 102 | done = true; 103 | } 104 | } else { 105 | done = true; 106 | } 107 | } 108 | return result; 109 | } 110 | 111 | exports.base64 = encodeBase64; 112 | exports.dbase64 = decodeBase64; -------------------------------------------------------------------------------- /lib/nodester-api.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | encode = require("./encoding"), 3 | HOST = "api.nodester.com", 4 | PORT = 80, 5 | debug = process.env.NODE_ENV === 'debug'; 6 | // Generate Query parameters from params(json) 7 | function generateQueryParams(params) { 8 | console.log('generating params'); 9 | tail = []; 10 | for (var p in params) { 11 | if (params.hasOwnProperty(p)) { 12 | tail.push(p + "=" + encodeURIComponent(params[p])); 13 | } 14 | } 15 | if (tail.length > 0) return tail.join("&"); 16 | else return ""; 17 | } 18 | 19 | // authorize user 20 | function authorize(credentials, callback) { 21 | // options for credential checkin 22 | var options = { 23 | host: HOST, 24 | port: PORT 25 | }; 26 | options.path = "/apps"; 27 | options.headers = { 28 | "Authorization": "Basic " + credentials 29 | }; 30 | 31 | var req = http.request(options, function(res) { 32 | console.log("got it"); 33 | (res.statusCode == "200") ? callback(true) : callback(false); 34 | }); 35 | req.end(); 36 | } 37 | 38 | // interface to nodester api 39 | function request(method, path, data, credentials, callback) { 40 | var queryString = generateQueryParams(data); 41 | var formattedPath = "/" + path + ((method == "GET" && queryString.length > 0) ? "?" + queryString : ""); 42 | 43 | if (method == 'DELETE' && formattedPath == '/apps') { 44 | formattedPath = '/app/' + data.appname; 45 | queryString = ''; 46 | data = ''; 47 | } 48 | if (method == 'DELETE' && formattedPath == '/appdomains') { 49 | formattedPath = '/appdomains/' + data.appname + '/' + data.domain; 50 | queryString = ''; 51 | data = ''; 52 | } 53 | 54 | // Map RESTful PUT to Nodester API 55 | if (method == 'PUT' && formattedPath ==='/apps') { 56 | formattedPath = '/apps/' + data.name; 57 | 58 | queryString = generateQueryParams(data); 59 | } 60 | 61 | 62 | if (true || debug) { 63 | console.log("formatted path ===> ", formattedPath); 64 | console.log("method ===> ", method); 65 | console.log("params ===> ", data); 66 | } 67 | 68 | var options = { 69 | host: HOST, 70 | port: PORT, 71 | path: formattedPath, 72 | method: method, 73 | headers: { 74 | "Authorization": "Basic " + credentials 75 | } 76 | }; 77 | 78 | // headers 79 | if (method != "GET") { 80 | options.headers["Content-Length"] = queryString.length.toString(); 81 | options.headers["Content-Type"] = "application/x-www-form-urlencoded"; 82 | } 83 | if (debug) { 84 | console.log("---Options---"); 85 | console.log('HOST ===> ' + options.host); 86 | console.log('PORT ===> ' + options.port); 87 | console.log('PATH ===> ' + options.path); 88 | console.log('METHOD ===> ' + options.method); 89 | console.log('---END Options---'); 90 | } 91 | // request object 92 | var req = http.request(options); 93 | // write post body data 94 | if (method != "GET") req.write(queryString); 95 | 96 | req.on("response", function(res) { 97 | if (debug) { 98 | console.log('STATUS: ' + res.statusCode); 99 | console.log('HEADERS: ' + JSON.stringify(res.headers)); 100 | } 101 | 102 | res.setEncoding('utf8'); 103 | data = ''; 104 | res.on('data', function(chunk) { 105 | if (typeof(callback) == "function") { 106 | data += chunk; 107 | } 108 | }); 109 | 110 | res.on('end', function() { callback(data); }) 111 | }); 112 | 113 | req.on("error", function(err) { 114 | console.log('ERROR: ', err); 115 | }); 116 | 117 | req.end(); 118 | } 119 | 120 | // Expose Library Methods 121 | exports.request = request; 122 | exports.authorize = authorize; 123 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2011 Micheal Benedict Arul 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "rowoot", 3 | "name": "nodester-admin", 4 | "version": "0.0.1", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:nodester/admin-panel.git" 8 | }, 9 | "main": "server.js", 10 | "directories": { 11 | "lib": "." 12 | }, 13 | "dependencies": { 14 | "connect": ">=v1.4.0", 15 | "express": "v2.3.2", 16 | "jade": ">=v0.19.0", 17 | "connect-auth": ">=0.2.2" 18 | }, 19 | "devDependencies": { 20 | "request": ">=v0.0.1", 21 | "node-fakeweb": ">=v0.0.2" 22 | }, 23 | "description": "Admin interface for Nodester" 24 | } 25 | -------------------------------------------------------------------------------- /public/i/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodester/admin-panel/4a4391191a303abbbbff0f2100ac9a4c6f272f26/public/i/favicon.ico -------------------------------------------------------------------------------- /public/i/loader-small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodester/admin-panel/4a4391191a303abbbbff0f2100ac9a4c6f272f26/public/i/loader-small.gif -------------------------------------------------------------------------------- /public/i/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodester/admin-panel/4a4391191a303abbbbff0f2100ac9a4c6f272f26/public/i/loader.gif -------------------------------------------------------------------------------- /public/i/rocket-logo-extra-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodester/admin-panel/4a4391191a303abbbbff0f2100ac9a4c6f272f26/public/i/rocket-logo-extra-small.png -------------------------------------------------------------------------------- /public/i/rocket-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodester/admin-panel/4a4391191a303abbbbff0f2100ac9a4c6f272f26/public/i/rocket-logo-small.png -------------------------------------------------------------------------------- /public/i/rocket-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodester/admin-panel/4a4391191a303abbbbff0f2100ac9a4c6f272f26/public/i/rocket-logo.png -------------------------------------------------------------------------------- /public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodester/admin-panel/4a4391191a303abbbbff0f2100ac9a4c6f272f26/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodester/admin-panel/4a4391191a303abbbbff0f2100ac9a4c6f272f26/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/javascripts/lib/backbone-min.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 0.9.1 2 | 3 | // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. 4 | // Backbone may be freely distributed under the MIT license. 5 | // For all details and documentation: 6 | // http://backbonejs.org 7 | (function(){var i=this,r=i.Backbone,s=Array.prototype.slice,t=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:i.Backbone={};g.VERSION="0.9.1";var f=i._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var h=i.jQuery||i.Zepto||i.ender;g.setDomLibrary=function(a){h=a};g.noConflict=function(){i.Backbone=r;return this};g.emulateHTTP=!1;g.emulateJSON=!1;g.Events={on:function(a,b,c){for(var d,a=a.split(/\s+/),e=this._callbacks||(this._callbacks={});d=a.shift();){d=e[d]||(e[d]= 8 | {});var f=d.tail||(d.tail=d.next={});f.callback=b;f.context=c;d.tail=f.next={}}return this},off:function(a,b,c){var d,e,f;if(a){if(e=this._callbacks)for(a=a.split(/\s+/);d=a.shift();)if(f=e[d],delete e[d],b&&f)for(;(f=f.next)&&f.next;)if(!(f.callback===b&&(!c||f.context===c)))this.on(d,f.callback,f.context)}else delete this._callbacks;return this},trigger:function(a){var b,c,d,e;if(!(d=this._callbacks))return this;e=d.all;for((a=a.split(/\s+/)).push(null);b=a.shift();)e&&a.push({next:e.next,tail:e.tail, 9 | event:b}),(c=d[b])&&a.push({next:c.next,tail:c.tail});for(e=s.call(arguments,1);c=a.pop();){b=c.tail;for(d=c.event?[c.event].concat(e):e;(c=c.next)!==b;)c.callback.apply(c.context||this,d)}return this}};g.Events.bind=g.Events.on;g.Events.unbind=g.Events.off;g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=j(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");if(!this.set(a, 10 | {silent:!0}))throw Error("Can't create an invalid model");delete this._changed;this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(g.Model.prototype,g.Events,{idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.attributes[a];return this._escapedAttributes[a]=f.escape(null==b?"":""+b)},has:function(a){return null!= 11 | this.attributes[a]},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof g.Model&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=this.attributes,k=this._escapedAttributes,n=this._previousAttributes||{},h=this._setting;this._changed||(this._changed={});this._setting=!0;for(e in d)if(a=d[e],f.isEqual(b[e],a)||delete k[e],c.unset?delete b[e]:b[e]= 12 | a,this._changing&&!f.isEqual(this._changed[e],a)&&(this.trigger("change:"+e,this,a,c),this._moreChanges=!0),delete this._changed[e],!f.isEqual(n[e],a)||f.has(b,e)!=f.has(n,e))this._changed[e]=a;h||(!c.silent&&this.hasChanged()&&this.change(c),this._setting=!1);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d, 13 | e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};c.wait&&(e=f.clone(this.attributes));a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var k=this,h=c.success;c.success=function(a,b,e){b=k.parse(a,e);c.wait&&(b=f.extend(d||{},b));if(!k.set(b,c))return!1;h?h(k,a):k.trigger("sync",k,a,c)};c.error=g.wrapError(c.error, 14 | k,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d();a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=j(this.collection,"url")||j(this,"urlRoot")||o();return this.isNew()? 15 | a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){if(this._changing||!this.hasChanged())return this;this._moreChanges=this._changing=!0;for(var b in this._changed)this.trigger("change:"+b,this,this._changed[b],a);for(;this._moreChanges;)this._moreChanges=!1,this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes); 16 | delete this._changed;this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this._changed):this._changed&&f.has(this._changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this._changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)}, 17 | isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});g.Collection=function(a,b){b||(b={});b.comparator&&(this.comparator=b.comparator);this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(g.Collection.prototype,g.Events,{model:g.Model,initialize:function(){}, 18 | toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){var c,d,e,g,h,i={},j={};b||(b={});a=f.isArray(a)?a.slice():[a];for(c=0,d=a.length;c=b))this.iframe=h('