gitauth 0.0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +661 -0
- data/README.rdoc +141 -0
- data/USAGE +70 -0
- data/bin/gitauth +261 -0
- data/bin/gitauth-shell +30 -0
- data/config.ru +22 -0
- data/lib/gitauth.rb +100 -0
- data/lib/gitauth/apache_authentication.rb +64 -0
- data/lib/gitauth/auth_setup_middleware.rb +44 -0
- data/lib/gitauth/client.rb +95 -0
- data/lib/gitauth/command.rb +101 -0
- data/lib/gitauth/group.rb +87 -0
- data/lib/gitauth/message.rb +69 -0
- data/lib/gitauth/repo.rb +155 -0
- data/lib/gitauth/saveable_class.rb +54 -0
- data/lib/gitauth/user.rb +135 -0
- data/lib/gitauth/web_app.rb +310 -0
- data/public/gitauth.css +316 -0
- data/public/gitauth.js +17 -0
- data/public/jquery.js +19 -0
- data/resources/messages.yml +9 -0
- data/views/auth_setup.erb +27 -0
- data/views/clone_repo.erb +24 -0
- data/views/group.erb +24 -0
- data/views/index.erb +88 -0
- data/views/layout.erb +27 -0
- data/views/repo.erb +57 -0
- data/views/user.erb +51 -0
- metadata +152 -0
@@ -0,0 +1,310 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 Brown Beagle Software
|
3
|
+
# Copyright (C) 2009 Darcy Laycock <sutto@sutto.net>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#++
|
18
|
+
|
19
|
+
require 'rack'
|
20
|
+
require 'sinatra'
|
21
|
+
require 'digest/sha2'
|
22
|
+
|
23
|
+
module GitAuth
|
24
|
+
class WebApp < Sinatra::Base
|
25
|
+
include GitAuth::Loggable
|
26
|
+
|
27
|
+
cattr_accessor :current_server
|
28
|
+
|
29
|
+
def self.has_auth?
|
30
|
+
username = GitAuth::Settings["web_username"]
|
31
|
+
password = GitAuth::Settings["web_password_hash"]
|
32
|
+
!(username.blank? || password.blank?)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.update_auth
|
36
|
+
raw_username = Readline.readline('GitAuth Username (default is \'gitauth\'): ')
|
37
|
+
raw_username = 'gitauth' if raw_username.blank?
|
38
|
+
raw_password = ''
|
39
|
+
while raw_password.blank?
|
40
|
+
system "stty -echo"
|
41
|
+
raw_password = Readline.readline('GitAuth Password: ')
|
42
|
+
system "stty echo"
|
43
|
+
print "\n"
|
44
|
+
puts "You need to provide a password, please try again" if raw_password.blank?
|
45
|
+
end
|
46
|
+
password_confirmation = nil
|
47
|
+
while password_confirmation != raw_password
|
48
|
+
system "stty -echo"
|
49
|
+
password_confirmation = Readline.readline('Confirm Password: ')
|
50
|
+
system "stty echo"
|
51
|
+
print "\n"
|
52
|
+
puts "The confirmation doesn't match your password, please try again" if raw_password != password_confirmation
|
53
|
+
end
|
54
|
+
GitAuth::Settings.update!({
|
55
|
+
:web_username => raw_username,
|
56
|
+
:web_password_hash => Digest::SHA256.hexdigest(raw_password)
|
57
|
+
})
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.check_auth
|
61
|
+
GitAuth.prepare
|
62
|
+
if !has_auth?
|
63
|
+
if $stderr.tty?
|
64
|
+
logger.verbose = true
|
65
|
+
puts "For gitauth to continue, you need to provide a username and password."
|
66
|
+
update_auth
|
67
|
+
else
|
68
|
+
logger.fatal "You need to provide a username and password for GitAuth to function; Please run 'gitauth webapp` once"
|
69
|
+
exit!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.run(options = {})
|
75
|
+
check_auth
|
76
|
+
set options
|
77
|
+
handler = detect_rack_handler
|
78
|
+
handler_name = handler.name.gsub(/.*::/, '')
|
79
|
+
logger.info "Starting up web server on #{port}"
|
80
|
+
handler.run self, :Host => host, :Port => port do |server|
|
81
|
+
GitAuth::WebApp.current_server = server
|
82
|
+
set :running, true
|
83
|
+
end
|
84
|
+
rescue Errno::EADDRINUSE => e
|
85
|
+
logger.fatal "Server is already running on port #{port}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.stop
|
89
|
+
if current_server.present?
|
90
|
+
current_server.respond_to?(:stop!) ? current_server.stop! : current_server.stop
|
91
|
+
end
|
92
|
+
exit!
|
93
|
+
logger.debug "Stopped Server."
|
94
|
+
end
|
95
|
+
|
96
|
+
unless GitAuth::ApacheAuthentication.setup?
|
97
|
+
|
98
|
+
use GitAuth::AuthSetupMiddleware
|
99
|
+
|
100
|
+
use Rack::Auth::Basic do |username, password|
|
101
|
+
[username, Digest::SHA256.hexdigest(password)] == [GitAuth::Settings["web_username"], GitAuth::Settings["web_password_hash"]]
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
configure do
|
107
|
+
set :port, 8998
|
108
|
+
set :views, GitAuth::BASE_DIR.join("views")
|
109
|
+
set :public, GitAuth::BASE_DIR.join("public")
|
110
|
+
set :static, true
|
111
|
+
set :methodoverride, true
|
112
|
+
end
|
113
|
+
|
114
|
+
before { GitAuth.reload_models! }
|
115
|
+
|
116
|
+
helpers do
|
117
|
+
include Rack::Utils
|
118
|
+
alias_method :h, :escape_html
|
119
|
+
|
120
|
+
def link_to(text, link)
|
121
|
+
"<a href='#{link}'>#{text}</a>"
|
122
|
+
end
|
123
|
+
|
124
|
+
def delete_link(text, url)
|
125
|
+
id = "deleteable-#{Digest::SHA256.hexdigest(url.to_s)[0, 6]}"
|
126
|
+
html = "<div class='deletable-container' style='display: none; margin: 0; padding: 0;'>"
|
127
|
+
html << "<form method='post' action='#{url}' id='#{id}'>"
|
128
|
+
html << "<input name='_method' type='hidden' value='delete' />"
|
129
|
+
html << "</form></div>"
|
130
|
+
html << "<a href='#' onclick='if(confirm(\"Are you sure you want to do that? Deletion can not be reversed.\")) $(\"##{id}\").submit(); return false;'>#{text}</a>"
|
131
|
+
return html
|
132
|
+
end
|
133
|
+
|
134
|
+
def auto_link(member)
|
135
|
+
member = member.to_s
|
136
|
+
url = (member[0] == ?@ ? "/groups/#{URI.encode(member[1..-1])}" : "/users/#{URI.encode(member)}")
|
137
|
+
return link_to(member, url)
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
get '/' do
|
143
|
+
@repos = GitAuth::Repo.all
|
144
|
+
@users = GitAuth::User.all
|
145
|
+
@groups = GitAuth::Group.all
|
146
|
+
erb :index
|
147
|
+
end
|
148
|
+
|
149
|
+
# Listing / Index Page
|
150
|
+
|
151
|
+
get '/repos/:name' do
|
152
|
+
@repo = GitAuth::Repo.get(params[:name])
|
153
|
+
if @repo.nil?
|
154
|
+
redirect root_with_message("The given repository couldn't be found.")
|
155
|
+
else
|
156
|
+
read_perms, write_perms = (@repo.permissions[:read]||[]), (@repo.permissions[:write]||[])
|
157
|
+
@all_access = read_perms & write_perms
|
158
|
+
@read_only = read_perms - @all_access
|
159
|
+
@write_only = write_perms - @all_access
|
160
|
+
erb :repo
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
get '/users/:name' do
|
165
|
+
@user = GitAuth::User.get(params[:name])
|
166
|
+
if @user.nil?
|
167
|
+
redirect root_with_message("The given user couldn't be found.")
|
168
|
+
else
|
169
|
+
repos = GitAuth::Repo.all
|
170
|
+
read_perms = repos.select { |r| r.readable_by?(@user) }
|
171
|
+
write_perms = repos.select { |r| r.writeable_by?(@user) }
|
172
|
+
@all_access = read_perms & write_perms
|
173
|
+
@read_only = read_perms - @all_access
|
174
|
+
@write_only = write_perms - @all_access
|
175
|
+
@groups = GitAuth::Group.all.select { |g| g.member?(@user) }
|
176
|
+
erb :user
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
get '/groups/:name' do
|
181
|
+
@group = GitAuth::Group.get(params[:name])
|
182
|
+
if @group.nil?
|
183
|
+
redirect root_with_message("The given group could not be found.")
|
184
|
+
else
|
185
|
+
erb :group
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Create and update repos
|
190
|
+
|
191
|
+
post '/repos' do
|
192
|
+
name = params[:repo][:name]
|
193
|
+
path = params[:repo][:path]
|
194
|
+
path = name if path.to_s.strip.empty?
|
195
|
+
if repo = GitAuth::Repo.create(name, path)
|
196
|
+
make_empty = (params[:repo][:make_empty] == "1")
|
197
|
+
repo.make_empty! if make_empty
|
198
|
+
if repo.execute_post_create_hook!
|
199
|
+
redirect "/?repo_name=#{URI.encode(name)}&made_empty=#{make_empty ? "yes" : "no"}"
|
200
|
+
else
|
201
|
+
redirect root_with_message("Repository added but the post-create hook exited unsuccessfully.")
|
202
|
+
end
|
203
|
+
else
|
204
|
+
redirect root_with_message("There was an error adding the repository.")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
post '/repos/:name' do
|
209
|
+
repo = GitAuth::Repo.get(params[:name])
|
210
|
+
if repo.nil?
|
211
|
+
redirect root_with_message("The given repository couldn't be found.")
|
212
|
+
else
|
213
|
+
new_permissions = Hash.new([])
|
214
|
+
[:all, :read, :write].each do |k|
|
215
|
+
if params[:repo][k]
|
216
|
+
perm_lines = params[:repo][k].to_s.split("\n")
|
217
|
+
new_permissions[k] = perm_lines.map do |l|
|
218
|
+
i = GitAuth.get_user_or_group(l.strip)
|
219
|
+
i.nil? ? nil : i.to_s
|
220
|
+
end.compact
|
221
|
+
end
|
222
|
+
end
|
223
|
+
all = new_permissions.delete(:all)
|
224
|
+
new_permissions[:read] |= all
|
225
|
+
new_permissions[:write] |= all
|
226
|
+
new_permissions.each_value { |v| v.uniq! }
|
227
|
+
repo.permissions = new_permissions
|
228
|
+
GitAuth::Repo.save!
|
229
|
+
redirect "/repos/#{URI.encode(repo.name)}"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
delete '/repos/:name' do
|
234
|
+
repo = GitAuth::Repo.get(params[:name])
|
235
|
+
if repo.nil?
|
236
|
+
redirect root_with_message("The given repository couldn't be found.")
|
237
|
+
else
|
238
|
+
repo.destroy!
|
239
|
+
redirect root_with_message("Repository removed.")
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Create, delete and update users
|
244
|
+
|
245
|
+
post '/users' do
|
246
|
+
name = params[:user][:name]
|
247
|
+
admin = params[:user][:admin].to_s == "1"
|
248
|
+
key = params[:user][:key]
|
249
|
+
if GitAuth::User.create(name, admin, key)
|
250
|
+
redirect root_with_message("User Added")
|
251
|
+
else
|
252
|
+
redirect root_with_message("There was an error adding the requested user.")
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
delete '/users/:name' do
|
257
|
+
user = GitAuth::User.get(params[:name])
|
258
|
+
if user.nil?
|
259
|
+
redirect root_with_message("The specified user couldn't be found.")
|
260
|
+
else
|
261
|
+
user.destroy!
|
262
|
+
redirect root_with_message("User removed.")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Create and Update Groups
|
267
|
+
|
268
|
+
post '/groups' do
|
269
|
+
if GitAuth::Group.create(params[:group][:name])
|
270
|
+
redirect root_with_message("Group added")
|
271
|
+
else
|
272
|
+
redirect root_with_message("There was an error adding the requested group.")
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
post '/groups/:name' do
|
277
|
+
group = GitAuth::Group.get(params[:name])
|
278
|
+
if group.nil?
|
279
|
+
redirect root_with_message("The specified group couldn't be found.")
|
280
|
+
else
|
281
|
+
if params[:group][:members]
|
282
|
+
member_lines = params[:group][:members].to_s.split("\n")
|
283
|
+
group.members = member_lines.map do |l|
|
284
|
+
i = GitAuth.get_user_or_group(l.strip)
|
285
|
+
i.nil? ? nil : i.to_s
|
286
|
+
end.compact - [group.to_s]
|
287
|
+
GitAuth::Group.save!
|
288
|
+
end
|
289
|
+
redirect "/groups/#{URI.encode(group.name)}"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
delete '/groups/:name' do
|
294
|
+
group = GitAuth::Group.get(params[:name])
|
295
|
+
if group.nil?
|
296
|
+
redirect root_with_message("The specified group couldn't be found.")
|
297
|
+
else
|
298
|
+
group.destroy!
|
299
|
+
redirect root_with_message("Group removed.")
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Misc Helpers
|
304
|
+
|
305
|
+
def root_with_message(message)
|
306
|
+
"/?message=#{URI.encode(message)}"
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
end
|
data/public/gitauth.css
ADDED
@@ -0,0 +1,316 @@
|
|
1
|
+
/* Reset */
|
2
|
+
html, body, div, span, applet, object, iframe,
|
3
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
4
|
+
a, abbr, acronym, address, big, cite, code,
|
5
|
+
del, dfn, em, font, img, ins, kbd, q, s, samp,
|
6
|
+
small, strike, strong, sub, sup, tt, var,
|
7
|
+
dl, dt, dd, ol, ul, li,
|
8
|
+
fieldset, form, label, legend,
|
9
|
+
table, caption, tbody, tfoot, thead, tr, th, td {
|
10
|
+
margin: 0;
|
11
|
+
padding: 0;
|
12
|
+
border: 0;
|
13
|
+
outline: 0;
|
14
|
+
font-weight: inherit;
|
15
|
+
font-style: inherit;
|
16
|
+
font-size: 100%;
|
17
|
+
font-family: inherit;
|
18
|
+
vertical-align: baseline;
|
19
|
+
}
|
20
|
+
/* remember to define focus styles! */
|
21
|
+
:focus {
|
22
|
+
outline: 0;
|
23
|
+
}
|
24
|
+
body {
|
25
|
+
line-height: 1;
|
26
|
+
color: black;
|
27
|
+
background: white;
|
28
|
+
}
|
29
|
+
ol, ul {
|
30
|
+
list-style: none;
|
31
|
+
}
|
32
|
+
/* tables still need 'cellspacing="0"' in the markup */
|
33
|
+
table {
|
34
|
+
border-collapse: separate;
|
35
|
+
border-spacing: 0;
|
36
|
+
}
|
37
|
+
caption, th, td {
|
38
|
+
text-align: left;
|
39
|
+
font-weight: normal;
|
40
|
+
}
|
41
|
+
blockquote:before, blockquote:after,
|
42
|
+
q:before, q:after {
|
43
|
+
content: "";
|
44
|
+
}
|
45
|
+
blockquote, q {
|
46
|
+
quotes: "" "";
|
47
|
+
}
|
48
|
+
|
49
|
+
/* Other Stuff */
|
50
|
+
|
51
|
+
body {
|
52
|
+
background: #222;
|
53
|
+
}
|
54
|
+
|
55
|
+
#container {
|
56
|
+
width: 640px;
|
57
|
+
margin: 2em auto;
|
58
|
+
padding: 1em;
|
59
|
+
background: #FFF;
|
60
|
+
-moz-border-radius: 0.5em;
|
61
|
+
-webkit-border-radius: 0.5em;
|
62
|
+
border-radius: 0.5em;
|
63
|
+
}
|
64
|
+
|
65
|
+
#header {
|
66
|
+
text-align: center;
|
67
|
+
margin: 0.5em 0 1em;
|
68
|
+
}
|
69
|
+
|
70
|
+
#header h1 {
|
71
|
+
font-size: 2em;
|
72
|
+
font-weight: bold;
|
73
|
+
}
|
74
|
+
|
75
|
+
#header h1 a {
|
76
|
+
text-decoration: none;
|
77
|
+
color: #000;
|
78
|
+
}
|
79
|
+
|
80
|
+
#footer {
|
81
|
+
color: #777;
|
82
|
+
text-align: center;
|
83
|
+
margin: 1em 0 0;
|
84
|
+
}
|
85
|
+
|
86
|
+
.section {
|
87
|
+
margin: 0.5em 0 1.5em;
|
88
|
+
}
|
89
|
+
|
90
|
+
.section h2 {
|
91
|
+
padding: 0.5em;
|
92
|
+
text-transform: uppercase;
|
93
|
+
background: #EEE;
|
94
|
+
border-top: 0.1em solid #999;
|
95
|
+
border-bottom: 0.1em solid #999;
|
96
|
+
font-weight: bold;
|
97
|
+
font-size: 1.25em;
|
98
|
+
color: #333;
|
99
|
+
}
|
100
|
+
|
101
|
+
.section h2 a {
|
102
|
+
text-decoration: none;
|
103
|
+
color: #233967;
|
104
|
+
margin-left: 0.15em;
|
105
|
+
}
|
106
|
+
|
107
|
+
form {
|
108
|
+
background: #FDFFE9;
|
109
|
+
border: 0.5em solid #F0EBD4;
|
110
|
+
padding: 0.5em;
|
111
|
+
margin: 1em;
|
112
|
+
}
|
113
|
+
|
114
|
+
.hidden {
|
115
|
+
display: none;
|
116
|
+
}
|
117
|
+
|
118
|
+
form .row {
|
119
|
+
margin: 0 0.5em 0.5em;
|
120
|
+
line-height: 1.8em;
|
121
|
+
display: inline-block;
|
122
|
+
display: block;
|
123
|
+
}
|
124
|
+
|
125
|
+
form .row:after {
|
126
|
+
clear: both;
|
127
|
+
content: '.';
|
128
|
+
display: block;
|
129
|
+
visibility: hidden;
|
130
|
+
height: 0;
|
131
|
+
}
|
132
|
+
|
133
|
+
form .row label {
|
134
|
+
display: block;
|
135
|
+
float: left;
|
136
|
+
zoom: 1;
|
137
|
+
width: 30%;
|
138
|
+
font-weight: bold;
|
139
|
+
font-size: 1.1em;
|
140
|
+
padding-top: 0.15em;
|
141
|
+
}
|
142
|
+
|
143
|
+
form .row input, form .row textarea {
|
144
|
+
font-size: 1.1em;
|
145
|
+
padding: 0.25em;
|
146
|
+
border: 0.15em solid #AAA;
|
147
|
+
}
|
148
|
+
|
149
|
+
form .row input {
|
150
|
+
width: 60%;
|
151
|
+
}
|
152
|
+
|
153
|
+
form .row input:focus, form .row textarea:focus {
|
154
|
+
border-color: #3E5299;
|
155
|
+
}
|
156
|
+
|
157
|
+
form .row p.hint {
|
158
|
+
color: #24813D;
|
159
|
+
padding-left: 30%;
|
160
|
+
}
|
161
|
+
|
162
|
+
form .buttons {
|
163
|
+
text-align: center;
|
164
|
+
padding: 0.75em;
|
165
|
+
}
|
166
|
+
|
167
|
+
form .buttons a {
|
168
|
+
text-decoration: none;
|
169
|
+
color: #204B99;
|
170
|
+
}
|
171
|
+
|
172
|
+
div.message {
|
173
|
+
font-size: 1.2em;
|
174
|
+
text-align: center;
|
175
|
+
padding: 0.5em 1em 1.5em;
|
176
|
+
color: #DE701E;
|
177
|
+
}
|
178
|
+
|
179
|
+
/* Listings */
|
180
|
+
|
181
|
+
.section ul {
|
182
|
+
line-height: 1.8em;
|
183
|
+
margin: 0.5em 1em;
|
184
|
+
font-family: Helvetica, Arial, Sans-Serif;
|
185
|
+
}
|
186
|
+
|
187
|
+
.section ul li {
|
188
|
+
padding: 0.5em 0;
|
189
|
+
color: #333;
|
190
|
+
}
|
191
|
+
|
192
|
+
.section ul li span.tag {
|
193
|
+
display: inline-block;
|
194
|
+
width: 4em;
|
195
|
+
color: white;
|
196
|
+
background: #666;
|
197
|
+
font-size: 0.65em;
|
198
|
+
text-transform: uppercase;
|
199
|
+
font-weight: bold;
|
200
|
+
text-align: center;
|
201
|
+
padding: 0 0.5em;
|
202
|
+
margin-right: 0.5em;
|
203
|
+
-moz-border-radius: 0.6em;
|
204
|
+
-webkit-border-radius: 0.6em;
|
205
|
+
font-family: Helvetica, Arial, Sans-Serif;
|
206
|
+
}
|
207
|
+
|
208
|
+
#users .tag.admin {
|
209
|
+
background: #163F10;
|
210
|
+
}
|
211
|
+
|
212
|
+
#users .tag.user {
|
213
|
+
background: #141A3F;
|
214
|
+
}
|
215
|
+
|
216
|
+
#repositories .tag.repo {
|
217
|
+
background: #720E12;
|
218
|
+
}
|
219
|
+
|
220
|
+
#groups .tag.group {
|
221
|
+
background: #17768A;
|
222
|
+
}
|
223
|
+
|
224
|
+
.section ul li a, #repository-details a {
|
225
|
+
text-decoration: none;
|
226
|
+
color: #395F99;
|
227
|
+
}
|
228
|
+
|
229
|
+
/* View Stuff */
|
230
|
+
|
231
|
+
.view h2 {
|
232
|
+
margin: 0 0 1em;
|
233
|
+
text-align: center;
|
234
|
+
font-weight: bold;
|
235
|
+
font-size: 1.3em;
|
236
|
+
color: #333;
|
237
|
+
border-top: 0.1em solid #DDD;
|
238
|
+
border-bottom: 0.1em solid #DDD;
|
239
|
+
padding: 0.5em;
|
240
|
+
}
|
241
|
+
|
242
|
+
.view h3 {
|
243
|
+
margin: 0.5em 1em;
|
244
|
+
font-weight: bold;
|
245
|
+
color: #555;
|
246
|
+
font-size: 1.2em;
|
247
|
+
}
|
248
|
+
|
249
|
+
.view ul {
|
250
|
+
margin: 0.5em 2em;
|
251
|
+
line-height: 1.8em;
|
252
|
+
font-size: 1.1em;
|
253
|
+
list-style: disc inside;
|
254
|
+
}
|
255
|
+
|
256
|
+
.view ul li {
|
257
|
+
}
|
258
|
+
|
259
|
+
.view ul li.empty {
|
260
|
+
color: #4A5258;
|
261
|
+
}
|
262
|
+
|
263
|
+
br.clear { clear: both; }
|
264
|
+
|
265
|
+
.view ul li a {
|
266
|
+
text-decoration: none;
|
267
|
+
color: #416999;
|
268
|
+
}
|
269
|
+
|
270
|
+
#repository-details {
|
271
|
+
line-height: 1.8em;
|
272
|
+
padding: 0.5em 1em;
|
273
|
+
margin-bottom: 2em;
|
274
|
+
}
|
275
|
+
|
276
|
+
#repository-details h3 {
|
277
|
+
font-weight: bold;
|
278
|
+
font-size: 1.1em;
|
279
|
+
text-align: center;
|
280
|
+
margin-bottom: 0.75em;
|
281
|
+
color: #C55E25;
|
282
|
+
}
|
283
|
+
|
284
|
+
#repository-details code {
|
285
|
+
display: block;
|
286
|
+
white-space: pre;
|
287
|
+
padding: 0.5em;
|
288
|
+
font-family: Consolas, Lucida Console, Monaco, monospace;
|
289
|
+
background: #F4F4F4;
|
290
|
+
margin: 0.25em 0;
|
291
|
+
}
|
292
|
+
|
293
|
+
#error-container {
|
294
|
+
padding: 1em 3em 1.5em;
|
295
|
+
}
|
296
|
+
|
297
|
+
#error-container h2 {
|
298
|
+
font-size: 1.5em;
|
299
|
+
text-align: center;
|
300
|
+
font-weight: bold;
|
301
|
+
padding: 0 0 0.5em;
|
302
|
+
color: red;
|
303
|
+
}
|
304
|
+
|
305
|
+
#error-container p {
|
306
|
+
font-size: 1.2em;
|
307
|
+
line-height: 1.5em;
|
308
|
+
color: #222;
|
309
|
+
text-align: center;
|
310
|
+
}
|
311
|
+
|
312
|
+
#error-container p code {
|
313
|
+
font-style: italic;
|
314
|
+
color: black;
|
315
|
+
font-weight: bold;
|
316
|
+
}
|