gitauth 0.0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|