tolaria 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +66 -0
- data/.yardopts +4 -0
- data/CNAME +1 -0
- data/CONTRIBUTING.md +32 -0
- data/Gemfile +2 -0
- data/LICENSE.md +9 -0
- data/README.md +538 -0
- data/Rakefile +52 -0
- data/app/assets/fonts/admin/fontawesome.eot +0 -0
- data/app/assets/fonts/admin/fontawesome.svg +565 -0
- data/app/assets/fonts/admin/fontawesome.ttf +0 -0
- data/app/assets/fonts/admin/fontawesome.woff +0 -0
- data/app/assets/fonts/admin/fontawesome.woff2 +0 -0
- data/app/assets/images/admin/columbia_banner.png +0 -0
- data/app/assets/images/admin/favicon.ico +0 -0
- data/app/assets/images/admin/noise.png +0 -0
- data/app/assets/images/admin/select_arrows.svg +1 -0
- data/app/assets/javascripts/admin/admin.js +4 -0
- data/app/assets/javascripts/admin/base.js +12 -0
- data/app/assets/javascripts/admin/lib/backbone.js +1888 -0
- data/app/assets/javascripts/admin/lib/jquery.chosen.js +1272 -0
- data/app/assets/javascripts/admin/lib/jquery.js +10361 -0
- data/app/assets/javascripts/admin/lib/jquery.selection.js +352 -0
- data/app/assets/javascripts/admin/lib/moment.js +3103 -0
- data/app/assets/javascripts/admin/lib/no.js +6 -0
- data/app/assets/javascripts/admin/lib/underscore.js +1570 -0
- data/app/assets/javascripts/admin/models/composer_buttons.js +45 -0
- data/app/assets/javascripts/admin/models/rails_meta.js +4 -0
- data/app/assets/javascripts/admin/views/field_with_errors.js +19 -0
- data/app/assets/javascripts/admin/views/fields/attachment_field.js +32 -0
- data/app/assets/javascripts/admin/views/fields/has_many.js +64 -0
- data/app/assets/javascripts/admin/views/fields/image_association_select.js +31 -0
- data/app/assets/javascripts/admin/views/fields/markdown_composer.js +167 -0
- data/app/assets/javascripts/admin/views/fields/searchable_select.js +70 -0
- data/app/assets/javascripts/admin/views/fields/slug_field.js +38 -0
- data/app/assets/javascripts/admin/views/fields/swatch_field.js +55 -0
- data/app/assets/javascripts/admin/views/fields/timestamp_field.js +80 -0
- data/app/assets/javascripts/admin/views/flash_message.js +18 -0
- data/app/assets/javascripts/admin/views/form_orchestrator.js +41 -0
- data/app/assets/javascripts/admin/views/navigation.js +20 -0
- data/app/assets/javascripts/admin/views/resource_form.js +18 -0
- data/app/assets/javascripts/admin/views/search_form.js +20 -0
- data/app/assets/javascripts/admin/views/sessions.js +109 -0
- data/app/assets/javascripts/admin/views/virtual_form.js +47 -0
- data/app/assets/stylesheets/admin/_base.scss +5 -0
- data/app/assets/stylesheets/admin/_reset.scss +149 -0
- data/app/assets/stylesheets/admin/_root.scss +63 -0
- data/app/assets/stylesheets/admin/admin.scss +4 -0
- data/app/assets/stylesheets/admin/components/_blank_slate.scss +44 -0
- data/app/assets/stylesheets/admin/components/_buttons.scss +82 -0
- data/app/assets/stylesheets/admin/components/_flash_message.scss +64 -0
- data/app/assets/stylesheets/admin/components/_footer.scss +9 -0
- data/app/assets/stylesheets/admin/components/_header.scss +107 -0
- data/app/assets/stylesheets/admin/components/_index_table.scss +120 -0
- data/app/assets/stylesheets/admin/components/_main.scss +68 -0
- data/app/assets/stylesheets/admin/components/_markdown_body.scss +109 -0
- data/app/assets/stylesheets/admin/components/_navigation.scss +110 -0
- data/app/assets/stylesheets/admin/components/_pagination.scss +36 -0
- data/app/assets/stylesheets/admin/components/_pill.scss +11 -0
- data/app/assets/stylesheets/admin/components/_resource_form.scss +222 -0
- data/app/assets/stylesheets/admin/components/_search_form.scss +36 -0
- data/app/assets/stylesheets/admin/components/_sessions.scss +152 -0
- data/app/assets/stylesheets/admin/components/_show_table.scss +67 -0
- data/app/assets/stylesheets/admin/components/forms/_attachment_field.scss +59 -0
- data/app/assets/stylesheets/admin/components/forms/_chosen.scss +478 -0
- data/app/assets/stylesheets/admin/components/forms/_image_association_select.scss +20 -0
- data/app/assets/stylesheets/admin/components/forms/_markdown_composer.scss +149 -0
- data/app/assets/stylesheets/admin/components/forms/_nested_fields.scss +63 -0
- data/app/assets/stylesheets/admin/components/forms/_searchable_select.scss +8 -0
- data/app/assets/stylesheets/admin/components/forms/_slug_field.scss +20 -0
- data/app/assets/stylesheets/admin/components/forms/_swatch_field.scss +47 -0
- data/app/assets/stylesheets/admin/components/forms/_timestamp_field.scss +15 -0
- data/app/assets/stylesheets/admin/components/help_link.scss +6 -0
- data/app/assets/stylesheets/admin/mixins/_clearfix.scss +18 -0
- data/app/assets/stylesheets/admin/mixins/_min_max_width.scss +11 -0
- data/app/assets/stylesheets/admin/mixins/_rgbb.scss +7 -0
- data/app/assets/stylesheets/admin/mixins/_visuallyhidden.scss +33 -0
- data/app/assets/stylesheets/admin/settings/_animations.scss +21 -0
- data/app/assets/stylesheets/admin/settings/_breakpoints.scss +2 -0
- data/app/assets/stylesheets/admin/settings/_colors.scss +32 -0
- data/app/assets/stylesheets/admin/settings/_fonts.scss +31 -0
- data/app/assets/stylesheets/admin/settings/_icons.scss +1658 -0
- data/app/controllers/admin/admin_controller.rb +21 -0
- data/app/controllers/admin/sessions_controller.rb +112 -0
- data/app/controllers/tolaria/resource_controller.rb +132 -0
- data/app/controllers/tolaria/tolaria_controller.rb +40 -0
- data/app/helpers/admin/table_helper.rb +175 -0
- data/app/helpers/admin/view_helper.rb +76 -0
- data/app/mailers/passcode_mailer.rb +11 -0
- data/app/models/administrator.rb +146 -0
- data/app/views/admin/administrators/_form.html.erb +16 -0
- data/app/views/admin/administrators/_index.html.erb +20 -0
- data/app/views/admin/administrators/_search.html.erb +5 -0
- data/app/views/admin/administrators/_show.html.erb +14 -0
- data/app/views/admin/help/help_link.html.erb +16 -0
- data/app/views/admin/session/form.html.erb +52 -0
- data/app/views/admin/shared/_flash_messages.html.erb +42 -0
- data/app/views/admin/shared/_footer.html.erb +8 -0
- data/app/views/admin/shared/_head.html.erb +11 -0
- data/app/views/admin/shared/_header.html.erb +43 -0
- data/app/views/admin/shared/_navigation.html.erb +47 -0
- data/app/views/admin/shared/_skiplinks.html.erb +0 -0
- data/app/views/admin/shared/forms/_attachment_field.html.erb +17 -0
- data/app/views/admin/shared/forms/_has_many.html.erb +14 -0
- data/app/views/admin/shared/forms/_has_many_header.html.erb +19 -0
- data/app/views/admin/shared/forms/_image_association_select.html.erb +6 -0
- data/app/views/admin/shared/forms/_image_field.html.erb +19 -0
- data/app/views/admin/shared/forms/_markdown_composer.html.erb +29 -0
- data/app/views/admin/shared/forms/_searchable_select.html.erb +3 -0
- data/app/views/admin/shared/forms/_slug_field.html.erb +9 -0
- data/app/views/admin/shared/forms/_swatch_field.html.erb +4 -0
- data/app/views/admin/shared/forms/_timestamp_field.html.erb +19 -0
- data/app/views/admin/tolaria_resource/_form_buttons.html.erb +10 -0
- data/app/views/admin/tolaria_resource/_index_table.html.erb +73 -0
- data/app/views/admin/tolaria_resource/_search_form.html.erb +32 -0
- data/app/views/admin/tolaria_resource/_show_buttons.html.erb +13 -0
- data/app/views/admin/tolaria_resource/edit.html.erb +34 -0
- data/app/views/admin/tolaria_resource/index.html.erb +36 -0
- data/app/views/admin/tolaria_resource/new.html.erb +1 -0
- data/app/views/admin/tolaria_resource/show.html.erb +52 -0
- data/app/views/kaminari/admin/_first_page.html.erb +9 -0
- data/app/views/kaminari/admin/_last_page.html.erb +9 -0
- data/app/views/kaminari/admin/_next_page.html.erb +9 -0
- data/app/views/kaminari/admin/_page.html.erb +17 -0
- data/app/views/kaminari/admin/_paginator.html.erb +21 -0
- data/app/views/kaminari/admin/_prev_page.html.erb +9 -0
- data/app/views/layouts/admin/admin.html.erb +21 -0
- data/app/views/layouts/admin/sessions.html.erb +12 -0
- data/app/views/passcode_mailer/passcode.text.erb +5 -0
- data/lib/generators/tolaria/install/install_generator.rb +21 -0
- data/lib/generators/tolaria/install/templates/administrators_migration.rb +31 -0
- data/lib/generators/tolaria/install/templates/tolaria_initializer.rb +93 -0
- data/lib/tasks/admin.rake +32 -0
- data/lib/tolaria.rb +27 -0
- data/lib/tolaria/active_record.rb +55 -0
- data/lib/tolaria/admin.rb +4 -0
- data/lib/tolaria/categories.rb +21 -0
- data/lib/tolaria/config.rb +40 -0
- data/lib/tolaria/default_config.rb +74 -0
- data/lib/tolaria/engine.rb +23 -0
- data/lib/tolaria/form_buildable.rb +203 -0
- data/lib/tolaria/help_links.rb +78 -0
- data/lib/tolaria/introspection.rb +13 -0
- data/lib/tolaria/manage.rb +57 -0
- data/lib/tolaria/managed_class.rb +90 -0
- data/lib/tolaria/markdown.rb +28 -0
- data/lib/tolaria/random_tokens.rb +16 -0
- data/lib/tolaria/reload.rb +21 -0
- data/lib/tolaria/routes.rb +33 -0
- data/lib/tolaria/version.rb +13 -0
- data/test/demo/Rakefile +4 -0
- data/test/demo/app/assets/javascripts/application.js +1 -0
- data/test/demo/app/assets/stylesheets/application.scss +1 -0
- data/test/demo/app/controllers/application_controller.rb +5 -0
- data/test/demo/app/controllers/concerns/.keep +0 -0
- data/test/demo/app/controllers/homepage_controller.rb +4 -0
- data/test/demo/app/helpers/application_helper.rb +2 -0
- data/test/demo/app/mailers/.keep +0 -0
- data/test/demo/app/models/.keep +0 -0
- data/test/demo/app/models/blog_post.rb +43 -0
- data/test/demo/app/models/footnote.rb +5 -0
- data/test/demo/app/models/image.rb +19 -0
- data/test/demo/app/models/legal_page.rb +24 -0
- data/test/demo/app/models/miscellany.rb +12 -0
- data/test/demo/app/models/topic.rb +22 -0
- data/test/demo/app/models/video.rb +16 -0
- data/test/demo/app/views/admin/blog_posts/_form.html.erb +48 -0
- data/test/demo/app/views/admin/blog_posts/_search.html.erb +5 -0
- data/test/demo/app/views/admin/help/markdown-help.md +95 -0
- data/test/demo/app/views/admin/images/_form.html.erb +26 -0
- data/test/demo/app/views/admin/legal_pages/_form.html.erb +15 -0
- data/test/demo/app/views/admin/topics/_form.html.erb +3 -0
- data/test/demo/app/views/admin/videos/_form.html.erb +11 -0
- data/test/demo/app/views/homepage/homepage.html.erb +3 -0
- data/test/demo/app/views/layouts/application.html.erb +14 -0
- data/test/demo/bin/bundle +3 -0
- data/test/demo/bin/rails +4 -0
- data/test/demo/bin/rake +4 -0
- data/test/demo/bin/setup +29 -0
- data/test/demo/config.ru +4 -0
- data/test/demo/config/application.rb +26 -0
- data/test/demo/config/boot.rb +4 -0
- data/test/demo/config/database.yml +18 -0
- data/test/demo/config/environment.rb +3 -0
- data/test/demo/config/environments/development.rb +43 -0
- data/test/demo/config/environments/test.rb +44 -0
- data/test/demo/config/initializers/assets.rb +11 -0
- data/test/demo/config/initializers/cookies_serializer.rb +2 -0
- data/test/demo/config/initializers/filter_parameter_logging.rb +3 -0
- data/test/demo/config/initializers/inflections.rb +17 -0
- data/test/demo/config/initializers/markdown.rb +44 -0
- data/test/demo/config/initializers/secret_token.rb +2 -0
- data/test/demo/config/initializers/session_store.rb +2 -0
- data/test/demo/config/initializers/tolaria.rb +17 -0
- data/test/demo/config/initializers/wrap_parameters.rb +14 -0
- data/test/demo/config/routes.rb +4 -0
- data/test/demo/db/migrate/20150601202901_create_administrators.rb +31 -0
- data/test/demo/db/migrate/20150603204006_add_testing_models.rb +27 -0
- data/test/demo/db/migrate/20150609232013_create_footnotes.rb +10 -0
- data/test/demo/db/migrate/20150610135235_create_additional_demo_objects.rb +50 -0
- data/test/demo/db/schema.rb +112 -0
- data/test/demo/log/.keep +0 -0
- data/test/demo/public/404.html +67 -0
- data/test/demo/public/422.html +67 -0
- data/test/demo/public/500.html +66 -0
- data/test/demo/public/favicon.ico +0 -0
- data/test/integration/help_link_test.rb +73 -0
- data/test/integration/interface_test.rb +63 -0
- data/test/integration/router_test.rb +73 -0
- data/test/integration/session_test.rb +88 -0
- data/test/test_helper.rb +58 -0
- data/test/unit/configuration_test.rb +21 -0
- data/test/unit/managed_classes_test.rb +54 -0
- data/test/unit/markdown_test.rb +12 -0
- data/test/unit/menu_test.rb +32 -0
- data/test/unit/random_tokens_test.rb +13 -0
- data/tolaria.gemspec +35 -0
- metadata +499 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
module Admin::ViewHelper
|
2
|
+
|
3
|
+
ADMIN_PILL_COLORS = HashWithIndifferentAccess.new({
|
4
|
+
green: "3BAB46",
|
5
|
+
red: "DA3946",
|
6
|
+
blue: "3497BE",
|
7
|
+
black: "343242",
|
8
|
+
grey: "777777",
|
9
|
+
})
|
10
|
+
|
11
|
+
# Returns a navigation menu link with the given label,
|
12
|
+
# Font Awesome icon name, and URI
|
13
|
+
def tolaria_navigation_link(label, icon, index_path, options = {})
|
14
|
+
options[:class] = "#{options[:class]} current" if index_path.in?(url_for)
|
15
|
+
link_to index_path, options do
|
16
|
+
fontawesome_icon(icon) << " #{label}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns an `<i>` tag that displays a Font Awesome icon
|
21
|
+
def fontawesome_icon(icon = "", options = {})
|
22
|
+
icon = icon.to_s.parameterize.tr("_", "-")
|
23
|
+
content_tag :i, nil, options.reverse_merge({
|
24
|
+
:class => "icon icon-#{icon}",
|
25
|
+
:"aria-hidden" => true,
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a URI to a Gravatar for the given email
|
30
|
+
def gravatar_for(email)
|
31
|
+
digest = Digest::MD5.hexdigest(email)
|
32
|
+
return "https://secure.gravatar.com/avatar/#{digest}?d=retro&s=36"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns true if a partial is available at "admin/#{template_path}"
|
36
|
+
def admin_template_exists?(template_path)
|
37
|
+
lookup_context.template_exists?("admin/#{template_path}", [], true)
|
38
|
+
end
|
39
|
+
|
40
|
+
# True if Ransack filtering parameters are present
|
41
|
+
def currently_filtering?
|
42
|
+
params[:q].present? && params[:q].is_a?(Hash) && params[:q].keys.reject{|key| key == "s"}.any?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Attempt to automatically construct a default text search
|
46
|
+
# field name for Ransack from a given model's table settings
|
47
|
+
def ransack_text_search_chain(model)
|
48
|
+
textual_columns = model.columns_hash.select do |column, settings|
|
49
|
+
settings.sql_type.include?("char") || settings.sql_type.include?("text")
|
50
|
+
end
|
51
|
+
textual_columns = {id:"id"} if textual_columns.none?
|
52
|
+
return %{#{textual_columns.keys.join("_or_")}_cont}.to_sym
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a deletion warning message for the given ActiveRecord instance
|
56
|
+
def deletion_warning(resource)
|
57
|
+
return %{Are you sure you want to delete the #{resource.model_name.human.downcase} “#{Tolaria.display_name(resource)}”? This action is not reversible.}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns a `<span>` tag that displays the given +label+ as a pill
|
61
|
+
# status badge. You can change the color of the pill by providing a
|
62
|
+
# six-digit hexadecimal +color+, or passing one of the predefined
|
63
|
+
# color names: `:green`, `:red`, `:blue`, `:black`, `:grey`.
|
64
|
+
def pill(label, color: :black)
|
65
|
+
|
66
|
+
if ADMIN_PILL_COLORS[color].present?
|
67
|
+
color = ADMIN_PILL_COLORS[color]
|
68
|
+
else
|
69
|
+
color = color.to_s.delete("#")
|
70
|
+
end
|
71
|
+
|
72
|
+
content_tag :span, label.to_s, class:"pill", style:"background-color:##{color}"
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class PasscodeMailer < ActionMailer::Base
|
2
|
+
|
3
|
+
default from:Tolaria.config.from_address
|
4
|
+
|
5
|
+
def passcode(administrator, passcode)
|
6
|
+
@administrator = administrator
|
7
|
+
@passcode = passcode
|
8
|
+
mail(to:administrator.email, subject:"#{Tolaria.config.company_name} Passcode")
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
class Administrator < ActiveRecord::Base
|
2
|
+
|
3
|
+
after_initialize :initialize_authentication!
|
4
|
+
before_validation :normalize_email!
|
5
|
+
|
6
|
+
# -----------------------------------------------------------------------------
|
7
|
+
# VALIDATIONS
|
8
|
+
# Use some extra validation redundancy
|
9
|
+
# -----------------------------------------------------------------------------
|
10
|
+
|
11
|
+
validates :email, {
|
12
|
+
uniqueness: true,
|
13
|
+
# Don't try to predict all of the possible crazy emails people can have
|
14
|
+
# Just validate that there is one @ and at least one dot: *@*.*
|
15
|
+
format: /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/
|
16
|
+
}
|
17
|
+
|
18
|
+
# Require all user-visible fields
|
19
|
+
validates_presence_of :name
|
20
|
+
validates_presence_of :organization
|
21
|
+
|
22
|
+
# Parts of the admin system should not be empty
|
23
|
+
validates_presence_of :auth_token
|
24
|
+
validates_presence_of :passcode
|
25
|
+
validates_presence_of :passcode_expires_at
|
26
|
+
validates_presence_of :account_unlocks_at
|
27
|
+
|
28
|
+
# Downcase and strip whitespace from the current email
|
29
|
+
def normalize_email!
|
30
|
+
self.email = self.email.to_s.downcase.squish
|
31
|
+
end
|
32
|
+
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
# AUTHENTICATION SYSTEM
|
35
|
+
# The admin must request a passcode challenge via email
|
36
|
+
# To pass the challenge, the admin must enter the correct email address
|
37
|
+
# and passcode inside the time window
|
38
|
+
# -----------------------------------------------------------------------------
|
39
|
+
|
40
|
+
# Initialize +passcode+, +passcode_expires_at+,
|
41
|
+
# +auth_token+, and +account_unlocks_at+ for a new admin.
|
42
|
+
# To prevent passcode system fields from being null,
|
43
|
+
# we fill them with an immediately expired passcode.
|
44
|
+
def initialize_authentication!
|
45
|
+
self.passcode ||= BCrypt::Password.create(Tolaria::RandomTokens.passcode, cost:Tolaria.config.bcrypt_cost)
|
46
|
+
self.passcode_expires_at ||= Time.current
|
47
|
+
self.auth_token ||= Tolaria::RandomTokens.auth_token
|
48
|
+
self.account_unlocks_at ||= Time.current
|
49
|
+
end
|
50
|
+
|
51
|
+
# Send a passcode challenge email to the admin
|
52
|
+
def send_passcode_email!
|
53
|
+
plaintext_passcode = self.set_passcode!
|
54
|
+
PasscodeMailer.passcode(self, plaintext_passcode).deliver_now
|
55
|
+
end
|
56
|
+
|
57
|
+
# Generate a new passcode challenge.
|
58
|
+
# Create a passcode, save it, and set an expiration window.
|
59
|
+
# Returns the plaintext passcode to send to the user.
|
60
|
+
def set_passcode!
|
61
|
+
plaintext_passcode = Tolaria::RandomTokens.passcode
|
62
|
+
self.update!(
|
63
|
+
passcode: BCrypt::Password.create(plaintext_passcode, cost:Tolaria.config.bcrypt_cost),
|
64
|
+
passcode_expires_at: Time.current + Tolaria.config.passcode_lifespan,
|
65
|
+
)
|
66
|
+
return plaintext_passcode
|
67
|
+
end
|
68
|
+
|
69
|
+
# Attempt to authenticate the account with the given plaintext +passcode+.
|
70
|
+
# Returns true if the passcode was valid, false otherwise.
|
71
|
+
def authenticate!(passcode)
|
72
|
+
|
73
|
+
# Always run bcrypt first so that we incur the time penalty
|
74
|
+
bcrypt_valid = BCrypt::Password.new(self.passcode) == passcode
|
75
|
+
|
76
|
+
# Reject if currently locked
|
77
|
+
return false if self.locked?
|
78
|
+
|
79
|
+
# Clear strikes and consume the passcode if the passcode was valid.
|
80
|
+
# Reject and incur a strike if the challenge was failed.
|
81
|
+
if bcrypt_valid && Time.current < self.passcode_expires_at
|
82
|
+
self.consume_passcode!
|
83
|
+
return true
|
84
|
+
else
|
85
|
+
self.accrue_strike!
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
# Immediately expire the current passcode and reset lockout strikes.
|
92
|
+
def consume_passcode!
|
93
|
+
self.update!(
|
94
|
+
passcode_expires_at: Time.current,
|
95
|
+
lockout_strikes: 0,
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Add one strike to the account.
|
100
|
+
# An admin is given a strike for requesting a code or flunking a challenge.
|
101
|
+
# Will lock the account if they’ve hit the threshold.
|
102
|
+
def accrue_strike!
|
103
|
+
self.update!(
|
104
|
+
lockout_strikes: self.lockout_strikes + 1,
|
105
|
+
total_strikes: self.total_strikes + 1,
|
106
|
+
)
|
107
|
+
if self.lockout_strikes >= Tolaria.config.lockout_threshold
|
108
|
+
self.lock_account!
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Lock this account immediately and reset lockout strikes.
|
113
|
+
def lock_account!
|
114
|
+
self.update!(
|
115
|
+
account_unlocks_at: Time.current + Tolaria.config.lockout_duration,
|
116
|
+
lockout_strikes: 0,
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Unlock the user’s account. Currently only usable by someone
|
121
|
+
# with Rails console access.
|
122
|
+
def unlock_account!
|
123
|
+
self.update!(
|
124
|
+
account_unlocks_at: Time.current,
|
125
|
+
lockout_strikes: 0,
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
# True if the account is currently inside a lockout window
|
130
|
+
def locked?
|
131
|
+
return Time.current < self.account_unlocks_at
|
132
|
+
end
|
133
|
+
|
134
|
+
# -----------------------------------------------------------------------------
|
135
|
+
# MANAGE
|
136
|
+
# Register this model with Tolaria
|
137
|
+
# -----------------------------------------------------------------------------
|
138
|
+
|
139
|
+
manage_with_tolaria using: {
|
140
|
+
icon: "shield",
|
141
|
+
category: "Settings",
|
142
|
+
priority: 100,
|
143
|
+
permit_params: %i[email name organization],
|
144
|
+
}
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= f.label :email %>
|
2
|
+
<%= f.text_field :email, placeholder:"lovelace@example.org" %>
|
3
|
+
<%= f.hint %{
|
4
|
+
The email address this administrator will use to sign in.
|
5
|
+
Administrators have the power to change site content, and can create other administrators.
|
6
|
+
Only people who work directly on web content should be given accounts.
|
7
|
+
} %>
|
8
|
+
|
9
|
+
<%= f.label :name %>
|
10
|
+
<%= f.text_field :name, placeholder:"Ada Lovelace" %>
|
11
|
+
<%= f.hint "The full name of this person." %>
|
12
|
+
|
13
|
+
<%= f.label :organization %>
|
14
|
+
<%= f.text_field :organization, placeholder:"Analytical Engineering" %>
|
15
|
+
<%= f.hint "This person's organization or department." %>
|
16
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<table class="index-table">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<%= index_th :id %>
|
5
|
+
<%= index_th :name %>
|
6
|
+
<%= index_th :organization %>
|
7
|
+
<%= actions_th %>
|
8
|
+
</tr>
|
9
|
+
</thead>
|
10
|
+
<tbody>
|
11
|
+
<% @resources.each do |resource| %>
|
12
|
+
<tr>
|
13
|
+
<%= index_td resource, :id %>
|
14
|
+
<%= index_td resource, :name, image:gravatar_for(resource.email) %>
|
15
|
+
<%= index_td resource, :organization %>
|
16
|
+
<%= actions_td resource %>
|
17
|
+
</tr>
|
18
|
+
<% end %>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<%= f.label :email_cont, "Email contains" %>
|
2
|
+
<%= f.search_field :email_cont, placeholder:"Contains anything" %>
|
3
|
+
|
4
|
+
<%= f.label ransack_text_search_chain(@managed_class.klass), "Containing the text" %>
|
5
|
+
<%= f.search_field ransack_text_search_chain(@managed_class.klass), placeholder:"Anything" %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<%= show_table do %>
|
2
|
+
|
3
|
+
<thead>
|
4
|
+
<%= show_thead_tr %>
|
5
|
+
</thead>
|
6
|
+
<tbody>
|
7
|
+
<%= show_tr "Avatar", image_tag(gravatar_for(@resource.email)) %>
|
8
|
+
<%= show_tr :name %>
|
9
|
+
<%= show_tr :organization %>
|
10
|
+
<%= show_tr :email, class:"monospace" %>
|
11
|
+
<%= show_tr "Created At", @resource.created_at.presence.try(:strftime, "%Y-%m-%d %I:%M %p"), class:"monospace" %>
|
12
|
+
</tbody>
|
13
|
+
|
14
|
+
<% end %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= content_for :title, @help_link.title %>
|
2
|
+
|
3
|
+
<div class="main-controls">
|
4
|
+
|
5
|
+
<div class="main-controls-left">
|
6
|
+
<h1>
|
7
|
+
<%= fontawesome_icon :info_circle %>
|
8
|
+
<%= content_for :title %>
|
9
|
+
</h1>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div class="help-link-body markdown-body">
|
15
|
+
<%= Tolaria.render_markdown(File.read(@help_link.markdown_file)) %>
|
16
|
+
</div>
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<%= form_for @admin, url:admin_signin_path, html:{id:"session-form", class:"session-form"} do |f| %>
|
2
|
+
|
3
|
+
<h1><%= @greeting %></h1>
|
4
|
+
|
5
|
+
<div class="session-spinner" id="session-spinner" style="display:none"></div>
|
6
|
+
|
7
|
+
<% if flash[:error] || flash[:success] %>
|
8
|
+
<p class="session-form-feedback" id="session-form-feedback">
|
9
|
+
<%= flash[:error] %> <%= flash[:success] %>
|
10
|
+
</p>
|
11
|
+
<% else %>
|
12
|
+
<p class="session-form-feedback" id="session-form-feedback" style="display:none"></p>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<%= f.label :email, "Email address", for:"session-form-email", class:"visuallyhidden" %>
|
16
|
+
<%= f.email_field :email, {
|
17
|
+
id: "session-form-email",
|
18
|
+
placeholder: "Enter your email address",
|
19
|
+
autocorrect: "off",
|
20
|
+
autocapitalize: "none",
|
21
|
+
tabindex: 1,
|
22
|
+
value: @email,
|
23
|
+
} %>
|
24
|
+
|
25
|
+
<%= f.label :passcode, "Passcode", for:"session-form-passcode", class:"visuallyhidden" %>
|
26
|
+
<%= f.text_field :passcode, {
|
27
|
+
id: "session-form-passcode",
|
28
|
+
placeholder: "Passcode",
|
29
|
+
autocorrect: "off",
|
30
|
+
autocomplete: "off",
|
31
|
+
spellcheck: "false",
|
32
|
+
pattern: "[0-9]*",
|
33
|
+
tabindex: 2,
|
34
|
+
style: "display:none"
|
35
|
+
} %>
|
36
|
+
|
37
|
+
<%= f.button "Request a passcode", type:"submit", id:"session-form-submit", class:"session-button" %>
|
38
|
+
|
39
|
+
<div id="session-form-remember-group" class="session-form-checkbox-group" style="display:none">
|
40
|
+
<%= hidden_field_tag :remember_me, 1 %>
|
41
|
+
<%= check_box_tag :remember_me, 1, true, {
|
42
|
+
class: "session-form-remember",
|
43
|
+
id: "session-form-remember"
|
44
|
+
} %>
|
45
|
+
<%= label_tag "session-form-remember", "Remember this device" %>
|
46
|
+
</div>
|
47
|
+
|
48
|
+
<p class="session-form-resend" id="session-form-resend" style="display:none">
|
49
|
+
<button type="button">Request another code</button>
|
50
|
+
</p>
|
51
|
+
|
52
|
+
<% end %>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<% if flash[:success].present? %>
|
2
|
+
|
3
|
+
<div class="flash-message">
|
4
|
+
<div class="inner-align">
|
5
|
+
<button class="dismiss">Dismiss</button>
|
6
|
+
<p><%= fontawesome_icon :check %> <%= flash[:success] %></p>
|
7
|
+
</div>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<% elsif flash[:error].present? %>
|
11
|
+
|
12
|
+
<div class="flash-message -error">
|
13
|
+
<div class="inner-align">
|
14
|
+
<button class="dismiss">Dismiss</button>
|
15
|
+
<p>
|
16
|
+
<%= fontawesome_icon :exclamation_triangle %> <%= flash[:error] %>
|
17
|
+
<% if defined?(@resource) && @resource.respond_to?(:errors) && @resource.errors.any? %>
|
18
|
+
<%= @resource.errors.full_messages.to_sentence.capitalize %>.
|
19
|
+
<% end %>
|
20
|
+
</p>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<% elsif flash[:destructive].present? %>
|
25
|
+
|
26
|
+
<div class="flash-message">
|
27
|
+
<div class="inner-align">
|
28
|
+
<button class="dismiss">Dismiss</button>
|
29
|
+
<p><%= fontawesome_icon :trash %> <%= flash[:destructive] %></p>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<% elsif flash[:restricted].present? %>
|
34
|
+
|
35
|
+
<div class="flash-message -error">
|
36
|
+
<div class="inner-align">
|
37
|
+
<button class="dismiss">Dismiss</button>
|
38
|
+
<p><%= fontawesome_icon :minus_circle %> <%= flash[:restricted] %></p>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
|
42
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<head>
|
2
|
+
<meta charset="utf-8">
|
3
|
+
<title><%= "#{content_for(:title)} • " if content_for(:title) %><%= Tolaria.config.company_name %></title>
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
5
|
+
<meta name="robots" content="noindex">
|
6
|
+
<meta name="google" content="notranslate">
|
7
|
+
<%= favicon_link_tag "admin/favicon.ico" %>
|
8
|
+
<%= csrf_meta_tags %>
|
9
|
+
<%= stylesheet_link_tag "admin/admin", media:"all" %>
|
10
|
+
<%= javascript_include_tag "admin/lib/no" %>
|
11
|
+
</head>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<div class="header-stripe"></div>
|
2
|
+
|
3
|
+
<div class="header">
|
4
|
+
<div class="inner-align">
|
5
|
+
|
6
|
+
<%= link_to Tolaria.config.default_redirect, class:"header-menu-trigger mobile-only" do %>
|
7
|
+
<span><%= fontawesome_icon :bars %> <%= Tolaria.config.company_name %></span>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<%= link_to Tolaria.config.default_redirect, class:"header-app-title" do %>
|
11
|
+
<%= Tolaria.config.company_name %>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<ul class="header-user-controls">
|
15
|
+
|
16
|
+
<% Tolaria.help_links.each do |help_link| %>
|
17
|
+
<% if help_link.markdown_type? %>
|
18
|
+
<li><%= link_to help_link.title, admin_help_link_path(help_link), class:"header-help-link", target:"_blank" %></li>
|
19
|
+
<% else %>
|
20
|
+
<li><%= link_to help_link.title, help_link.link_to, class:"header-help-link", target:"_blank" %></li>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<li>
|
25
|
+
<%= link_to edit_admin_administrator_path(current_administrator), class:"header-user-profile desktop-only" do %>
|
26
|
+
<%= image_tag gravatar_for(current_administrator.email), alt:"", size:"18x18" %>
|
27
|
+
<%= current_administrator.name %>
|
28
|
+
<% end %>
|
29
|
+
</li>
|
30
|
+
|
31
|
+
<li>
|
32
|
+
<%= form_tag admin_destroy_session_path, method:"delete" do %>
|
33
|
+
<%= button_tag class:"header-user-signout" do %>
|
34
|
+
<%= fontawesome_icon :sign_out %>
|
35
|
+
Sign Out
|
36
|
+
<% end %>
|
37
|
+
<% end %>
|
38
|
+
</li>
|
39
|
+
|
40
|
+
</ul>
|
41
|
+
|
42
|
+
</div>
|
43
|
+
</div>
|