OpenStack Summit Austin - Demo

- List view and search: Show/filter all the trackers.
- Detailed view: User story, cross-project spec, project spec, blueprints and patches.

Signed-off-by: Marcela Bonell <marcelabonell@gmail.com>
This commit is contained in:
Marcela Bonell 2016-04-25 02:12:24 -05:00
parent 9905961b62
commit b9fc423644
117 changed files with 5260 additions and 0 deletions

View File

@ -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

20
dashboard-project-api/.gitignore vendored Normal file
View File

@ -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/

View File

@ -0,0 +1,2 @@
/client/
/node_modules/

View File

@ -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
}

View File

@ -0,0 +1,3 @@
{
"generator-loopback": {}
}

View File

@ -0,0 +1,3 @@
# My Application
The project is generated by [LoopBack](http://loopback.io).

View File

@ -0,0 +1,3 @@
## Client
This is the place for your application front-end files.

View File

@ -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'}
}
);
};

View File

@ -0,0 +1,16 @@
{
"name": "Blueprint",
"base": "PersistedModel",
"idInjection": true,
"options": {},
"properties": {
"url": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}

View File

@ -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();
});
};

View File

@ -0,0 +1,16 @@
{
"name": "Patch",
"base": "PersistedModel",
"properties": {
"name": {
"type": "string"
},
"patchValues": {
"type": "object"
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}

View File

@ -0,0 +1,10 @@
'strict mode';
var async = require('async');
module.exports = function(Rst) {
Rst.afterRemote('list', function (ctx, response, next) {
next();
});
};

View File

@ -0,0 +1,17 @@
{
"name": "Rst",
"plural": "Rsts",
"base": "PersistedModel",
"properties": {
"name": {
"type": "string"
},
"patchValues": {
"type": "object"
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}

View File

@ -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;
}
]);
}
})
};

View File

@ -0,0 +1,16 @@
{
"name": "UserStory",
"base": "PersistedModel",
"idInjection": false,
"options": {},
"properties": {
"UserStory": {
"type": "array",
"required": false
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}

View File

@ -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"
}

View File

@ -0,0 +1,4 @@
module.exports = function enableAuthentication(server) {
// enable authentication
server.enableAuth();
};

View File

@ -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);
};

View File

@ -0,0 +1,5 @@
{
"loopback-component-explorer": {
"mountPath": "/explorer"
}
}

View File

@ -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
}

View File

@ -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"
]
}
}
]
}
}

View File

@ -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": {}
}
}

View File

@ -0,0 +1,9 @@
{
"final:after": {
"loopback#errorHandler": {
"params": {
"includeStack": false
}
}
}
}

View File

@ -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
}
}

View File

@ -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();
});

4
dashboard-project-app/.babelrc Executable file
View File

@ -0,0 +1,4 @@
{
"presets": ["es2015"],
"plugins": ["transform-class-properties"]
}

4
dashboard-project-app/.bowerrc Executable file
View File

@ -0,0 +1,4 @@
{
"directory": "client/bower_components"
}

View File

View File

@ -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

43
dashboard-project-app/.gitattributes vendored Executable file
View File

@ -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

14
dashboard-project-app/.gitignore vendored Executable file
View File

@ -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

48
dashboard-project-app/.jscsrc Executable file
View File

@ -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": ", "
}

View File

@ -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

View File

@ -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": ""
}
}

View File

@ -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 '<script src="' + filePath + '"></script>';
},
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: '<!-- injector:js -->',
endtag: '<!-- endinjector -->'
},
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 '<link rel="stylesheet" href="' + filePath + '">';
},
starttag: '<!-- injector:css -->',
endtag: '<!-- endinjector -->'
},
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'
]);
};

31
dashboard-project-app/README.md Executable file
View File

@ -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.

View File

@ -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"
}
}

View File

@ -0,0 +1,5 @@
#!/bin/sh
mkdir client/components/sdk
cp ../dashboard-project-api/js/lbServices.js client/components/sdk/
grunt serve

View File

@ -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/
# <IfModule mod_headers.c>
# Header set Access-Control-Allow-Origin "*"
# </IfModule>
# ------------------------------------------------------------------------------
# | 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/
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
<FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
SetEnvIf Origin ":" IS_CORS
Header set Access-Control-Allow-Origin "*" env=IS_CORS
</FilesMatch>
</IfModule>
</IfModule>
# ------------------------------------------------------------------------------
# | Web fonts access |
# ------------------------------------------------------------------------------
# Allow access from all domains for web fonts
<IfModule mod_headers.c>
<FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>
# ##############################################################################
# # 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.
<IfModule mod_headers.c>
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
<FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
Header unset X-UA-Compatible
</FilesMatch>
</IfModule>
# ------------------------------------------------------------------------------
# | Cookie setting from iframes |
# ------------------------------------------------------------------------------
# Allow cookies to be set from iframes in IE.
# <IfModule mod_headers.c>
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
# </IfModule>
# ------------------------------------------------------------------------------
# | 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 |
# ------------------------------------------------------------------------------
<IfModule mod_mime.c>
# 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
</IfModule>
# ------------------------------------------------------------------------------
# | 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.
<IfModule mod_mime.c>
AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
</IfModule>
# ##############################################################################
# # 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
<IfModule mod_rewrite.c>
Options +FollowSymlinks
# Options +SymLinksIfOwnerMatch
RewriteEngine On
# RewriteBase /
</IfModule>
# ------------------------------------------------------------------------------
# | 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
<IfModule mod_rewrite.c>
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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.
# <IfModule mod_rewrite.c>
# RewriteCond %{HTTPS} !=on
# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# </IfModule>
# ##############################################################################
# # 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).
# <IfModule mod_headers.c>
# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
# <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
# Header unset Content-Security-Policy
# </FilesMatch>
# </IfModule>
# ------------------------------------------------------------------------------
# | 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).
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Block access to hidden files and directories.
# This includes directories used by version control systems such as Git and SVN.
<IfModule mod_rewrite.c>
RewriteCond %{SCRIPT_FILENAME} -d [OR]
RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule "(^|/)\." - [F]
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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.
<FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$">
Order allow,deny
Deny from all
Satisfy All
</FilesMatch>
# ------------------------------------------------------------------------------
# | 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`.
# <IfModule mod_rewrite.c>
# RewriteCond %{SERVER_PORT} !^443
# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
# </IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 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/
# <IfModule mod_headers.c>
# Header set Strict-Transport-Security max-age=16070400;
# </IfModule>
# ------------------------------------------------------------------------------
# | 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 |
# ------------------------------------------------------------------------------
<IfModule mod_deflate.c>
# Force compression for mangled headers.
# http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
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
</IfModule>
</IfModule>
# 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 `<IfModule mod_filter.c>` and `</IfModule>` lines
# as `AddOutputFilterByType` is still in the core directives).
<IfModule mod_filter.c>
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
</IfModule>
</IfModule>
# ------------------------------------------------------------------------------
# | 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.
# <IfModule mod_headers.c>
# Header set Cache-Control "no-transform"
# </IfModule>
# ------------------------------------------------------------------------------
# | 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.
<IfModule mod_headers.c>
Header unset ETag
</IfModule>
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.
<IfModule mod_expires.c>
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"
</IfModule>
# ------------------------------------------------------------------------------
# | 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
# <IfModule mod_rewrite.c>
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
# </IfModule>
# ------------------------------------------------------------------------------
# | File concatenation |
# ------------------------------------------------------------------------------
# Allow concatenation from within specific CSS and JS files, e.g.:
# Inside of `script.combined.js` you could have
# <!--#include file="libs/jquery.js" -->
# <!--#include file="plugins/jquery.idletimer.js" -->
# and they would be included into this single file.
# <IfModule mod_include.c>
# <FilesMatch "\.combined\.js$">
# Options +Includes
# AddOutputFilterByType INCLUDES application/javascript application/json
# SetOutputFilter INCLUDES
# </FilesMatch>
# <FilesMatch "\.combined\.css$">
# Options +Includes
# AddOutputFilterByType INCLUDES text/css
# SetOutputFilter INCLUDES
# </FilesMatch>
# </IfModule>
# ------------------------------------------------------------------------------
# | 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!
# <IfModule mod_headers.c>
# Header set Connection Keep-Alive
# </IfModule>

View File

@ -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
}
}

View File

@ -0,0 +1,9 @@
(function(angular, undefined) {
'use strict';
angular.module('dashboardProjectApp.constants', [])
.constant('appConfig', {userRoles:['guest','user','admin']})
;
})(angular);

View File

@ -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;
}

View File

@ -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")
});

View File

@ -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, ' ');
};
})
})();

View File

@ -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'
})
});

View File

@ -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 {};
};
}]);

View File

@ -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; }

View File

@ -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);
});
});

View File

@ -0,0 +1,12 @@
<div class="views-details">
<div ng-include="'/app/projectDetail/views/quickResume.html'"></div>
<div class="tasks container-fluid">
<div class="container">
<div ng-repeat="(key, task) in userStory.tasks_status"
ng-include="'/app/projectDetail/views/task.html'"></div>
</div>
</div>
</div>

View File

@ -0,0 +1,59 @@
<div class="quick-resume">
<div class="container">
<div class="well frm-well">
<div class="row">
<div class="col-xs-11">
<div class="dp-title ">
{{userStory.title}}
<span class="dp-label label label-primary dp-label-{{userStory.status}}">
{{userStory.status | removeDashes | capitalize}}
</span>
</div>
</div>
<div class="col-xs-1">
<div class="pull-right">
<strong> ID: {{userStory.id}}</strong>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<a ng-href="{{userStory.showDetailedUri}}"
target="_blank"
class="dp-link-title">
<i class="fa fa-external-link"></i>
Show detailed user history
</a>
</div>
</div>
<div class="row add-margin-top">
<div class="col-xs-3">
<i class="fa fa-user" aria-hidden="true"></i>
<strong>Submitted by</strong>
<a href ng-click="mailTo(userStory.submittedByEmail)">
{{userStory.submittedBy}}
</a>
</div>
<div class="col-xs-3">
<i class="fa fa-calendar" aria-hidden="true"></i>
<strong> Created on</strong> {{userStory.createdOn}}
</div>
<div class="col-xs-3">
<i class="fa fa-calendar" aria-hidden="true"></i>
<strong>Updated on</strong> {{userStory.updatedOn}}
</div>
</div>
<div class="row add-margin-top">
<div class="col-xs-12">
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar"
aria-valuenow="{{userStory.percentageComplete}}" aria-valuemin="0" aria-valuemax="100" style="width: {{userStory.percentageComplete}}%">
<span class="sr-only">{{userStory.percentageComplete}}% Complete</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,139 @@
<div class="panel dp-panel">
<div class="panel-body">
<div class="row">
<div class="col-xs-12">
<div class="dp-title">
<h4>
<i class="fa fa-sort-desc task-open-icon"
ng-class="{'down': openTasks[key]}"
ng-click="closeTask(key)" aria-hidden="true"></i>
{{key}}
<span class="dp-label label label-primary dp-label-{{task.xp_status}}">
{{task.xp_status | removeDashes | capitalize}}
</span>
</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<a ng-href="{{task.url}}"
target="_blank"
class="dp-link-title">
<i class="fa fa-external-link"></i>
Show detailed task history
</a>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<p class="compress-text"
ng-class="{'uncompress-text': showText[key]}">
{{task.description}}
</p>
<a ng-href=""
ng-click="showMore(key)"
class="dp-link-title"
ng-hide="showText[key] || task.description.length < 420">
Show More
</a>
<a ng-href=""
ng-click="showLess(key)"
class="dp-link-title"
ng-show="showText[key]">
Show Less
</a>
</div>
</div>
<hr class="dp-hr" ng-show="openTasks[key]">
<h5 class="dp-subtitle text-center" ng-show="openTasks[key]">
Projects
</h5>
<div class="row" ng-show="openTasks[key]">
<div class="col-xs-3">
<ul class="dp-nav-pills nav nav-pills nav-stacked">
<li role="{{actualProject.key}}"
ng-repeat="(keyProject, project) in task.projects_status"
ng-class="{'active': actualProject[key] == keyProject}"
ng-click="selectProject(keyProject, key)">
<a href="">
<i class="{{getProjectIcon(keyProject)}}"
aria-hidden="true"></i>
{{keyProject | capitalize | capitalize}}
</a>
</li>
</ul>
</div>
<div class="col-xs-9">
<table class="table">
<thead>
<tr>
<th class="dp-subtitle">Spec</th>
<th class="dp-subtitle">Status</th>
<th ></th>
</tr>
</thead>
<tbody>
<tr>
<th>{{task.projects_status[actualProject[key]].spec_name | removeDashes | capitalize}}</th>
<td>
<span class="label label-primary dp-label-{{task.projects_status[actualProject[key]].spec_status}}">
{{task.projects_status[actualProject[key]].spec_status | removeDashes | capitalize}}
</span>
</td>
<td>
<a ng-href="{{task.projects_status[actualProject[key]].spec}}"
target="_blank"
class="dp-link-title">
<i class="fa fa-external-link"></i>
Show details
</a>
</td>
</tr>
</tbody>
<thead>
<tr class="separator" />
<tr>
<th class="dp-subtitle">Blueprints</th>
<th class="dp-subtitle">Status</th>
<th class="dp-subtitle">Review Links</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="blueprint in task.projects_status[actualProject[key]].blueprints">
<td>
<a href="{{blueprint.uri}}" class="dp-link-title" target="_blank">
{{blueprint.name | capitalize | capitalize}}
</a>
</td>
<td>
<span class="label label-primary dp-label-{{blueprint.status}}">
{{blueprint.status | removeDashes | capitalize}}
</span>
</td>
<td>
<ul>
<li ng-repeat="review_link in blueprint.review_link">
<a ng-href="{{review_link.url}}"
class="dp-link-title"
target="_blank">
<i class="fa fa-external-link"></i>
{{review_link.name}}
</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@ -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();
});

View File

@ -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'
});
});

View File

@ -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;
}

View File

@ -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);
});
});

View File

@ -0,0 +1,27 @@
<div class="col-xs-12">
<div class="row dp-top-alignment">
<div class="container">
<div id="toolbar">
<div>
<Label class="dp-title">OpenStack Cross-project Dashboard</label>
</div>
</div>
<table id="table"
data-pagination="true"
data-search="true"
data-toolbar="#toolbar"
data-sort-name="lastUpdate"
data-sort-order="desc"
data-row-style="rowStyle">
<thead>
<tr>
<th data-field="userStory" data-sortable="true" data-formatter="nameFormatter">User Story</th>
<th data-field="dateCreated" data-sortable="true">Date Created</th>
<th data-field="lastUpdate" data-sortable="true">Last Update</th>
<th data-field="completed" data-sortable="true" data-formatter="progressBar">% Completed</th>
</tr>
</thead>
</table>
</div>
</div>
</div>

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,6 @@
footer.footer {
text-align: center;
padding: 30px 0;
margin-top: 70px;
border-top: 1px solid #E5E5E5;
}

View File

@ -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');
}
};
});

View File

@ -0,0 +1,6 @@
<div class="container">
<p>Angular Fullstack v3.5.0 |
<a href="https://twitter.com/tyhenkel">@tyhenkel</a> |
<a href="https://github.com/DaftMonk/generator-angular-fullstack/issues?state=open">Issues</a>
</p>
</div>

View File

@ -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;
}

View File

@ -0,0 +1,11 @@
<div class="modal-header">
<button ng-if="modal.dismissable" type="button" ng-click="$dismiss()" class="close">&times;</button>
<h4 ng-if="modal.title" ng-bind="modal.title" class="modal-title"></h4>
</div>
<div class="modal-body">
<p ng-if="modal.text" ng-bind="modal.text"></p>
<div ng-if="modal.html" ng-bind-html="modal.html"></div>
</div>
<div class="modal-footer">
<button ng-repeat="button in modal.buttons" ng-class="button.classes" ng-click="button.click($event)" ng-bind="button.text" class="btn"></button>
</div>

View File

@ -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: '<p>Are you sure you want to delete <strong>' + name + '</strong> ?</p>',
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);
});
};
}
}
};
});

View File

@ -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);

View File

@ -0,0 +1,9 @@
'use strict';
angular.module('dashboardProjectApp')
.directive('navbar', () => ({
templateUrl: 'components/navbar/navbar.html',
restrict: 'E',
controller: 'NavbarController',
controllerAs: 'nav'
}));

View File

@ -0,0 +1,20 @@
<div class="navbar navbar-default navbar-static-top" ng-controller="NavbarController">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" type="button" ng-click="nav.isCollapsed = !nav.isCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/" class="navbar-brand">dashboard-project</a>
</div>
<div uib-collapse="nav.isCollapsed" class="navbar-collapse collapse" id="navbar-main">
<ul class="nav navbar-nav">
<li ng-repeat="item in nav.menu" ui-sref-active="active">
<a ui-sref="{{item.state}}">{{item.title}}</a>
</li>
</ul>
</div>
</div>
</div>

View File

@ -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!');
}
};
});

View File

@ -0,0 +1,3 @@
'use strict';
angular.module('dashboardProjectApp.util', []);

View File

@ -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);
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,127 @@
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<base href="/">
<title>OpenStack Cross-project Dashboard</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css(client) app/vendor.css -->
<!-- bower:css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="bower_components/bootstrap-table/src/bootstrap-table.css" />
<!-- endbower -->
<!-- endbuild -->
<link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css" />
<!-- build:css({.tmp,client}) app/app.css -->
<link rel="stylesheet" href="app/app.css">
<!-- injector:css -->
<link rel="stylesheet" href="app/app.css">
<link rel="stylesheet" href="app/projectDetail/styles/projectDetail.css">
<link rel="stylesheet" href="app/projectList/styles/projectList.css">
<link rel="stylesheet" href="components/footer/footer.css">
<link rel="stylesheet" href="components/modal/modal.css">
<!-- endinjector -->
<!-- endbuild -->
</head>
<body ng-app="dashboardProjectApp">
<!--[if lt IE 7]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<!-- Add your site or application content here -->
<header>
<div class="container" style="padding-left:0px;">
<a href="/projectList">
<img src="../../assets/images/openstack-logo-full.png" />
</a>
</div>
</header>
<div class="container-fluid main-wrapper">
<div ui-view="" class="row"></div>
</div>
<!--<footer></footer>-->
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-X');
ga('send', 'pageview');
</script>
<script>
function nameFormatter(value) {
var story = value.split("-");
return '<a class="dp-link-title" href="projectDetail/'+ story[0] +'">' + story[1] + '</a>';
}
function progressBar(value, row) {
return '<div class="progress dp-progress">'+
'<div class="progress-bar progress-bar-success progress-bar-striped"'+
'role="progressbar" aria-valuenow="'+value+'" aria-valuemin="0"' +
'aria-valuemax="100" style="width: '+value+'%">'+
'<span class="sr-only">'+value+'%</span>' +
'</div>' +
'</div>';
}
function rowStyle(row, index) {
var classes = ['active', 'active'];
if (index % 2 === 0 && index/2 < classes.length) {
return {
classes: classes[index/2]
};
}
return {};
}
</script>
<!--[if lt IE 9]>
<script src="bower_components/es5-shim/es5-shim.js"></script>
<script src="bower_components/json3/lib/json3.min.js"></script>
<![endif]-->
<!-- build:js({client,node_modules}) app/vendor.js -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
<script src="bower_components/lodash/dist/lodash.compat.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<script src="bower_components/bootstrap-table/src/bootstrap-table.js"></script>
<script src="bower_components/moment/moment.js"></script>
<!-- endbower -->
<!-- endbuild -->
<!-- build:js(.tmp) app/app.js -->
<script src="app/app.js"></script>
<!-- injector:js -->
<script src="components/util/util.module.js"></script>
<script src="components/footer/footer.directive.js"></script>
<script src="app/projectDetail/projectDetail.js"></script>
<script src="app/projectDetail/services/userStoryService.js"></script>
<script src="app/projectList/controllers/projectList.controller.js"></script>
<script src="app/projectList/projectList.js"></script>
<script src="app/projectDetail/controllers/projectDetail.controller.js"></script>
<script src="components/modal/modal.service.js"></script>
<script src="components/navbar/navbar.controller.js"></script>
<script src="components/navbar/navbar.directive.js"></script>
<script src="components/sdk/lbServices.js"></script>
<script src="app/app.constant.js"></script>
<script src="components/util/util.service.js"></script>
<!-- endinjector -->
<!-- endbuild -->
</body>
</html>

View File

@ -0,0 +1,3 @@
# robotstxt.org
User-agent: *

View File

@ -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();

View File

@ -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();

View File

@ -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');
});
});

View File

@ -0,0 +1,7 @@
#!/bin/sh
cd ../dashboard-project-api;
npm install;
cd ../dashboard-project-app
npm install;
bower install;

View File

@ -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
});
};

View File

@ -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'))

View File

@ -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
}

View File

@ -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;

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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;

View File

@ -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;
});
});
});

View File

@ -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));
}

View File

@ -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;

View File

@ -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();
});
});
});
});

Some files were not shown because too many files have changed in this diff Show More