├── 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 |
2 |
JenkinsLight by M6Web
3 |
4 |
5 |
6 |

{{ job.name }}

7 |
8 |
9 |
10 |
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 [![Build Status](https://api.travis-ci.org/M6Web/JenkinsLight.png?branch=master)](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 | ![JenkinsLight](http://img818.imageshack.us/img818/6423/mz5c.png "JenkinsLight") 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 | --------------------------------------------------------------------------------