diff --git a/dashboard-project-api/.editorconfig b/dashboard-project-api/.editorconfig new file mode 100644 index 0000000..3ee22e5 --- /dev/null +++ b/dashboard-project-api/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/dashboard-project-api/.gitignore b/dashboard-project-api/.gitignore new file mode 100644 index 0000000..deac3d2 --- /dev/null +++ b/dashboard-project-api/.gitignore @@ -0,0 +1,20 @@ +*.csv +*.dat +*.iml +*.log +*.out +*.pid +*.seed +*.sublime-* +*.swo +*.swp +*.tgz +*.xml +.DS_Store +.idea +.project +.strong-pm +coverage +node_modules +npm-debug.log +js/ diff --git a/dashboard-project-api/.jshintignore b/dashboard-project-api/.jshintignore new file mode 100644 index 0000000..ee8c771 --- /dev/null +++ b/dashboard-project-api/.jshintignore @@ -0,0 +1,2 @@ +/client/ +/node_modules/ diff --git a/dashboard-project-api/.jshintrc b/dashboard-project-api/.jshintrc new file mode 100644 index 0000000..feb0928 --- /dev/null +++ b/dashboard-project-api/.jshintrc @@ -0,0 +1,21 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "eqeqeq": true, + "eqnull": true, + "immed": true, + "indent": 2, + "latedef": "nofunc", + "newcap": true, + "nonew": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": false, + "trailing": true, + "sub": true, + "maxlen": 80 +} diff --git a/dashboard-project-api/.yo-rc.json b/dashboard-project-api/.yo-rc.json new file mode 100644 index 0000000..02f3fc1 --- /dev/null +++ b/dashboard-project-api/.yo-rc.json @@ -0,0 +1,3 @@ +{ + "generator-loopback": {} +} \ No newline at end of file diff --git a/dashboard-project-api/README.md b/dashboard-project-api/README.md new file mode 100644 index 0000000..866baed --- /dev/null +++ b/dashboard-project-api/README.md @@ -0,0 +1,3 @@ +# My Application + +The project is generated by [LoopBack](http://loopback.io). \ No newline at end of file diff --git a/dashboard-project-api/client/README.md b/dashboard-project-api/client/README.md new file mode 100644 index 0000000..dd00c9e --- /dev/null +++ b/dashboard-project-api/client/README.md @@ -0,0 +1,3 @@ +## Client + +This is the place for your application front-end files. diff --git a/dashboard-project-api/common/models/blueprint.js b/dashboard-project-api/common/models/blueprint.js new file mode 100644 index 0000000..820be0c --- /dev/null +++ b/dashboard-project-api/common/models/blueprint.js @@ -0,0 +1,19 @@ +module.exports = function(Blueprint) { + + Blueprint.url = function(projectName, blueprintName, cb) { + var url = 'https://blueprints.launchpad.net/' + projectName + '/+spec/' + blueprintName; + cb(null, url); + } + + Blueprint.remoteMethod( + 'url', + { + accepts: [ + {arg: 'projectName', type: 'string', required: true}, + {arg: 'blueprintName', type: 'string', required: true} + ], + http: {verb: 'get'}, + returns: {arg: 'url', type: 'string'} + } + ); +}; diff --git a/dashboard-project-api/common/models/blueprint.json b/dashboard-project-api/common/models/blueprint.json new file mode 100644 index 0000000..5fa984b --- /dev/null +++ b/dashboard-project-api/common/models/blueprint.json @@ -0,0 +1,16 @@ +{ + "name": "Blueprint", + "base": "PersistedModel", + "idInjection": true, + "options": {}, + "properties": { + "url": { + "type": "string", + "required": true + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/dashboard-project-api/common/models/patch.js b/dashboard-project-api/common/models/patch.js new file mode 100644 index 0000000..07ba406 --- /dev/null +++ b/dashboard-project-api/common/models/patch.js @@ -0,0 +1,51 @@ +'strict mode'; +var async = require('async'); + +module.exports = function(Patch) { + Patch.afterRemote('list', function (ctx, response, next) { + var data = []; + + response = JSON.parse(response.substring(5)); + async.waterfall([ + function createOutput(next){ + response.forEach( function each (element){ + data.push( + { + url: "https://review.openstack.org/#/c/" + element._number, + name: element.subject + }); + }); + next(); + }, + function sendData (next) { + ctx.result = data; + next; + } + ], next); + next(); + }); + + Patch.afterRemote('latestUpdate', function (ctx, response, next) { + var data = []; + + response = JSON.parse(response.substring(5)); + async.waterfall([ + function createOutput(next){ + response.forEach( function each (element){ + data.push( + { + latestDate: element.updated, + }); + }); + next(); + }, + function sendData (next) { + data = data.sort(); + var lastUpdate = data.shift(); + ctx.result = lastUpdate; + next; + } + ], next); + next(); + }); +}; diff --git a/dashboard-project-api/common/models/patch.json b/dashboard-project-api/common/models/patch.json new file mode 100644 index 0000000..33c440b --- /dev/null +++ b/dashboard-project-api/common/models/patch.json @@ -0,0 +1,16 @@ +{ + "name": "Patch", + "base": "PersistedModel", + "properties": { + "name": { + "type": "string" + }, + "patchValues": { + "type": "object" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/dashboard-project-api/common/models/rst.js b/dashboard-project-api/common/models/rst.js new file mode 100644 index 0000000..4162515 --- /dev/null +++ b/dashboard-project-api/common/models/rst.js @@ -0,0 +1,10 @@ +'strict mode'; +var async = require('async'); + +module.exports = function(Rst) { + + Rst.afterRemote('list', function (ctx, response, next) { + next(); + }); + +}; diff --git a/dashboard-project-api/common/models/rst.json b/dashboard-project-api/common/models/rst.json new file mode 100644 index 0000000..5709236 --- /dev/null +++ b/dashboard-project-api/common/models/rst.json @@ -0,0 +1,17 @@ +{ + "name": "Rst", + "plural": "Rsts", + "base": "PersistedModel", + "properties": { + "name": { + "type": "string" + }, + "patchValues": { + "type": "object" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/dashboard-project-api/common/models/userStory.js b/dashboard-project-api/common/models/userStory.js new file mode 100644 index 0000000..1bf804f --- /dev/null +++ b/dashboard-project-api/common/models/userStory.js @@ -0,0 +1,419 @@ +module.exports = function(UserStory) { + var fs = require("fs"); + var route = '../tracker'; + var app = require('../../server/server'); + var http = require("http"); + var https = require('https'); + var markdown = require("markdown").markdown; + var async = require("async"); + var htmlparser = require("htmlparser"); + var cheerio = require('cheerio'); + + + var blueprintsResume = []; + + + var getAllfiles = function(getPercentage){ + + return fs.readdirSync(route) + .map(function(file){ + var route = "../tracker/" + file ; + var data = JSON.parse(fs.readFileSync(route)); + var parsedData; + + if(getPercentage){ + parsedData = getTotalPercentage(data); + data.blueprintList = parsedData; + } + + return data; + }); + + } + + //get completed percentage of all the userstories + var getTotalPercentage = function(userStory){ + + blueprintsResume.completed = 0; + blueprintsResume.total = 0; + var blueprintList = []; + + userStory.tasks.forEach(function (taskName, index, array) { + + var task = userStory.tasks_status[taskName]; + + task.projects.forEach(function (projectName, index, array) { + + var blueprints = task.projects_status[projectName].blueprints; + var blueprintNames = Object.keys(blueprints); + blueprintList.push(blueprintNames); + + blueprintNames.forEach(function (blueprintName, index, array) { + + if (blueprints[blueprintName] == 'completed') + blueprintsResume.completed = blueprintsResume.completed + 1; + + blueprintsResume.total = blueprintsResume.total + 1; + + }) + + }) + + }) + + var finalPercentage = Math.round((blueprintsResume.completed*100)/blueprintsResume.total) + blueprintList.push([{percentage:(finalPercentage)}]); + return blueprintList; + + } + + //get completed percentage of a User story + var getPercentage = function(blueprints){ + var total = blueprints.length; + var complete = 0; + + blueprints.forEach(function(element, index, array) { + + if(element == 'completed') + complete = complete + 1; + }) + return Math.round((complete*100)/total); + + } + + var getFileById = function(id){ + + //get all files + var userStories = getAllfiles(); + //filter by Id + var file = userStories.filter(function(item){ + return item.id == id; + }) + + if(file.length > 0){ + file = file[0]; + }else{ + file = 'The file with id: '+ id +' does not exist.' + } + return file; + + }; + + var getDetailedUri = function(source) { + var base = 'http://specs.openstack.org/openstack/openstack-user-stories/user-stories/proposed/'; + + return base + source + '.html'; + + } + + var parseUserStory = function(id){ + var data = []; + return function(callback){ + var file = getFileById(id); + + var Patch = app.models.Patch; + + Patch.latestUpdate(file.source, function(err, response) { + + var lastUpdate = ''; + + if(response){ + response = JSON.parse(response.substring(5)); + response.forEach( function each (element){ + data.push( + { + latestDate: element.updated, + }); + }); + + data = data.sort(); + lastUpdate = data.shift(); + + if(lastUpdate){ + lastUpdate = lastUpdate.latestDate; + } + } + + var userStory = { + title:file.description, + description:'', + status:file.status, + showDetailedUri:getDetailedUri(file.source), + submittedBy:file.submitted_by.name, + submittedByEmail:file.submitted_by.email, + createdOn:file.date, + updatedOn:lastUpdate, + id:file.id, + percentageComplete:'' + }; + callback(null, userStory, file.tasks, file.tasks_status); + + }) + + } + } + + + var getTaskDescription = function(task, callback){ + + var Rst = app.models.Rst; + var spec = task['cross-project spec'] + '.rst' + + Rst.list(spec, function(err, data){ + + var html_content = markdown.toHTML(data); + var $ = cheerio.load(html_content); + + var index = null; + //Find the title + var description = $('h1').each(function(i, elem) { + if(elem.children[0].data == 'Problem description'){ + index = i; + } + }); + + //get Text description + if(index != null){ + description = $($('h1')[index]).next().text() + }else{ + description = ''; + } + + callback(null, description); + + }) + + } + + var getUriTask = function(spec){ + var base = 'https://github.com/openstack/openstack-specs/blob/master/specs/'; + + return base + spec + '.rst'; + } + + var parseTask = function(originalTask, callback){ + + getTaskDescription(originalTask, function(err, description){ + originalTask.description = description; + originalTask.url = getUriTask(originalTask['cross-project spec']); + originalTask.xp_status = originalTask.xp_status; + callback(null, originalTask) + }) + + } + + + var parseProject = function(originalProject, callback){ + + var urlArray = originalProject.spec.split('/'); + var nameArray = urlArray[urlArray.length-1].split('.') + originalProject.spec_name = nameArray[0]; + + callback(null, originalProject) + } + + + var parseBlueprint = function(originalBlueprint, blueprintName, projectName, callback){ + var Blueprint = app.models.Blueprint; + var Patch = app.models.Patch; + + var status = originalBlueprint; + + Blueprint.url(projectName, blueprintName, function(err, uri){ + + blueprintsResume.push(status) + + blueprintsResume.complete = blueprintsResume.total +1 ; + + + Patch.list(blueprintName, function(err, response) { + + var data = []; + + response = JSON.parse(response.substring(5)); + + response.forEach(function each(element) { + data.push( + { + url: "https://review.openstack.org/#/c/" + element._number, + name: element.subject + }); + }); + + originalBlueprint = { + name: blueprintName.replace(/-/g, " "), + uri: uri, + status: status, + review_link:data + } + callback(null, originalBlueprint) + + }) + + }) + + } + + + //TODO: APPLY async.waterfall + var parseTasks = function(userStory, tasksNames, tasks, callback) { //get tasks + + var tmpTasks = {}; + async.each(tasksNames, function(taskName, callbackInner) { + + parseTask(tasks[taskName], function(err, parsedTask){ + + tmpTasks[taskName] = parsedTask; + + var tmpProjects = {}; + async.each(tmpTasks[taskName].projects, function(projectName, callbackInner2) { + + parseProject(tmpTasks[taskName].projects_status[projectName], function(err, parsedProject){ + tmpProjects[projectName] = parsedProject; + + //Bluprints + var blueprintNames = Object.keys(tmpProjects[projectName].blueprints); + + async.map(blueprintNames, function(blueprintName, callbackInner3) { + + parseBlueprint(tmpProjects[projectName].blueprints[blueprintName], blueprintName, projectName, function(err, parsedBlueprint){ + // tmpProjects[projectName].blueprints[blueprintName] = parsedBlueprint + callbackInner3(null, parsedBlueprint) + }) + + }, function(err, blueprints) { + + tmpProjects[projectName].blueprints = blueprints; + + var project = {}; + project[projectName] = tmpProjects[projectName]; + + callbackInner2(null) + + }); + //fin blueprints + + }) + + }, function(err) { + + tmpTasks[taskName].projects_status = tmpProjects; + callbackInner(null) + }); + + }) + + }, function(err) { + + userStory.tasks = tasksNames; + userStory.tasks_status = tmpTasks; + + userStory.percentageComplete = getPercentage(blueprintsResume) + + callback(null, userStory); + + }); + + } + + UserStory.on('attached',function(){ + + UserStory.findById = function(id, params, cb){ + + async.waterfall([ + parseUserStory(id), + parseTasks + ], function (err, result) { + cb(null,result) + }); + + }; + + UserStory.find = function(params, cb){ + var Patch = app.models.Patch; + //get all files + var userStories = getAllfiles(true); + var percentage; + var blueprintKeys; + var dateList = []; + //var response = userStories; + + var result; + + // looking dates + var lookingDates = function (id, blueprintKeys) { + blueprintKeys.forEach(function each (key) { + var data = []; + Patch.latestUpdate(key, function (err, response, next) { + + response = JSON.parse(response.substring(5)); + async.waterfall([ + function createOutput(next){ + response.forEach( function each (element){ + data.push( + { + latestDate: element.updated, + }); + }); + next(); + }, + function sendData (next) { + data = data.sort(); + var lastUpdate = data.shift(); + if(lastUpdate !== undefined) { + dateList.push({ + id: id, + value: lastUpdate.latestDate + }); + } + next; + } + ], next); + }); + }); + } + + function keysrt(key,asc) { + return function(a,b){ + return asc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]); + } + } + + async.waterfall ([ + function readStories (next) { + userStories.forEach (function eachStory (story, x) { + //console.log(story.blueprintList); + //TODO change the array of percentage + percentage = (story.blueprintList).pop(); + blueprintKeys = story.blueprintList; + result = lookingDates((story.id), blueprintKeys); + story.percentage = percentage[0].percentage; + }); + next(); + }, + function resumeDate (next){ + setTimeout(function(){ + dateList = dateList.sort(keysrt('value')); + userStories.forEach (function eachStory (story) { + dateList.forEach (function eachDate (dateElement){ + if(dateElement.id === story.id){ + story.latestUpdate = dateList + } + }); + }); + cb(null,userStories); + }, 1000); + + next; + }, + function sendResult(next){ + //cb(null,userStories); + next; + } + ]); + + + } + }) + +}; diff --git a/dashboard-project-api/common/models/userStory.json b/dashboard-project-api/common/models/userStory.json new file mode 100644 index 0000000..2d07b5f --- /dev/null +++ b/dashboard-project-api/common/models/userStory.json @@ -0,0 +1,16 @@ +{ + "name": "UserStory", + "base": "PersistedModel", + "idInjection": false, + "options": {}, + "properties": { + "UserStory": { + "type": "array", + "required": false + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/dashboard-project-api/package.json b/dashboard-project-api/package.json new file mode 100644 index 0000000..48c7a6e --- /dev/null +++ b/dashboard-project-api/package.json @@ -0,0 +1,37 @@ +{ + "name": "dashboard-project-api", + "version": "1.0.0", + "main": "server/server.js", + "scripts": { + "start": "node .", + "build-sdk": "mkdir js; lb-ng server/server.js js/lbServices.js", + "pretest": "jshint .", + "posttest": "nsp check" + }, + "dependencies": { + "async": "^2.0.0-rc.3", + "cheerio": "^0.0.3", + "compression": "^1.0.3", + "cors": "^2.5.2", + "helmet": "~0.14.0", + "htmlparser": "^1.7.7", + "loopback": "^2.22.0", + "loopback-boot": "^2.6.5", + "loopback-component-explorer": "^2.4.0", + "loopback-connector-rest": "^1.5.0", + "loopback-datasource-juggler": "^2.39.0", + "markdown": "^0.5.0", + "serve-favicon": "^2.0.1", + "underscore": "^1.8.3" + }, + "devDependencies": { + "jshint": "^2.5.6", + "nsp": "^2.1.0" + }, + "repository": { + "type": "", + "url": "" + }, + "license": "UNLICENSED", + "description": "dashboard-project-api" +} diff --git a/dashboard-project-api/server/boot/authentication.js b/dashboard-project-api/server/boot/authentication.js new file mode 100644 index 0000000..a87cd08 --- /dev/null +++ b/dashboard-project-api/server/boot/authentication.js @@ -0,0 +1,4 @@ +module.exports = function enableAuthentication(server) { + // enable authentication + server.enableAuth(); +}; diff --git a/dashboard-project-api/server/boot/root.js b/dashboard-project-api/server/boot/root.js new file mode 100644 index 0000000..e106142 --- /dev/null +++ b/dashboard-project-api/server/boot/root.js @@ -0,0 +1,6 @@ +module.exports = function(server) { + // Install a `/` route that returns server status + var router = server.loopback.Router(); + router.get('/', server.loopback.status()); + server.use(router); +}; diff --git a/dashboard-project-api/server/component-config.json b/dashboard-project-api/server/component-config.json new file mode 100644 index 0000000..f36959a --- /dev/null +++ b/dashboard-project-api/server/component-config.json @@ -0,0 +1,5 @@ +{ + "loopback-component-explorer": { + "mountPath": "/explorer" + } +} diff --git a/dashboard-project-api/server/config.json b/dashboard-project-api/server/config.json new file mode 100644 index 0000000..38e008e --- /dev/null +++ b/dashboard-project-api/server/config.json @@ -0,0 +1,27 @@ +{ + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3004, + "remoting": { + "context": { + "enableHttpContext": false + }, + "rest": { + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false, + "errorHandler": { + "disableStackTrace": false + } + }, + "legacyExplorer": false +} diff --git a/dashboard-project-api/server/constants.js b/dashboard-project-api/server/constants.js new file mode 100644 index 0000000..e69de29 diff --git a/dashboard-project-api/server/datasources.json b/dashboard-project-api/server/datasources.json new file mode 100644 index 0000000..130ccc7 --- /dev/null +++ b/dashboard-project-api/server/datasources.json @@ -0,0 +1,47 @@ +{ + "db": { + "name": "db", + "connector": "memory" + }, + "Patch": { + "name": "Patch", + "connector": "rest", + "operations": [ + { + "template": { + "method": "GET", + "url": "https://review.openstack.org/changes/?q=topic:{topic}", + "headers": { + "accepts": "application/json", + "content-type": "application/json" + } + }, + "functions": { + "list": [ + "topic" + ], + "latestUpdate": [ + "topic" + ] + } + } + ] + }, + "Rst": { + "name": "Rst", + "connector": "rest", + "operations": [ + { + "template": { + "method": "GET", + "url": "https://raw.githubusercontent.com/openstack/openstack-specs/master/specs/{file}" + }, + "functions": { + "list": [ + "file" + ] + } + } + ] + } +} diff --git a/dashboard-project-api/server/middleware.json b/dashboard-project-api/server/middleware.json new file mode 100644 index 0000000..b2eb840 --- /dev/null +++ b/dashboard-project-api/server/middleware.json @@ -0,0 +1,50 @@ +{ + "initial:before": { + "loopback#favicon": {} + }, + "initial": { + "compression": {}, + "cors": { + "params": { + "origin": true, + "credentials": true, + "maxAge": 86400 + } + }, + "helmet#xssFilter": {}, + "helmet#frameguard": { + "params": [ + "deny" + ] + }, + "helmet#hsts": { + "params": { + "maxAge": 0, + "includeSubdomains": true + } + }, + "helmet#hidePoweredBy": {}, + "helmet#ieNoOpen": {}, + "helmet#noSniff": {}, + "helmet#noCache": { + "enabled": false + } + }, + "session": {}, + "auth": {}, + "parse": {}, + "routes": { + "loopback#rest": { + "paths": [ + "${restApiRoot}" + ] + } + }, + "files": {}, + "final": { + "loopback#urlNotFound": {} + }, + "final:after": { + "loopback#errorHandler": {} + } +} diff --git a/dashboard-project-api/server/middleware.production.json b/dashboard-project-api/server/middleware.production.json new file mode 100644 index 0000000..4572b0c --- /dev/null +++ b/dashboard-project-api/server/middleware.production.json @@ -0,0 +1,9 @@ +{ + "final:after": { + "loopback#errorHandler": { + "params": { + "includeStack": false + } + } + } +} diff --git a/dashboard-project-api/server/model-config.json b/dashboard-project-api/server/model-config.json new file mode 100644 index 0000000..473d888 --- /dev/null +++ b/dashboard-project-api/server/model-config.json @@ -0,0 +1,47 @@ +{ + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "../common/models", + "./models" + ], + "mixins": [ + "loopback/common/mixins", + "loopback/server/mixins", + "../common/mixins", + "./mixins" + ] + }, + "UserStory": { + "dataSource": "db" + }, + "AccessToken": { + "dataSource": "db", + "public": false + }, + "ACL": { + "dataSource": "db", + "public": false + }, + "RoleMapping": { + "dataSource": "db", + "public": false + }, + "Role": { + "dataSource": "db", + "public": false + }, + "Blueprint": { + "dataSource": "db", + "public": true + }, + "Patch": { + "dataSource": "Patch", + "public": true + }, + "Rst": { + "dataSource": "Rst", + "public": true + } +} diff --git a/dashboard-project-api/server/server.js b/dashboard-project-api/server/server.js new file mode 100644 index 0000000..3be7b08 --- /dev/null +++ b/dashboard-project-api/server/server.js @@ -0,0 +1,27 @@ +var loopback = require('loopback'); +var boot = require('loopback-boot'); + +var app = module.exports = loopback(); + +app.start = function() { + // start the web server + return app.listen(function() { + app.emit('started'); + var baseUrl = app.get('url').replace(/\/$/, ''); + console.log('Web server listening at: %s', baseUrl); + if (app.get('loopback-component-explorer')) { + var explorerPath = app.get('loopback-component-explorer').mountPath; + console.log('Browse your REST API at %s%s', baseUrl, explorerPath); + } + }); +}; + +// Bootstrap the application, configure models, datasources and middleware. +// Sub-apps like REST API are mounted via boot scripts. +boot(app, __dirname, function(err) { + if (err) throw err; + + // start the server if `$ node server.js` + if (require.main === module) + app.start(); +}); diff --git a/dashboard-project-app/.babelrc b/dashboard-project-app/.babelrc new file mode 100755 index 0000000..bde6914 --- /dev/null +++ b/dashboard-project-app/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015"], + "plugins": ["transform-class-properties"] +} \ No newline at end of file diff --git a/dashboard-project-app/.bowerrc b/dashboard-project-app/.bowerrc new file mode 100755 index 0000000..76b30f4 --- /dev/null +++ b/dashboard-project-app/.bowerrc @@ -0,0 +1,4 @@ +{ + "directory": "client/bower_components" + +} diff --git a/dashboard-project-app/.buildignore b/dashboard-project-app/.buildignore new file mode 100755 index 0000000..e69de29 diff --git a/dashboard-project-app/.editorconfig b/dashboard-project-app/.editorconfig new file mode 100755 index 0000000..c2cdfb8 --- /dev/null +++ b/dashboard-project-app/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/dashboard-project-app/.gitattributes b/dashboard-project-app/.gitattributes new file mode 100755 index 0000000..d9a5563 --- /dev/null +++ b/dashboard-project-app/.gitattributes @@ -0,0 +1,43 @@ +* text=auto + +# These files are text and should be normalized (Convert crlf => lf) +*.php text +*.css text +*.js text +*.htm text +*.html text +*.xml text +*.txt text +*.ini text +*.inc text +.htaccess text + +# Denote all files that are truly binary and should not be modified. +# (binary is a macro for -text -diff) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary + +# Documents +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/dashboard-project-app/.gitignore b/dashboard-project-app/.gitignore new file mode 100755 index 0000000..914ada4 --- /dev/null +++ b/dashboard-project-app/.gitignore @@ -0,0 +1,14 @@ +node_modules +public +.tmp +.idea +client/bower_components +dist +/server/config/local.env.js +npm-debug.log +coverage +.DS_Store +/client/.DS_Store +/client/app/.DS_Store +/client/components/sdk/ +../.idea diff --git a/dashboard-project-app/.jscsrc b/dashboard-project-app/.jscsrc new file mode 100755 index 0000000..8923ad5 --- /dev/null +++ b/dashboard-project-app/.jscsrc @@ -0,0 +1,48 @@ +{ + "excludeFiles": [ + "client/app/app.constant.js" + ], + "esnext": true, + "maximumLineLength": { + "value": 100, + "allowComments": true, + "allowRegex": true + }, + "disallowMixedSpacesAndTabs": true, + "disallowMultipleLineStrings": true, + "disallowNewlineBeforeBlockStatements": true, + "disallowSpaceAfterObjectKeys": true, + "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], + "disallowSpaceBeforeBinaryOperators": [","], + "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], + "disallowSpacesInAnonymousFunctionExpression": { + "beforeOpeningRoundBrace": true + }, + "disallowSpacesInFunctionDeclaration": { + "beforeOpeningRoundBrace": true + }, + "disallowSpacesInNamedFunctionExpression": { + "beforeOpeningRoundBrace": true + }, + "disallowSpacesInsideArrayBrackets": true, + "disallowSpacesInsideParentheses": true, + "disallowTrailingComma": true, + "disallowTrailingWhitespace": true, + "requireCommaBeforeLineBreak": true, + "requireLineFeedAtFileEnd": true, + "requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"], + "requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"], + "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], + "requireSpaceBeforeBlockStatements": true, + "requireSpacesInConditionalExpression": { + "afterTest": true, + "beforeConsequent": true, + "afterConsequent": true, + "beforeAlternate": true + }, + "requireSpacesInFunction": { + "beforeOpeningCurlyBrace": true + }, + "validateLineBreaks": "LF", + "validateParameterSeparator": ", " +} diff --git a/dashboard-project-app/.travis.yml b/dashboard-project-app/.travis.yml new file mode 100755 index 0000000..85b9616 --- /dev/null +++ b/dashboard-project-app/.travis.yml @@ -0,0 +1,11 @@ +language: node_js +node_js: + - 4.2.3 +matrix: + fast_finish: true + allow_failures: + - node_js: 5.1.1 +before_script: + - npm install -g bower grunt-cli + - bower install +services: mongodb diff --git a/dashboard-project-app/.yo-rc.json b/dashboard-project-app/.yo-rc.json new file mode 100755 index 0000000..6c782bf --- /dev/null +++ b/dashboard-project-app/.yo-rc.json @@ -0,0 +1,67 @@ +{ + "generator-angular-fullstack": { + "generatorVersion": "3.5.0", + "endpointDirectory": "server/api/", + "insertRoutes": true, + "registerRoutesFile": "server/routes.js", + "routesNeedle": "// Insert routes below", + "routesBase": "/api/", + "pluralizeRoutes": true, + "insertSockets": true, + "registerSocketsFile": "server/config/socketio.js", + "socketsNeedle": "// Insert sockets below", + "insertModels": true, + "registerModelsFile": "server/sqldb/index.js", + "modelsNeedle": "// Insert models below", + "filters": { + "js": true, + "babel": true, + "html": true, + "css": true, + "uirouter": true, + "bootstrap": true, + "uibootstrap": true, + "models": true, + "mongooseModels": true, + "mongoose": true, + "grunt": true, + "mocha": true, + "jasmine": false, + "should": false, + "expect": true + } + }, + "generator-ng-component": { + "routeDirectory": "client/app/", + "directiveDirectory": "client/app/", + "componentDirectory": "app/components/", + "filterDirectory": "client/app/", + "serviceDirectory": "client/app/", + "basePath": "client", + "moduleName": "", + "modulePrompt": true, + "filters": [ + "uirouter", + "mocha", + "expect", + "uirouter", + "es6" + ], + "extensions": [ + "babel", + "js", + "html", + "css" + ], + "directiveSimpleTemplates": "", + "directiveComplexTemplates": "", + "filterTemplates": "", + "serviceTemplates": "", + "factoryTemplates": "", + "controllerTemplates": "", + "componentTemplates": "", + "decoratorTemplates": "", + "providerTemplates": "", + "routeTemplates": "" + } +} \ No newline at end of file diff --git a/dashboard-project-app/Gruntfile.js b/dashboard-project-app/Gruntfile.js new file mode 100755 index 0000000..4c8cf81 --- /dev/null +++ b/dashboard-project-app/Gruntfile.js @@ -0,0 +1,782 @@ +// Generated on 2016-03-29 using generator-angular-fullstack 3.5.0 +'use strict'; + +module.exports = function (grunt) { + var localConfig; + try { + localConfig = require('./server/config/local.env'); + } catch(e) { + localConfig = {}; + } + + // Load grunt tasks automatically, when needed + require('jit-grunt')(grunt, { + express: 'grunt-express-server', + useminPrepare: 'grunt-usemin', + ngtemplates: 'grunt-angular-templates', + cdnify: 'grunt-google-cdn', + protractor: 'grunt-protractor-runner', + buildcontrol: 'grunt-build-control', + istanbul_check_coverage: 'grunt-mocha-istanbul', + ngconstant: 'grunt-ng-constant' + }); + + // Time how long tasks take. Can help when optimizing build times + require('time-grunt')(grunt); + + // Define the configuration for all the tasks + grunt.initConfig({ + + // Project settings + pkg: grunt.file.readJSON('package.json'), + yeoman: { + // configurable paths + client: require('./bower.json').appPath || 'client', + server: 'server', + dist: 'dist' + }, + express: { + options: { + port: process.env.PORT || 9000 + }, + dev: { + options: { + script: '<%= yeoman.server %>', + debug: true + } + }, + prod: { + options: { + script: '<%= yeoman.dist %>/<%= yeoman.server %>' + } + } + }, + open: { + server: { + url: 'http://localhost:<%= express.options.port %>' + } + }, + watch: { + babel: { + files: ['<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js'], + tasks: ['newer:babel:client'] + }, + ngconstant: { + files: ['<%= yeoman.server %>/config/environment/shared.js'], + tasks: ['ngconstant'] + }, + injectJS: { + files: [ + '<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js', + '!<%= yeoman.client %>/app/app.js' + ], + tasks: ['injector:scripts'] + }, + injectCss: { + files: ['<%= yeoman.client %>/{app,components}/**/*.css'], + tasks: ['injector:css'] + }, + mochaTest: { + files: ['<%= yeoman.server %>/**/*.{spec,integration}.js'], + tasks: ['env:test', 'mochaTest'] + }, + jsTest: { + files: ['<%= yeoman.client %>/{app,components}/**/*.{spec,mock}.js'], + tasks: ['newer:jshint:all', 'wiredep:test', 'karma'] + }, + gruntfile: { + files: ['Gruntfile.js'] + }, + livereload: { + files: [ + '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.{css,html}', + '{.tmp,<%= yeoman.client %>}/{app,components}/**/!(*.spec|*.mock).js', + '<%= yeoman.client %>/assets/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}' + ], + options: { + livereload: true + } + }, + express: { + files: ['<%= yeoman.server %>/**/*.{js,json}'], + tasks: ['express:dev', 'wait'], + options: { + livereload: true, + spawn: false //Without this option specified express won't be reloaded + } + }, + bower: { + files: ['bower.json'], + tasks: ['wiredep'] + }, + }, + + // Make sure code styles are up to par and there are no obvious mistakes + jshint: { + options: { + jshintrc: '<%= yeoman.client %>/.jshintrc', + reporter: require('jshint-stylish') + }, + server: { + options: { + jshintrc: '<%= yeoman.server %>/.jshintrc' + }, + src: ['<%= yeoman.server %>/**/!(*.spec|*.integration).js'] + }, + serverTest: { + options: { + jshintrc: '<%= yeoman.server %>/.jshintrc-spec' + }, + src: ['<%= yeoman.server %>/**/*.{spec,integration}.js'] + }, + all: ['<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock|app.constant).js'], + test: { + src: ['<%= yeoman.client %>/{app,components}/**/*.{spec,mock}.js'] + } + }, + + jscs: { + options: { + config: ".jscsrc" + }, + main: { + files: { + src: [ + '<%= yeoman.client %>/app/**/*.js', + '<%= yeoman.server %>/**/*.js' + ] + } + } + }, + + // Empties folders to start fresh + clean: { + dist: { + files: [{ + dot: true, + src: [ + '.tmp', + '<%= yeoman.dist %>/!(.git*|.openshift|Procfile)**' + ] + }] + }, + server: '.tmp' + }, + + // Add vendor prefixed styles + postcss: { + options: { + map: true, + processors: [ + require('autoprefixer')({browsers: ['last 2 version']}) + ] + }, + dist: { + files: [{ + expand: true, + cwd: '.tmp/', + src: '{,*/}*.css', + dest: '.tmp/' + }] + } + }, + + // Debugging with node inspector + 'node-inspector': { + custom: { + options: { + 'web-host': 'localhost' + } + } + }, + + // Use nodemon to run server in debug mode with an initial breakpoint + nodemon: { + debug: { + script: '<%= yeoman.server %>', + options: { + nodeArgs: ['--debug-brk'], + env: { + PORT: process.env.PORT || 9000 + }, + callback: function (nodemon) { + nodemon.on('log', function (event) { + console.log(event.colour); + }); + + // opens browser on initial server start + nodemon.on('config:update', function () { + setTimeout(function () { + require('open')('http://localhost:8080/debug?port=5858'); + }, 500); + }); + } + } + } + }, + + // Automatically inject Bower components into the app and karma.conf.js + wiredep: { + options: { + exclude: [ + /bootstrap.js/, + '/json3/', + '/es5-shim/' + ] + }, + client: { + src: '<%= yeoman.client %>/index.html', + ignorePath: '<%= yeoman.client %>/', + }, + test: { + src: './karma.conf.js', + devDependencies: true + } + }, + + // Renames files for browser caching purposes + filerev: { + dist: { + src: [ + '<%= yeoman.dist %>/<%= yeoman.client %>/!(bower_components){,*/}*.{js,css}', + '<%= yeoman.dist %>/<%= yeoman.client %>/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' + ] + } + }, + + // Reads HTML for usemin blocks to enable smart builds that automatically + // concat, minify and revision files. Creates configurations in memory so + // additional tasks can operate on them + useminPrepare: { + html: ['<%= yeoman.client %>/index.html'], + options: { + dest: '<%= yeoman.dist %>/<%= yeoman.client %>' + } + }, + + // Performs rewrites based on rev and the useminPrepare configuration + usemin: { + html: ['<%= yeoman.dist %>/<%= yeoman.client %>/{,!(bower_components)/**/}*.html'], + css: ['<%= yeoman.dist %>/<%= yeoman.client %>/!(bower_components){,*/}*.css'], + js: ['<%= yeoman.dist %>/<%= yeoman.client %>/!(bower_components){,*/}*.js'], + options: { + assetsDirs: [ + '<%= yeoman.dist %>/<%= yeoman.client %>', + '<%= yeoman.dist %>/<%= yeoman.client %>/assets/images' + ], + // This is so we update image references in our ng-templates + patterns: { + css: [ + [/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the CSS to reference our revved images'] + ], + js: [ + [/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images'] + ] + } + } + }, + + // The following *-min tasks produce minified files in the dist folder + imagemin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.client %>/assets/images', + src: '{,*/}*.{png,jpg,jpeg,gif,svg}', + dest: '<%= yeoman.dist %>/<%= yeoman.client %>/assets/images' + }] + } + }, + + // Allow the use of non-minsafe AngularJS files. Automatically makes it + // minsafe compatible so Uglify does not destroy the ng references + ngAnnotate: { + dist: { + files: [{ + expand: true, + cwd: '.tmp/concat', + src: '**/*.js', + dest: '.tmp/concat' + }] + } + }, + + // Dynamically generate angular constant `appConfig` from + // `server/config/environment/shared.js` + ngconstant: { + options: { + name: 'dashboardProjectApp.constants', + dest: '<%= yeoman.client %>/app/app.constant.js', + deps: [], + wrap: true, + configPath: '<%= yeoman.server %>/config/environment/shared' + }, + app: { + constants: function() { + return { + appConfig: require('./' + grunt.config.get('ngconstant.options.configPath')) + }; + } + } + }, + + // Package all the html partials into a single javascript payload + ngtemplates: { + options: { + // This should be the name of your apps angular module + module: 'dashboardProjectApp', + htmlmin: { + collapseBooleanAttributes: true, + collapseWhitespace: true, + removeAttributeQuotes: true, + removeEmptyAttributes: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true + }, + usemin: 'app/app.js' + }, + main: { + cwd: '<%= yeoman.client %>', + src: ['{app,components}/**/*.html'], + dest: '.tmp/templates.js' + }, + tmp: { + cwd: '.tmp', + src: ['{app,components}/**/*.html'], + dest: '.tmp/tmp-templates.js' + } + }, + + // Replace Google CDN references + cdnify: { + dist: { + html: ['<%= yeoman.dist %>/<%= yeoman.client %>/*.html'] + } + }, + + // Copies remaining files to places other tasks can use + copy: { + dist: { + files: [{ + expand: true, + dot: true, + cwd: '<%= yeoman.client %>', + dest: '<%= yeoman.dist %>/<%= yeoman.client %>', + src: [ + '*.{ico,png,txt}', + '.htaccess', + 'bower_components/**/*', + 'assets/images/{,*/}*.{webp}', + 'assets/fonts/**/*', + 'index.html' + ] + }, { + expand: true, + cwd: '.tmp/images', + dest: '<%= yeoman.dist %>/<%= yeoman.client %>/assets/images', + src: ['generated/*'] + }, { + expand: true, + dest: '<%= yeoman.dist %>', + src: [ + 'package.json', + '<%= yeoman.server %>/**/*', + '!<%= yeoman.server %>/config/local.env.sample.js' + ] + }] + }, + styles: { + expand: true, + cwd: '<%= yeoman.client %>', + dest: '.tmp/', + src: ['{app,components}/**/*.css'] + } + }, + + buildcontrol: { + options: { + dir: '<%= yeoman.dist %>', + commit: true, + push: true, + connectCommits: false, + message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%' + }, + heroku: { + options: { + remote: 'heroku', + branch: 'master' + } + }, + openshift: { + options: { + remote: 'openshift', + branch: 'master' + } + } + }, + + // Run some tasks in parallel to speed up the build process + concurrent: { + pre: [ + 'ngconstant' + ], + server: [ + 'newer:babel:client', + ], + test: [ + 'newer:babel:client', + ], + debug: { + tasks: [ + 'nodemon', + 'node-inspector' + ], + options: { + logConcurrentOutput: true + } + }, + dist: [ + 'newer:babel:client', + 'imagemin' + ] + }, + + // Test settings + karma: { + unit: { + configFile: 'karma.conf.js', + singleRun: true + } + }, + + mochaTest: { + options: { + reporter: 'spec', + require: 'mocha.conf.js', + timeout: 5000 // set default mocha spec timeout + }, + unit: { + src: ['<%= yeoman.server %>/**/*.spec.js'] + }, + integration: { + src: ['<%= yeoman.server %>/**/*.integration.js'] + } + }, + + mocha_istanbul: { + unit: { + options: { + excludes: ['**/*.{spec,mock,integration}.js'], + reporter: 'spec', + require: ['mocha.conf.js'], + mask: '**/*.spec.js', + coverageFolder: 'coverage/server/unit' + }, + src: '<%= yeoman.server %>' + }, + integration: { + options: { + excludes: ['**/*.{spec,mock,integration}.js'], + reporter: 'spec', + require: ['mocha.conf.js'], + mask: '**/*.integration.js', + coverageFolder: 'coverage/server/integration' + }, + src: '<%= yeoman.server %>' + } + }, + + istanbul_check_coverage: { + default: { + options: { + coverageFolder: 'coverage/**', + check: { + lines: 80, + statements: 80, + branches: 80, + functions: 80 + } + } + } + }, + + protractor: { + options: { + configFile: 'protractor.conf.js' + }, + chrome: { + options: { + args: { + browser: 'chrome' + } + } + } + }, + + env: { + test: { + NODE_ENV: 'test' + }, + prod: { + NODE_ENV: 'production' + }, + all: localConfig + }, + + // Compiles ES6 to JavaScript using Babel + babel: { + options: { + sourceMap: true + }, + client: { + files: [{ + expand: true, + cwd: '<%= yeoman.client %>', + src: ['{app,components}/**/!(*.spec).js'], + dest: '.tmp' + }] + }, + server: { + options: { + plugins: [ + 'transform-class-properties', + 'transform-runtime' + ] + }, + files: [{ + expand: true, + cwd: '<%= yeoman.server %>', + src: [ + '**/*.js', + '!config/local.env.sample.js' + ], + dest: '<%= yeoman.dist %>/<%= yeoman.server %>' + }] + } + }, + + injector: { + options: {}, + // Inject application script files into index.html (doesn't include bower) + scripts: { + options: { + transform: function(filePath) { + var yoClient = grunt.config.get('yeoman.client'); + filePath = filePath.replace('/' + yoClient + '/', ''); + filePath = filePath.replace('/.tmp/', ''); + return ''; + }, + sort: function(a, b) { + var module = /\.module\.(js|ts)$/; + var aMod = module.test(a); + var bMod = module.test(b); + // inject *.module.js first + return (aMod === bMod) ? 0 : (aMod ? -1 : 1); + }, + starttag: '', + endtag: '' + }, + files: { + '<%= yeoman.client %>/index.html': [ + [ + '<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js', + '!{.tmp,<%= yeoman.client %>}/app/app.{js,ts}' + ] + ] + } + }, + + // Inject component css into index.html + css: { + options: { + transform: function(filePath) { + var yoClient = grunt.config.get('yeoman.client'); + filePath = filePath.replace('/' + yoClient + '/', ''); + filePath = filePath.replace('/.tmp/', ''); + return ''; + }, + starttag: '', + endtag: '' + }, + files: { + '<%= yeoman.client %>/index.html': [ + '<%= yeoman.client %>/{app,components}/**/*.css' + ] + } + } + }, + }); + + // Used for delaying livereload until after server has restarted + grunt.registerTask('wait', function () { + grunt.log.ok('Waiting for server reload...'); + + var done = this.async(); + + setTimeout(function () { + grunt.log.writeln('Done waiting!'); + done(); + }, 1500); + }); + + grunt.registerTask('express-keepalive', 'Keep grunt running', function() { + this.async(); + }); + + grunt.registerTask('serve', function (target) { + if (target === 'dist') { + return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']); + } + + if (target === 'debug') { + return grunt.task.run([ + // 'clean:server', + 'env:all', + 'concurrent:pre', + 'concurrent:server', + 'injector', + 'wiredep:client', + 'postcss', + 'concurrent:debug' + ]); + } + + grunt.task.run([ + // 'clean:server', + 'env:all', + 'concurrent:pre', + 'concurrent:server', + 'injector', + 'wiredep:client', + 'postcss', + 'express:dev', + 'wait', + 'open', + 'watch' + ]); + }); + + grunt.registerTask('server', function () { + grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); + grunt.task.run(['serve']); + }); + + grunt.registerTask('test', function(target, option) { + if (target === 'server') { + return grunt.task.run([ + 'env:all', + 'env:test', + 'mochaTest:unit', + 'mochaTest:integration' + ]); + } + + else if (target === 'client') { + return grunt.task.run([ + // 'clean:server', + 'env:all', + 'concurrent:pre', + 'concurrent:test', + 'injector', + 'postcss', + 'wiredep:test', + 'karma' + ]); + } + + else if (target === 'e2e') { + + if (option === 'prod') { + return grunt.task.run([ + 'build', + 'env:all', + 'env:prod', + 'express:prod', + 'protractor' + ]); + } + + else { + return grunt.task.run([ + // 'clean:server', + 'env:all', + 'env:test', + 'concurrent:pre', + 'concurrent:test', + 'injector', + 'wiredep:client', + 'postcss', + 'express:dev', + 'protractor' + ]); + } + } + + else if (target === 'coverage') { + + if (option === 'unit') { + return grunt.task.run([ + 'env:all', + 'env:test', + 'mocha_istanbul:unit' + ]); + } + + else if (option === 'integration') { + return grunt.task.run([ + 'env:all', + 'env:test', + 'mocha_istanbul:integration' + ]); + } + + else if (option === 'check') { + return grunt.task.run([ + 'istanbul_check_coverage' + ]); + } + + else { + return grunt.task.run([ + 'env:all', + 'env:test', + 'mocha_istanbul', + 'istanbul_check_coverage' + ]); + } + + } + + else grunt.task.run([ + 'test:server', + 'test:client' + ]); + }); + + grunt.registerTask('build', [ + // 'clean:dist', + 'concurrent:pre', + 'concurrent:dist', + 'injector', + 'wiredep:client', + 'useminPrepare', + 'postcss', + 'ngtemplates', + 'concat', + 'ngAnnotate', + 'copy:dist', + 'babel:server', + 'cdnify', + 'cssmin', + 'uglify', + 'filerev', + 'usemin' + ]); + + grunt.registerTask('default', [ + 'newer:jshint', + 'test', + 'build' + ]); +}; diff --git a/dashboard-project-app/README.md b/dashboard-project-app/README.md new file mode 100755 index 0000000..51dd054 --- /dev/null +++ b/dashboard-project-app/README.md @@ -0,0 +1,31 @@ +# dashboard-project + +This project was generated with the [Angular Full-Stack Generator](https://github.com/DaftMonk/generator-angular-fullstack) version 3.5.0. + +## Getting Started + +### Prerequisites + +- [Git](https://git-scm.com/) +- [Node.js and npm](nodejs.org) Node ^4.2.3, npm ^2.14.7 +- [Bower](bower.io) (`npm install --global bower`) +- [Grunt](http://gruntjs.com/) (`npm install --global grunt-cli`) +- [MongoDB](https://www.mongodb.org/) - Keep a running daemon with `mongod` + +### Developing + +1. Run `npm install` to install server dependencies. + +2. Run `bower install` to install front-end dependencies. + +3. Run `mongod` in a separate shell to keep an instance of the MongoDB Daemon running + +4. Run `grunt serve` to start the development server. It should automatically open the client in your browser when ready. + +## Build & development + +Run `grunt build` for building and `grunt serve` for preview. + +## Testing + +Running `npm test` will run the unit tests with karma. diff --git a/dashboard-project-app/bower.json b/dashboard-project-app/bower.json new file mode 100755 index 0000000..72507af --- /dev/null +++ b/dashboard-project-app/bower.json @@ -0,0 +1,22 @@ +{ + "name": "dashboard-project", + "version": "0.0.0", + "dependencies": { + "angular": "~1.5.0", + "json3": "~3.3.1", + "es5-shim": "~3.0.1", + "bootstrap": "~3.1.1", + "angular-resource": "~1.5.0", + "angular-cookies": "~1.5.0", + "angular-sanitize": "~1.5.0", + "angular-bootstrap": "~1.1.2", + "font-awesome": ">=4.1.0", + "lodash": "~2.4.1", + "angular-ui-router": "~0.2.15", + "bootstrap-table": "^1.10.1", + "moment": "^2.13.0" + }, + "devDependencies": { + "angular-mocks": "~1.5.0" + } +} diff --git a/dashboard-project-app/build-sdk.sh b/dashboard-project-app/build-sdk.sh new file mode 100755 index 0000000..182f5b6 --- /dev/null +++ b/dashboard-project-app/build-sdk.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +mkdir client/components/sdk +cp ../dashboard-project-api/js/lbServices.js client/components/sdk/ +grunt serve diff --git a/dashboard-project-app/client/.htaccess b/dashboard-project-app/client/.htaccess new file mode 100755 index 0000000..cb84cb9 --- /dev/null +++ b/dashboard-project-app/client/.htaccess @@ -0,0 +1,543 @@ +# Apache Configuration File + +# (!) Using `.htaccess` files slows down Apache, therefore, if you have access +# to the main server config file (usually called `httpd.conf`), you should add +# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. + +# ############################################################################## +# # CROSS-ORIGIN RESOURCE SHARING (CORS) # +# ############################################################################## + +# ------------------------------------------------------------------------------ +# | Cross-domain AJAX requests | +# ------------------------------------------------------------------------------ + +# Enable cross-origin AJAX requests. +# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity +# http://enable-cors.org/ + +# +# Header set Access-Control-Allow-Origin "*" +# + +# ------------------------------------------------------------------------------ +# | CORS-enabled images | +# ------------------------------------------------------------------------------ + +# Send the CORS header for images when browsers request it. +# https://developer.mozilla.org/en/CORS_Enabled_Image +# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html +# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ + + + + + SetEnvIf Origin ":" IS_CORS + Header set Access-Control-Allow-Origin "*" env=IS_CORS + + + + +# ------------------------------------------------------------------------------ +# | Web fonts access | +# ------------------------------------------------------------------------------ + +# Allow access from all domains for web fonts + + + + Header set Access-Control-Allow-Origin "*" + + + + +# ############################################################################## +# # ERRORS # +# ############################################################################## + +# ------------------------------------------------------------------------------ +# | 404 error prevention for non-existing redirected folders | +# ------------------------------------------------------------------------------ + +# Prevent Apache from returning a 404 error for a rewrite if a directory +# with the same name does not exist. +# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews +# http://www.webmasterworld.com/apache/3808792.htm + +Options -MultiViews + +# ------------------------------------------------------------------------------ +# | Custom error messages / pages | +# ------------------------------------------------------------------------------ + +# You can customize what Apache returns to the client in case of an error (see +# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: + +ErrorDocument 404 /404.html + + +# ############################################################################## +# # INTERNET EXPLORER # +# ############################################################################## + +# ------------------------------------------------------------------------------ +# | Better website experience | +# ------------------------------------------------------------------------------ + +# Force IE to render pages in the highest available mode in the various +# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. + + + Header set X-UA-Compatible "IE=edge" + # `mod_headers` can't match based on the content-type, however, we only + # want to send this header for HTML pages and not for the other resources + + Header unset X-UA-Compatible + + + +# ------------------------------------------------------------------------------ +# | Cookie setting from iframes | +# ------------------------------------------------------------------------------ + +# Allow cookies to be set from iframes in IE. + +# +# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" +# + +# ------------------------------------------------------------------------------ +# | Screen flicker | +# ------------------------------------------------------------------------------ + +# Stop screen flicker in IE on CSS rollovers (this only works in +# combination with the `ExpiresByType` directives for images from below). + +# BrowserMatch "MSIE" brokenvary=1 +# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 +# BrowserMatch "Opera" !brokenvary +# SetEnvIf brokenvary 1 force-no-vary + + +# ############################################################################## +# # MIME TYPES AND ENCODING # +# ############################################################################## + +# ------------------------------------------------------------------------------ +# | Proper MIME types for all files | +# ------------------------------------------------------------------------------ + + + + # Audio + AddType audio/mp4 m4a f4a f4b + AddType audio/ogg oga ogg + + # JavaScript + # Normalize to standard type (it's sniffed in IE anyways): + # http://tools.ietf.org/html/rfc4329#section-7.2 + AddType application/javascript js jsonp + AddType application/json json + + # Video + AddType video/mp4 mp4 m4v f4v f4p + AddType video/ogg ogv + AddType video/webm webm + AddType video/x-flv flv + + # Web fonts + AddType application/font-woff woff + AddType application/vnd.ms-fontobject eot + + # Browsers usually ignore the font MIME types and sniff the content, + # however, Chrome shows a warning if other MIME types are used for the + # following fonts. + AddType application/x-font-ttf ttc ttf + AddType font/opentype otf + + # Make SVGZ fonts work on iPad: + # https://twitter.com/FontSquirrel/status/14855840545 + AddType image/svg+xml svg svgz + AddEncoding gzip svgz + + # Other + AddType application/octet-stream safariextz + AddType application/x-chrome-extension crx + AddType application/x-opera-extension oex + AddType application/x-shockwave-flash swf + AddType application/x-web-app-manifest+json webapp + AddType application/x-xpinstall xpi + AddType application/xml atom rdf rss xml + AddType image/webp webp + AddType image/x-icon ico + AddType text/cache-manifest appcache manifest + AddType text/vtt vtt + AddType text/x-component htc + AddType text/x-vcard vcf + + + +# ------------------------------------------------------------------------------ +# | UTF-8 encoding | +# ------------------------------------------------------------------------------ + +# Use UTF-8 encoding for anything served as `text/html` or `text/plain`. +AddDefaultCharset utf-8 + +# Force UTF-8 for certain file formats. + + AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml + + + +# ############################################################################## +# # URL REWRITES # +# ############################################################################## + +# ------------------------------------------------------------------------------ +# | Rewrite engine | +# ------------------------------------------------------------------------------ + +# Turning on the rewrite engine and enabling the `FollowSymLinks` option is +# necessary for the following directives to work. + +# If your web host doesn't allow the `FollowSymlinks` option, you may need to +# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the +# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks + +# Also, some cloud hosting services require `RewriteBase` to be set: +# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site + + + Options +FollowSymlinks + # Options +SymLinksIfOwnerMatch + RewriteEngine On + # RewriteBase / + + +# ------------------------------------------------------------------------------ +# | Suppressing / Forcing the "www." at the beginning of URLs | +# ------------------------------------------------------------------------------ + +# The same content should never be available under two different URLs especially +# not with and without "www." at the beginning. This can cause SEO problems +# (duplicate content), therefore, you should choose one of the alternatives and +# redirect the other one. + +# By default option 1 (no "www.") is activated: +# http://no-www.org/faq.php?q=class_b + +# If you'd prefer to use option 2, just comment out all the lines from option 1 +# and uncomment the ones from option 2. + +# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# Option 1: rewrite www.example.com → example.com + + + RewriteCond %{HTTPS} !=on + RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] + RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# Option 2: rewrite example.com → www.example.com + +# Be aware that the following might not be a good idea if you use "real" +# subdomains for certain parts of your website. + +# +# RewriteCond %{HTTPS} !=on +# RewriteCond %{HTTP_HOST} !^www\..+$ [NC] +# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] +# + + +# ############################################################################## +# # SECURITY # +# ############################################################################## + +# ------------------------------------------------------------------------------ +# | Content Security Policy (CSP) | +# ------------------------------------------------------------------------------ + +# You can mitigate the risk of cross-site scripting and other content-injection +# attacks by setting a Content Security Policy which whitelists trusted sources +# of content for your site. + +# The example header below allows ONLY scripts that are loaded from the current +# site's origin (no inline scripts, no CDN, etc). This almost certainly won't +# work as-is for your site! + +# To get all the details you'll need to craft a reasonable policy for your site, +# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or +# see the specification: http://w3.org/TR/CSP). + +# +# Header set Content-Security-Policy "script-src 'self'; object-src 'self'" +# +# Header unset Content-Security-Policy +# +# + +# ------------------------------------------------------------------------------ +# | File access | +# ------------------------------------------------------------------------------ + +# Block access to directories without a default document. +# Usually you should leave this uncommented because you shouldn't allow anyone +# to surf through every directory on your server (which may includes rather +# private places like the CMS's directories). + + + Options -Indexes + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# Block access to hidden files and directories. +# This includes directories used by version control systems such as Git and SVN. + + + RewriteCond %{SCRIPT_FILENAME} -d [OR] + RewriteCond %{SCRIPT_FILENAME} -f + RewriteRule "(^|/)\." - [F] + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# Block access to backup and source files. +# These files may be left by some text editors and can pose a great security +# danger when anyone has access to them. + + + Order allow,deny + Deny from all + Satisfy All + + +# ------------------------------------------------------------------------------ +# | Secure Sockets Layer (SSL) | +# ------------------------------------------------------------------------------ + +# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: +# prevent `https://www.example.com` when your certificate only allows +# `https://secure.example.com`. + +# +# RewriteCond %{SERVER_PORT} !^443 +# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] +# + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# Force client-side SSL redirection. + +# If a user types "example.com" in his browser, the above rule will redirect him +# to the secure version of the site. That still leaves a window of opportunity +# (the initial HTTP connection) for an attacker to downgrade or redirect the +# request. The following header ensures that browser will ONLY connect to your +# server via HTTPS, regardless of what the users type in the address bar. +# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ + +# +# Header set Strict-Transport-Security max-age=16070400; +# + +# ------------------------------------------------------------------------------ +# | Server software information | +# ------------------------------------------------------------------------------ + +# Avoid displaying the exact Apache version number, the description of the +# generic OS-type and the information about Apache's compiled-in modules. + +# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! + +# ServerTokens Prod + + +# ############################################################################## +# # WEB PERFORMANCE # +# ############################################################################## + +# ------------------------------------------------------------------------------ +# | Compression | +# ------------------------------------------------------------------------------ + + + + # Force compression for mangled headers. + # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping + + + SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding + RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding + + + + # Compress all output labeled with one of the following MIME-types + # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` + # and can remove the `` and `` lines + # as `AddOutputFilterByType` is still in the core directives). + + AddOutputFilterByType DEFLATE application/atom+xml \ + application/javascript \ + application/json \ + application/rss+xml \ + application/vnd.ms-fontobject \ + application/x-font-ttf \ + application/x-web-app-manifest+json \ + application/xhtml+xml \ + application/xml \ + font/opentype \ + image/svg+xml \ + image/x-icon \ + text/css \ + text/html \ + text/plain \ + text/x-component \ + text/xml + + + + +# ------------------------------------------------------------------------------ +# | Content transformations | +# ------------------------------------------------------------------------------ + +# Prevent some of the mobile network providers from modifying the content of +# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. + +# +# Header set Cache-Control "no-transform" +# + +# ------------------------------------------------------------------------------ +# | ETag removal | +# ------------------------------------------------------------------------------ + +# Since we're sending far-future expires headers (see below), ETags can +# be removed: http://developer.yahoo.com/performance/rules.html#etags. + +# `FileETag None` is not enough for every server. + + Header unset ETag + + +FileETag None + +# ------------------------------------------------------------------------------ +# | Expires headers (for better cache control) | +# ------------------------------------------------------------------------------ + +# The following expires headers are set pretty far in the future. If you don't +# control versioning with filename-based cache busting, consider lowering the +# cache time for resources like CSS and JS to something like 1 week. + + + + ExpiresActive on + ExpiresDefault "access plus 1 month" + + # CSS + ExpiresByType text/css "access plus 1 year" + + # Data interchange + ExpiresByType application/json "access plus 0 seconds" + ExpiresByType application/xml "access plus 0 seconds" + ExpiresByType text/xml "access plus 0 seconds" + + # Favicon (cannot be renamed!) + ExpiresByType image/x-icon "access plus 1 week" + + # HTML components (HTCs) + ExpiresByType text/x-component "access plus 1 month" + + # HTML + ExpiresByType text/html "access plus 0 seconds" + + # JavaScript + ExpiresByType application/javascript "access plus 1 year" + + # Manifest files + ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" + ExpiresByType text/cache-manifest "access plus 0 seconds" + + # Media + ExpiresByType audio/ogg "access plus 1 month" + ExpiresByType image/gif "access plus 1 month" + ExpiresByType image/jpeg "access plus 1 month" + ExpiresByType image/png "access plus 1 month" + ExpiresByType video/mp4 "access plus 1 month" + ExpiresByType video/ogg "access plus 1 month" + ExpiresByType video/webm "access plus 1 month" + + # Web feeds + ExpiresByType application/atom+xml "access plus 1 hour" + ExpiresByType application/rss+xml "access plus 1 hour" + + # Web fonts + ExpiresByType application/font-woff "access plus 1 month" + ExpiresByType application/vnd.ms-fontobject "access plus 1 month" + ExpiresByType application/x-font-ttf "access plus 1 month" + ExpiresByType font/opentype "access plus 1 month" + ExpiresByType image/svg+xml "access plus 1 month" + + + +# ------------------------------------------------------------------------------ +# | Filename-based cache busting | +# ------------------------------------------------------------------------------ + +# If you're not using a build process to manage your filename version revving, +# you might want to consider enabling the following directives to route all +# requests such as `/css/style.12345.css` to `/css/style.css`. + +# To understand why this is important and a better idea than `*.css?v231`, read: +# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring + +# +# RewriteCond %{REQUEST_FILENAME} !-f +# RewriteCond %{REQUEST_FILENAME} !-d +# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] +# + +# ------------------------------------------------------------------------------ +# | File concatenation | +# ------------------------------------------------------------------------------ + +# Allow concatenation from within specific CSS and JS files, e.g.: +# Inside of `script.combined.js` you could have +# +# +# and they would be included into this single file. + +# +# +# Options +Includes +# AddOutputFilterByType INCLUDES application/javascript application/json +# SetOutputFilter INCLUDES +# +# +# Options +Includes +# AddOutputFilterByType INCLUDES text/css +# SetOutputFilter INCLUDES +# +# + +# ------------------------------------------------------------------------------ +# | Persistent connections | +# ------------------------------------------------------------------------------ + +# Allow multiple requests to be sent over the same TCP connection: +# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. + +# Enable if you serve a lot of static content but, be aware of the +# possible disadvantages! + +# +# Header set Connection Keep-Alive +# diff --git a/dashboard-project-app/client/.jshintrc b/dashboard-project-app/client/.jshintrc new file mode 100755 index 0000000..6d5c2ca --- /dev/null +++ b/dashboard-project-app/client/.jshintrc @@ -0,0 +1,39 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "ignoreDelimiters": [ + { "start": "start-non-standard", "end": "end-non-standard" } + ], + "globals": { + "jQuery": true, + "angular": true, + "console": true, + "$": true, + "_": true, + "moment": true, + "describe": true, + "beforeEach": true, + "module": true, + "inject": true, + "it": true, + "expect": true, + "browser": true, + "element": true, + "by": true + } +} diff --git a/dashboard-project-app/client/app/app.constant.js b/dashboard-project-app/client/app/app.constant.js new file mode 100755 index 0000000..b239b30 --- /dev/null +++ b/dashboard-project-app/client/app/app.constant.js @@ -0,0 +1,9 @@ +(function(angular, undefined) { +'use strict'; + +angular.module('dashboardProjectApp.constants', []) + +.constant('appConfig', {userRoles:['guest','user','admin']}) + +; +})(angular); \ No newline at end of file diff --git a/dashboard-project-app/client/app/app.css b/dashboard-project-app/client/app/app.css new file mode 100755 index 0000000..7a97efc --- /dev/null +++ b/dashboard-project-app/client/app/app.css @@ -0,0 +1,135 @@ + +/** + * Bootstrap Fonts + */ + +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot'); + src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), + url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), + url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), + url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} + +/** + *Font Awesome Fonts + */ + +@font-face { + font-family: 'FontAwesome'; + src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0'); + src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), + url('../bower_components/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), + url('../bower_components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), + url('../bower_components/font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +/** +* General font +**/ +@font-face { + font-family:'OpenSans-Regular'; + src: url('../assets/fonts/open-sans/OpenSans-Regular.ttf') format('truetype'); +} +@font-face { + font-family:'OpenSans-Bold'; + src: url('../assets/fonts/open-sans/OpenSans-Bold.ttf') format('truetype'); +} +@font-face { + font-family:'OpenSans-Light'; + src: url('../assets/fonts/open-sans/OpenSans-Light.ttf') format('truetype'); +} +/** + * App-wide Styles + */ + +.browserupgrade { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} +body { + font-family:"OpenSans-Regular", Helvetica, Arial, sans-serif; +} +header { + padding-top: 20px; + padding-bottom: 20px; + padding-left: 0px; + border-bottom: 2px solid #e1e1e1; +} + +h3 { + font-family: "OpenSans-Bold", Helvetica, Arial, sans-serif; + color: #2A4E68; +} + +.dp-title { + font-family: "OpenSans-Bold", Helvetica, Arial, sans-serif; + color: #2A4E68; + font-size: 24px; +} +.dp-label{ + font-size: 12px; + top: -5px; + position: relative; +} + +.dp-label-completed{ + background-color: #58B957; +} +.dp-label-in-progress{ + background-color: #0D5B9D; +} +.dp-label-none{ + background-color: #999999; +} +.dp-label-pending{ + background-color: #EFAC43; +} + +.dp-link-title { + color:#2E729A; +} +.dp-link-title:hover { + font-weight: bold; + text-decoration: none; +} + +.add-margin-top{ + margin-top: 10px; +} +.dp-hr{ + margin-bottom: 5px; +} + +.dp-subtitle{ + font-weight: bolder; +} + +.dp-nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus{ + background-color: #0D5B9D; +} + +.progress-bar .sr-only{ + position: relative; +} + +.progress-bar[aria-valuenow="0"] { + color: #000; + min-width: 100%; + background: transparent; + box-shadow: none; +} + +.compress-text{ + max-height: 63px; + overflow: hidden; +} + +.uncompress-text{ + max-height: none !important; +} diff --git a/dashboard-project-app/client/app/app.js b/dashboard-project-app/client/app/app.js new file mode 100755 index 0000000..99cb27f --- /dev/null +++ b/dashboard-project-app/client/app/app.js @@ -0,0 +1,20 @@ +'use strict'; + +angular.module('dashboardProjectApp', [ + 'dashboardProjectApp.constants', + 'ngCookies', + 'ngResource', + 'ngSanitize', + 'ui.router', + 'ui.bootstrap', + 'lbServices' +]) + .config(function($urlRouterProvider, $locationProvider, LoopBackResourceProvider) { + LoopBackResourceProvider.setUrlBase('http://localhost:3004/api'); + //$urlRouterProvider.otherwise('/'); + + $locationProvider.html5Mode(true); + }) + .run(function($rootScope, $state) { + $state.go("projectList") + }); diff --git a/dashboard-project-app/client/app/projectDetail/controllers/projectDetail.controller.js b/dashboard-project-app/client/app/projectDetail/controllers/projectDetail.controller.js new file mode 100755 index 0000000..48ab848 --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/controllers/projectDetail.controller.js @@ -0,0 +1,89 @@ +'use strict'; +(function(){ + + angular.module('dashboardProjectApp') + .controller('projectDetailController', ['$scope','$state', 'UserStory'/*, 'tasksService', 'tasks', 'task'*/, + function($scope, $state, UserStory/*, tasksService, tasks, task*/) { + + $scope.taskId = $state.params.id; + $scope.openTasks = {}; + $scope.showText = {} + + $scope.closeTask = function(task){ + $scope.openTasks[task] = !$scope.openTasks[task]; + } + + $scope.getProjectIcon = function(key){ + + if(key == 'nova') + return 'fa fa-cogs'; + + if(key == 'cinder') + return 'fa fa-archive' + + if(key == 'keystone') + return 'fa fa-key'; + + if(key == 'swift') + return 'fa fa-object-group'; + + if(key == 'neutron') + return 'fa fa-cloud-upload'; + + if(key == 'glance') + return 'fa fa-key'; + + return 'fa fa-cog'; + } + + $scope.actualProject = {}; + + function getFile() { + UserStory.findById({id:$scope.taskId}, + function success(userStory) { + $scope.userStory = userStory; + + $scope.userStory.updatedOn = moment($scope.userStory.updatedOn).format("MM-DD-YYYY"); + for(var key in $scope.userStory.tasks_status) { + $scope.actualProject[key] = $scope.userStory.tasks_status[key].projects[0] + } + + }); + }; + + $scope.selectProject = function(keyProject, idTask){ + $scope.actualProject[idTask] = keyProject + } + + getFile(); + + $scope.showMore = function(key){ + $scope.showText[key] = true; + } + + $scope.showLess = function(key){ + $scope.showText[key] = false; + } + + $scope.mailTo = function(user, email){ + window.location.href = "mailto:" + email + "?subject=Mail to " + email; + } + + + }]) + .filter('capitalize', function() { + return function(input) { + return (!!input) ? input.charAt(0).toUpperCase() + input.substr(1).toLowerCase() : ''; + } + }).filter('removeDashes', function() { + return function(string) { + + if (!angular.isString(string)) { + return string; + } + + return string.replace(/-/g, ' '); + }; + }) + +})(); diff --git a/dashboard-project-app/client/app/projectDetail/projectDetail.js b/dashboard-project-app/client/app/projectDetail/projectDetail.js new file mode 100755 index 0000000..eb6dfac --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/projectDetail.js @@ -0,0 +1,19 @@ +'use strict'; + +angular.module('dashboardProjectApp') + .config(function ($stateProvider) { + + $stateProvider + .state('projectDetail', { + url: '/projectDetail/:id', + /* resolve: { + userStory: ['userStoryService', + function(userStoryService) { + return userStoryService.getTasks(); + }] + },*/ + templateUrl: 'app/projectDetail/views/projectDetail.html', + controller: 'projectDetailController' + }) + + }); diff --git a/dashboard-project-app/client/app/projectDetail/services/userStoryService.js b/dashboard-project-app/client/app/projectDetail/services/userStoryService.js new file mode 100755 index 0000000..8e9b147 --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/services/userStoryService.js @@ -0,0 +1,18 @@ +angular.module('dashboardProjectApp') + .service('UserStoryService', ['$http','UserStory', function($http, UserStory) { + + this.searchTask = function(query) { + //return $http.get('/tasks/search/' + query); + }; + + $scope.getUserStory = function getFiles() { + + console.log('aaaaaaa') + return UserStory.findById({id:$scope.taskId}); + }; + + this.getTask = function(name) { + //return $http.get('/tasks/' + name); + return {}; + }; + }]); diff --git a/dashboard-project-app/client/app/projectDetail/styles/projectDetail.css b/dashboard-project-app/client/app/projectDetail/styles/projectDetail.css new file mode 100755 index 0000000..7099c66 --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/styles/projectDetail.css @@ -0,0 +1,44 @@ + +.frm-well{ + border: 0px; + border-radius: 0px; + box-shadow: none; +} + +.tasks{ + background-color: #EDF2F7; + padding: 20px; +} + +.quick-resume{ + margin-top: 20px; +} + +.progress{ + background-color: #DEDEDE; +} + +.dp-panel{ + border-top: 6px solid #DA6359; +} + +.task-open-icon{ + font-size: 30px; + top: 4px; + position: relative; + + -ms-transform: rotate(-90deg); + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); +} + +.task-open-icon.down{ + -ms-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + top: 0px; +} + + +table tr.separator { height: 30px; } + diff --git a/dashboard-project-app/client/app/projectDetail/tests/projectDetail.controller.spec.js b/dashboard-project-app/client/app/projectDetail/tests/projectDetail.controller.spec.js new file mode 100755 index 0000000..1401a97 --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/tests/projectDetail.controller.spec.js @@ -0,0 +1,21 @@ +'use strict'; + +describe('Component: ProjectDetailComponent', function () { + + // load the controller's module + beforeEach(module('dashboardProjectApp')); + + var ProjectDetailComponent, scope; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($componentController, $rootScope) { + scope = $rootScope.$new(); + ProjectDetailComponent = $componentController('ProjectDetailComponent', { + $scope: scope + }); + })); + + it('should ...', function () { + expect(1).to.equal(1); + }); +}); diff --git a/dashboard-project-app/client/app/projectDetail/views/projectDetail.html b/dashboard-project-app/client/app/projectDetail/views/projectDetail.html new file mode 100755 index 0000000..ea10dc5 --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/views/projectDetail.html @@ -0,0 +1,12 @@ +
+
+
+
+
+
+
+ +
+ + diff --git a/dashboard-project-app/client/app/projectDetail/views/quickResume.html b/dashboard-project-app/client/app/projectDetail/views/quickResume.html new file mode 100755 index 0000000..8d288d3 --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/views/quickResume.html @@ -0,0 +1,59 @@ +
+
+
+
+
+
+ {{userStory.title}} + + {{userStory.status | removeDashes | capitalize}} + +
+
+
+
+ ID: {{userStory.id}} +
+
+
+ +
+
+ + Submitted by + + {{userStory.submittedBy}} + + +
+
+ + Created on {{userStory.createdOn}} +
+
+ + Updated on {{userStory.updatedOn}} +
+
+
+
+
+
+ {{userStory.percentageComplete}}% Complete +
+
+
+
+
+
+
diff --git a/dashboard-project-app/client/app/projectDetail/views/task.html b/dashboard-project-app/client/app/projectDetail/views/task.html new file mode 100755 index 0000000..5c349d1 --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/views/task.html @@ -0,0 +1,139 @@ +
+
+ +
+
+
+

+ + {{key}} + + {{task.xp_status | removeDashes | capitalize}} + +

+
+
+
+ + + +
+
+

+ {{task.description}} +

+ + Show More + + + Show Less + +
+
+ +
+
+ Projects +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SpecStatus
{{task.projects_status[actualProject[key]].spec_name | removeDashes | capitalize}} + + {{task.projects_status[actualProject[key]].spec_status | removeDashes | capitalize}} + + + + + Show details + +
BlueprintsStatusReview Links
+ + {{blueprint.name | capitalize | capitalize}} + + + + {{blueprint.status | removeDashes | capitalize}} + + + +
+ +
+
+ +
+
diff --git a/dashboard-project-app/client/app/projectList/controllers/projectList.controller.js b/dashboard-project-app/client/app/projectList/controllers/projectList.controller.js new file mode 100755 index 0000000..514ea3a --- /dev/null +++ b/dashboard-project-app/client/app/projectList/controllers/projectList.controller.js @@ -0,0 +1,42 @@ +'use strict'; + +angular.module('dashboardProjectApp').controller('projectListCtrl', function( + $scope, + UserStory +){ + var data = []; + var lastUpdate; + function getFiles() { + UserStory.find( + function success(userStories, fillTable) { + new Promise(function(resolve, reject) { + var stringDate, realDate; + + userStories.forEach(function each (story) { + if(story.latestUpdate !== undefined) { + lastUpdate = moment(story.latestUpdate[0].value).format("MM-DD-YYYY"); + } else { + lastUpdate = moment(story.date, "DD-MM-YYYY").format("MM-DD-YYYY"); + } + data.push( + { + userStory: story.id+'-'+story.description, + dateCreated: moment(story.date, "DD-MM-YYYY").format("MM-DD-YYYY"), + lastUpdate: lastUpdate, + completed: story.percentage + } + ) + resolve(data); + }); + }) + .then(function(result) { + $(function () { + $('#table').bootstrapTable({ + data: data + }); + }); + }); + }); + }; + getFiles(); +}); diff --git a/dashboard-project-app/client/app/projectList/projectList.js b/dashboard-project-app/client/app/projectList/projectList.js new file mode 100755 index 0000000..a55d8cc --- /dev/null +++ b/dashboard-project-app/client/app/projectList/projectList.js @@ -0,0 +1,10 @@ +'use strict'; + +angular.module('dashboardProjectApp').config(function ($stateProvider) { + $stateProvider + .state('projectList', { + url: '/projectList', + templateUrl: 'app/projectList/views/projectList.html', + controller: 'projectListCtrl' + }); +}); diff --git a/dashboard-project-app/client/app/projectList/styles/projectList.css b/dashboard-project-app/client/app/projectList/styles/projectList.css new file mode 100755 index 0000000..6f8a01c --- /dev/null +++ b/dashboard-project-app/client/app/projectList/styles/projectList.css @@ -0,0 +1,25 @@ +/* +* Dashboard project classes "dp" +*/ +.dp-top-alignment { + padding-top: 20px; +} +.dp-progress { + margin-bottom: 0px; + background-color: #E6E6E6; +} +.pagination-detail { + font-size: 12px; +} +.search { + width: 50%; +} +.fixed-table-container tbody td { + border-left: none; +} +.fixed-table-container thead th{ + border-left: none; +} +.fixed-table-toolbar .bars, .fixed-table-toolbar .search, .fixed-table-toolbar .columns{ + margin-bottom: 25px; +} diff --git a/dashboard-project-app/client/app/projectList/tests/projectList.controller.spec.js b/dashboard-project-app/client/app/projectList/tests/projectList.controller.spec.js new file mode 100755 index 0000000..b7868bd --- /dev/null +++ b/dashboard-project-app/client/app/projectList/tests/projectList.controller.spec.js @@ -0,0 +1,21 @@ +'use strict'; + +describe('Component: ProjectListComponent', function () { + + // load the controller's module + beforeEach(module('dashboardProjectApp')); + + var ProjectListComponent, scope; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($componentController, $rootScope) { + scope = $rootScope.$new(); + ProjectListComponent = $componentController('ProjectListComponent', { + $scope: scope + }); + })); + + it('should ...', function () { + expect(1).to.equal(1); + }); +}); diff --git a/dashboard-project-app/client/app/projectList/views/projectList.html b/dashboard-project-app/client/app/projectList/views/projectList.html new file mode 100755 index 0000000..814bca9 --- /dev/null +++ b/dashboard-project-app/client/app/projectList/views/projectList.html @@ -0,0 +1,27 @@ +
+
+
+
+
+ +
+
+ + + + + + + + + +
User StoryDate CreatedLast Update% Completed
+
+
+
diff --git a/dashboard-project-app/client/assets/fonts/open-sans/LICENSE.txt b/dashboard-project-app/client/assets/fonts/open-sans/LICENSE.txt new file mode 100755 index 0000000..d645695 --- /dev/null +++ b/dashboard-project-app/client/assets/fonts/open-sans/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Bold.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Bold.ttf new file mode 100755 index 0000000..fd79d43 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Bold.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-BoldItalic.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-BoldItalic.ttf new file mode 100755 index 0000000..9bc8009 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-BoldItalic.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-ExtraBold.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-ExtraBold.ttf new file mode 100755 index 0000000..21f6f84 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-ExtraBold.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf new file mode 100755 index 0000000..31cb688 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Italic.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Italic.ttf new file mode 100755 index 0000000..c90da48 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Italic.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Light.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Light.ttf new file mode 100755 index 0000000..0d38189 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Light.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-LightItalic.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-LightItalic.ttf new file mode 100755 index 0000000..68299c4 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-LightItalic.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Regular.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Regular.ttf new file mode 100755 index 0000000..db43334 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Regular.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Semibold.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Semibold.ttf new file mode 100755 index 0000000..1a7679e Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-Semibold.ttf differ diff --git a/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-SemiboldItalic.ttf b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-SemiboldItalic.ttf new file mode 100755 index 0000000..59b6d16 Binary files /dev/null and b/dashboard-project-app/client/assets/fonts/open-sans/OpenSans-SemiboldItalic.ttf differ diff --git a/dashboard-project-app/client/assets/images/openstack-logo-full.png b/dashboard-project-app/client/assets/images/openstack-logo-full.png new file mode 100755 index 0000000..e1426e5 Binary files /dev/null and b/dashboard-project-app/client/assets/images/openstack-logo-full.png differ diff --git a/dashboard-project-app/client/assets/images/yeoman.png b/dashboard-project-app/client/assets/images/yeoman.png new file mode 100755 index 0000000..7d0a1ac Binary files /dev/null and b/dashboard-project-app/client/assets/images/yeoman.png differ diff --git a/dashboard-project-app/client/components/footer/footer.css b/dashboard-project-app/client/components/footer/footer.css new file mode 100755 index 0000000..cd1753e --- /dev/null +++ b/dashboard-project-app/client/components/footer/footer.css @@ -0,0 +1,6 @@ +footer.footer { + text-align: center; + padding: 30px 0; + margin-top: 70px; + border-top: 1px solid #E5E5E5; +} diff --git a/dashboard-project-app/client/components/footer/footer.directive.js b/dashboard-project-app/client/components/footer/footer.directive.js new file mode 100755 index 0000000..fbaa3fe --- /dev/null +++ b/dashboard-project-app/client/components/footer/footer.directive.js @@ -0,0 +1,12 @@ +'use strict'; + +angular.module('dashboardProjectApp') + .directive('footer', function() { + return { + templateUrl: 'components/footer/footer.html', + restrict: 'E', + link: function(scope, element) { + element.addClass('footer'); + } + }; + }); diff --git a/dashboard-project-app/client/components/footer/footer.html b/dashboard-project-app/client/components/footer/footer.html new file mode 100755 index 0000000..fcbe358 --- /dev/null +++ b/dashboard-project-app/client/components/footer/footer.html @@ -0,0 +1,6 @@ +
+

Angular Fullstack v3.5.0 | + @tyhenkel | + Issues +

+
diff --git a/dashboard-project-app/client/components/modal/modal.css b/dashboard-project-app/client/components/modal/modal.css new file mode 100755 index 0000000..ae04068 --- /dev/null +++ b/dashboard-project-app/client/components/modal/modal.css @@ -0,0 +1,23 @@ +.modal-primary .modal-header, +.modal-info .modal-header, +.modal-success .modal-header, +.modal-warning .modal-header, +.modal-danger .modal-header { + color: #fff; + border-radius: 5px 5px 0 0; +} +.modal-primary .modal-header { + background: #428bca; +} +.modal-info .modal-header { + background: #5bc0de; +} +.modal-success .modal-header { + background: #5cb85c; +} +.modal-warning .modal-header { + background: #f0ad4e; +} +.modal-danger .modal-header { + background: #d9534f; +} diff --git a/dashboard-project-app/client/components/modal/modal.html b/dashboard-project-app/client/components/modal/modal.html new file mode 100755 index 0000000..f04d0db --- /dev/null +++ b/dashboard-project-app/client/components/modal/modal.html @@ -0,0 +1,11 @@ + + + diff --git a/dashboard-project-app/client/components/modal/modal.service.js b/dashboard-project-app/client/components/modal/modal.service.js new file mode 100755 index 0000000..51eb77b --- /dev/null +++ b/dashboard-project-app/client/components/modal/modal.service.js @@ -0,0 +1,73 @@ +'use strict'; + +angular.module('dashboardProjectApp') + .factory('Modal', function($rootScope, $uibModal) { + /** + * Opens a modal + * @param {Object} scope - an object to be merged with modal's scope + * @param {String} modalClass - (optional) class(es) to be applied to the modal + * @return {Object} - the instance $uibModal.open() returns + */ + function openModal(scope = {}, modalClass = 'modal-default') { + var modalScope = $rootScope.$new(); + + angular.extend(modalScope, scope); + + return $uibModal.open({ + templateUrl: 'components/modal/modal.html', + windowClass: modalClass, + scope: modalScope + }); + } + + // Public API here + return { + + /* Confirmation modals */ + confirm: { + + /** + * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)') + * @param {Function} del - callback, ran when delete is confirmed + * @return {Function} - the function to open the modal (ex. myModalFn) + */ + delete(del = angular.noop) { + /** + * Open a delete confirmation modal + * @param {String} name - name or info to show on modal + * @param {All} - any additional args are passed straight to del callback + */ + return function() { + var args = Array.prototype.slice.call(arguments), + name = args.shift(), + deleteModal; + + deleteModal = openModal({ + modal: { + dismissable: true, + title: 'Confirm Delete', + html: '

Are you sure you want to delete ' + name + ' ?

', + buttons: [{ + classes: 'btn-danger', + text: 'Delete', + click: function(e) { + deleteModal.close(e); + } + }, { + classes: 'btn-default', + text: 'Cancel', + click: function(e) { + deleteModal.dismiss(e); + } + }] + } + }, 'modal-danger'); + + deleteModal.result.then(function(event) { + del.apply(event, args); + }); + }; + } + } + }; + }); diff --git a/dashboard-project-app/client/components/navbar/navbar.controller.js b/dashboard-project-app/client/components/navbar/navbar.controller.js new file mode 100755 index 0000000..4c5be84 --- /dev/null +++ b/dashboard-project-app/client/components/navbar/navbar.controller.js @@ -0,0 +1,18 @@ +'use strict'; + +class NavbarController { + //start-non-standard + menu = [{ + 'title': 'Home', + 'state': 'main' + }]; + + isCollapsed = true; + //end-non-standard + + constructor() { + } +} + +angular.module('dashboardProjectApp') + .controller('NavbarController', NavbarController); diff --git a/dashboard-project-app/client/components/navbar/navbar.directive.js b/dashboard-project-app/client/components/navbar/navbar.directive.js new file mode 100755 index 0000000..a989021 --- /dev/null +++ b/dashboard-project-app/client/components/navbar/navbar.directive.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('dashboardProjectApp') + .directive('navbar', () => ({ + templateUrl: 'components/navbar/navbar.html', + restrict: 'E', + controller: 'NavbarController', + controllerAs: 'nav' + })); diff --git a/dashboard-project-app/client/components/navbar/navbar.html b/dashboard-project-app/client/components/navbar/navbar.html new file mode 100755 index 0000000..e3013db --- /dev/null +++ b/dashboard-project-app/client/components/navbar/navbar.html @@ -0,0 +1,20 @@ + diff --git a/dashboard-project-app/client/components/ui-router/ui-router.mock.js b/dashboard-project-app/client/components/ui-router/ui-router.mock.js new file mode 100755 index 0000000..a5a1bf4 --- /dev/null +++ b/dashboard-project-app/client/components/ui-router/ui-router.mock.js @@ -0,0 +1,34 @@ +'use strict'; + +angular.module('stateMock', []); +angular.module('stateMock').service('$state', function($q) { + this.expectedTransitions = []; + + this.transitionTo = function(stateName) { + if (this.expectedTransitions.length > 0) { + var expectedState = this.expectedTransitions.shift(); + if (expectedState !== stateName) { + throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName); + } + } else { + throw Error('No more transitions were expected! Tried to transition to ' + stateName); + } + console.log('Mock transition to: ' + stateName); + var deferred = $q.defer(); + var promise = deferred.promise; + deferred.resolve(); + return promise; + }; + + this.go = this.transitionTo; + + this.expectTransitionTo = function(stateName) { + this.expectedTransitions.push(stateName); + }; + + this.ensureAllTransitionsHappened = function() { + if (this.expectedTransitions.length > 0) { + throw Error('Not all transitions happened!'); + } + }; +}); diff --git a/dashboard-project-app/client/components/util/util.module.js b/dashboard-project-app/client/components/util/util.module.js new file mode 100755 index 0000000..532c0c4 --- /dev/null +++ b/dashboard-project-app/client/components/util/util.module.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('dashboardProjectApp.util', []); diff --git a/dashboard-project-app/client/components/util/util.service.js b/dashboard-project-app/client/components/util/util.service.js new file mode 100755 index 0000000..54ea4e5 --- /dev/null +++ b/dashboard-project-app/client/components/util/util.service.js @@ -0,0 +1,65 @@ +'use strict'; + +(function() { + +/** + * The Util service is for thin, globally reusable, utility functions + */ +function UtilService($window) { + var Util = { + /** + * Return a callback or noop function + * + * @param {Function|*} cb - a 'potential' function + * @return {Function} + */ + safeCb(cb) { + return (angular.isFunction(cb)) ? cb : angular.noop; + }, + + /** + * Parse a given url with the use of an anchor element + * + * @param {String} url - the url to parse + * @return {Object} - the parsed url, anchor element + */ + urlParse(url) { + var a = document.createElement('a'); + a.href = url; + + // Special treatment for IE, see http://stackoverflow.com/a/13405933 for details + if (a.host === '') { + a.href = a.href; + } + + return a; + }, + + /** + * Test whether or not a given url is same origin + * + * @param {String} url - url to test + * @param {String|String[]} [origins] - additional origins to test against + * @return {Boolean} - true if url is same origin + */ + isSameOrigin(url, origins) { + url = Util.urlParse(url); + origins = (origins && [].concat(origins)) || []; + origins = origins.map(Util.urlParse); + origins.push($window.location); + origins = origins.filter(function(o) { + return url.hostname === o.hostname && + url.port === o.port && + url.protocol === o.protocol; + }); + return (origins.length >= 1); + } + }; + + return Util; +} + +angular.module('dashboardProjectApp.util') + .factory('Util', UtilService); + +})(); diff --git a/dashboard-project-app/client/favicon.ico b/dashboard-project-app/client/favicon.ico new file mode 100755 index 0000000..8a163fb Binary files /dev/null and b/dashboard-project-app/client/favicon.ico differ diff --git a/dashboard-project-app/client/index.html b/dashboard-project-app/client/index.html new file mode 100755 index 0000000..7183ec9 --- /dev/null +++ b/dashboard-project-app/client/index.html @@ -0,0 +1,127 @@ + + + + + + + + + + OpenStack Cross-project Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dashboard-project-app/client/robots.txt b/dashboard-project-app/client/robots.txt new file mode 100755 index 0000000..9417495 --- /dev/null +++ b/dashboard-project-app/client/robots.txt @@ -0,0 +1,3 @@ +# robotstxt.org + +User-agent: * diff --git a/dashboard-project-app/e2e/components/navbar/navbar.po.js b/dashboard-project-app/e2e/components/navbar/navbar.po.js new file mode 100755 index 0000000..94b8d14 --- /dev/null +++ b/dashboard-project-app/e2e/components/navbar/navbar.po.js @@ -0,0 +1,14 @@ +/** + * This file uses the Page Object pattern to define the main page for tests + * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ + */ + +'use strict'; + +var NavbarComponent = function() { + this.navbar = element(by.css('.navbar')); + this.navbarHeader = this.navbar.element(by.css('.navbar-header')); + this.navbarNav = this.navbar.element(by.css('#navbar-main .nav.navbar-nav:not(.navbar-right)')); +}; + +module.exports = new NavbarComponent(); diff --git a/dashboard-project-app/e2e/main/main.po.js b/dashboard-project-app/e2e/main/main.po.js new file mode 100755 index 0000000..6718608 --- /dev/null +++ b/dashboard-project-app/e2e/main/main.po.js @@ -0,0 +1,15 @@ +/** + * This file uses the Page Object pattern to define the main page for tests + * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ + */ + +'use strict'; + +var MainPage = function() { + this.heroEl = element(by.css('.hero-unit')); + this.h1El = this.heroEl.element(by.css('h1')); + this.imgEl = this.heroEl.element(by.css('img')); +}; + +module.exports = new MainPage(); + diff --git a/dashboard-project-app/e2e/main/main.spec.js b/dashboard-project-app/e2e/main/main.spec.js new file mode 100755 index 0000000..cf6e337 --- /dev/null +++ b/dashboard-project-app/e2e/main/main.spec.js @@ -0,0 +1,19 @@ +'use strict'; + +var config = browser.params; + +describe('Main View', function() { + var page; + + beforeEach(function() { + let promise = browser.get(config.baseUrl + '/'); + page = require('./main.po'); + return promise; + }); + + it('should include jumbotron with correct data', function() { + expect(page.h1El.getText()).to.eventually.equal('\'Allo, \'Allo!'); + expect(page.imgEl.getAttribute('src')).to.eventually.match(/yeoman.png$/); + expect(page.imgEl.getAttribute('alt')).to.eventually.equal('I\'m Yeoman'); + }); +}); diff --git a/dashboard-project-app/install.sh b/dashboard-project-app/install.sh new file mode 100755 index 0000000..c0a3273 --- /dev/null +++ b/dashboard-project-app/install.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +cd ../dashboard-project-api; +npm install; +cd ../dashboard-project-app +npm install; +bower install; diff --git a/dashboard-project-app/karma.conf.js b/dashboard-project-app/karma.conf.js new file mode 100755 index 0000000..d8b5b0f --- /dev/null +++ b/dashboard-project-app/karma.conf.js @@ -0,0 +1,94 @@ +// Karma configuration +// http://karma-runner.github.io/0.10/config/configuration-file.html + +module.exports = function(config) { + config.set({ + // base path, that will be used to resolve files and exclude + basePath: '', + + // testing framework to use (jasmine/mocha/qunit/...) + frameworks: ['mocha', 'chai', 'sinon-chai', 'chai-as-promised', 'chai-things'], + + client: { + mocha: { + timeout: 5000 // set default mocha spec timeout + } + }, + + // list of files / patterns to load in the browser + files: [ + // bower:js + 'client/bower_components/jquery/dist/jquery.js', + 'client/bower_components/angular/angular.js', + 'client/bower_components/angular-resource/angular-resource.js', + 'client/bower_components/angular-cookies/angular-cookies.js', + 'client/bower_components/angular-sanitize/angular-sanitize.js', + 'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', + 'client/bower_components/lodash/dist/lodash.compat.js', + 'client/bower_components/angular-ui-router/release/angular-ui-router.js', + 'client/bower_components/angular-mocks/angular-mocks.js', + // endbower + 'client/app/app.js', + 'client/{app,components}/**/*.module.js', + 'client/{app,components}/**/*.js', + 'client/{app,components}/**/*.html' + ], + + preprocessors: { + '**/*.html': 'ng-html2js', + 'client/{app,components}/**/*.js': 'babel' + }, + + ngHtml2JsPreprocessor: { + stripPrefix: 'client/' + }, + + babelPreprocessor: { + options: { + sourceMap: 'inline' + }, + filename: function (file) { + return file.originalPath.replace(/\.js$/, '.es5.js'); + }, + sourceFileName: function (file) { + return file.originalPath; + } + }, + + // list of files / patterns to exclude + exclude: [], + + // web server port + port: 8080, + + // level of logging + // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG + logLevel: config.LOG_INFO, + + // reporter types: + // - dots + // - progress (default) + // - spec (karma-spec-reporter) + // - junit + // - growl + // - coverage + reporters: ['spec'], + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: ['PhantomJS'], + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false + }); +}; diff --git a/dashboard-project-app/mocha.conf.js b/dashboard-project-app/mocha.conf.js new file mode 100755 index 0000000..76f5662 --- /dev/null +++ b/dashboard-project-app/mocha.conf.js @@ -0,0 +1,19 @@ +'use strict'; + +// Register the Babel require hook +require('babel-core/register'); + +var chai = require('chai'); + +// Load Chai assertions +global.expect = chai.expect; +global.assert = chai.assert; +chai.should(); + +// Load Sinon +global.sinon = require('sinon'); + +// Initialize Chai plugins +chai.use(require('sinon-chai')); +chai.use(require('chai-as-promised')); +chai.use(require('chai-things')) diff --git a/dashboard-project-app/package.json b/dashboard-project-app/package.json new file mode 100644 index 0000000..cfde499 --- /dev/null +++ b/dashboard-project-app/package.json @@ -0,0 +1,105 @@ +{ + "name": "dashboard-project", + "version": "0.0.0", + "main": "server/app.js", + "dependencies": { + "babel-polyfill": "^6.7.2", + "babel-runtime": "^6.6.1", + "bluebird": "^3.3.3", + "body-parser": "^1.13.3", + "composable-middleware": "^0.3.0", + "compression": "^1.5.2", + "connect-mongo": "^0.8.1", + "cookie-parser": "^1.3.5", + "ejs": "^2.3.3", + "errorhandler": "^1.4.2", + "express": "^4.13.3", + "express-session": "^1.11.3", + "lodash": "^4.6.1", + "lusca": "^1.3.0", + "method-override": "^2.3.5", + "mongoose": "^4.1.2", + "morgan": "~1.7.0", + "serve-favicon": "^2.3.0" + }, + "devDependencies": { + "autoprefixer": "^6.0.0", + "babel-core": "^6.6.5", + "babel-register": "^6.6.5", + "babel-plugin-transform-class-properties": "^6.6.0", + "babel-plugin-transform-runtime": "^6.6.0", + "babel-preset-es2015": "^6.6.0", + "grunt": "~0.4.5", + "grunt-wiredep": "^2.0.0", + "grunt-concurrent": "^2.0.1", + "grunt-contrib-clean": "^1.0.0", + "grunt-contrib-concat": "^1.0.0", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-cssmin": "^1.0.0", + "grunt-contrib-imagemin": "^1.0.0", + "grunt-contrib-jshint": "^1.0.0", + "grunt-contrib-uglify": "^1.0.0", + "grunt-contrib-watch": "~0.6.1", + "grunt-babel": "~6.0.0", + "grunt-google-cdn": "~0.4.0", + "grunt-jscs": "^2.1.0", + "grunt-newer": "^1.1.1", + "grunt-ng-annotate": "^2.0.1", + "grunt-ng-constant": "^2.0.1", + "grunt-filerev": "^2.3.1", + "grunt-usemin": "^3.0.0", + "grunt-env": "~0.4.1", + "grunt-node-inspector": "^0.4.1", + "grunt-nodemon": "^0.4.0", + "grunt-angular-templates": "^1.0.3", + "grunt-dom-munger": "^3.4.0", + "grunt-protractor-runner": "^2.0.0", + "grunt-injector": "^0.6.0", + "grunt-karma": "~0.12.0", + "grunt-build-control": "^0.6.0", + "jit-grunt": "~0.10.0", + "grunt-express-server": "^0.5.1", + "grunt-postcss": "~0.8.0", + "grunt-open": "~0.2.3", + "time-grunt": "^1.2.1", + "grunt-mocha-test": "~0.12.7", + "grunt-mocha-istanbul": "^3.0.1", + "open": "~0.0.4", + "jshint-stylish": "~2.1.0", + "connect-livereload": "^0.5.3", + "istanbul": "~0.4.1", + "chai": "^3.2.0", + "sinon": "^1.16.1", + "chai-as-promised": "^5.1.0", + "chai-things": "^0.2.0", + "karma": "~0.13.3", + "karma-ng-scenario": "~0.1.0", + "karma-firefox-launcher": "~0.1.6", + "karma-script-launcher": "~0.1.0", + "karma-chrome-launcher": "~0.2.0", + "karma-requirejs": "~0.2.2", + "karma-jade-preprocessor": "0.0.11", + "karma-phantomjs-launcher": "~1.0.0", + "karma-ng-html2js-preprocessor": "~0.2.0", + "karma-spec-reporter": "~0.0.20", + "sinon-chai": "^2.8.0", + "mocha": "^2.2.5", + "karma-mocha": "^0.2.0", + "karma-chai-plugins": "~0.7.0", + "karma-babel-preprocessor": "^6.0.1", + "requirejs": "~2.1.11", + "phantomjs-prebuilt": "^2.1.4", + "proxyquire": "^1.0.1", + "supertest": "^1.1.0" + }, + "engines": { + "node": "^4.4.0", + "npm": "^2.14.20" + }, + "scripts": { + "start": "node server", + "test": "grunt test", + "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update" + }, + "private": true +} diff --git a/dashboard-project-app/protractor.conf.js b/dashboard-project-app/protractor.conf.js new file mode 100755 index 0000000..a9669d9 --- /dev/null +++ b/dashboard-project-app/protractor.conf.js @@ -0,0 +1,81 @@ +// Protractor configuration +// https://github.com/angular/protractor/blob/master/referenceConf.js + +'use strict'; + +var config = { + // The timeout for each script run on the browser. This should be longer + // than the maximum time your application needs to stabilize between tasks. + allScriptsTimeout: 110000, + + // A base URL for your application under test. Calls to protractor.get() + // with relative paths will be prepended with this. + baseUrl: 'http://localhost:' + (process.env.PORT || '9000'), + + // Credientials for Saucelabs + sauceUser: process.env.SAUCE_USERNAME, + + sauceKey: process.env.SAUCE_ACCESS_KEY, + + // list of files / patterns to load in the browser + specs: [ + 'e2e/**/*.spec.js' + ], + + // Patterns to exclude. + exclude: [], + + // ----- Capabilities to be passed to the webdriver instance ---- + // + // For a full list of available capabilities, see + // https://code.google.com/p/selenium/wiki/DesiredCapabilities + // and + // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js + capabilities: { + 'browserName': 'chrome', + 'name': 'Fullstack E2E', + 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, + 'build': process.env.TRAVIS_BUILD_NUMBER + }, + + // ----- The test framework ----- + // + // Jasmine and Cucumber are fully supported as a test and assertion framework. + // Mocha has limited beta support. You will need to include your own + // assertion framework if working with mocha. + framework: 'mocha', + + // ----- Options to be passed to mocha ----- + mochaOpts: { + reporter: 'spec', + timeout: 30000, + defaultTimeoutInterval: 30000 + }, + + // Prepare environment for tests + params: { + serverConfig: require('./server/config/environment') + }, + + onPrepare: function() { + require('babel-register'); + // Load Mocha and Chai + plugins + require('./mocha.conf'); + + // Expose should assertions (see https://github.com/angular/protractor/issues/633) + Object.defineProperty( + protractor.promise.Promise.prototype, + 'should', + Object.getOwnPropertyDescriptor(Object.prototype, 'should') + ); + + var serverConfig = config.params.serverConfig; + + // Setup mongo for tests + var mongoose = require('mongoose'); + mongoose.connect(serverConfig.mongo.uri, serverConfig.mongo.options); // Connect to database + } +}; + +config.params.baseUrl = config.baseUrl; +exports.config = config; diff --git a/dashboard-project-app/server/.jshintrc b/dashboard-project-app/server/.jshintrc new file mode 100755 index 0000000..69f3b00 --- /dev/null +++ b/dashboard-project-app/server/.jshintrc @@ -0,0 +1,15 @@ +{ + "expr": true, + "node": true, + "esnext": true, + "bitwise": true, + "eqeqeq": true, + "immed": true, + "latedef": "nofunc", + "newcap": true, + "noarg": true, + "undef": true, + "smarttabs": true, + "asi": true, + "debug": true +} diff --git a/dashboard-project-app/server/.jshintrc-spec b/dashboard-project-app/server/.jshintrc-spec new file mode 100755 index 0000000..b9390c3 --- /dev/null +++ b/dashboard-project-app/server/.jshintrc-spec @@ -0,0 +1,14 @@ +{ + "extends": ".jshintrc", + "globals": { + "describe": true, + "it": true, + "before": true, + "beforeEach": true, + "after": true, + "afterEach": true, + "expect": true, + "assert": true, + "sinon": true + } +} diff --git a/dashboard-project-app/server/api/thing/index.js b/dashboard-project-app/server/api/thing/index.js new file mode 100755 index 0000000..f82400a --- /dev/null +++ b/dashboard-project-app/server/api/thing/index.js @@ -0,0 +1,15 @@ +'use strict'; + +var express = require('express'); +var controller = require('./thing.controller'); + +var router = express.Router(); + +router.get('/', controller.index); +router.get('/:id', controller.show); +router.post('/', controller.create); +router.put('/:id', controller.update); +router.patch('/:id', controller.update); +router.delete('/:id', controller.destroy); + +module.exports = router; diff --git a/dashboard-project-app/server/api/thing/index.spec.js b/dashboard-project-app/server/api/thing/index.spec.js new file mode 100755 index 0000000..890f3cf --- /dev/null +++ b/dashboard-project-app/server/api/thing/index.spec.js @@ -0,0 +1,97 @@ +'use strict'; + +var proxyquire = require('proxyquire').noPreserveCache(); + +var thingCtrlStub = { + index: 'thingCtrl.index', + show: 'thingCtrl.show', + create: 'thingCtrl.create', + update: 'thingCtrl.update', + destroy: 'thingCtrl.destroy' +}; + +var routerStub = { + get: sinon.spy(), + put: sinon.spy(), + patch: sinon.spy(), + post: sinon.spy(), + delete: sinon.spy() +}; + +// require the index with our stubbed out modules +var thingIndex = proxyquire('./index.js', { + 'express': { + Router: function() { + return routerStub; + } + }, + './thing.controller': thingCtrlStub +}); + +describe('Thing API Router:', function() { + + it('should return an express router instance', function() { + expect(thingIndex).to.equal(routerStub); + }); + + describe('GET /api/things', function() { + + it('should route to thing.controller.index', function() { + expect(routerStub.get + .withArgs('/', 'thingCtrl.index') + ).to.have.been.calledOnce; + }); + + }); + + describe('GET /api/things/:id', function() { + + it('should route to thing.controller.show', function() { + expect(routerStub.get + .withArgs('/:id', 'thingCtrl.show') + ).to.have.been.calledOnce; + }); + + }); + + describe('POST /api/things', function() { + + it('should route to thing.controller.create', function() { + expect(routerStub.post + .withArgs('/', 'thingCtrl.create') + ).to.have.been.calledOnce; + }); + + }); + + describe('PUT /api/things/:id', function() { + + it('should route to thing.controller.update', function() { + expect(routerStub.put + .withArgs('/:id', 'thingCtrl.update') + ).to.have.been.calledOnce; + }); + + }); + + describe('PATCH /api/things/:id', function() { + + it('should route to thing.controller.update', function() { + expect(routerStub.patch + .withArgs('/:id', 'thingCtrl.update') + ).to.have.been.calledOnce; + }); + + }); + + describe('DELETE /api/things/:id', function() { + + it('should route to thing.controller.destroy', function() { + expect(routerStub.delete + .withArgs('/:id', 'thingCtrl.destroy') + ).to.have.been.calledOnce; + }); + + }); + +}); diff --git a/dashboard-project-app/server/api/thing/thing.controller.js b/dashboard-project-app/server/api/thing/thing.controller.js new file mode 100755 index 0000000..8f15856 --- /dev/null +++ b/dashboard-project-app/server/api/thing/thing.controller.js @@ -0,0 +1,102 @@ +/** + * Using Rails-like standard naming convention for endpoints. + * GET /api/things -> index + * POST /api/things -> create + * GET /api/things/:id -> show + * PUT /api/things/:id -> update + * DELETE /api/things/:id -> destroy + */ + +'use strict'; + +import _ from 'lodash'; +import Thing from './thing.model'; + +function respondWithResult(res, statusCode) { + statusCode = statusCode || 200; + return function(entity) { + if (entity) { + res.status(statusCode).json(entity); + } + }; +} + +function saveUpdates(updates) { + return function(entity) { + var updated = _.merge(entity, updates); + return updated.save() + .then(updated => { + return updated; + }); + }; +} + +function removeEntity(res) { + return function(entity) { + if (entity) { + return entity.remove() + .then(() => { + res.status(204).end(); + }); + } + }; +} + +function handleEntityNotFound(res) { + return function(entity) { + if (!entity) { + res.status(404).end(); + return null; + } + return entity; + }; +} + +function handleError(res, statusCode) { + statusCode = statusCode || 500; + return function(err) { + res.status(statusCode).send(err); + }; +} + +// Gets a list of Things +export function index(req, res) { + return Thing.find().exec() + .then(respondWithResult(res)) + .catch(handleError(res)); +} + +// Gets a single Thing from the DB +export function show(req, res) { + return Thing.findById(req.params.id).exec() + .then(handleEntityNotFound(res)) + .then(respondWithResult(res)) + .catch(handleError(res)); +} + +// Creates a new Thing in the DB +export function create(req, res) { + return Thing.create(req.body) + .then(respondWithResult(res, 201)) + .catch(handleError(res)); +} + +// Updates an existing Thing in the DB +export function update(req, res) { + if (req.body._id) { + delete req.body._id; + } + return Thing.findById(req.params.id).exec() + .then(handleEntityNotFound(res)) + .then(saveUpdates(req.body)) + .then(respondWithResult(res)) + .catch(handleError(res)); +} + +// Deletes a Thing from the DB +export function destroy(req, res) { + return Thing.findById(req.params.id).exec() + .then(handleEntityNotFound(res)) + .then(removeEntity(res)) + .catch(handleError(res)); +} diff --git a/dashboard-project-app/server/api/thing/thing.events.js b/dashboard-project-app/server/api/thing/thing.events.js new file mode 100755 index 0000000..4e2e2d6 --- /dev/null +++ b/dashboard-project-app/server/api/thing/thing.events.js @@ -0,0 +1,33 @@ +/** + * Thing model events + */ + +'use strict'; + +import {EventEmitter} from 'events'; +import Thing from './thing.model'; +var ThingEvents = new EventEmitter(); + +// Set max event listeners (0 == unlimited) +ThingEvents.setMaxListeners(0); + +// Model events +var events = { + 'save': 'save', + 'remove': 'remove' +}; + +// Register the event emitter to the model events +for (var e in events) { + var event = events[e]; + Thing.schema.post(e, emitEvent(event)); +} + +function emitEvent(event) { + return function(doc) { + ThingEvents.emit(event + ':' + doc._id, doc); + ThingEvents.emit(event, doc); + } +} + +export default ThingEvents; diff --git a/dashboard-project-app/server/api/thing/thing.integration.js b/dashboard-project-app/server/api/thing/thing.integration.js new file mode 100755 index 0000000..9021d07 --- /dev/null +++ b/dashboard-project-app/server/api/thing/thing.integration.js @@ -0,0 +1,147 @@ +'use strict'; + +var app = require('../..'); +import request from 'supertest'; + +var newThing; + +describe('Thing API:', function() { + + describe('GET /api/things', function() { + var things; + + beforeEach(function(done) { + request(app) + .get('/api/things') + .expect(200) + .expect('Content-Type', /json/) + .end((err, res) => { + if (err) { + return done(err); + } + things = res.body; + done(); + }); + }); + + it('should respond with JSON array', function() { + expect(things).to.be.instanceOf(Array); + }); + + }); + + describe('POST /api/things', function() { + beforeEach(function(done) { + request(app) + .post('/api/things') + .send({ + name: 'New Thing', + info: 'This is the brand new thing!!!' + }) + .expect(201) + .expect('Content-Type', /json/) + .end((err, res) => { + if (err) { + return done(err); + } + newThing = res.body; + done(); + }); + }); + + it('should respond with the newly created thing', function() { + expect(newThing.name).to.equal('New Thing'); + expect(newThing.info).to.equal('This is the brand new thing!!!'); + }); + + }); + + describe('GET /api/things/:id', function() { + var thing; + + beforeEach(function(done) { + request(app) + .get('/api/things/' + newThing._id) + .expect(200) + .expect('Content-Type', /json/) + .end((err, res) => { + if (err) { + return done(err); + } + thing = res.body; + done(); + }); + }); + + afterEach(function() { + thing = {}; + }); + + it('should respond with the requested thing', function() { + expect(thing.name).to.equal('New Thing'); + expect(thing.info).to.equal('This is the brand new thing!!!'); + }); + + }); + + describe('PUT /api/things/:id', function() { + var updatedThing; + + beforeEach(function(done) { + request(app) + .put('/api/things/' + newThing._id) + .send({ + name: 'Updated Thing', + info: 'This is the updated thing!!!' + }) + .expect(200) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + return done(err); + } + updatedThing = res.body; + done(); + }); + }); + + afterEach(function() { + updatedThing = {}; + }); + + it('should respond with the updated thing', function() { + expect(updatedThing.name).to.equal('Updated Thing'); + expect(updatedThing.info).to.equal('This is the updated thing!!!'); + }); + + }); + + describe('DELETE /api/things/:id', function() { + + it('should respond with 204 on successful removal', function(done) { + request(app) + .delete('/api/things/' + newThing._id) + .expect(204) + .end((err, res) => { + if (err) { + return done(err); + } + done(); + }); + }); + + it('should respond with 404 when thing does not exist', function(done) { + request(app) + .delete('/api/things/' + newThing._id) + .expect(404) + .end((err, res) => { + if (err) { + return done(err); + } + done(); + }); + }); + + }); + +}); diff --git a/dashboard-project-app/server/api/thing/thing.model.js b/dashboard-project-app/server/api/thing/thing.model.js new file mode 100755 index 0000000..9514577 --- /dev/null +++ b/dashboard-project-app/server/api/thing/thing.model.js @@ -0,0 +1,11 @@ +'use strict'; + +import mongoose from 'mongoose'; + +var ThingSchema = new mongoose.Schema({ + name: String, + info: String, + active: Boolean +}); + +export default mongoose.model('Thing', ThingSchema); diff --git a/dashboard-project-app/server/app.js b/dashboard-project-app/server/app.js new file mode 100755 index 0000000..693a730 --- /dev/null +++ b/dashboard-project-app/server/app.js @@ -0,0 +1,39 @@ +/** + * Main application file + */ + +'use strict'; + +import express from 'express'; +import mongoose from 'mongoose'; +mongoose.Promise = require('bluebird'); +import config from './config/environment'; +import http from 'http'; + +// Connect to MongoDB +mongoose.connect(config.mongo.uri, config.mongo.options); +mongoose.connection.on('error', function(err) { + console.error('MongoDB connection error: ' + err); + process.exit(-1); +}); + +// Populate databases with sample data +if (config.seedDB) { require('./config/seed'); } + +// Setup server +var app = express(); +var server = http.createServer(app); +require('./config/express').default(app); +require('./routes').default(app); + +// Start server +function startServer() { + app.angularFullstack = server.listen(config.port, config.ip, function() { + console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); + }); +} + +setImmediate(startServer); + +// Expose app +exports = module.exports = app; diff --git a/dashboard-project-app/server/components/errors/index.js b/dashboard-project-app/server/components/errors/index.js new file mode 100755 index 0000000..5986981 --- /dev/null +++ b/dashboard-project-app/server/components/errors/index.js @@ -0,0 +1,22 @@ +/** + * Error responses + */ + +'use strict'; + +module.exports[404] = function pageNotFound(req, res) { + var viewFilePath = '404'; + var statusCode = 404; + var result = { + status: statusCode + }; + + res.status(result.status); + res.render(viewFilePath, {}, function(err, html) { + if (err) { + return res.status(result.status).json(result); + } + + res.send(html); + }); +}; diff --git a/dashboard-project-app/server/config/environment/development.js b/dashboard-project-app/server/config/environment/development.js new file mode 100755 index 0000000..f6ee702 --- /dev/null +++ b/dashboard-project-app/server/config/environment/development.js @@ -0,0 +1,15 @@ +'use strict'; + +// Development specific configuration +// ================================== +module.exports = { + + // MongoDB connection options + mongo: { + uri: 'mongodb://localhost/dashboardproject-dev' + }, + + // Seed database on startup + seedDB: true + +}; diff --git a/dashboard-project-app/server/config/environment/index.js b/dashboard-project-app/server/config/environment/index.js new file mode 100755 index 0000000..f17e8ee --- /dev/null +++ b/dashboard-project-app/server/config/environment/index.js @@ -0,0 +1,50 @@ +'use strict'; + +var path = require('path'); +var _ = require('lodash'); + +function requiredProcessEnv(name) { + if (!process.env[name]) { + throw new Error('You must set the ' + name + ' environment variable'); + } + return process.env[name]; +} + +// All configurations will extend these options +// ============================================ +var all = { + env: process.env.NODE_ENV, + + // Root path of server + root: path.normalize(__dirname + '/../../..'), + + // Server port + port: process.env.PORT || 9000, + + // Server IP + ip: process.env.IP || '0.0.0.0', + + // Should we populate the DB with sample data? + seedDB: false, + + // Secret for session, you will want to change this and make it an environment variable + secrets: { + session: 'dashboard-project-secret' + }, + + // MongoDB connection options + mongo: { + options: { + db: { + safe: true + } + } + } +}; + +// Export the config object based on the NODE_ENV +// ============================================== +module.exports = _.merge( + all, + require('./shared'), + require('./' + process.env.NODE_ENV + '.js') || {}); diff --git a/dashboard-project-app/server/config/environment/production.js b/dashboard-project-app/server/config/environment/production.js new file mode 100755 index 0000000..a33eb80 --- /dev/null +++ b/dashboard-project-app/server/config/environment/production.js @@ -0,0 +1,24 @@ +'use strict'; + +// Production specific configuration +// ================================= +module.exports = { + // Server IP + ip: process.env.OPENSHIFT_NODEJS_IP || + process.env.IP || + undefined, + + // Server port + port: process.env.OPENSHIFT_NODEJS_PORT || + process.env.PORT || + 8080, + + // MongoDB connection options + mongo: { + uri: process.env.MONGOLAB_URI || + process.env.MONGOHQ_URL || + process.env.OPENSHIFT_MONGODB_DB_URL + + process.env.OPENSHIFT_APP_NAME || + 'mongodb://localhost/dashboardproject' + } +}; diff --git a/dashboard-project-app/server/config/environment/shared.js b/dashboard-project-app/server/config/environment/shared.js new file mode 100755 index 0000000..64a5eab --- /dev/null +++ b/dashboard-project-app/server/config/environment/shared.js @@ -0,0 +1,6 @@ +'use strict'; + +exports = module.exports = { + // List of user roles + userRoles: ['guest', 'user', 'admin'] +}; diff --git a/dashboard-project-app/server/config/environment/test.js b/dashboard-project-app/server/config/environment/test.js new file mode 100755 index 0000000..6e4ca7a --- /dev/null +++ b/dashboard-project-app/server/config/environment/test.js @@ -0,0 +1,20 @@ +'use strict'; + +// Test specific configuration +// =========================== +module.exports = { + // MongoDB connection options + mongo: { + uri: 'mongodb://localhost/dashboardproject-test' + }, + sequelize: { + uri: 'sqlite://', + options: { + logging: false, + storage: 'test.sqlite', + define: { + timestamps: false + } + } + } +}; diff --git a/dashboard-project-app/server/config/express.js b/dashboard-project-app/server/config/express.js new file mode 100755 index 0000000..836d5d0 --- /dev/null +++ b/dashboard-project-app/server/config/express.js @@ -0,0 +1,85 @@ +/** + * Express configuration + */ + +'use strict'; + +import express from 'express'; +import favicon from 'serve-favicon'; +import morgan from 'morgan'; +import compression from 'compression'; +import bodyParser from 'body-parser'; +import methodOverride from 'method-override'; +import cookieParser from 'cookie-parser'; +import errorHandler from 'errorhandler'; +import path from 'path'; +import lusca from 'lusca'; +import config from './environment'; +import session from 'express-session'; +import connectMongo from 'connect-mongo'; +import mongoose from 'mongoose'; +var MongoStore = connectMongo(session); + +export default function(app) { + var env = app.get('env'); + + app.set('views', config.root + '/server/views'); + app.engine('html', require('ejs').renderFile); + app.set('view engine', 'html'); + app.use(compression()); + app.use(bodyParser.urlencoded({ extended: false })); + app.use(bodyParser.json()); + app.use(methodOverride()); + app.use(cookieParser()); + + // Persist sessions with MongoStore / sequelizeStore + // We need to enable sessions for passport-twitter because it's an + // oauth 1.0 strategy, and Lusca depends on sessions + app.use(session({ + secret: config.secrets.session, + saveUninitialized: true, + resave: false, + store: new MongoStore({ + mongooseConnection: mongoose.connection, + db: 'dashboard-project' + }) + })); + + /** + * Lusca - express server security + * https://github.com/krakenjs/lusca + */ + if ('test' !== env) { + app.use(lusca({ + csrf: { + angular: true + }, + xframe: 'SAMEORIGIN', + hsts: { + maxAge: 31536000, //1 year, in seconds + includeSubDomains: true, + preload: true + }, + xssProtection: true + })); + } + + app.set('appPath', path.join(config.root, 'client')); + + if ('production' === env) { + app.use(favicon(path.join(config.root, 'client', 'favicon.ico'))); + app.use(express.static(app.get('appPath'))); + app.use(morgan('dev')); + } + + if ('development' === env) { + app.use(require('connect-livereload')()); + } + + if ('development' === env || 'test' === env) { + app.use(express.static(path.join(config.root, '.tmp'))); + app.use(express.static(app.get('appPath'))); + app.use(morgan('dev')); + app.use(errorHandler()); // Error handler - has to be last + } +} diff --git a/dashboard-project-app/server/config/local.env.sample.js b/dashboard-project-app/server/config/local.env.sample.js new file mode 100755 index 0000000..33c3b7c --- /dev/null +++ b/dashboard-project-app/server/config/local.env.sample.js @@ -0,0 +1,14 @@ +'use strict'; + +// Use local.env.js for environment variables that grunt will set when the server starts locally. +// Use for your api keys, secrets, etc. This file should not be tracked by git. +// +// You will need to set these on the server you deploy to. + +module.exports = { + DOMAIN: 'http://localhost:9000', + SESSION_SECRET: 'dashboardproject-secret', + + // Control debug level for modules using visionmedia/debug + DEBUG: '' +}; diff --git a/dashboard-project-app/server/config/seed.js b/dashboard-project-app/server/config/seed.js new file mode 100755 index 0000000..0a0567d --- /dev/null +++ b/dashboard-project-app/server/config/seed.js @@ -0,0 +1,40 @@ +/** + * Populate DB with sample data on server start + * to disable, edit config/environment/index.js, and set `seedDB: false` + */ + +'use strict'; +import Thing from '../api/thing/thing.model'; + +Thing.find({}).remove() + .then(() => { + Thing.create({ + name: 'Development Tools', + info: 'Integration with popular tools such as Bower, Grunt, Babel, Karma, ' + + 'Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, ' + + 'Stylus, Sass, and Less.' + }, { + name: 'Server and Client integration', + info: 'Built with a powerful and fun stack: MongoDB, Express, ' + + 'AngularJS, and Node.' + }, { + name: 'Smart Build System', + info: 'Build system ignores `spec` files, allowing you to keep ' + + 'tests alongside code. Automatic injection of scripts and ' + + 'styles into your index.html' + }, { + name: 'Modular Structure', + info: 'Best practice client and server structures allow for more ' + + 'code reusability and maximum scalability' + }, { + name: 'Optimized Build', + info: 'Build process packs up your templates as a single JavaScript ' + + 'payload, minifies your scripts/css/images, and rewrites asset ' + + 'names for caching.' + }, { + name: 'Deployment Ready', + info: 'Easily deploy your app to Heroku or Openshift with the heroku ' + + 'and openshift subgenerators' + }); + }); + diff --git a/dashboard-project-app/server/index.js b/dashboard-project-app/server/index.js new file mode 100755 index 0000000..c3ee524 --- /dev/null +++ b/dashboard-project-app/server/index.js @@ -0,0 +1,12 @@ +'use strict'; + +// Set default node environment to development +var env = process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +if (env === 'development' || env === 'test') { + // Register the Babel require hook + require('babel-register'); +} + +// Export the application +exports = module.exports = require('./app'); diff --git a/dashboard-project-app/server/routes.js b/dashboard-project-app/server/routes.js new file mode 100755 index 0000000..f67f143 --- /dev/null +++ b/dashboard-project-app/server/routes.js @@ -0,0 +1,22 @@ +/** + * Main application routes + */ + +'use strict'; + +import errors from './components/errors'; +import path from 'path'; + +export default function(app) { + // Insert routes below + app.use('/api/things', require('./api/thing')); + // All undefined asset or api routes should return a 404 + app.route('/:url(api|auth|components|app|bower_components|assets)/*') + .get(errors[404]); + + // All other routes should redirect to the index.html + app.route('/*') + .get((req, res) => { + res.sendFile(path.resolve(app.get('appPath') + '/index.html')); + }); +} diff --git a/dashboard-project-app/server/views/404.html b/dashboard-project-app/server/views/404.html new file mode 100755 index 0000000..ec98e3c --- /dev/null +++ b/dashboard-project-app/server/views/404.html @@ -0,0 +1,157 @@ + + + + + Page Not Found :( + + + +
+

Not found :(

+

Sorry, but the page you were trying to view does not exist.

+

It looks like this was the result of either:

+ + + +
+ + diff --git a/tracker/capacity-management-tracker.json b/tracker/capacity-management-tracker.json new file mode 100644 index 0000000..3908fd1 --- /dev/null +++ b/tracker/capacity-management-tracker.json @@ -0,0 +1,33 @@ +{ + "id": "0002", + "date": "05-04-2016", + "submitted_by": { + "name": "Shamail Tahir", + "email": "itzshamail@gmail.com" + }, + "description": "Capacity Management", + "source": "capacity_management", + "status": "in-progress", + "tasks": [ + "Service Catalog Standardization" + ], + "tasks_status": { + "Service Catalog Standardization": { + "cross-project spec": "service-catalog", + "gerrit-topic": "", + "xp_status": "in-progress", + "projects": [ + "keystone" + ], + "projects_status": { + "keystone": { + "blueprints": { + "service-catalog-standards": "in-progress" + }, + "spec": "http://specs.openstack.org/openstack/openstack-specs/specs/service-catalog.html", + "spec_status": "completed" + } + } + } + } +} \ No newline at end of file diff --git a/tracker/ha-vm-tracker.json b/tracker/ha-vm-tracker.json new file mode 100644 index 0000000..87bc7ce --- /dev/null +++ b/tracker/ha-vm-tracker.json @@ -0,0 +1,34 @@ +{ + "id": "0003", + "date": "08-04-2016", + "submitted_by": { + "name": "Yih Leong Sun", + "email": "yihleong@gmail.com>" + }, + "description": "High Availability for Virtual Machines", + "source": "ha_vm", + "status": "in-progress", + "tasks": [ + "clouds.yaml support in clients" + ], + "tasks_status": { + "clouds.yaml support in clients": { + "cross-project spec": "clouds-yaml-support", + "gerrit-topic": "", + "xp_status": "in-progress", + "projects": [ + "nova" + ], + "projects_status": { + "nova": { + "blueprints": { + "bp/os-vif-library": "completed", + "bp/privsep": "completed" + }, + "spec": "http://specs.openstack.org/openstack/openstack-user-stories/user-stories/proposed/capacity_management.html", + "spec_status": "completed" + } + } + } + } +} \ No newline at end of file diff --git a/tracker/rollingupgrades-tracker.json b/tracker/rollingupgrades-tracker.json new file mode 100644 index 0000000..9bb8cd8 --- /dev/null +++ b/tracker/rollingupgrades-tracker.json @@ -0,0 +1,75 @@ +{ + "id": "0001", + "date": "23-03-2016", + "submitted_by": { + "name": "Kenny Johnston", + "email": "kencjohnston@gmail.com" + }, + "description": "Rolling Upgrades", + "source": "rolling-upgrades", + "status": "in-progress", + "tasks": [ + "Versioned Objects", + "Online Schema Change" + ], + "tasks_status": { + "Versioned Objects": { + "cross-project spec": "chronicles-of-a-dlm", + "gerrit-topic": "tooz", + "xp_status": "in-progress", + "projects": [ + "nova", + "cinder" + ], + "projects_status": { + "nova": { + "blueprints": { + "bp/service-version-number": "completed" + }, + "spec": "http://specs.openstack.org/openstack/nova-specs/specs/liberty/implemented/service-version-number.html", + "spec_status": "completed" + }, + "cinder": { + "blueprints": { + "bp/cinder-object-fields": "completed" + }, + "spec": "https://github.com/openstack/cinder-specs/blob/master/specs/kilo/cinder-objects.rst", + "spec_status": "completed" + } + } + }, + "Online Schema Change": { + "cross-project spec": "no-downward-sql-migration", + "gerrit-topic": "online-schema-changes", + "xp_status": "in-progress", + "projects": [ + "nova", + "cinder", + "keystone" + ], + "projects_status": { + "nova": { + "blueprints": { + "online-schema-changes": "completed" + }, + "spec": "http://specs.openstack.org/openstack/nova-specs/specs/liberty/implemented/online-schema-changes.html", + "spec_status": "completed" + }, + "cinder": { + "blueprints": { + "bp/online-schema-upgrades": "completed" + }, + "spec": "https://github.com/openstack/cinder-specs/blob/master/specs/kilo/cinder-objects.rst", + "spec_status": "completed" + }, + "keystone": { + "blueprints": { + "bp/online-schema-migration": "pending" + }, + "spec": "http://specs.openstack.org/openstack/nova-specs/specs/liberty/implemented/online-schema-changes.html", + "spec_status": "pending" + } + } + } + } +}