buildboard 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/LICENSE +20 -0
- data/README.md +72 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/bin/buildboard +7 -0
- data/bin/update-buildboard +58 -0
- data/lib/buildboard.rb +133 -0
- data/public/buttons.css +82 -0
- data/public/favicon.ico +0 -0
- data/public/integrity.css +354 -0
- data/public/reset.css +7 -0
- data/spec/buildboard_spec.rb +4 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/views/_build.erb +7 -0
- data/views/_buildline.erb +5 -0
- data/views/_header.erb +16 -0
- data/views/_smallbuildline.erb +6 -0
- data/views/build.erb +4 -0
- data/views/index.erb +12 -0
- data/views/layout.erb +14 -0
- data/views/project.erb +15 -0
- metadata +101 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jacob Atzen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# buildboard
|
2
|
+
|
3
|
+
buildboard is an [Integrity][1] style dashboard meant to be used in
|
4
|
+
combination with [CI Joe][2]. It does not provide any build features only
|
5
|
+
aggregation of the output from a continuous integration system like CI Joe.
|
6
|
+
|
7
|
+
[1]: http://www.integrityapp.com
|
8
|
+
[2]: http://github.com/defunkt/cijoe
|
9
|
+
|
10
|
+
## Getting started
|
11
|
+
|
12
|
+
To get started run:
|
13
|
+
|
14
|
+
gem install buildboard
|
15
|
+
buildboard
|
16
|
+
|
17
|
+
This will provide you with a running buildboard instance on port 5678 if
|
18
|
+
nothing else is already running on that port.
|
19
|
+
|
20
|
+
## Setting up CI Joe
|
21
|
+
|
22
|
+
To start collecting data from CI Joe setup a `build-failed` and a
|
23
|
+
`build-worked` hook with the following content:
|
24
|
+
|
25
|
+
#!/bin/sh
|
26
|
+
update-buildboard --directory [path to your CI Joe working repository]
|
27
|
+
|
28
|
+
See the CI Joe documentation if you're in doubt about this.
|
29
|
+
|
30
|
+
The options for update-buildboard are:
|
31
|
+
|
32
|
+
Usage: update-buildboard [-hpd]
|
33
|
+
|
34
|
+
Specific options:
|
35
|
+
-h, --host=HOST The hostname or ip of the host running buildboard (default 127.0.0.1)
|
36
|
+
-p, --port=PORT The port buildboard is running on (default 5678)
|
37
|
+
-d, --directory=DIRECTORY The directory containing the CI Joe repository
|
38
|
+
|
39
|
+
## Inner workings
|
40
|
+
|
41
|
+
You don't need to know this if you don't care.
|
42
|
+
|
43
|
+
All build information are stored in files in $HOME/.buildboard, they're just
|
44
|
+
YAML files, go peek if you want.
|
45
|
+
|
46
|
+
The protocol for integrating with buildboard can be derived from Build#load\_file.
|
47
|
+
|
48
|
+
## Acknowledgements
|
49
|
+
|
50
|
+
buildboard is based on and/or stole from, and would like to thank:
|
51
|
+
|
52
|
+
* [Integrity][1] provided a nice design to steal
|
53
|
+
* [CI Joe][2] is the reason buildboard was made
|
54
|
+
* [Sinatra][3] is the muse that underlies it all
|
55
|
+
* [Vegas][4] for making it crazy easy to provide a stage for Sinatra
|
56
|
+
|
57
|
+
[3]: http://www.sinatrarb.com/
|
58
|
+
[4]: http://code.quirkey.com/vegas/
|
59
|
+
|
60
|
+
## Note on Patches/Pull Requests
|
61
|
+
|
62
|
+
* Fork the project.
|
63
|
+
* Make your feature addition or bug fix.
|
64
|
+
* Add tests for it. This is important so I don't break it in a
|
65
|
+
future version unintentionally.
|
66
|
+
* Commit, do not mess with rakefile, version, or history.
|
67
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
68
|
+
* Send me a pull request. Bonus points for topic branches.
|
69
|
+
|
70
|
+
## Copyright
|
71
|
+
|
72
|
+
Copyright (c) 2010 Jacob Atzen. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "buildboard"
|
8
|
+
gem.summary = %Q{Integrity style dashboard for CI Joe}
|
9
|
+
gem.description = %Q{Buildboard is an Integrity style dashboard for CI Joe}
|
10
|
+
gem.email = "jacob@incremental.dk"
|
11
|
+
gem.homepage = "http://github.com/jacobat/buildboard"
|
12
|
+
gem.authors = ["Jacob Atzen"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "buildboard #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/buildboard
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'uri'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'net/http'
|
7
|
+
require 'cijoe/build'
|
8
|
+
require 'choice'
|
9
|
+
|
10
|
+
Choice.options do
|
11
|
+
header ''
|
12
|
+
header 'Specific options:'
|
13
|
+
|
14
|
+
option :host do
|
15
|
+
short '-h'
|
16
|
+
long '--host=HOST'
|
17
|
+
desc 'The hostname or ip of the host running buildboard (default 127.0.0.1)'
|
18
|
+
default '127.0.0.1'
|
19
|
+
end
|
20
|
+
|
21
|
+
option :port do
|
22
|
+
short '-p'
|
23
|
+
long '--port=PORT'
|
24
|
+
desc 'The port buildboard is running on (default 5678)'
|
25
|
+
cast Integer
|
26
|
+
default 5678
|
27
|
+
end
|
28
|
+
|
29
|
+
option :directory do
|
30
|
+
short '-d'
|
31
|
+
long '--directory=DIRECTORY'
|
32
|
+
desc 'The directory containing the CI Joe repository'
|
33
|
+
end
|
34
|
+
|
35
|
+
separator ''
|
36
|
+
separator 'Common options: '
|
37
|
+
|
38
|
+
option :help do
|
39
|
+
long '--help'
|
40
|
+
desc 'Show this message'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
puts "You must supply a directory" and exit 1 unless Choice.choices[:directory]
|
45
|
+
|
46
|
+
build_file = File.join(Choice.choices[:directory], '.git', 'builds', 'last')
|
47
|
+
raise("Couldn't find file #{build_file}") unless File.exists?(build_file)
|
48
|
+
build = CIJoe::Build.load(build_file)
|
49
|
+
Net::HTTP.post_form(URI.parse("http://#{Choice.choices[:host]}:#{Choice.choices[:port]}/"),
|
50
|
+
{
|
51
|
+
:output => build.output,
|
52
|
+
:status => build.status == :failed ? 'failed' : 'success',
|
53
|
+
:sha => build.sha,
|
54
|
+
:started_at => build.started_at,
|
55
|
+
:finished_at => build.finished_at,
|
56
|
+
:project => build.project,
|
57
|
+
:git => `git log -1 #{build.sha}`
|
58
|
+
})
|
data/lib/buildboard.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'yaml'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
class Build < Struct.new(:sha, :started_at, :finished_at, :status, :output, :project, :path, :git)
|
7
|
+
class << self
|
8
|
+
def load_file(filename)
|
9
|
+
build_data = YAML::load_file(filename)
|
10
|
+
new(build_data['sha'],
|
11
|
+
Time.parse(build_data['started_at']),
|
12
|
+
Time.parse(build_data['finished_at']),
|
13
|
+
build_data['status'],
|
14
|
+
build_data['output'],
|
15
|
+
build_data['project'],
|
16
|
+
filename,
|
17
|
+
build_data['git'])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def css_class
|
22
|
+
status
|
23
|
+
end
|
24
|
+
|
25
|
+
def file
|
26
|
+
File.basename(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def author
|
30
|
+
git.match(/Author: (.*?) \</).to_a[1]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Builder
|
35
|
+
class << self
|
36
|
+
def projects
|
37
|
+
Dir[File.join(build_dir, '*')].map{|project|
|
38
|
+
project_name = project.gsub("#{build_dir}/", '')
|
39
|
+
last_build = Dir[File.join(project, '*')].last
|
40
|
+
Build.load_file(last_build)
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def find(projectname, filename)
|
45
|
+
Build.load_file(File.join(build_dir, sanitize_filename(projectname), sanitize_filename(filename)))
|
46
|
+
end
|
47
|
+
|
48
|
+
def builds_for(projectname)
|
49
|
+
Dir[File.join(build_dir, sanitize_filename(projectname), '*-*-*')].reverse[0..9].map{|build|
|
50
|
+
Build.load_file(build)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Borrowed from the Rails security guide:
|
55
|
+
# http://guides.rubyonrails.org/security.html#file-uploads
|
56
|
+
def sanitize_filename(filename)
|
57
|
+
name = filename.strip
|
58
|
+
# NOTE: File.basename doesn't work right with Windows paths on Unix
|
59
|
+
# get only the filename, not the whole path
|
60
|
+
name.gsub! /^.*(\\|\/)/, ''
|
61
|
+
# Finally, replace all non alphanumeric, underscore
|
62
|
+
# or periods with underscore
|
63
|
+
name.gsub! /[^\w\.\-]/, '_'
|
64
|
+
name
|
65
|
+
end
|
66
|
+
|
67
|
+
def builds
|
68
|
+
Dir[File.join(build_dir, '**', '*-*-*')].map{|build|
|
69
|
+
match = build.match(/.*\/(.*)-(.*)-(.*)/)
|
70
|
+
{ :started => Time.at(match[1].to_i),
|
71
|
+
:sha => match[2],
|
72
|
+
:status => match[3],
|
73
|
+
:integrity_status => (match[3] == 'failure' ? 'failed' : 'success')
|
74
|
+
}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_dir
|
79
|
+
File.join(ENV['HOME'], '.buildboard')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(params)
|
84
|
+
@params = params
|
85
|
+
end
|
86
|
+
|
87
|
+
def project_dir
|
88
|
+
File.join(self.class.build_dir, @params[:project])
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_file
|
92
|
+
started = Time.parse(@params[:started_at]).to_i
|
93
|
+
File.join(project_dir, "#{started}-#{@params[:sha][0..7]}-#{@params[:status]}")
|
94
|
+
end
|
95
|
+
|
96
|
+
def dump
|
97
|
+
FileUtils.mkdir_p(project_dir)
|
98
|
+
File.open(build_file, "w") do |file|
|
99
|
+
YAML::dump(@params, file)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Buildboard < Sinatra::Base
|
105
|
+
helpers do
|
106
|
+
include Rack::Utils
|
107
|
+
alias_method :h, :escape_html
|
108
|
+
end
|
109
|
+
|
110
|
+
set :public, File.dirname(__FILE__) + '/../public'
|
111
|
+
set :views, File.dirname(__FILE__) + '/../views'
|
112
|
+
|
113
|
+
get '/' do
|
114
|
+
@builds = Builder.projects
|
115
|
+
erb :index
|
116
|
+
end
|
117
|
+
|
118
|
+
get /\/(.*)\/(.*)/ do |projectname, sha|
|
119
|
+
@build = Builder.find(projectname, sha)
|
120
|
+
erb :build
|
121
|
+
end
|
122
|
+
|
123
|
+
get /\/(.*)/ do |projectname|
|
124
|
+
@builds = Builder.builds_for(projectname)
|
125
|
+
@projectname = projectname
|
126
|
+
erb :project
|
127
|
+
end
|
128
|
+
|
129
|
+
post '/' do
|
130
|
+
Builder.new(params).dump
|
131
|
+
"Alrighty, Joe"
|
132
|
+
end
|
133
|
+
end
|
data/public/buttons.css
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
/* --------------------------------------------------------------
|
2
|
+
|
3
|
+
buttons.css
|
4
|
+
* Gives you some great CSS-only buttons.
|
5
|
+
|
6
|
+
Created by Kevin Hale [particletree.com]
|
7
|
+
* particletree.com/features/rediscovering-the-button-element
|
8
|
+
|
9
|
+
See Readme.txt in this folder for instructions.
|
10
|
+
|
11
|
+
-------------------------------------------------------------- */
|
12
|
+
|
13
|
+
button {
|
14
|
+
display:block;
|
15
|
+
float:left;
|
16
|
+
margin:0 0.583em 0.667em 0;
|
17
|
+
padding:5px 10px 5px 7px; /* Links */
|
18
|
+
|
19
|
+
border:1px solid #dedede;
|
20
|
+
border-top:1px solid #eee;
|
21
|
+
border-left:1px solid #eee;
|
22
|
+
|
23
|
+
background-color:#f5f5f5;
|
24
|
+
font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
|
25
|
+
font-size:100%;
|
26
|
+
line-height:130%;
|
27
|
+
text-decoration:none;
|
28
|
+
font-weight:bold;
|
29
|
+
color:#565656;
|
30
|
+
cursor:pointer;
|
31
|
+
}
|
32
|
+
button {
|
33
|
+
width:auto;
|
34
|
+
overflow:visible;
|
35
|
+
padding:4px 10px 3px 7px; /* IE6 */
|
36
|
+
}
|
37
|
+
button[type] {
|
38
|
+
padding:4px 10px 4px 7px; /* Firefox */
|
39
|
+
line-height:17px; /* Safari */
|
40
|
+
}
|
41
|
+
*:first-child+html button[type] {
|
42
|
+
padding:4px 10px 3px 7px; /* IE7 */
|
43
|
+
}
|
44
|
+
button img {
|
45
|
+
margin:0 3px -3px 0 !important;
|
46
|
+
padding:0;
|
47
|
+
border:none;
|
48
|
+
width:16px;
|
49
|
+
height:16px;
|
50
|
+
float:none;
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
/* Button colors
|
55
|
+
-------------------------------------------------------------- */
|
56
|
+
|
57
|
+
/* Standard */
|
58
|
+
button:hover {
|
59
|
+
background-color:#dff4ff;
|
60
|
+
border:1px solid #c2e1ef;
|
61
|
+
color:#336699;
|
62
|
+
}
|
63
|
+
|
64
|
+
/* Positive */
|
65
|
+
body .positive {
|
66
|
+
color:#529214;
|
67
|
+
}
|
68
|
+
button.positive:hover {
|
69
|
+
background-color:#E6EFC2;
|
70
|
+
border:1px solid #C6D880;
|
71
|
+
color:#529214;
|
72
|
+
}
|
73
|
+
|
74
|
+
/* Negative */
|
75
|
+
body .negative {
|
76
|
+
color:#d12f19;
|
77
|
+
}
|
78
|
+
button.negative:hover {
|
79
|
+
background:#fbe3e4;
|
80
|
+
border:1px solid #fbc2c4;
|
81
|
+
color:#d12f19;
|
82
|
+
}
|
data/public/favicon.ico
ADDED
File without changes
|
@@ -0,0 +1,354 @@
|
|
1
|
+
html {
|
2
|
+
background-color: #e0e0e0; }
|
3
|
+
|
4
|
+
body {
|
5
|
+
font-size: 100%;
|
6
|
+
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
|
7
|
+
color: #333333; }
|
8
|
+
|
9
|
+
a {
|
10
|
+
color: #ed1556;
|
11
|
+
text-decoration: none; }
|
12
|
+
a:hover {
|
13
|
+
color: white;
|
14
|
+
background-color: #ed1556; }
|
15
|
+
|
16
|
+
#header, #content, #footer {
|
17
|
+
width: 40em;
|
18
|
+
margin: 0 auto;
|
19
|
+
background: #eeeeee;
|
20
|
+
padding: 0 2em;
|
21
|
+
z-index: 0;
|
22
|
+
position: relative;
|
23
|
+
font-size: 1em; }
|
24
|
+
|
25
|
+
#header {
|
26
|
+
background: white; }
|
27
|
+
#header h1 {
|
28
|
+
font-weight: bold;
|
29
|
+
font-size: 1.5em; }
|
30
|
+
#header address.watermark {
|
31
|
+
position: absolute;
|
32
|
+
font-weight: bold;
|
33
|
+
right: 3em;
|
34
|
+
top: 0;
|
35
|
+
font-size: .75em;
|
36
|
+
color: #cccccc; }
|
37
|
+
#header address.watermark a {
|
38
|
+
color: #cccccc;
|
39
|
+
font-weight: bold;
|
40
|
+
font-size: 2em; }
|
41
|
+
#header address.watermark a:hover {
|
42
|
+
background: transparent;
|
43
|
+
color: #aaaaaa; }
|
44
|
+
|
45
|
+
#content {
|
46
|
+
padding-top: 1em;
|
47
|
+
padding-bottom: 2em; }
|
48
|
+
#content strong {
|
49
|
+
font-weight: bold; }
|
50
|
+
#content em {
|
51
|
+
font-style: italic; }
|
52
|
+
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6 {
|
53
|
+
color: #4e4e4e; }
|
54
|
+
#content h1 {
|
55
|
+
font-size: 2em;
|
56
|
+
font-weight: bold;
|
57
|
+
margin-bottom: .75em;
|
58
|
+
padding: .25em 0;
|
59
|
+
line-height: 1.2;
|
60
|
+
border-bottom: 1px solid silver; }
|
61
|
+
#content h2 {
|
62
|
+
font-weight: bold;
|
63
|
+
font-size: 1.5em;
|
64
|
+
margin: 1em 0 .2em; }
|
65
|
+
#content h3 {
|
66
|
+
font-weight: bold;
|
67
|
+
font-size: 1.25em;
|
68
|
+
margin: .25em 0; }
|
69
|
+
#content h4, #content h5, #content h6 {
|
70
|
+
font-weight: bold;
|
71
|
+
margin-top: .5em; }
|
72
|
+
#content code, #content pre, #content textarea, #content input {
|
73
|
+
font-family: Monaco, Deja Vu Sans Mono, Inconsolata, Consolas, monospace; }
|
74
|
+
#content pre {
|
75
|
+
margin: .5em;
|
76
|
+
padding: .5em; }
|
77
|
+
#content form p {
|
78
|
+
margin-top: 1em;
|
79
|
+
position: relative; }
|
80
|
+
#content form p.checkbox label {
|
81
|
+
margin-top: 0 !important; }
|
82
|
+
#content form input.text, #content form textarea, #content form select {
|
83
|
+
width: 30em;
|
84
|
+
padding: .2em .4em;
|
85
|
+
color: #4e4e4e; }
|
86
|
+
#content form input.text {
|
87
|
+
height: 1.4em; }
|
88
|
+
#content form label {
|
89
|
+
float: left;
|
90
|
+
display: block;
|
91
|
+
margin-top: .5em;
|
92
|
+
width: 8em;
|
93
|
+
margin-right: .75em; }
|
94
|
+
#content form .with_errors label {
|
95
|
+
background: red;
|
96
|
+
color: white;
|
97
|
+
position: relative;
|
98
|
+
top: -.7em; }
|
99
|
+
#content form .with_errors.required label {
|
100
|
+
position: static;
|
101
|
+
margin-right: .25em;
|
102
|
+
padding: 0 .2em; }
|
103
|
+
#content form .with_errors input, #content form .with_errors textarea, #content form .with_errors select {
|
104
|
+
border: 2px solid #f22;
|
105
|
+
background: #fee;
|
106
|
+
color: #222222; }
|
107
|
+
#content form .required label {
|
108
|
+
float: none;
|
109
|
+
display: block;
|
110
|
+
width: auto;
|
111
|
+
position: relative;
|
112
|
+
font-weight: bold;
|
113
|
+
margin-top: 1em;
|
114
|
+
text-indent: -.65em; }
|
115
|
+
#content form .required label:before {
|
116
|
+
content: "* ";
|
117
|
+
color: #ed1556; }
|
118
|
+
#content form .required input.text {
|
119
|
+
width: 25.6em;
|
120
|
+
font-size: 24px;
|
121
|
+
font-weight: bold; }
|
122
|
+
#content form .normal {
|
123
|
+
margin-top: 2em; }
|
124
|
+
#content form h2.notifier label {
|
125
|
+
float: none;
|
126
|
+
width: auto;
|
127
|
+
margin-right: 0; }
|
128
|
+
#content form h2.notifier label .warning {
|
129
|
+
font-size: .5em;
|
130
|
+
font-weight: normal;
|
131
|
+
color: #999999; }
|
132
|
+
#content form fieldset {
|
133
|
+
padding-bottom: 1em;
|
134
|
+
margin-left: 1.35em;
|
135
|
+
border-bottom: 1px solid silver;
|
136
|
+
margin-bottom: 1em; }
|
137
|
+
#content form fieldset h3 {
|
138
|
+
margin-top: 1em;
|
139
|
+
margin-bottom: 0; }
|
140
|
+
#content form fieldset p.normal {
|
141
|
+
margin-top: 1em; }
|
142
|
+
#content form fieldset p label {
|
143
|
+
width: 6.7em; }
|
144
|
+
#content form p.submit {
|
145
|
+
margin-top: 2em; }
|
146
|
+
#content form p.submit:after {
|
147
|
+
display: block;
|
148
|
+
clear: both;
|
149
|
+
float: none;
|
150
|
+
content: ".";
|
151
|
+
text-indent: -9999em;
|
152
|
+
text-align: left; }
|
153
|
+
#content form p.submit.destroy button, #content form p.submit.manual-build button {
|
154
|
+
float: none;
|
155
|
+
display: inline; }
|
156
|
+
#content form p.submit.manual-build button {
|
157
|
+
margin-right: 0; }
|
158
|
+
#content #build form, #content #last_build form {
|
159
|
+
font-size: .75em; }
|
160
|
+
#content #build form p.submit, #content #last_build form p.submit {
|
161
|
+
margin: 0;
|
162
|
+
padding: 0;
|
163
|
+
position: absolute;
|
164
|
+
right: .5em;
|
165
|
+
top: 1.25em; }
|
166
|
+
#content .blank_slate p, #content .error p {
|
167
|
+
position: relative;
|
168
|
+
top: .3em; }
|
169
|
+
#content .blank_slate h1, #content .error h1 {
|
170
|
+
border-width: 0;
|
171
|
+
margin: 0;
|
172
|
+
padding: 0; }
|
173
|
+
#content .blank_slate h1 button, #content .error h1 button {
|
174
|
+
float: none;
|
175
|
+
border: 0 none;
|
176
|
+
background: transparent;
|
177
|
+
display: inline;
|
178
|
+
color: #ed1556;
|
179
|
+
padding: 0.25em 0;
|
180
|
+
margin: 0; }
|
181
|
+
#content .blank_slate h1 button:hover, #content .error h1 button:hover {
|
182
|
+
background: #ed1556;
|
183
|
+
color: white; }
|
184
|
+
#content .error dt {
|
185
|
+
margin-top: 1.4em;
|
186
|
+
margin-bottom: .3em;
|
187
|
+
font-size: 1.75em;
|
188
|
+
font-family: Georgia, Times New Roman, serif; }
|
189
|
+
#content .error dd {
|
190
|
+
line-height: 1.4; }
|
191
|
+
#content .error .backtrace {
|
192
|
+
margin: 1em 0;
|
193
|
+
overflow: "scroll";
|
194
|
+
height: 30em;
|
195
|
+
border: 1px solid silver;
|
196
|
+
line-height: 1.6; }
|
197
|
+
#content #projects {
|
198
|
+
margin: 1em 0 2em;
|
199
|
+
border-top: 1px solid silver; }
|
200
|
+
#content #projects li {
|
201
|
+
position: relative;
|
202
|
+
border-bottom: 1px solid silver; }
|
203
|
+
#content #projects li.odd {
|
204
|
+
background: #e6e6e6; }
|
205
|
+
#content #projects li a {
|
206
|
+
font-size: 2em;
|
207
|
+
padding: .25em;
|
208
|
+
line-height: 1.2;
|
209
|
+
font-weight: bold;
|
210
|
+
display: block; }
|
211
|
+
#content #projects li a.success {
|
212
|
+
color: #337022; }
|
213
|
+
#content #projects li a.failed {
|
214
|
+
color: #ff1100; }
|
215
|
+
#content #projects li a.building {
|
216
|
+
color: #777777; }
|
217
|
+
#content #projects li .meta {
|
218
|
+
position: absolute;
|
219
|
+
right: .6em;
|
220
|
+
top: 1.5em;
|
221
|
+
font-size: 0.8em;
|
222
|
+
color: #999999;
|
223
|
+
text-align: right; }
|
224
|
+
#content #projects li.success .meta {
|
225
|
+
color: #337022; }
|
226
|
+
#content #projects li.failed .meta {
|
227
|
+
color: #ff1100; }
|
228
|
+
#content #projects li.building .meta {
|
229
|
+
color: #777777; }
|
230
|
+
#content #previous_builds li a {
|
231
|
+
display: block;
|
232
|
+
padding: .25em;
|
233
|
+
margin-bottom: .25em;
|
234
|
+
border-width: 1px;
|
235
|
+
border-style: solid; }
|
236
|
+
#content #previous_builds li a strong {
|
237
|
+
font-size: 1.3em; }
|
238
|
+
#content #previous_builds li a .attribution {
|
239
|
+
font-size: .9em; }
|
240
|
+
#content #projects li.success a, #content #previous_builds li.success a {
|
241
|
+
background-color: #bbf8aa;
|
242
|
+
border-color: #99d688;
|
243
|
+
color: #337022; }
|
244
|
+
#content #projects li.success a .attribution, #content #previous_builds li.success a .attribution {
|
245
|
+
color: #77b466; }
|
246
|
+
#content #projects li.success a:hover, #content #previous_builds li.success a:hover {
|
247
|
+
background-color: #ddffcc; }
|
248
|
+
#content #projects li.failed a, #content #previous_builds li.failed a {
|
249
|
+
background-color: #ffbbaa;
|
250
|
+
border-color: #dd9988;
|
251
|
+
color: #ff1100; }
|
252
|
+
#content #projects li.failed a .attribution, #content #previous_builds li.failed a .attribution {
|
253
|
+
color: #bb7766; }
|
254
|
+
#content #projects li.failed a:hover, #content #previous_builds li.failed a:hover {
|
255
|
+
background-color: #ffddcc; }
|
256
|
+
#content #projects li.building a, #content #previous_builds li.building a {
|
257
|
+
background-color: #cccccc;
|
258
|
+
border-color: #aaaaaa;
|
259
|
+
color: #777777; }
|
260
|
+
#content #projects li.building a .attribution, #content #previous_builds li.building a .attribution {
|
261
|
+
color: #888888; }
|
262
|
+
#content #projects li.building a:hover, #content #previous_builds li.building a:hover {
|
263
|
+
background-color: #eeeeee; }
|
264
|
+
#content #build, #content #last_build {
|
265
|
+
position: relative; }
|
266
|
+
#content #build h1, #content #build blockquote, #content #last_build h1, #content #last_build blockquote {
|
267
|
+
border-width: 0 1px;
|
268
|
+
border-style: solid; }
|
269
|
+
#content #build h1, #content #last_build h1 {
|
270
|
+
border-top-width: 1px; }
|
271
|
+
#content #build blockquote, #content #last_build blockquote {
|
272
|
+
bottom-bottom-width: 1px;
|
273
|
+
line-height: 1.4; }
|
274
|
+
#content #build.success h1, #content #build.success blockquote, #content #last_build.success h1, #content #last_build.success blockquote {
|
275
|
+
background-color: #bbf8aa;
|
276
|
+
border-color: #99d688 #ccffbb #ccffbb #99d688; }
|
277
|
+
#content #build.success h1, #content #last_build.success h1 {
|
278
|
+
color: #337022; }
|
279
|
+
#content #build.success .meta, #content #last_build.success .meta {
|
280
|
+
color: #77b466; }
|
281
|
+
#content #build.failed h1, #content #build.failed blockquote, #content #last_build.failed h1, #content #last_build.failed blockquote {
|
282
|
+
background-color: #ffbbaa;
|
283
|
+
border-color: #dd9988 #ffccbb #ffccbb #dd9988; }
|
284
|
+
#content #build.failed h1, #content #last_build.failed h1 {
|
285
|
+
color: #ff1100; }
|
286
|
+
#content #build.failed .meta, #content #last_build.failed .meta {
|
287
|
+
color: #bb7766; }
|
288
|
+
#content #build.building h1, #content #build.building blockquote, #content #last_build.building h1, #content #last_build.building blockquote {
|
289
|
+
background-color: #cccccc;
|
290
|
+
border-color: #aaaaaa #dddddd #dddddd #aaaaaa; }
|
291
|
+
#content #build.building h1, #content #last_build.building h1 {
|
292
|
+
color: #777777; }
|
293
|
+
#content #build.building .meta, #content #last_build.building .meta {
|
294
|
+
color: #333333; }
|
295
|
+
#content #build h1, #content #last_build h1 {
|
296
|
+
margin-top: .5em;
|
297
|
+
margin-bottom: 0;
|
298
|
+
padding: .25em;
|
299
|
+
color: #337022; }
|
300
|
+
#content #build blockquote, #content #last_build blockquote {
|
301
|
+
padding: .75em;
|
302
|
+
margin-bottom: 2em; }
|
303
|
+
#content #build blockquote .meta, #content #last_build blockquote .meta {
|
304
|
+
margin-top: 1em;
|
305
|
+
display: block;
|
306
|
+
font-size: .9em; }
|
307
|
+
#content #build pre.output, #content #last_build pre.output {
|
308
|
+
background: #111;
|
309
|
+
color: #fff;
|
310
|
+
padding: .5em;
|
311
|
+
overflow: auto;
|
312
|
+
max-height: 50em;
|
313
|
+
font-size: .825em; }
|
314
|
+
#content #build pre.output .color30, #content #last_build pre.output .color30 {
|
315
|
+
color: #333; }
|
316
|
+
#content #build pre.output .color31, #content #last_build pre.output .color31 {
|
317
|
+
color: #e33; }
|
318
|
+
#content #build pre.output .color32, #content #last_build pre.output .color32 {
|
319
|
+
color: #3e3; }
|
320
|
+
#content #build pre.output .color33, #content #last_build pre.output .color33 {
|
321
|
+
color: #ee3; }
|
322
|
+
#content #build pre.output .color34, #content #last_build pre.output .color34 {
|
323
|
+
color: #33e; }
|
324
|
+
#content #build pre.output .color35, #content #last_build pre.output .color35 {
|
325
|
+
color: #e3e; }
|
326
|
+
#content #build pre.output .color36, #content #last_build pre.output .color36 {
|
327
|
+
color: #3ee; }
|
328
|
+
#content #build pre.output .color37, #content #last_build pre.output .color37 {
|
329
|
+
color: #fff; }
|
330
|
+
#content #push_path {
|
331
|
+
display: block;
|
332
|
+
margin-top: 1em;
|
333
|
+
margin-left: 2em; }
|
334
|
+
|
335
|
+
a.success {
|
336
|
+
color: #bbf8aa; }
|
337
|
+
a.success:hover {
|
338
|
+
background-color: #bbf8aa;
|
339
|
+
color: white; }
|
340
|
+
a.failed {
|
341
|
+
color: #ffbbaa; }
|
342
|
+
a.failed:hover {
|
343
|
+
background-color: #ffbbaa;
|
344
|
+
color: white; }
|
345
|
+
|
346
|
+
#footer {
|
347
|
+
padding: 1.5em 2.5em;
|
348
|
+
border-top: 1px solid #ccc;
|
349
|
+
font-size: .8em;
|
350
|
+
width: 50em !important;
|
351
|
+
color: #666;
|
352
|
+
text-align: right; }
|
353
|
+
#footer strong {
|
354
|
+
font-weight: bold; }
|
data/public/reset.css
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
/*
|
2
|
+
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
|
3
|
+
Code licensed under the BSD License:
|
4
|
+
http://developer.yahoo.net/yui/license.txt
|
5
|
+
version: 2.5.2
|
6
|
+
*/
|
7
|
+
html{color:#000;background:#FFF;}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;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
data/views/_build.erb
ADDED
data/views/_header.erb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
<div id='header'>
|
2
|
+
<h1>
|
3
|
+
<% header_items.each do |item| %>
|
4
|
+
<% if item == header_items.last %>
|
5
|
+
<%= item %>
|
6
|
+
<% elsif item == header_items.first %>
|
7
|
+
<a href="/"><%= item %></a> /
|
8
|
+
<% else %>
|
9
|
+
<a href="/<%= item %>"><%= item %></a> /
|
10
|
+
<% end%>
|
11
|
+
<% end %>
|
12
|
+
</h1>
|
13
|
+
<address class='watermark'>
|
14
|
+
<a href="/">buildboard</a>
|
15
|
+
</address>
|
16
|
+
</div>
|
data/views/build.erb
ADDED
data/views/index.erb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
<%= erb(:_header, :locals => { :header_items => [ 'projects' ]})%>
|
2
|
+
<div id='content'>
|
3
|
+
<ul id='projects'>
|
4
|
+
<% @builds.each do |build| %>
|
5
|
+
<%= erb(:_buildline,
|
6
|
+
:layout => false,
|
7
|
+
:locals => { :build => build,
|
8
|
+
:title => build.project,
|
9
|
+
:link => build.project }) %>
|
10
|
+
<% end %>
|
11
|
+
</ul>
|
12
|
+
</div>
|
data/views/layout.erb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
2
|
+
<html lang='en' xml:lang='en' xmlns='http://www.w3.org/1999/xhtml'>
|
3
|
+
<head>
|
4
|
+
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
5
|
+
<meta content='en' http-equiv='Content-Language' />
|
6
|
+
<title><%= @projectname %> | buildboard</title>
|
7
|
+
<link href='/reset.css' rel='stylesheet' type='text/css' />
|
8
|
+
<link href='/buttons.css' rel='stylesheet' type='text/css' />
|
9
|
+
<link href='/integrity.css' rel='stylesheet' type='text/css' />
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<%= yield %>
|
13
|
+
</body>
|
14
|
+
</html>
|
data/views/project.erb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
<%= erb(:_header, :locals => { :header_items => [ 'projects', @projectname] })%>
|
2
|
+
<div id='content'>
|
3
|
+
<% build = @builds.shift %>
|
4
|
+
<%= erb(:_build, :locals => { :build => build }) %>
|
5
|
+
<h2>Builds</h2>
|
6
|
+
<ul id='previous_builds'>
|
7
|
+
<% @builds.each do |build| %>
|
8
|
+
<%= erb(:_smallbuildline,
|
9
|
+
:layout => false,
|
10
|
+
:locals => { :build => build,
|
11
|
+
:title => build.sha[0..7],
|
12
|
+
:link => "#{build.project}/#{build.file}" }) %>
|
13
|
+
<% end %>
|
14
|
+
</ul>
|
15
|
+
</div>
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: buildboard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jacob Atzen
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-01 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 2
|
30
|
+
- 9
|
31
|
+
version: 1.2.9
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
description: Buildboard is an Integrity style dashboard for CI Joe
|
35
|
+
email: jacob@incremental.dk
|
36
|
+
executables:
|
37
|
+
- buildboard
|
38
|
+
- update-buildboard
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.md
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- bin/buildboard
|
52
|
+
- bin/update-buildboard
|
53
|
+
- lib/buildboard.rb
|
54
|
+
- public/buttons.css
|
55
|
+
- public/favicon.ico
|
56
|
+
- public/integrity.css
|
57
|
+
- public/reset.css
|
58
|
+
- spec/buildboard_spec.rb
|
59
|
+
- spec/spec.opts
|
60
|
+
- spec/spec_helper.rb
|
61
|
+
- views/_build.erb
|
62
|
+
- views/_buildline.erb
|
63
|
+
- views/_header.erb
|
64
|
+
- views/_smallbuildline.erb
|
65
|
+
- views/build.erb
|
66
|
+
- views/index.erb
|
67
|
+
- views/layout.erb
|
68
|
+
- views/project.erb
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: http://github.com/jacobat/buildboard
|
71
|
+
licenses: []
|
72
|
+
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options:
|
75
|
+
- --charset=UTF-8
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
requirements: []
|
93
|
+
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.3.6
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Integrity style dashboard for CI Joe
|
99
|
+
test_files:
|
100
|
+
- spec/buildboard_spec.rb
|
101
|
+
- spec/spec_helper.rb
|