├── app
├── .buildignore
├── scripts
│ ├── config.js.dist
│ ├── app.js
│ ├── e2e-mocks.js
│ ├── services
│ │ └── jenkins.js
│ └── controllers
│ │ └── main.js
├── views
│ └── main.html
├── index.html
└── styles
│ └── main.css
├── manifests
├── bootstrap.sh
└── init.pp
├── .bowerrc
├── bin
└── cors.hpi
├── .gitignore
├── .travis.yml
├── bower.json
├── test
├── e2e
│ ├── runner.html
│ └── scenario.js
└── .jshintrc
├── .gitmodules
├── .jshintrc
├── Vagrantfile.dist
├── LICENSE
├── package.json
├── karma-e2e.conf.js
├── README.md
└── Gruntfile.js
/app/.buildignore:
--------------------------------------------------------------------------------
1 | *.coffee
--------------------------------------------------------------------------------
/manifests/bootstrap.sh:
--------------------------------------------------------------------------------
1 | sudo apt-get update
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "app/bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/bin/cors.hpi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BedrockStreaming/JenkinsLight/HEAD/bin/cors.hpi
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .tmp
4 | .sass-cache
5 | .vagrant
6 | app/bower_components
7 | app/scripts/config.js
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | before_script:
5 | - 'npm install -g bower grunt-cli'
6 | - 'bower install'
7 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jenkinsLight",
3 | "version": "v1.0.0",
4 | "dependencies": {
5 | "angular": "~1.2.0",
6 | "angular-route": "~1.2.0"
7 | },
8 | "devDependencies": {
9 | "angular-mocks": "~1.2.0",
10 | "angular-scenario": "~1.2.0"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/e2e/runner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | End2end Test Runner
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "modules/wget"]
2 | path = modules/wget
3 | url = git://github.com/maestrodev/puppet-wget.git
4 | [submodule "modules/stdlib"]
5 | path = modules/stdlib
6 | url = git://github.com/puppetlabs/puppetlabs-stdlib.git
7 | [submodule "modules/nodejs"]
8 | path = modules/nodejs
9 | url = git://github.com/willdurand/puppet-nodejs.git
10 |
--------------------------------------------------------------------------------
/manifests/init.pp:
--------------------------------------------------------------------------------
1 | # dependecies for node
2 | package { 'g++':
3 | ensure => present
4 | } ->
5 |
6 | package { 'make':
7 | ensure => present
8 | }
9 |
10 | include nodejs
11 |
12 | # essentials package
13 |
14 | package { 'git':
15 | ensure => present
16 | } ->
17 |
18 | package { 'vim':
19 | ensure => present
20 | } ->
21 |
22 | # npm modules
23 |
24 | package { 'grunt-cli':
25 | provider => npm
26 | } ->
27 | package { 'bower':
28 | provider => npm
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 4,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "angular": false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/scripts/config.js.dist:
--------------------------------------------------------------------------------
1 | angular.module('config', [])
2 | .constant('CONFIG', {
3 | CI: {
4 | JENKINS: {
5 | URL: '{#JENKINS_URL#}',
6 | AUTHORIZATION_TOKEN: '{#AUTHORIZATION_TOKEN#}',
7 | DEFAULT_VIEW: 'All',
8 | JOBS_TO_BE_DISPLAYED: '{#DISPLAYED_JOB_TYPES#}'
9 | }
10 | },
11 | MAX_JOBS_PER_LINE: 4,
12 | REFRESH_TIME: 10000,
13 | BACKGROUND_BLANK_SCREEN_URL: '{#BACKGROUND_URL#}',
14 | JOBS_NOT_DISPLAYED_REGEXP: '{#NOT_DISPLAYED_REGEXP#}'
15 | });
16 |
--------------------------------------------------------------------------------
/app/views/main.html:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/Vagrantfile.dist:
--------------------------------------------------------------------------------
1 | Vagrant::Config.run do |config|
2 | config.vm.host_name = "jenkinslight"
3 |
4 | config.vm.box = "precise32"
5 | config.vm.box_url = "http://files.vagrantup.com/precise32.box"
6 |
7 | config.vm.network :hostonly, "10.0.0.2", :netmask => "255.255.255.0"
8 | config.vm.forward_port 9000, 8888
9 |
10 | config.vm.share_folder("vagrant-root", "/vagrant", ".", :nfs => false)
11 |
12 | config.vm.provision :shell, path: "manifests/bootstrap.sh"
13 | config.vm.provision :puppet do |puppet|
14 | puppet.module_path = "modules"
15 | puppet.manifests_path = "manifests"
16 | puppet.manifest_file = "init.pp"
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('jenkinsLightApp', ['config', 'ngRoute'])
4 | .config(function ($routeProvider) {
5 | $routeProvider
6 | .when('/', {
7 | templateUrl: 'views/main.html',
8 | controller: 'JenkinsLightCtrl'
9 | })
10 | .otherwise({
11 | redirectTo: '/'
12 | });
13 | })
14 | .config(function($httpProvider, CONFIG) {
15 | if (CONFIG.CI.JENKINS.AUTHORIZATION_TOKEN) {
16 | // Set authorization token (http://en.wikipedia.org/wiki/Basic_access_authentication#Client_side)
17 | $httpProvider.defaults.headers.common.Authorization = CONFIG.CI.JENKINS.AUTHORIZATION_TOKEN;
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 4,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "after": false,
23 | "afterEach": false,
24 | "angular": false,
25 | "before": false,
26 | "beforeEach": false,
27 | "browser": false,
28 | "describe": false,
29 | "expect": false,
30 | "inject": false,
31 | "it": false,
32 | "spyOn": false
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 M6Web
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jenkinslight",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "bower": "~1.3.9"
6 | },
7 | "devDependencies": {
8 | "grunt": "~0.4.2",
9 | "grunt-preprocess":"~4.0.0",
10 | "grunt-env":"~0.4.1",
11 | "grunt-autoprefixer": "~0.4.0",
12 | "grunt-concurrent": "~0.4.1",
13 | "grunt-contrib-clean": "~0.5.0",
14 | "grunt-contrib-concat": "~0.3.0",
15 | "grunt-contrib-connect": "~0.6.0",
16 | "grunt-contrib-copy": "~0.4.1",
17 | "grunt-contrib-cssmin": "~0.7.0",
18 | "grunt-contrib-htmlmin": "~0.1.3",
19 | "grunt-contrib-jshint": "~0.7.2",
20 | "grunt-contrib-uglify": "~0.2.7",
21 | "grunt-contrib-watch": "~0.5.3",
22 | "grunt-ngmin": "~0.0.3",
23 | "grunt-rev": "~0.1.0",
24 | "grunt-usemin": "~2.0.0",
25 | "jshint-stylish": "~0.1.5",
26 | "load-grunt-tasks": "~0.2.1",
27 | "karma-ng-html2js-preprocessor": "~0.1.0",
28 | "karma-ng-scenario": "~0.1.0",
29 | "grunt-karma": "~0.7.2",
30 | "karma": "~0.12.0",
31 | "karma-phantomjs-launcher": "~0.1.1"
32 | },
33 | "engines": {
34 | "node": ">=0.8.0"
35 | },
36 | "scripts": {
37 | "test": "grunt test"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/e2e/scenario.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('jenkinsLightApp', function() {
4 | it('should be blue when job success', function() {
5 | browser().navigateTo('/#/?view=blue');
6 | expect(element('.job.job-color-red').count()).toBe(0);
7 | expect(element('.job.job-color-blue').count()).toBe(1);
8 | expect(element('#jenkinsLight').attr('style')).toBe(undefined);
9 | });
10 |
11 | it('should be red when job fails', function() {
12 | browser().navigateTo('/#/?view=red');
13 | expect(element('.job.job-color-red').count()).toBe(1);
14 | expect(element('.job.job-color-blue').count()).toBe(0);
15 | expect(element('#jenkinsLight').attr('style')).toBe(undefined);
16 | });
17 |
18 | it('should be background when no jobs is displayed', function() {
19 | browser().navigateTo('/#/?view=none');
20 | expect(element('.job.job-color-red').count()).toBe(0);
21 | expect(element('.job.job-color-blue').count()).toBe(0);
22 | expect(element('#jenkinsLight').attr('style')).toMatch('image-url-or-null');
23 | });
24 |
25 | it('should be not displays job when the title containing "dev"', function() {
26 | browser().navigateTo('/#/?view=regexp');
27 | expect(element('.job.job-color-red').count()).toBe(1);
28 | expect(element('.job.job-color-blue').count()).toBe(1);
29 | expect(element('#jenkinsLight').attr('style')).toBe(undefined);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/karma-e2e.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.10/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | // base path, that will be used to resolve files and exclude
7 | basePath: '',
8 |
9 | // testing framework to use (jasmine/mocha/qunit/...)
10 | frameworks: ['ng-scenario'],
11 |
12 | // list of files / patterns to load in the browser
13 | files: [
14 | 'test/e2e/**/*.js'
15 | ],
16 |
17 | // list of files / patterns to exclude
18 | exclude: [],
19 |
20 | // web server port
21 | port: 8080,
22 |
23 | // level of logging
24 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
25 | logLevel: config.LOG_INFO,
26 |
27 |
28 | // enable / disable watching file and executing tests whenever any file changes
29 | autoWatch: false,
30 |
31 |
32 | // Start these browsers, currently available:
33 | // - Chrome
34 | // - ChromeCanary
35 | // - Firefox
36 | // - Opera
37 | // - Safari (only Mac)
38 | // - PhantomJS
39 | // - IE (only Windows)
40 | browsers: ['PhantomJS'],
41 |
42 |
43 | // Continuous Integration mode
44 | // if true, it capture browsers, run tests and exit
45 | singleRun: true,
46 |
47 | // Uncomment the following lines if you are using grunt's server to run the tests
48 | proxies: {
49 | '/': 'http://localhost:9001/'
50 | },
51 | // URL root prevent conflicts with the site root
52 | urlRoot: '/_karma_/'
53 | });
54 | };
55 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | JenkinsLight by M6Web
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/scripts/e2e-mocks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('e2e-mocks', ['ngMockE2E'])
4 | .run(function($httpBackend, CONFIG) {
5 |
6 | CONFIG.CI.JENKINS.JOBS_TO_BE_DISPLAYED = ['blue', 'red'];
7 | CONFIG.BACKGROUND_BLANK_SCREEN_URL = 'image-url-or-null';
8 | CONFIG.JOBS_NOT_DISPLAYED_REGEXP = '^regexp$';
9 |
10 | // Don't mock the html views
11 | $httpBackend.whenGET(/views\/\w+.*/).passThrough();
12 |
13 | $httpBackend.whenGET('{#JENKINS_URL#}/view/blue/api/json').respond(
14 | {'description':null,'jobs':[{'name':'panic-sdc-homepages','url':'https://jenkins-light-url/job/panic-sdc-homepages/','color':'blue'}],'name':'blue','property':[],'url':'https://jenkins-light-url/view/blue/'}
15 | );
16 |
17 | $httpBackend.whenGET('{#JENKINS_URL#}/view/red/api/json').respond(
18 | {'description':null,'jobs':[{'name':'panic-sdc-homepages','url':'https://jenkins-light-url/job/panic-sdc-homepages/','color':'red'}],'name':'red','property':[],'url':'https://jenkins-light-url/view/red/'}
19 | );
20 |
21 | $httpBackend.whenGET('{#JENKINS_URL#}/view/none/api/json').respond(
22 | {'description':null,'jobs':[],'name':'none','property':[],'url':'https://jenkins-light-url/view/none/'}
23 | );
24 |
25 | $httpBackend.whenGET('{#JENKINS_URL#}/view/regexp/api/json').respond(function () {
26 | var jobs = [
27 | {'name':'regexp','url':'https://jenkins-light-url/job/regexp/','color':'red'},
28 | {'name':'a-regexp','url':'https://jenkins-light-url/job/a-regexp/','color':'blue'},
29 | {'name':'regexp-troll','url':'https://jenkins-light-url/job/regexp-troll/','color':'red'}
30 | ];
31 |
32 | return [200, {'description':null,'jobs':jobs,'name':'regexp','property':[],'url':'https://jenkins-light-url/view/regexp/'}];
33 | });
34 | });
35 |
36 | angular.module('jenkinsLightApp').requires.push('e2e-mocks');
37 |
--------------------------------------------------------------------------------
/app/scripts/services/jenkins.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('jenkinsLightApp')
4 | .factory('JenkinsService', function (CONFIG, $location, $http) {
5 | return {
6 | getJobs: function () {
7 | var viewParameter = CONFIG.CI.JENKINS.DEFAULT_VIEW;
8 | if ($location.search().view) {
9 |
10 | // Set the value of the view query parameter
11 | viewParameter = $location.search().view;
12 | }
13 |
14 | // Call Jenkins API
15 | var promise = $http({method: 'GET', url: CONFIG.CI.JENKINS.URL + '/view/' + viewParameter + '/api/json'}).
16 | then(function(response) {
17 |
18 | // Initialize jobs data
19 | var data = response.data;
20 | var jobs = [];
21 |
22 | data.jobs.forEach(function(job) {
23 |
24 | // Check if this `job` can be displayable
25 | if (CONFIG.CI.JENKINS.JOBS_TO_BE_DISPLAYED.indexOf(job.color) > -1) {
26 |
27 | // Filter jobs not displayed
28 | if (CONFIG.JOBS_NOT_DISPLAYED_REGEXP && new RegExp(CONFIG.JOBS_NOT_DISPLAYED_REGEXP, 'gi').test(job.name)) {
29 | return;
30 | }
31 |
32 | job.name = job.name.
33 | split('-').join(' ').
34 |
35 | // Remove all occurrence of view name in `job` name
36 | split(new RegExp(viewParameter, 'gi')).join('');
37 |
38 | // Push job on screen
39 | jobs .push(job);
40 | }
41 | });
42 |
43 | // Return jobs filtered
44 | return jobs;
45 | });
46 |
47 | // Return the promise to the controller
48 | return promise;
49 | }
50 | };
51 | });
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JenkinsLight [](http://travis-ci.org/M6Web/JenkinsLight)
2 |
3 | A build monitoring tool (buildwall) that allows you to quickly detect failing projects for Jenkins.
4 |
5 | 
6 |
7 | ## Requirements
8 |
9 | This project required a [cors-plugin Jenkins plugin](https://github.com/jhinrichsen/cors-plugin) to enabled CORS.
10 | Enabling CORS would let you call the Jenkins REST API from javascript (you can use [the provided packaged plugin](bin/cors.hpi)).
11 |
12 | ## Installation
13 |
14 | #### Clone the project
15 |
16 | ```
17 | $ git clone https://github.com/M6Web/JenkinsLight.git
18 | $ cd JenkinsLight
19 | ```
20 |
21 | #### Install dependencies
22 |
23 | ```
24 | $ npm install -g bower grunt-cli
25 | $ npm install
26 | $ bower install
27 | ```
28 |
29 | ## Configuration
30 |
31 | Please configure a new `app/scripts/config.js` file from [`app/scripts/config.js.dist`](app/scripts/config.js.dist).
32 |
33 | Jenkins options :
34 |
35 | * **CI.JENKINS.URL** : Jenkins server url
36 | * **CI.JENKINS.AUTHORIZATION_TOKEN** : authorization token if your Jenkins server is secured, eg: "Basic 0123456=" (opt.)
37 | * **CI.JENKINS.DEFAULT_VIEW** : default Jenkins view to display, eg : "All"
38 | * **CI.JENKINS.JOBS_TO_BE_DISPLAYED** : array of all job types that can be displayed :
39 | * *red* : failing job,
40 | * *red_anime* : building failed job,
41 | * *blue* : succeeding job,
42 | * *blue_anime* : building succeeded job.
43 |
44 | Display options :
45 |
46 | * **MAX_JOBS_PER_LINE** : maximum number of jobs displayed per line
47 | * **REFRESH_TIME** : refresh time (ms)
48 | * **BACKGROUND_BLANK_SCREEN_URL** : background image url use if no job are dislayed
49 | * **JOBS_NOT_DISPLAYED_REGEXP** : exclude jobs which name match this regexp
50 |
51 | Then you have to build the server code.
52 |
53 | ```shell
54 | $ grunt build
55 | ```
56 |
57 | Your server root url must target the `dist` folder.
58 |
59 | ## Use
60 |
61 | Use `view` query parameter for select a Jenkins view.
62 |
63 | ```
64 | http://jenkins-light-url/index.html#?view=MyView
65 | ```
66 |
67 | ## Installation for dev
68 |
69 | #### Clone and init the project
70 |
71 | ```
72 | $ git clone https://github.com/M6Web/JenkinsLight.git
73 | $ cd JenkinsLight/vagrant
74 | $ git submodule install --init
75 | ```
76 |
77 | Install [Vagrant](http://www.vagrantup.com/downloads) and configure `Vagrantfile` :
78 |
79 | ```
80 | $ cp Vagrantfile.dist Vagrantfile
81 | ```
82 |
83 | *Note : configure your own Vagrantfile and provisionning if necessary.*
84 |
85 | ```
86 | $ vagrant up
87 | $ vagrant ssh
88 | $ cd /vagrant
89 | ```
90 |
91 | #### Install dependencies
92 |
93 | ```
94 | $ sudo npm install --no-bin-links
95 | $ bower install
96 | ```
97 |
98 | [Configure your application](#configuration) via `app/scripts/config.js`.
99 |
100 | #### Run the server
101 |
102 | ```
103 | $ grunt server
104 | ```
105 |
106 | You can now access the application at `http://localhost:8888`.
107 |
108 | ## Tests
109 |
110 | ```shell
111 | $ npm test
112 | ```
113 |
114 | ## Credits
115 |
116 | Developed by the [Cytron Team](http://cytron.fr/) of [M6 Web](http://tech.m6web.fr/).
117 |
118 | ## License
119 |
120 | [JenkinsLight](https://github.com/M6Web/JenkinsLight) is licensed under the [MIT license](LICENSE).
121 |
--------------------------------------------------------------------------------
/app/scripts/controllers/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('jenkinsLightApp')
4 | .controller('JenkinsLightCtrl', function JenkinsLightCtrl ($scope, $window, CONFIG, JenkinsService, $interval) {
5 | $scope.jobs = [];
6 | $scope.jobsPerLine = CONFIG.DEFAULT_JOBS_PER_LINE;
7 | $scope.backgroundBlankScreen = null;
8 |
9 | var callAPI = function () {
10 | JenkinsService.getJobs().
11 | then(function (jobs) {
12 |
13 | // Display background image on blank screen
14 | if (CONFIG.BACKGROUND_BLANK_SCREEN_URL) {
15 | if (jobs.length == 0) {
16 | $scope.backgroundBlankScreen = {
17 | 'background-image': 'url(' + CONFIG.BACKGROUND_BLANK_SCREEN_URL + ')'
18 | };
19 | } else {
20 | $scope.backgroundBlankScreen = null;
21 | }
22 | }
23 |
24 | // Calculation of optimized job area
25 | var minJobHeight = 100;
26 | var minJobWidth = 200;
27 | var screenHeigth = $window.innerHeight - 40;
28 | var screenWidth = $window.innerWidth;
29 | var sizeSet = [];
30 | var tooLargeSet = [];
31 | var oneJobWidth, oneJobHeight, jobsPerColumn, jobsPerLine;
32 |
33 | for (var i = 0; i <= CONFIG.MAX_JOBS_PER_LINE ; i++) {
34 | jobsPerLine = i;
35 | jobsPerColumn = Math.ceil(jobs.length / jobsPerLine);
36 | oneJobWidth = Math.ceil(screenWidth / jobsPerLine);
37 | oneJobHeight = Math.ceil(screenHeigth / jobsPerColumn);
38 |
39 | if ((oneJobHeight < minJobHeight) && (oneJobWidth >= minJobWidth)) {
40 | oneJobHeight = minJobHeight;
41 | tooLargeSet.push({'oneJobHeight': oneJobHeight, 'jobsPerLine': jobsPerLine});
42 | continue;
43 | } else if (oneJobWidth < minJobWidth) {
44 | continue;
45 | }
46 |
47 | sizeSet.push({'oneJobHeight': oneJobHeight, 'jobsPerLine': jobsPerLine, 'ratio': oneJobWidth / oneJobHeight});
48 | }
49 |
50 | // If at least one solution fit in screen
51 | if (sizeSet.length !== 0) {
52 | // Searching ratio most closer to 4
53 | var baseRatio = 4;
54 | sizeSet.sort(function(a, b) {
55 | return (Math.abs(a['ratio'] - baseRatio) > Math.abs(b['ratio'] - baseRatio)) ? 1 : -1;
56 | });
57 |
58 | oneJobHeight = sizeSet[0]['oneJobHeight'];
59 | jobsPerLine = sizeSet[0]['jobsPerLine'];
60 | } else {
61 | var solution = tooLargeSet.pop();
62 | oneJobHeight = solution['oneJobHeight'];
63 | jobsPerLine = solution['jobsPerLine'];
64 | }
65 |
66 | var fontSize = Math.floor(15 * (oneJobHeight / minJobHeight));
67 |
68 | $scope.oneJobHeight = oneJobHeight;
69 | $scope.jobsPerLine = jobsPerLine;
70 | $scope.fontSize = fontSize;
71 | $scope.jobs = jobs;
72 | });
73 | };
74 |
75 | callAPI();
76 |
77 | // Begin interval
78 | $interval(callAPI, CONFIG.REFRESH_TIME);
79 | });
80 |
--------------------------------------------------------------------------------
/app/styles/main.css:
--------------------------------------------------------------------------------
1 | html{color:#000;background:#FFF}
2 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}
3 | table{border-collapse:collapse;border-spacing:0}
4 | fieldset,img{border:0}
5 | address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}
6 | ol,ul{list-style:none}
7 | caption,th{text-align:left}
8 | h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}
9 | q:before,q:after{content:''}
10 | abbr,acronym{border:0;font-variant:normal}
11 | sup{vertical-align:text-top}
12 | sub{vertical-align:text-bottom}
13 | input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}
14 | input,textarea,select{*font-size:100%}legend{color:#000}#yui3-css-stamp.cssreset{display:none}
15 | *,*:after,*:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
16 | body{margin:0}[class*='col-']{float:left}
17 | .grid{width:100%;margin:0 auto;overflow:hidden}
18 | .grid:after{content:"";display:table;clear:both}
19 | .grid-pad{padding:20px 0 0 20px}
20 | .grid-pad>[class*='col-']:last-of-type{padding-right:20px}
21 | .push-right{float:right}
22 | .col-1-1{width:100%}
23 | .col-2-3,.col-8-12{width:66.66%}
24 | .col-1-2,.col-6-12{width:50%}
25 | .col-1-3,.col-4-12{width:33.33%}
26 | .col-1-4,.col-3-12{width:25%}
27 | .col-1-5{width:20%}
28 | .col-1-6,.col-2-12{width:16.667%}
29 | .col-1-7{width:14.28%}
30 | .col-1-8{width:12.5%}
31 | .col-1-9{width:11.1%}
32 | .col-1-10{width:10%}
33 | .col-1-11{width:9.09%}
34 | .col-1-12{width:8.33%}
35 | .col-11-12{width:91.66%}
36 | .col-10-12{width:83.333%}
37 | .col-9-12{width:75%}
38 | .col-5-12{width:41.66%}
39 | .col-7-12{width:58.33%}
40 |
41 | html, body, .container {
42 | background-color: #ecf0f1;
43 | font-family: "Sintony";
44 | font-size: 16px;
45 | height: 100%;
46 | }
47 | a {
48 | color: inherit;
49 | text-decoration: none;
50 | }
51 | #jenkinsLight {
52 | min-height: 100%;
53 | margin:0;
54 | padding:0;
55 | background-repeat: no-repeat;
56 | background-position: center;
57 | -webkit-background-size: cover;
58 | -moz-background-size: cover;
59 | -o-background-size: cover;
60 | background-size: cover;
61 | }
62 | .info {
63 | font-size: 12px;
64 | background-color: #ddd;
65 | border-bottom: 1px solid #ccc;
66 | color: #555;
67 | line-height: 20px;
68 | padding: 4px 16px;
69 | }
70 | .info a {
71 | font-weight: bold;
72 | }
73 | .wall .job {
74 | height: 85px;
75 | margin: 10px;
76 | padding: 10px;
77 | box-shadow: 0 10px 10px -10px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
78 | }
79 | .wall .job h2 {
80 | color: white;
81 | font-weight: bold;
82 | text-transform: uppercase;
83 | }
84 | .wall .job h2 a {
85 | text-decoration: none;
86 | }
87 | .wall .job-color-blue,
88 | .wall .job-color-blue_anime {
89 | background-color: #2ecc71;
90 | border-right: 1px solid #27ae60;
91 | border-bottom: 1px solid #27ae60;
92 | }
93 | .wall .job-color-red,
94 | .wall .job-color-red_anime {
95 | background-color: #e74c3c;
96 | border-right: 1px solid #c0392b;
97 | border-bottom: 1px solid #c0392b;
98 | }
99 | .wall .job-color-blue_anime,
100 | .wall .job-color-red_anime {
101 | animation-name: blinker;
102 | animation-duration: 1s;
103 | animation-timing-function: linear;
104 | animation-iteration-count: infinite;
105 | -webkit-animation-name: blinker;
106 | -webkit-animation-duration: 1s;
107 | -webkit-animation-timing-function: linear;
108 | -webkit-animation-iteration-count: infinite;
109 | }
110 |
111 | @-moz-keyframes blinker {
112 | 0% { opacity: 1.0; }
113 | 50% { opacity: 0.3; }
114 | 100% { opacity: 1.0; }
115 | }
116 |
117 | @-webkit-keyframes blinker {
118 | 0% { opacity: 1.0; }
119 | 50% { opacity: 0.3; }
120 | 100% { opacity: 1.0; }
121 | }
122 |
123 | @keyframes blinker {
124 | 0% { opacity: 1.0; }
125 | 50% { opacity: 0.3; }
126 | 100% { opacity: 1.0; }
127 | }
128 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // Generated on 2013-11-21 using generator-angular 0.6.0-rc.1
2 | 'use strict';
3 |
4 | // # Globbing
5 | // for performance reasons we're only matching one level down:
6 | // 'test/spec/{,*/}*.js'
7 | // use this if you want to recursively match all subfolders:
8 | // 'test/spec/**/*.js'
9 |
10 | module.exports = function (grunt) {
11 | require('load-grunt-tasks')(grunt);
12 |
13 | grunt.initConfig({
14 | yeoman: {
15 | // configurable paths
16 | app: require('./bower.json').appPath || 'app',
17 | dist: 'dist'
18 | },
19 | env: {
20 | prod: {}
21 | },
22 | preprocess: {
23 | prod: {
24 | src : '<%= yeoman.dist %>/index.html',
25 | dest : '<%= yeoman.dist %>/index.html'
26 | }
27 | },
28 | watch: {
29 | styles: {
30 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
31 | tasks: ['copy:styles', 'autoprefixer']
32 | },
33 | livereload: {
34 | options: {
35 | livereload: '<%= connect.options.livereload %>'
36 | },
37 | files: [
38 | '<%= yeoman.app %>/{,*/}*.html',
39 | '.tmp/styles/{,*/}*.css',
40 | '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
41 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
42 | ]
43 | }
44 | },
45 | autoprefixer: {
46 | options: ['last 1 version'],
47 | dist: {
48 | files: [{
49 | expand: true,
50 | cwd: '.tmp/styles/',
51 | src: '{,*/}*.css',
52 | dest: '.tmp/styles/'
53 | }]
54 | }
55 | },
56 | connect: {
57 | options: {
58 | port: 9000,
59 | // Change this to '0.0.0.0' to access the server from outside.
60 | hostname: '0.0.0.0',
61 | livereload: 35729
62 | },
63 | livereload: {
64 | options: {
65 | open: true,
66 | base: [
67 | '.tmp',
68 | '<%= yeoman.app %>'
69 | ]
70 | }
71 | },
72 | test: {
73 | options: {
74 | port: 9001,
75 | base: [
76 | '.tmp',
77 | 'test',
78 | '<%= yeoman.app %>'
79 | ]
80 | }
81 | },
82 | dist: {
83 | options: {
84 | base: '<%= yeoman.dist %>'
85 | }
86 | }
87 | },
88 | clean: {
89 | dist: {
90 | files: [{
91 | dot: true,
92 | src: [
93 | '.tmp',
94 | '<%= yeoman.dist %>/*',
95 | '!<%= yeoman.dist %>/.git*'
96 | ]
97 | }]
98 | },
99 | server: '.tmp'
100 | },
101 | jshint: {
102 | options: {
103 | jshintrc: '.jshintrc',
104 | reporter: require('jshint-stylish')
105 | },
106 | all: [
107 | 'Gruntfile.js',
108 | '<%= yeoman.app %>/scripts/{,*/}*.js'
109 | ]
110 | },
111 | // not used since Uglify task does concat,
112 | // but still available if needed
113 | /*concat: {
114 | dist: {}
115 | },*/
116 | rev: {
117 | dist: {
118 | files: {
119 | src: [
120 | '<%= yeoman.dist %>/scripts/{,*/}*.js',
121 | '<%= yeoman.dist %>/styles/{,*/}*.css',
122 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
123 | '<%= yeoman.dist %>/styles/fonts/*'
124 | ]
125 | }
126 | }
127 | },
128 | useminPrepare: {
129 | html: '<%= yeoman.app %>/index.html',
130 | options: {
131 | dest: '<%= yeoman.dist %>'
132 | }
133 | },
134 | usemin: {
135 | html: ['<%= yeoman.dist %>/{,*/}*.html'],
136 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
137 | options: {
138 | assetsDirs: ['<%= yeoman.dist %>']
139 | }
140 | },
141 | cssmin: {
142 | // By default, your `index.html` will take care of
143 | // minification. This option is pre-configured if you do not wish to use
144 | // Usemin blocks.
145 | // dist: {
146 | // files: {
147 | // '<%= yeoman.dist %>/styles/main.css': [
148 | // '.tmp/styles/{,*/}*.css',
149 | // '<%= yeoman.app %>/styles/{,*/}*.css'
150 | // ]
151 | // }
152 | // }
153 | },
154 | htmlmin: {
155 | dist: {
156 | options: {
157 | /*removeCommentsFromCDATA: true,
158 | // https://github.com/yeoman/grunt-usemin/issues/44
159 | //collapseWhitespace: true,
160 | collapseBooleanAttributes: true,
161 | removeAttributeQuotes: true,
162 | removeRedundantAttributes: true,
163 | useShortDoctype: true,
164 | removeEmptyAttributes: true,
165 | removeOptionalTags: true*/
166 | },
167 | files: [{
168 | expand: true,
169 | cwd: '<%= yeoman.app %>',
170 | src: ['*.html', 'views/*.html'],
171 | dest: '<%= yeoman.dist %>'
172 | }]
173 | }
174 | },
175 | // Put files not handled in other tasks here
176 | copy: {
177 | dist: {
178 | files: [{
179 | expand: true,
180 | dot: true,
181 | cwd: '<%= yeoman.app %>',
182 | dest: '<%= yeoman.dist %>',
183 | src: [
184 | '*.{ico,png,txt}',
185 | '.htaccess',
186 | 'bower_components/**/*',
187 | 'images/{,*/}*.{gif,webp}',
188 | 'fonts/*'
189 | ]
190 | }, {
191 | expand: true,
192 | cwd: '.tmp/images',
193 | dest: '<%= yeoman.dist %>/images',
194 | src: [
195 | 'generated/*'
196 | ]
197 | }]
198 | },
199 | styles: {
200 | expand: true,
201 | cwd: '<%= yeoman.app %>/styles',
202 | dest: '.tmp/styles/',
203 | src: '{,*/}*.css'
204 | }
205 | },
206 | concurrent: {
207 | server: [
208 | 'copy:styles'
209 | ],
210 | test: [
211 | 'copy:styles'
212 | ],
213 | dist: [
214 | 'copy:styles',
215 | 'htmlmin'
216 | ]
217 | },
218 | karma: {
219 | e2e: {
220 | configFile: 'karma-e2e.conf.js'
221 | }
222 | },
223 | ngmin: {
224 | dist: {
225 | files: [{
226 | expand: true,
227 | cwd: '.tmp/concat/scripts',
228 | src: '*.js',
229 | dest: '.tmp/concat/scripts'
230 | }]
231 | }
232 | },
233 | uglify: {
234 | dist: {
235 | files: {
236 | '<%= yeoman.dist %>/scripts/scripts.js': [
237 | '<%= yeoman.dist %>/scripts/scripts.js'
238 | ]
239 | }
240 | }
241 | }
242 | });
243 |
244 | grunt.registerTask('server', function (target) {
245 | if (target === 'dist') {
246 | return grunt.task.run(['build', 'connect:dist:keepalive']);
247 | }
248 |
249 | grunt.task.run([
250 | 'clean:server',
251 | 'concurrent:server',
252 | 'autoprefixer',
253 | 'connect:livereload',
254 | 'watch'
255 | ]);
256 | });
257 |
258 | grunt.registerTask('test', [
259 | 'clean:server',
260 | 'concurrent:test',
261 | 'autoprefixer',
262 | 'connect:test',
263 | 'karma'
264 | ]);
265 |
266 | grunt.registerTask('build', [
267 | 'clean:dist',
268 | 'env:prod',
269 | 'useminPrepare',
270 | 'concurrent:dist',
271 | 'autoprefixer',
272 | 'concat',
273 | 'ngmin',
274 | 'copy:dist',
275 | 'cssmin',
276 | 'uglify',
277 | 'rev',
278 | 'usemin',
279 | 'preprocess:prod'
280 | ]);
281 |
282 | grunt.registerTask('default', [
283 | 'jshint',
284 | 'test',
285 | 'build'
286 | ]);
287 | };
288 |
--------------------------------------------------------------------------------