adding thunderdome into git
commit
dc2c316246
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2009 Andrew Coleman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,96 @@
|
|||
# Welcome to the ThunderDome
|
||||
|
||||
This is an administrative panel for Rails styled models. In a nutshell, this application shows you a bunch of models in a list and lets you perform basic filtering, paginated displays and the usual edit/update/destroy functions.
|
||||
|
||||
The ThunderDome is released under the MIT license. See the `LICENSE` file for the complete text.
|
||||
|
||||
## Requirements
|
||||
|
||||
* Ruby
|
||||
* Sinatra
|
||||
* HAML
|
||||
* Web browser with or without JavaScript support
|
||||
|
||||
## Paginated Viewing
|
||||
|
||||
All models get paginated views. 100 records per-page. Filtering is available. It's all basic, but it really gets the job done and gets out of your way.
|
||||
|
||||
Sorting, by default, is `id ASC`. If you would like to change it, add a class method called `custom_order` that returns a string of the order you would like to use.
|
||||
|
||||
## Creation And Editing
|
||||
|
||||
The ThunderDome will introspect all of the objects and infer what needs to be done on each model and build a form with all of the attributes listed in alphabetical order. If a column is a boolean, it displays a checkbox, strings get a text field and texts get a text area. Parent model relationships (yes, it does those!) get a select.
|
||||
|
||||
## Has-Many / Belongs-To's
|
||||
|
||||
It will handle parent-child relationships. In the child paginated views, it will display the parent's ID as an integer, but it's an abbreviation and as long as your model responds to `to_s` it will put that into the full definition in the `<abbr>` tag. In the edit view, it will provide a `<select>` list of models you can pick from. To facilitate this, you must provide a `for_select` class method. It's pretty simple, here is an example of one:
|
||||
|
||||
class Agency < ActiveRecord::Base
|
||||
def self.for_select
|
||||
all(:order => 'name ASC').collect do |a|
|
||||
[ a.name, a.id.to_s ]
|
||||
end
|
||||
end
|
||||
def self.custom_order
|
||||
'agencies.code ASC'
|
||||
end
|
||||
end
|
||||
|
||||
It is important to note that i called `to_s` on the integer `id`. Trust me, this works and without the conversion it will not remember to pre-select values.
|
||||
|
||||
## Has And Belongs To Many
|
||||
|
||||
I will get back to you on this one.
|
||||
|
||||
## How does this really work? Really.
|
||||
|
||||
You `require` the appropriate files and populate a variable with a list of the models you want, i do the rest. Filtering requires you to add the column names of the columns you want filtered in a different variable.
|
||||
|
||||
The trickiest (and only) part of running this is you to appropriately load all of your files in the `one_man.rb` file. I have included a sample.
|
||||
|
||||
This application is less than 400 lines, so once you add it, it is unlikely to need to be changed or updated. It depends on nothing in your models and infers everything it needs to operate.
|
||||
|
||||
## How do i use this?
|
||||
|
||||
You can do with it as you need to, but i have a copy of this inside of my `RAILS_ROOT` directory in a directory called thunderdome. From there, you just configure your `one_man.rb` and your `config/database.yml` file. I will assume you know how to configure your database if you are this far into the game. Here is an example of a `one_man.rb` file that might be useful to some people. For this, i am going to examine NewEgg. Please note that i have never worked on anything for NewEgg. I have no idea what it is actually like. I am going to describe what i think the application structure might be like if it were written in Ruby, which it's not.
|
||||
|
||||
# avoid repetition, cache this value
|
||||
model_dir = File.join(File.dirname(__FILE__), '..', 'app', 'models')
|
||||
|
||||
# the main menu is stored the model ProductCategory. It has children
|
||||
# (submenu) and the parents (ProductCategory models with no parent) are the
|
||||
# topmost. an example would be like 'COMPUTER HARDWARE' and 'NETWORKING'.
|
||||
require File.join(model_dir, 'product_category')
|
||||
|
||||
# list of manufacturers, for any product.
|
||||
require File.join(model_dir, 'product_manufacturer')
|
||||
|
||||
# for role based access control, you control the permissions here, but
|
||||
# you configure the roles in the master application and which users belong
|
||||
# to which roles
|
||||
require File.join(model_dir, 'permission')
|
||||
|
||||
# the actual products you see on the page
|
||||
require File.join(model_dir, 'product')
|
||||
|
||||
# set up your variables all nicely so the app runs with your codes
|
||||
@@constants = %w(product_category product_manufacturer permission product)
|
||||
@@sortable_columns = %w(name controller action)
|
||||
|
||||
What all of that means is this:
|
||||
|
||||
1. You used `require` to load the models into Ruby.
|
||||
2. You told the `@@constants` variable what the symbolized name of those models are. If that variable does not contain your model, you cannot view it in the application. An exception will be thrown.
|
||||
3. Any table that has a `name`, `controller`, or `action` column will be filtered on those columns. Watch out and have your indexes ready.
|
||||
4. On the home page for ThunderDome it will have four entries, alphabetized.
|
||||
|
||||
## To Run
|
||||
|
||||
It's pretty easy once you configure your initialization file:
|
||||
|
||||
userbob@myhost> ruby thunderdome.rb
|
||||
|
||||
## What about REST?
|
||||
|
||||
I'll get back to you on this one when it matters for this type of thing.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
development:
|
||||
adapter: mysql
|
||||
host: localhost
|
||||
database: consolo_development
|
||||
username: andrew
|
||||
password:
|
||||
|
||||
test:
|
||||
adapter: mysql
|
||||
database: consolo_test
|
||||
host: localhost
|
||||
username: andrew
|
||||
password:
|
||||
|
||||
production:
|
||||
adapter: mysql
|
||||
database: consolo_development
|
||||
host: localhost
|
||||
username: andrew
|
||||
password:
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<VirtualHost *:80>
|
||||
ServerAdmin webmaster@consoloservices.com
|
||||
ServerName thunderdome.consoloservices.com
|
||||
ErrorLog /var/log/apache2/thunderdome.consoloservices.com-error_log
|
||||
CustomLog /var/log/apache2/thunderdome.consoloservices.com-access_log common
|
||||
DocumentRoot /consolo/trunk/thunderdome/public
|
||||
RackEnv production
|
||||
<Directory "/consolo/trunk/thunderdome/public">
|
||||
Options FollowSymlinks
|
||||
AllowOverride None
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/javascript text/css
|
||||
BrowserMatch ^Mozilla/4 gzip-only-text/html
|
||||
BrowserMatch ^Mozilla/4.0[678] no-gzip
|
||||
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
|
||||
</VirtualHost>
|
|
@ -0,0 +1,39 @@
|
|||
# monkey patch plugin for subdomain restrictions
|
||||
class ActiveRecord::Base
|
||||
def self.use_for_restricted_subdomains
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# load up all of my prerequisites
|
||||
require File.join(File.dirname(__FILE__), '..', 'lib', 'date_tools')
|
||||
require File.join(File.dirname(__FILE__), '..', 'lib', 'american_date_monkey_patch')
|
||||
require File.join(File.dirname(__FILE__), '..', 'lib', 'specialty_strings')
|
||||
|
||||
# directories containing my rails models
|
||||
mdir = File.join(File.dirname(__FILE__), '..', 'app', 'models')
|
||||
edir = File.join(File.dirname(__FILE__), '..', 'app', 'errands')
|
||||
|
||||
# base superclass, all of my models descend from this.
|
||||
require File.join(mdir, 'consolo_constant.rb')
|
||||
|
||||
# custom async job processing engine
|
||||
require File.join(mdir, 'errand.rb')
|
||||
require File.join(edir, 'errand_new_site.rb')
|
||||
require File.join(edir, 'errand_destroy_agency.rb')
|
||||
|
||||
# load every class that descends from my superclass
|
||||
Dir.glob(File.join(mdir, '*.rb')).each do |fname|
|
||||
next if fname =~ /consolo_constant/i
|
||||
cmdstr = "grep -E '^class' #{fname} | grep ConsoloConstant"
|
||||
if system("#{cmdstr} > /dev/null 2>&1")
|
||||
require fname
|
||||
@@constants << File.basename(fname, ".rb").capitalize.gsub(/_(.)/) do |s|
|
||||
$1.capitalize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# sort tables on any of these columns, if they are in the table
|
||||
@@sortable_columns = %w(code description name category action controller city county state year)
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 911 B |
Binary file not shown.
After Width: | Height: | Size: 655 B |
|
@ -0,0 +1,379 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
%w{ rubygems sinatra mysql active_record yaml haml sass }.each do |lib|
|
||||
require lib
|
||||
end
|
||||
|
||||
configure do
|
||||
# add your models into this variable in your initialization file
|
||||
@@constants = []
|
||||
# add the columns you want to search on into this variable
|
||||
@@sortable_columns = []
|
||||
|
||||
# load up what you want to do in the app
|
||||
init_file = File.join(File.dirname(__FILE__), 'one_man.rb')
|
||||
require init_file if File.exists?(init_file)
|
||||
|
||||
# connect to the database
|
||||
begin
|
||||
dbf = File.join(File.dirname(__FILE__), 'config', 'database.yml')
|
||||
dbconfig = YAML::load_file(dbf)[Sinatra::Application.environment.to_s]
|
||||
ActiveRecord::Base.establish_connection(dbconfig)
|
||||
rescue => exception
|
||||
$stderr.puts "There was a problem connecting to the database:"
|
||||
$stderr.puts "* #{exception.message}"
|
||||
exception.backtrace.each do |msg|
|
||||
$stderr.puts "-> #{msg}"
|
||||
end
|
||||
exit 1
|
||||
end
|
||||
|
||||
@@constants.sort!
|
||||
end
|
||||
|
||||
configure :development do
|
||||
set :logging, true
|
||||
ActiveRecord::Base.logger = Logger.new($stderr)
|
||||
end
|
||||
|
||||
configure :production do
|
||||
log = File.new(File.join(File.dirname(__FILE__), "thunderdome.log"), "a")
|
||||
$stdout.reopen(log)
|
||||
$stderr.reopen(log)
|
||||
ActiveRecord::Base.logger = Logger.new(log)
|
||||
end
|
||||
|
||||
get '/' do
|
||||
haml :index
|
||||
end
|
||||
|
||||
get '/stylesheet.css' do
|
||||
content_type 'text/css', :charset => 'utf-8'
|
||||
sass :stylesheet
|
||||
end
|
||||
|
||||
before do
|
||||
if params[:klass] and !@@constants.include? params[:klass]
|
||||
raise "Bad Constant!"
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
helpers do
|
||||
def constant_form
|
||||
haml :constant_form
|
||||
end
|
||||
def constant_url(pagenum)
|
||||
"/#{params[:klass]}/#{pagenum.to_i}?q=#{params[:q]}"
|
||||
end
|
||||
end
|
||||
|
||||
def all_booleans_to_false
|
||||
@constant.attributes.keys.each do |key|
|
||||
col = @constant.column_for_attribute(key)
|
||||
next unless col.type == :boolean
|
||||
@constant.send("#{key}=", false)
|
||||
end
|
||||
end
|
||||
|
||||
get '/:klass/edit/:id' do
|
||||
@klass = params[:klass].constantize
|
||||
@constant = @klass.find params[:id]
|
||||
haml :edit
|
||||
end
|
||||
|
||||
put '/:klass/edit/:id' do
|
||||
@klass = params[:klass].constantize
|
||||
@constant = @klass.find params[:id]
|
||||
all_booleans_to_false
|
||||
params[:constant].each do |name, value|
|
||||
params[:constant][name] = value.to_date if name =~ /_date$/
|
||||
end
|
||||
|
||||
@constant.attributes = params[:constant]
|
||||
if @constant.save
|
||||
redirect "/#{params[:klass]}/#{params[:page].to_i}?q=#{params[:q]}"
|
||||
else
|
||||
haml :edit
|
||||
end
|
||||
end
|
||||
|
||||
get '/:klass/new' do
|
||||
@klass = params[:klass].constantize
|
||||
@constant = @klass.new
|
||||
haml :edit
|
||||
end
|
||||
|
||||
post '/:klass/new' do
|
||||
@klass = params[:klass].constantize
|
||||
@constant = @klass.new
|
||||
all_booleans_to_false
|
||||
@constant.attributes = params[:constant]
|
||||
if @constant.save
|
||||
redirect "/#{params[:klass]}/#{params[:page].to_i}?q=#{params[:q]}"
|
||||
else
|
||||
haml :edit
|
||||
end
|
||||
end
|
||||
|
||||
get '/:klass/:page' do
|
||||
@klass = params[:klass].constantize
|
||||
conds, vars = [], {}
|
||||
if params[:q] and !params[:q].to_s.empty?
|
||||
vars[:q] = "%#{params[:q]}%"
|
||||
cols = @klass.columns.collect(&:name)
|
||||
@@sortable_columns.each do |col|
|
||||
conds << "#{col} LIKE :q" if cols.include? col
|
||||
end
|
||||
end
|
||||
@max_pages = (@klass.count(:conditions => [ conds.join(' OR '), vars ]) / 100).ceil
|
||||
page_order = @klass.respond_to?(:custom_order) ? @klass.custom_order : 'id ASC'
|
||||
@constants = @klass.find :all,
|
||||
:limit => 100,
|
||||
:order => page_order,
|
||||
:offset => (params[:page].to_i * 100),
|
||||
:conditions => [ conds.join(' OR '), vars ]
|
||||
haml :show
|
||||
end
|
||||
|
||||
delete '/:klass/:id' do
|
||||
@klass = params[:klass].constantize
|
||||
@constant = @klass.find params[:id]
|
||||
@constant.destroy
|
||||
redirect "/#{params[:klass]}/#{params[:page].to_i}?q=#{params[:q]}"
|
||||
end
|
||||
|
||||
use_in_file_templates!
|
||||
|
||||
__END__
|
||||
|
||||
@@ layout
|
||||
%html
|
||||
%head
|
||||
%title ThunderDome
|
||||
%link{ :rel => 'stylesheet', :href => '/stylesheet.css' }
|
||||
%body
|
||||
= yield
|
||||
|
||||
@@ index
|
||||
#header
|
||||
%h1
|
||||
Welcome to the ThunderDome!
|
||||
%span.version== The Dos!
|
||||
|
||||
#content
|
||||
%ol
|
||||
- @@constants.each do |c|
|
||||
%li
|
||||
%a{ :href => "/#{c}/0" }
|
||||
= c
|
||||
|
||||
@@ show
|
||||
#header
|
||||
%h1= params[:klass].pluralize
|
||||
|
||||
#content
|
||||
%div#navigation
|
||||
%a{ :href => '/', :title => 'Home' } Home
|
||||
|
|
||||
%a{ :href => "new?page=#{params[:page].to_i}&q=#{params[:q]}" }== New #{params[:klass]}
|
||||
- if @max_pages > 0
|
||||
|
|
||||
Jump to page:
|
||||
- if params[:page].to_i > 0
|
||||
%a{ :href => constant_url(0) } First
|
||||
|
|
||||
- if params[:page].to_i > 1
|
||||
%a{ :href => constant_url(params[:page].to_i - 1) } Previous
|
||||
|
|
||||
%select{ :id => 'pagenum' }
|
||||
- (0..@max_pages).each do |page_num|
|
||||
- if page_num == params[:page].to_i
|
||||
%option{ :value => page_num, :selected => 'selected' }= page_num
|
||||
- else
|
||||
%option{ :value => page_num }= page_num
|
||||
%form{ :method => 'GET' }
|
||||
%input{ :type => 'hidden', :name => 'q', :value => params[:q] }
|
||||
%button{ :onclick => 'this.form.action = "/' + params[:klass] + '/" + document.getElementById("pagenum").value; this.form.submit(); return false' } Go
|
||||
- if params[:page].to_i < @max_pages - 1
|
||||
|
|
||||
%a{ :href => constant_url(params[:page].to_i + 1) } Next
|
||||
- if params[:page].to_i < @max_pages
|
||||
|
|
||||
%a{ :href => constant_url(@max_pages) } Last
|
||||
|
|
||||
%form{ :method => 'GET', :action => "/#{params[:klass]}/#{params[:page]}" }
|
||||
%input{ :type => 'text', :name => 'q', :value => params[:q] }
|
||||
%button{ :onclick => 'this.form.submit(); return false' } Filter
|
||||
%form{ :method => 'GET', :action => "/#{params[:klass]}/0" }
|
||||
%button{ :onclick => 'this.form.submit(); return false' } Reset
|
||||
|
||||
- unless @constants.nil? or @constants.empty?
|
||||
- join_keys = []
|
||||
- parents = {}
|
||||
- @constants.first.class.reflect_on_all_associations.select { |x| x.macro == :belongs_to }.each { |x| join_keys << [(x.options[:foreign_key] || "#{x.name}_id"), (x.options[:class_name] || x.name.to_s.camelize)] ; parents[join_keys.last.last] = [] }
|
||||
- join_keys = join_keys.sort_by { |x| x.first }
|
||||
- sorted_keys = (@constants.first.attributes.keys.sort rescue []).reject! { |x| x == 'id' or join_keys.any? { |y| y.first == x } }
|
||||
%table.collectionList{ :cellspacing => 0 }
|
||||
%tr
|
||||
%th{ :width => "45px" }
|
||||
- join_keys.each do |jk|
|
||||
%th= jk.first
|
||||
- sorted_keys.each do |field|
|
||||
%th= field
|
||||
- @constants.each_with_index do |c, idx|
|
||||
%tr{ :class => (idx % 2 == 0 ? 'even' : 'odd') }
|
||||
%td
|
||||
%a{ :href => "/#{params[:klass]}/edit/#{c.id}?page=#{params[:page].to_i}&q=#{params[:q]}" }
|
||||
%img{ :src => '/images/document-save.png' }
|
||||
%a{ :onclick => "if (confirm('Are you sure you want to delete this entry?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;", :href => "/#{params[:klass]}/#{c.id}?page=#{params[:page]}&q=#{params[:q]}" }
|
||||
%img{ :src => '/images/user-trash.png' }
|
||||
- join_keys.each do |jk|
|
||||
%td
|
||||
- id = c.send(jk[0])
|
||||
%abbr{ :title => ((parents[jk[1]][id] ||= jk[1].constantize.find(id)) rescue 'NOTFOUND') }= id
|
||||
- sorted_keys.each do |field|
|
||||
%td= c.send(field)
|
||||
|
||||
@@ edit
|
||||
- unless @constant.errors.empty?
|
||||
#errorExplanation
|
||||
%p The model could not be saved:
|
||||
%ul
|
||||
- @constant.errors.each_full do |msg|
|
||||
%li= msg
|
||||
|
||||
#header
|
||||
%h1== #{@constant.new_record? ? 'Creating' : 'Editing'} #{@klass}
|
||||
|
||||
#content
|
||||
#navigation
|
||||
%a{ :href => '/' } Home
|
||||
|
|
||||
%a{ :href => constant_url(params[:page]) }= params[:klass].pluralize
|
||||
|
||||
- if @constant.new_record?
|
||||
%form{ :action => "/#{params[:klass]}/new", :method => 'POST' }
|
||||
= constant_form
|
||||
- else
|
||||
%form{ :action => "/#{params[:klass]}/edit/#{@constant.id}", :method => 'pOST' }
|
||||
%input{ :type => 'hidden', :name => '_method', :value => 'PUT' }
|
||||
= constant_form
|
||||
|
||||
@@ constant_form
|
||||
%input{ :type => 'hidden', :name => 'page', :value => params[:page].to_i }
|
||||
%input{ :type => 'hidden', :name => 'q', :value => params[:q] }
|
||||
%table{ :cellspacing => 0 }
|
||||
- join_keys = []
|
||||
- @constant.class.reflect_on_all_associations.select{|x| x.macro == :belongs_to}.each do |association|
|
||||
- column = association.options[:foreign_key] || "#{association.name}_id"
|
||||
- join_keys << column
|
||||
- kls = (association.options[:class_name] || association.name.to_s.camelize).constantize
|
||||
%tr
|
||||
%td{ :width => '25%', :align => 'right' }
|
||||
%label{ :for => "constant[#{column}]" }= kls
|
||||
%td
|
||||
%select{ :name => "constant[#{column}]", :id => "constant[#{column}]" }
|
||||
- (kls.for_select(true) rescue kls.for_select).each do |fkname, fkid|
|
||||
- if @constant.send(column).to_s == fkid
|
||||
%option{ :value => fkid, :selected => 'selected' }= fkname
|
||||
- else
|
||||
%option{ :value => fkid }= fkname
|
||||
- @constant.attributes.keys.sort.each do |key|
|
||||
- next if key == 'id' or join_keys.include? key
|
||||
%tr
|
||||
%td{ :width => '25%', :align => 'right' }
|
||||
%label{ :for => "constant[#{key}]" }= key
|
||||
%td
|
||||
- col = @constant.column_for_attribute(key)
|
||||
- if col.type == :boolean
|
||||
%input{ :type => 'checkbox', :name => "constant[#{key}]", :id => "constant[#{key}]", :checked => @constant.send("#{key}?"), :value => '1' }
|
||||
- elsif col.type == :date
|
||||
%input{ :type => 'date', :name => "constant[#{key}]", :id => "constant[#{key}]", :value => @constant.send(key) }
|
||||
- elsif col.type == :text
|
||||
%textarea{ :name => "constant[#{key}]", :id => "constant[#{key}]", :rows => 10, :cols => 50 }= @constant.send(key)
|
||||
- else
|
||||
- flen = col.limit.nil? ? 30 : col.limit
|
||||
%input{ :type => 'text', :size => (flen > 50 ? 50 : flen), :maxsize => flen, :name => "constant[#{key}]", :id => "constant[#{key}]", :value => @constant.send(key) }
|
||||
%input{ :type => 'submit', :value => 'Save' }
|
||||
|
||||
@@ stylesheet
|
||||
body
|
||||
margin: 0
|
||||
margin-bottom: 25px
|
||||
padding: 0
|
||||
background-color: #f0f0f0
|
||||
font:
|
||||
family: "Lucida Grande", "Bitstream Vera Sans", "Verdana"
|
||||
size: 12px
|
||||
color: #333
|
||||
|
||||
#content
|
||||
background-color: white
|
||||
border: 3px solid #aaa
|
||||
border-top: none
|
||||
width: 90%
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
margin-top: 15px
|
||||
padding: 5px
|
||||
|
||||
#navigation
|
||||
margin: 10px 25px
|
||||
form
|
||||
display: inline
|
||||
|
||||
ol
|
||||
margin-left: 25px
|
||||
|
||||
li
|
||||
&:selected
|
||||
background: #FCC
|
||||
|
||||
a
|
||||
color: #03c
|
||||
&:hover
|
||||
background-color: #03c
|
||||
color: white
|
||||
text-decoration: none
|
||||
img
|
||||
text-decoration: none
|
||||
border: none
|
||||
vertical-align: text-bottom
|
||||
|
||||
#header
|
||||
margin-top: 10px
|
||||
padding-left: 75px
|
||||
padding-right: 30px
|
||||
h1
|
||||
margin: 0
|
||||
.version
|
||||
color: #888
|
||||
font-size: 16px
|
||||
|
||||
.even
|
||||
background-color: #bbbbbb
|
||||
|
||||
.odd
|
||||
background-color: #dddddd
|
||||
|
||||
.collectionList
|
||||
padding: 2px
|
||||
width: 100%
|
||||
font-size: 12px
|
||||
th
|
||||
text-align: center
|
||||
border-bottom: 2px solid #aaa
|
||||
td
|
||||
padding: 5px
|
||||
tr:hover
|
||||
background-color: #fcffa2
|
||||
|
||||
#errorExplanation {
|
||||
padding: 10px
|
||||
margin: 10px 0px
|
||||
border: 2px solid #aaa
|
||||
font-style: italic
|
||||
background-color: #d28f8f
|
||||
color: #8b0000
|
Reference in New Issue