tolaria 1.0.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.
- 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>
|