kowl 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +7 -0
- data/README.md +383 -0
- data/bin/console +14 -0
- data/bin/kowl +106 -0
- data/bin/setup +13 -0
- data/lib/kowl/actions.rb +240 -0
- data/lib/kowl/docker.rb +447 -0
- data/lib/kowl/generators/README.md +5 -0
- data/lib/kowl/generators/action_generator.rb +113 -0
- data/lib/kowl/generators/admin_generator.rb +114 -0
- data/lib/kowl/generators/assets_generator.rb +188 -0
- data/lib/kowl/generators/base.rb +40 -0
- data/lib/kowl/generators/circleci_generator.rb +11 -0
- data/lib/kowl/generators/config_generator.rb +180 -0
- data/lib/kowl/generators/controller_generator.rb +64 -0
- data/lib/kowl/generators/database_generator.rb +42 -0
- data/lib/kowl/generators/decorators_generator.rb +16 -0
- data/lib/kowl/generators/docker_generator.rb +40 -0
- data/lib/kowl/generators/dotfiles_generator.rb +73 -0
- data/lib/kowl/generators/libs_generator.rb +19 -0
- data/lib/kowl/generators/mailer_generator.rb +58 -0
- data/lib/kowl/generators/misc_generator.rb +20 -0
- data/lib/kowl/generators/overrides/app_base.rb +15 -0
- data/lib/kowl/generators/overrides/app_builder.rb +58 -0
- data/lib/kowl/generators/overrides/app_generator.rb +249 -0
- data/lib/kowl/generators/pages_generator.rb +46 -0
- data/lib/kowl/generators/routes_generator.rb +20 -0
- data/lib/kowl/generators/sidekiq_generator.rb +31 -0
- data/lib/kowl/generators/staging_generator.rb +14 -0
- data/lib/kowl/generators/test_generator.rb +42 -0
- data/lib/kowl/generators/text_files_generator.rb +41 -0
- data/lib/kowl/generators/users_and_auth_generator.rb +143 -0
- data/lib/kowl/generators/uuid_generator.rb +39 -0
- data/lib/kowl/generators/views_and_helpers_generator.rb +104 -0
- data/lib/kowl/geodb.rb +96 -0
- data/lib/kowl/helpers.rb +139 -0
- data/lib/kowl/templates/Gemfile.erb.tt +253 -0
- data/lib/kowl/templates/README.md.erb +33 -0
- data/lib/kowl/templates/app/assets/javascripts/semantic.js.tt +26 -0
- data/lib/kowl/templates/app/assets/stylesheets/administrate/application.scss +44 -0
- data/lib/kowl/templates/app/assets/stylesheets/bootstrap/_custom_styling.scss +38 -0
- data/lib/kowl/templates/app/assets/stylesheets/bootstrap/application-mailer.scss +1 -0
- data/lib/kowl/templates/app/assets/stylesheets/bootstrap/application.scss +14 -0
- data/lib/kowl/templates/app/assets/stylesheets/bootstrap/components/_alerts.scss +7 -0
- data/lib/kowl/templates/app/assets/stylesheets/bootstrap/components/_errors.scss +28 -0
- data/lib/kowl/templates/app/assets/stylesheets/bootstrap/components/_navbar.scss +17 -0
- data/lib/kowl/templates/app/assets/stylesheets/bootstrap/variables.scss +6 -0
- data/lib/kowl/templates/app/assets/stylesheets/semantic/_custom_styling.scss +20 -0
- data/lib/kowl/templates/app/assets/stylesheets/semantic/application.scss +24 -0
- data/lib/kowl/templates/app/controllers/admin/application_controller.rb +29 -0
- data/lib/kowl/templates/app/controllers/admin/login_activities_controller.rb +29 -0
- data/lib/kowl/templates/app/controllers/admin/users_controller.rb +37 -0
- data/lib/kowl/templates/app/controllers/concerns/auth/authentication.rb +18 -0
- data/lib/kowl/templates/app/controllers/concerns/auth/authorization.rb +20 -0
- data/lib/kowl/templates/app/controllers/concerns/auth/error_handlers.rb +27 -0
- data/lib/kowl/templates/app/controllers/concerns/noauth/error_handlers.rb +25 -0
- data/lib/kowl/templates/app/controllers/users_controller.rb +17 -0
- data/lib/kowl/templates/app/dashboards/login_activity_dashboard.rb.tt +59 -0
- data/lib/kowl/templates/app/dashboards/rich_text_body_dashboard.rb.tt +17 -0
- data/lib/kowl/templates/app/dashboards/user_dashboard.rb.tt +60 -0
- data/lib/kowl/templates/app/fields/gravatar_field.rb +10 -0
- data/lib/kowl/templates/app/fields/rich_text_area_field.rb +8 -0
- data/lib/kowl/templates/app/inputs/rich_text_area_input.rb +11 -0
- data/lib/kowl/templates/app/javascript/administrate/components/date_time_picker.js.tt +32 -0
- data/lib/kowl/templates/app/javascript/administrate/components/table.js.tt +49 -0
- data/lib/kowl/templates/app/javascript/administrate/index.js +3 -0
- data/lib/kowl/templates/app/javascript/packs/administrate.js.tt +21 -0
- data/lib/kowl/templates/app/javascript/packs/semantic.js +11 -0
- data/lib/kowl/templates/app/mailers/application_mailer.rb +7 -0
- data/lib/kowl/templates/app/mailers/devise_mailer.rb +5 -0
- data/lib/kowl/templates/app/models/login_activity.rb.tt +29 -0
- data/lib/kowl/templates/app/models/user.rb.tt +72 -0
- data/lib/kowl/templates/app/policies/application_policy.rb +38 -0
- data/lib/kowl/templates/app/policies/login_activity_policy.rb +35 -0
- data/lib/kowl/templates/app/policies/user_policy.rb +43 -0
- data/lib/kowl/templates/app/views/admin/templates/navigation.erb.tt +23 -0
- data/lib/kowl/templates/app/views/admin/views/application/_javascript.html.erb +21 -0
- data/lib/kowl/templates/app/views/admin/views/users/_collection.html.erb +102 -0
- data/lib/kowl/templates/app/views/admin/views/users/_form.html.erb +46 -0
- data/lib/kowl/templates/app/views/admin/views/users/edit.html.erb +36 -0
- data/lib/kowl/templates/app/views/admin/views/users/index.html.erb +66 -0
- data/lib/kowl/templates/app/views/fields/gravatar_field/_form.html.erb +6 -0
- data/lib/kowl/templates/app/views/fields/gravatar_field/_index.html.erb +1 -0
- data/lib/kowl/templates/app/views/fields/gravatar_field/_show.html.erb +1 -0
- data/lib/kowl/templates/app/views/fields/rich_text_area_field/_form.html.erb +6 -0
- data/lib/kowl/templates/app/views/fields/rich_text_area_field/_index.html.erb +1 -0
- data/lib/kowl/templates/app/views/fields/rich_text_area_field/_show.html.erb +1 -0
- data/lib/kowl/templates/app/views/layouts/admin.html.erb.tt +47 -0
- data/lib/kowl/templates/app/views/layouts/devise_mailer.html.erb +14 -0
- data/lib/kowl/templates/app/views/pages/welcome/bootstrap.html.erb +18 -0
- data/lib/kowl/templates/app/views/pages/welcome/bootstrap.html.haml +17 -0
- data/lib/kowl/templates/app/views/pages/welcome/bootstrap.html.slim +17 -0
- data/lib/kowl/templates/app/views/pages/welcome/default.html.erb +0 -0
- data/lib/kowl/templates/app/views/pages/welcome/default.html.haml +0 -0
- data/lib/kowl/templates/app/views/pages/welcome/default.html.slim +0 -0
- data/lib/kowl/templates/app/views/pages/welcome/semantic.html.erb +20 -0
- data/lib/kowl/templates/app/views/pages/welcome/semantic.html.haml +17 -0
- data/lib/kowl/templates/app/views/pages/welcome/semantic.html.slim +17 -0
- data/lib/kowl/templates/app/views/shared/footer/bootstrap.html.erb.tt +5 -0
- data/lib/kowl/templates/app/views/shared/footer/bootstrap.html.haml.tt +3 -0
- data/lib/kowl/templates/app/views/shared/footer/bootstrap.html.slim.tt +3 -0
- data/lib/kowl/templates/app/views/shared/footer/semantic.html.erb.tt +7 -0
- data/lib/kowl/templates/app/views/shared/footer/semantic.html.haml.tt +5 -0
- data/lib/kowl/templates/app/views/shared/footer/semantic.html.slim.tt +5 -0
- data/lib/kowl/templates/app/views/shared/navigation/bootstrap.html.erb.tt +51 -0
- data/lib/kowl/templates/app/views/shared/navigation/bootstrap.html.haml.tt +36 -0
- data/lib/kowl/templates/app/views/shared/navigation/bootstrap.html.slim.tt +36 -0
- data/lib/kowl/templates/app/views/shared/navigation/semantic.html.erb.tt +48 -0
- data/lib/kowl/templates/app/views/shared/navigation/semantic.html.haml.tt +36 -0
- data/lib/kowl/templates/app/views/shared/navigation/semantic.html.slim.tt +36 -0
- data/lib/kowl/templates/app/workers/scheduler/pghero_scheduler.rb +11 -0
- data/lib/kowl/templates/config/autoprefixer.yml +4 -0
- data/lib/kowl/templates/config/db/mysql.yml.tt +43 -0
- data/lib/kowl/templates/config/db/oracle.yml.tt +29 -0
- data/lib/kowl/templates/config/db/postgresql.yml.tt +37 -0
- data/lib/kowl/templates/config/db/sqlite3.yml.tt +20 -0
- data/lib/kowl/templates/config/db/sqlserver.yml.tt +28 -0
- data/lib/kowl/templates/config/initializers/administrate.rb.tt +9 -0
- data/lib/kowl/templates/config/initializers/bullet.rb +21 -0
- data/lib/kowl/templates/config/initializers/devise-security.rb +44 -0
- data/lib/kowl/templates/config/initializers/devise_argon2.rb +29 -0
- data/lib/kowl/templates/config/initializers/faker.rb +6 -0
- data/lib/kowl/templates/config/initializers/generators.rb.tt +31 -0
- data/lib/kowl/templates/config/initializers/geocoder.rb +10 -0
- data/lib/kowl/templates/config/initializers/letter_opener.rb +13 -0
- data/lib/kowl/templates/config/initializers/lockbox.rb +11 -0
- data/lib/kowl/templates/config/initializers/logging.rb +6 -0
- data/lib/kowl/templates/config/initializers/lograge.rb +7 -0
- data/lib/kowl/templates/config/initializers/logstop.rb +4 -0
- data/lib/kowl/templates/config/initializers/middleware.rb +9 -0
- data/lib/kowl/templates/config/initializers/oj.rb +7 -0
- data/lib/kowl/templates/config/initializers/pagy.rb.tt +16 -0
- data/lib/kowl/templates/config/initializers/postmark.rb +8 -0
- data/lib/kowl/templates/config/initializers/rack_attack.rb.tt +71 -0
- data/lib/kowl/templates/config/initializers/sass.rb +5 -0
- data/lib/kowl/templates/config/initializers/sidekiq.rb +11 -0
- data/lib/kowl/templates/config/initializers/simpleform/semantic.rb +225 -0
- data/lib/kowl/templates/config/initializers/slim.rb +6 -0
- data/lib/kowl/templates/config/initializers/sparkpost.rb +13 -0
- data/lib/kowl/templates/config/routes.rb.tt +28 -0
- data/lib/kowl/templates/config/sidekiq.yml.tt +17 -0
- data/lib/kowl/templates/db/migrations/create_action_text_tables.action_text.rb.tt +16 -0
- data/lib/kowl/templates/db/migrations/create_active_storage_tables.active_storage.rb.tt +29 -0
- data/lib/kowl/templates/db/migrations/devise.rb.tt +59 -0
- data/lib/kowl/templates/db/migrations/login_activities.rb.tt +41 -0
- data/lib/kowl/templates/db/seeds.rb.tt +14 -0
- data/lib/kowl/templates/docker/Dockerfile.alpine.tt +92 -0
- data/lib/kowl/templates/docker/Dockerfile.debian.tt +133 -0
- data/lib/kowl/templates/docker/docker-compose.yml.tt +55 -0
- data/lib/kowl/templates/docker/mysql/Dockerfile.tt +15 -0
- data/lib/kowl/templates/dotfiles/Aptfile.tt +34 -0
- data/lib/kowl/templates/dotfiles/Brewfile.tt +49 -0
- data/lib/kowl/templates/dotfiles/Procfile.tt +12 -0
- data/lib/kowl/templates/dotfiles/codeclimate.yml +76 -0
- data/lib/kowl/templates/dotfiles/coffeelint.json +10 -0
- data/lib/kowl/templates/dotfiles/coffeelintignore +15 -0
- data/lib/kowl/templates/dotfiles/dockerignore +23 -0
- data/lib/kowl/templates/dotfiles/editorconfig +13 -0
- data/lib/kowl/templates/dotfiles/env.tt +58 -0
- data/lib/kowl/templates/dotfiles/erb-lint.yml +10 -0
- data/lib/kowl/templates/dotfiles/erdconfig.tt +24 -0
- data/lib/kowl/templates/dotfiles/eslintignore +20 -0
- data/lib/kowl/templates/dotfiles/eslintrc.js +31 -0
- data/lib/kowl/templates/dotfiles/fasterer.yml +14 -0
- data/lib/kowl/templates/dotfiles/foreman +1 -0
- data/lib/kowl/templates/dotfiles/gitattributes +15 -0
- data/lib/kowl/templates/dotfiles/gitignore +69 -0
- data/lib/kowl/templates/dotfiles/haml-lint.yml +23 -0
- data/lib/kowl/templates/dotfiles/jsbeautifyrc +15 -0
- data/lib/kowl/templates/dotfiles/jshintrc +3 -0
- data/lib/kowl/templates/dotfiles/mailmap +3 -0
- data/lib/kowl/templates/dotfiles/nvmrc +1 -0
- data/lib/kowl/templates/dotfiles/prettierignore +21 -0
- data/lib/kowl/templates/dotfiles/prettierrc.js +62 -0
- data/lib/kowl/templates/dotfiles/pryrc +7 -0
- data/lib/kowl/templates/dotfiles/rspec +3 -0
- data/lib/kowl/templates/dotfiles/rubocop.yml.tt +78 -0
- data/lib/kowl/templates/dotfiles/scss-lint.yml +132 -0
- data/lib/kowl/templates/dotfiles/simplecov +19 -0
- data/lib/kowl/templates/dotfiles/slim-lint.yml +23 -0
- data/lib/kowl/templates/dotfiles/slugignore +10 -0
- data/lib/kowl/templates/dotfiles/yamllint +7 -0
- data/lib/kowl/templates/dotfiles/yarnclean +46 -0
- data/lib/kowl/templates/lib/tasks/stats.rake +42 -0
- data/lib/kowl/templates/tests/factories/README.md +0 -0
- data/lib/kowl/templates/tests/factories/login_activity.rb +11 -0
- data/lib/kowl/templates/tests/factories/user.rb +11 -0
- data/lib/kowl/templates/tests/minitest/README.md +3 -0
- data/lib/kowl/templates/tests/minitest/application_system_test_case.rb +7 -0
- data/lib/kowl/templates/tests/minitest/controllers/pages_controller_test.rb +10 -0
- data/lib/kowl/templates/tests/minitest/models/login_activity_test.rb +8 -0
- data/lib/kowl/templates/tests/minitest/models/user_test.rb +15 -0
- data/lib/kowl/templates/tests/minitest/policies/login_activity_policy_test.rb +7 -0
- data/lib/kowl/templates/tests/minitest/policies/user_policy_test.rb +7 -0
- data/lib/kowl/templates/tests/minitest/support/capybara.rb +38 -0
- data/lib/kowl/templates/tests/minitest/support/database_cleaner.rb +20 -0
- data/lib/kowl/templates/tests/minitest/support/database_cleaner_support.rb +15 -0
- data/lib/kowl/templates/tests/minitest/support/deferred_garbage_collection.rb +21 -0
- data/lib/kowl/templates/tests/minitest/support/devise.rb +16 -0
- data/lib/kowl/templates/tests/minitest/support/factories.rb +6 -0
- data/lib/kowl/templates/tests/minitest/support/formulaic.rb +8 -0
- data/lib/kowl/templates/tests/minitest/support/helpers/devise_helper.rb +57 -0
- data/lib/kowl/templates/tests/minitest/support/papertrail.rb +17 -0
- data/lib/kowl/templates/tests/minitest/support/pundit.rb +0 -0
- data/lib/kowl/templates/tests/minitest/support/shoulda.rb +11 -0
- data/lib/kowl/templates/tests/minitest/support/simplecov.rb +8 -0
- data/lib/kowl/templates/tests/minitest/test_helper.rb +17 -0
- data/lib/kowl/templates/tests/rspec/README.md +9 -0
- data/lib/kowl/templates/tests/rspec/controllers/admin/application_controller_spec.rb +12 -0
- data/lib/kowl/templates/tests/rspec/controllers/admin/users_controller_spec.rb +14 -0
- data/lib/kowl/templates/tests/rspec/controllers/pages_controller_spec.rb +10 -0
- data/lib/kowl/templates/tests/rspec/features/README.md +15 -0
- data/lib/kowl/templates/tests/rspec/features/visitor_sign_up_spec.rb +25 -0
- data/lib/kowl/templates/tests/rspec/helpers/application_helper_spec.rb +7 -0
- data/lib/kowl/templates/tests/rspec/helpers/pages_helper_spec.rb +15 -0
- data/lib/kowl/templates/tests/rspec/models/login_activity_spec.rb +11 -0
- data/lib/kowl/templates/tests/rspec/models/user_spec.rb +21 -0
- data/lib/kowl/templates/tests/rspec/policies/login_activity_policy_spec.rb +59 -0
- data/lib/kowl/templates/tests/rspec/policies/user_policy_spec.rb +56 -0
- data/lib/kowl/templates/tests/rspec/rails_helper.rb +72 -0
- data/lib/kowl/templates/tests/rspec/requests/pages_spec.rb +11 -0
- data/lib/kowl/templates/tests/rspec/spec_helper.rb +96 -0
- data/lib/kowl/templates/tests/rspec/support/bullet.rb +17 -0
- data/lib/kowl/templates/tests/rspec/support/capybara.rb +37 -0
- data/lib/kowl/templates/tests/rspec/support/controller_testing.rb +10 -0
- data/lib/kowl/templates/tests/rspec/support/database_cleaner.rb +55 -0
- data/lib/kowl/templates/tests/rspec/support/deferred_garbage_collection.rb +32 -0
- data/lib/kowl/templates/tests/rspec/support/devise.rb +21 -0
- data/lib/kowl/templates/tests/rspec/support/factories.rb +5 -0
- data/lib/kowl/templates/tests/rspec/support/formulaic.rb +8 -0
- data/lib/kowl/templates/tests/rspec/support/helpers/devise_helpers.rb +56 -0
- data/lib/kowl/templates/tests/rspec/support/papertrail.rb +5 -0
- data/lib/kowl/templates/tests/rspec/support/pi_ci.rb +6 -0
- data/lib/kowl/templates/tests/rspec/support/pundit.rb +6 -0
- data/lib/kowl/templates/tests/rspec/support/shoulda.rb +10 -0
- data/lib/kowl/templates/tests/rspec/support/sidekiq.rb +10 -0
- data/lib/kowl/templates/tests/rspec/support/simplecov.rb +7 -0
- data/lib/kowl/templates/tests/rspec/support/warden.rb +8 -0
- data/lib/kowl/templates/text_files/AUTHORS.md +4 -0
- data/lib/kowl/templates/text_files/CHANGELOG.md.tt +12 -0
- data/lib/kowl/templates/text_files/CODE_OF_CONDUCT.md +76 -0
- data/lib/kowl/templates/text_files/TODO.md +7 -0
- data/lib/kowl/templates/text_files/VERSION +1 -0
- data/lib/kowl/templates/text_files/humans.txt.tt +15 -0
- data/lib/kowl/templates/text_files/robots.txt.tt +6 -0
- data/lib/kowl/templates/text_files/security.txt +6 -0
- data/lib/kowl/version.rb +9 -0
- data/lib/kowl.rb +18 -0
- metadata +404 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LoginActivity < ApplicationRecord
|
4
|
+
# == Attributes =====================================
|
5
|
+
<%- if options[:encrypt] -%>
|
6
|
+
encrypts :identity, :ip
|
7
|
+
blind_index :identity, :ip
|
8
|
+
<%- end -%>
|
9
|
+
|
10
|
+
# == Constants ======================================
|
11
|
+
|
12
|
+
# == Extensions =====================================
|
13
|
+
|
14
|
+
# == Relationships ==================================
|
15
|
+
belongs_to :user, polymorphic: true, optional: true
|
16
|
+
|
17
|
+
# == Validations ====================================
|
18
|
+
|
19
|
+
# == Scopes =========================================
|
20
|
+
# This is used, by default to only show successful logins
|
21
|
+
# => This will remove users that are seeded, creates, and/or failed attempts
|
22
|
+
default_scope -> { where(user_type: 'User') }
|
23
|
+
|
24
|
+
# == Callbacks ======================================
|
25
|
+
|
26
|
+
# == ClassMethods ===================================
|
27
|
+
|
28
|
+
# == InstanceMethods ================================
|
29
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class User < ApplicationRecord
|
4
|
+
# == Attributes =====================================
|
5
|
+
alias_attribute :avatar, :email
|
6
|
+
<%- unless options[:skip_mailer] -%>
|
7
|
+
def devise_mailer
|
8
|
+
DeviseMailer
|
9
|
+
end
|
10
|
+
<%- end -%>
|
11
|
+
<%- if options[:encrypt] -%>
|
12
|
+
# Encrypted user attributes
|
13
|
+
encrypts :email
|
14
|
+
blind_index :email
|
15
|
+
<%- end -%>
|
16
|
+
|
17
|
+
def self.policy_class
|
18
|
+
UserPolicy
|
19
|
+
end
|
20
|
+
|
21
|
+
# == Constants =====================================
|
22
|
+
enum role: { superuser: 0, staff: 1, user: 2, visitor: 3 }
|
23
|
+
# enum role: { superuser: 'superuser', staff: 'staff', user: 'user', visitor: 'visitor' }
|
24
|
+
|
25
|
+
# == Extensions ====================================
|
26
|
+
# Include default devise modules. Others available are:
|
27
|
+
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
28
|
+
devise :database_authenticatable, :registerable,
|
29
|
+
:recoverable, :rememberable, :secure_validatable,
|
30
|
+
:lockable, :timeoutable, :trackable,
|
31
|
+
email_validation: false # Since we are using valid_email validator for a stricter email validator
|
32
|
+
# https://github.com/micke/valid_email2/issues/121
|
33
|
+
|
34
|
+
# == Relationships =================================
|
35
|
+
<%- if options[:database] == 'sqlserver' -%>
|
36
|
+
has_many :login_activities, as: :user, dependent: :delete_all, inverse_of: :user # devise audit log
|
37
|
+
<%- else -%>
|
38
|
+
has_many :login_activities, -> { order(created_at: :desc) }, as: :user, dependent: :delete_all, inverse_of: :user # devise audit log
|
39
|
+
<%- end -%>
|
40
|
+
|
41
|
+
# == Validations ===================================
|
42
|
+
# minimum length of 5 to match: a@a.a
|
43
|
+
validates :email, presence: true, 'valid_email_2/email': { disposable: false }, length: { minimum: 5, maximum: 255 }, uniqueness: { case_sensitive: false }, allow_nil: false
|
44
|
+
validates :role, presence: true, inclusion: { in: roles.keys }, allow_nil: false
|
45
|
+
|
46
|
+
# == Scopes ========================================
|
47
|
+
|
48
|
+
# == Callbacks =====================================
|
49
|
+
# Before any validations ensure a default role is set
|
50
|
+
before_validation do
|
51
|
+
self.role ||= :user
|
52
|
+
end
|
53
|
+
|
54
|
+
# == Class Methods =================================
|
55
|
+
def admin?
|
56
|
+
role?(:superuser)
|
57
|
+
end
|
58
|
+
|
59
|
+
def staff?
|
60
|
+
role?(:staff)
|
61
|
+
end
|
62
|
+
|
63
|
+
def staff_member?
|
64
|
+
role?(:superuser) || role?(:staff)
|
65
|
+
end
|
66
|
+
|
67
|
+
def role?(role)
|
68
|
+
self.role == role.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
# == Instance Methods ==============================
|
72
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ApplicationPolicy
|
4
|
+
attr_reader :current_user, :record
|
5
|
+
|
6
|
+
def initialize(current_user, record)
|
7
|
+
raise Pundit::NotAuthorizedError, 'must be logged in' unless current_user
|
8
|
+
|
9
|
+
@current_user = current_user
|
10
|
+
@record = record
|
11
|
+
end
|
12
|
+
|
13
|
+
def admin?
|
14
|
+
current_user.admin? if current_user.present?
|
15
|
+
end
|
16
|
+
|
17
|
+
def staff?
|
18
|
+
current_user.staff? if current_user.present?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Used to verify if the persons role is set to either superuser or staff
|
22
|
+
def staff_member?
|
23
|
+
admin? || staff?
|
24
|
+
end
|
25
|
+
|
26
|
+
class Scope
|
27
|
+
attr_reader :user, :scope
|
28
|
+
|
29
|
+
def initialize(user, scope)
|
30
|
+
@user = user
|
31
|
+
@scope = scope
|
32
|
+
end
|
33
|
+
|
34
|
+
def resolve
|
35
|
+
scope.all
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LoginActivityPolicy < ApplicationPolicy
|
4
|
+
def show?
|
5
|
+
# Only display all login activity if the current user is an admin
|
6
|
+
# => Otherwise if the user is a staff member, only display login activity for everyone who isn't an admin
|
7
|
+
# => If somehow a user hits this page, only allow them to see login activity for themselves
|
8
|
+
admin? || (staff? && record.user.role != 'superuser') || (staff_member? && current_user.id == record.user_id)
|
9
|
+
end
|
10
|
+
|
11
|
+
def index?
|
12
|
+
admin? || current_user.id == record.user_id
|
13
|
+
end
|
14
|
+
|
15
|
+
# All audit logs should not be modifiable, by any means
|
16
|
+
def edit?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def new?
|
21
|
+
edit?
|
22
|
+
end
|
23
|
+
|
24
|
+
def create?
|
25
|
+
edit?
|
26
|
+
end
|
27
|
+
|
28
|
+
def update?
|
29
|
+
edit?
|
30
|
+
end
|
31
|
+
|
32
|
+
def destroy?
|
33
|
+
edit?
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class UserPolicy < ApplicationPolicy
|
4
|
+
def index?
|
5
|
+
admin?
|
6
|
+
end
|
7
|
+
|
8
|
+
# Account should not be create-able, unless someone is explicitly signing up to create an account
|
9
|
+
def new?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def create?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def show?
|
18
|
+
# Admins should be able to see all users
|
19
|
+
# Staff members should only be able to see people who aren't superusers
|
20
|
+
# And everyone should see records for themselves
|
21
|
+
admin? || (staff? && record.role != 'superuser') || (staff_member? && current_user.id == record.id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def update?
|
25
|
+
# Ensure only superusers can edit other superusers.
|
26
|
+
# => This is because we don't want a staff member editing a superuser/manager's account
|
27
|
+
# => If the current_user only has the staff as role they can only edit someone who isn't a superuser
|
28
|
+
admin? || (staff? && record.role != 'superuser') || (staff_member? && current_user.id == record.id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def edit?
|
32
|
+
update?
|
33
|
+
end
|
34
|
+
|
35
|
+
def destroy?
|
36
|
+
# This was added because a user shouldn't be able to delete themselves from the admin dashboard
|
37
|
+
admin? && current_user.id != record.id
|
38
|
+
end
|
39
|
+
|
40
|
+
def impersonate?
|
41
|
+
admin? && current_user.id != record.id
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<%%#
|
2
|
+
# Navigation
|
3
|
+
|
4
|
+
This partial is used to display the navigation in Administrate.
|
5
|
+
By default, the navigation contains navigation links
|
6
|
+
for all resources in the admin dashboard,
|
7
|
+
as defined by the routes in the `admin/` namespace
|
8
|
+
%>
|
9
|
+
|
10
|
+
<nav class="navigation" role="navigation">
|
11
|
+
<%%= link_to "#{icon('fas', 'home')} Homepage".html_safe, root_path, class: 'navigation__link navigation__link--inactive', title: 'Return to homepage' %>
|
12
|
+
<%% Administrate::Namespace.new(namespace).resources_with_index_route.each do |resource| %>
|
13
|
+
<%%= link_to(
|
14
|
+
display_resource_name(resource),
|
15
|
+
resource_index_route(resource),
|
16
|
+
class: "navigation__link navigation__link--#{nav_link_state(resource)}"
|
17
|
+
) if valid_action? :index, resource %>
|
18
|
+
<%% end %>
|
19
|
+
<%- unless options[:skip_sidekiq] -%>
|
20
|
+
<%%= link_to('Sidekiq', '/sidekiq', class: 'navigation__link navigation__link--inactive') if current_user.admin? %>
|
21
|
+
<%- end -%>
|
22
|
+
<%%= link_to('LetterOpener'.html_safe, '/letter_opener', class: 'navigation__link navigation__link--inactive') if Rails.env.development? && current_user.admin? %>
|
23
|
+
</nav>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<%#
|
2
|
+
# Javascript Partial
|
3
|
+
https://github.com/thoughtbot/administrate/wiki/Rails-6-&-Webpacker
|
4
|
+
|
5
|
+
This partial imports the necessary javascript on each page.
|
6
|
+
By default, it includes the application JS,
|
7
|
+
but each page can define additional JS sources
|
8
|
+
by providing a `content_for(:javascript)` block.
|
9
|
+
|
10
|
+
Administrate::Engine.javascripts.each do |js_path|
|
11
|
+
<= echo javascript_include_tag js_path
|
12
|
+
<% end
|
13
|
+
%>
|
14
|
+
|
15
|
+
<%= yield :javascript %>
|
16
|
+
<% if Rails.env.test? %>
|
17
|
+
<%= javascript_tag do %>
|
18
|
+
$.fx.off = true;
|
19
|
+
$.ajaxSetup({ async: false });
|
20
|
+
<% end %>
|
21
|
+
<% end %>
|
@@ -0,0 +1,102 @@
|
|
1
|
+
<%#
|
2
|
+
# Collection
|
3
|
+
|
4
|
+
This partial is used on the `index` and `show` pages
|
5
|
+
to display a collection of resources in an HTML table.
|
6
|
+
|
7
|
+
## Local variables:
|
8
|
+
|
9
|
+
- `collection_presenter`:
|
10
|
+
An instance of [Administrate::Page::Collection][1].
|
11
|
+
The table presenter uses `ResourceDashboard::COLLECTION_ATTRIBUTES` to determine
|
12
|
+
the columns displayed in the table
|
13
|
+
- `resources`:
|
14
|
+
An ActiveModel::Relation collection of resources to be displayed in the table.
|
15
|
+
By default, the number of resources is limited by pagination
|
16
|
+
or by a hard limit to prevent excessive page load times
|
17
|
+
|
18
|
+
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection
|
19
|
+
%>
|
20
|
+
<table aria-labelledby="<%= table_title %>">
|
21
|
+
<thead>
|
22
|
+
<tr>
|
23
|
+
<td> </td>
|
24
|
+
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %>
|
25
|
+
<th class="cell-label
|
26
|
+
cell-label--<%= attr_type.html_class %>
|
27
|
+
cell-label--<%= collection_presenter.ordered_html_class(attr_name) %>"
|
28
|
+
scope="col"
|
29
|
+
role="columnheader"
|
30
|
+
aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>">
|
31
|
+
<%= link_to(sanitized_order_params(page, collection_field_name).merge(
|
32
|
+
collection_presenter.order_params_for(attr_name, key: collection_field_name)
|
33
|
+
)) do %>
|
34
|
+
<%= t(
|
35
|
+
"helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
|
36
|
+
default: attr_name.to_s,
|
37
|
+
).titleize %>
|
38
|
+
<% if collection_presenter.ordered_by?(attr_name) %>
|
39
|
+
<span class="cell-label__sort-indicator cell-label__sort-indicator--<%= collection_presenter.ordered_html_class(attr_name) %>">
|
40
|
+
<svg aria-hidden="true">
|
41
|
+
<use xlink:href="#icon-up-caret" />
|
42
|
+
</svg>
|
43
|
+
</span>
|
44
|
+
<% end %>
|
45
|
+
<% end %>
|
46
|
+
</th>
|
47
|
+
<% end %>
|
48
|
+
<% [valid_action?(:edit, collection_presenter.resource_name),
|
49
|
+
valid_action?(:destroy, collection_presenter.resource_name)].count(true).times do %>
|
50
|
+
<th scope="col"></th>
|
51
|
+
<% end %>
|
52
|
+
</tr>
|
53
|
+
</thead>
|
54
|
+
|
55
|
+
<tbody>
|
56
|
+
<% resources.each do |resource| %>
|
57
|
+
<%- if show_action? :show, resource %>
|
58
|
+
<tr class="js-table-row"
|
59
|
+
tabindex="0"
|
60
|
+
<% if valid_action? :show, collection_presenter.resource_name %>
|
61
|
+
<%= %(role=link data-url=#{polymorphic_path([namespace, resource])}) %>
|
62
|
+
<% end %>
|
63
|
+
>
|
64
|
+
<td>
|
65
|
+
<%- if resource.class.to_s == "User" && policy(resource).impersonate? %>
|
66
|
+
<%= link_to (raw icon('fas','theater-masks')), impersonate_user_path(resource.id) %>
|
67
|
+
<% end %>
|
68
|
+
</td>
|
69
|
+
<% collection_presenter.attributes_for(resource).each do |attribute| %>
|
70
|
+
<td class="cell-data cell-data--<%= attribute.html_class %>">
|
71
|
+
<% if show_action? :show, resource -%>
|
72
|
+
<a href="<%= polymorphic_path([namespace, resource]) -%>"
|
73
|
+
class="action-show"
|
74
|
+
>
|
75
|
+
<%= render_field attribute %>
|
76
|
+
</a>
|
77
|
+
<% end -%>
|
78
|
+
</td>
|
79
|
+
<% end %>
|
80
|
+
|
81
|
+
<% if valid_action? :edit, collection_presenter.resource_name %>
|
82
|
+
<td><%= link_to(
|
83
|
+
t("administrate.actions.edit"),
|
84
|
+
[:edit, namespace, resource],
|
85
|
+
class: "action-edit",
|
86
|
+
) if show_action? :edit, resource%></td>
|
87
|
+
<% end %>
|
88
|
+
|
89
|
+
<% if valid_action? :destroy, collection_presenter.resource_name %>
|
90
|
+
<td><%= link_to(
|
91
|
+
t("administrate.actions.destroy"),
|
92
|
+
[namespace, resource],
|
93
|
+
class: "text-color-red",
|
94
|
+
method: :delete,
|
95
|
+
data: { confirm: t("administrate.actions.confirm") }
|
96
|
+
) if show_action? :destroy, resource %></td>
|
97
|
+
<% end %>
|
98
|
+
</tr>
|
99
|
+
<% end %>
|
100
|
+
<% end %>
|
101
|
+
</tbody>
|
102
|
+
</table>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<%#
|
2
|
+
# Form Partial
|
3
|
+
|
4
|
+
This partial is rendered on a resource's `new` and `edit` pages,
|
5
|
+
and renders all form fields for a resource's editable attributes.
|
6
|
+
|
7
|
+
## Local variables:
|
8
|
+
|
9
|
+
- `page`:
|
10
|
+
An instance of [Administrate::Page::Form][1].
|
11
|
+
Contains helper methods to display a form,
|
12
|
+
and knows which attributes should be displayed in the resource's form.
|
13
|
+
|
14
|
+
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Form
|
15
|
+
%>
|
16
|
+
|
17
|
+
<%= form_for([namespace, page.resource], html: { class: "form" }) do |f| %>
|
18
|
+
<% if page.resource.errors.any? %>
|
19
|
+
<div id="error_explanation">
|
20
|
+
<h2>
|
21
|
+
<%= t(
|
22
|
+
"administrate.form.errors",
|
23
|
+
pluralized_errors: pluralize(page.resource.errors.count, t("administrate.form.error")),
|
24
|
+
resource_name: display_resource_name(page.resource_name)
|
25
|
+
) %>
|
26
|
+
</h2>
|
27
|
+
|
28
|
+
<ul>
|
29
|
+
<% page.resource.errors.full_messages.each do |message| %>
|
30
|
+
<li class="flash-error"><%= message %></li>
|
31
|
+
<% end %>
|
32
|
+
</ul>
|
33
|
+
</div>
|
34
|
+
<% end %>
|
35
|
+
|
36
|
+
<% page.attributes.each do |attribute| -%>
|
37
|
+
<% next if attribute.attribute == :role && !current_user.admin? %>
|
38
|
+
<div class="field-unit field-unit--<%= attribute.html_class %>">
|
39
|
+
<%= render_field attribute, f: f %>
|
40
|
+
</div>
|
41
|
+
<% end -%>
|
42
|
+
|
43
|
+
<div class="form-actions">
|
44
|
+
<%= f.submit %>
|
45
|
+
</div>
|
46
|
+
<% end %>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<%#
|
2
|
+
# Edit
|
3
|
+
|
4
|
+
This view is the template for the edit page.
|
5
|
+
|
6
|
+
It displays a header, and renders the `_form` partial to do the heavy lifting.
|
7
|
+
|
8
|
+
## Local variables:
|
9
|
+
|
10
|
+
- `page`:
|
11
|
+
An instance of [Administrate::Page::Form][1].
|
12
|
+
Contains helper methods to help display a form,
|
13
|
+
and knows which attributes should be displayed in the resource's form.
|
14
|
+
|
15
|
+
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Form
|
16
|
+
%>
|
17
|
+
|
18
|
+
<% content_for(:title) { t("administrate.actions.edit_resource", name: page.page_title) } %>
|
19
|
+
|
20
|
+
<header class="main-content__header" role="banner">
|
21
|
+
<h1 class="main-content__page-title">
|
22
|
+
<%= content_for(:title) %>
|
23
|
+
</h1>
|
24
|
+
|
25
|
+
<div>
|
26
|
+
<%= link_to(
|
27
|
+
t("administrate.actions.show_resource", name: page.page_title),
|
28
|
+
[namespace, page.resource],
|
29
|
+
class: "button",
|
30
|
+
) if valid_action?(:show) && show_action?(:show, page.resource) %>
|
31
|
+
</div>
|
32
|
+
</header>
|
33
|
+
|
34
|
+
<section class="main-content__body">
|
35
|
+
<%= render "form", page: page %>
|
36
|
+
</section>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<%#
|
2
|
+
# Index
|
3
|
+
|
4
|
+
This view is the template for the index page.
|
5
|
+
It is responsible for rendering the search bar, header and pagination.
|
6
|
+
It renders the `_table` partial to display details about the resources.
|
7
|
+
|
8
|
+
## Local variables:
|
9
|
+
|
10
|
+
- `page`:
|
11
|
+
An instance of [Administrate::Page::Collection][1].
|
12
|
+
Contains helper methods to help display a table,
|
13
|
+
and knows which attributes should be displayed in the resource's table.
|
14
|
+
- `resources`:
|
15
|
+
An instance of `ActiveRecord::Relation` containing the resources
|
16
|
+
that match the user's search criteria.
|
17
|
+
By default, these resources are passed to the table partial to be displayed.
|
18
|
+
- `search_term`:
|
19
|
+
A string containing the term the user has searched for, if any.
|
20
|
+
- `show_search_bar`:
|
21
|
+
A boolean that determines if the search bar should be shown.
|
22
|
+
|
23
|
+
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection
|
24
|
+
%>
|
25
|
+
|
26
|
+
<% content_for(:title) do %>
|
27
|
+
<%= display_resource_name(page.resource_name) %>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<header class="main-content__header" role="banner">
|
31
|
+
<h1 class="main-content__page-title" id="page-title">
|
32
|
+
<%= content_for(:title) %>
|
33
|
+
</h1>
|
34
|
+
|
35
|
+
<% if show_search_bar %>
|
36
|
+
<%= render(
|
37
|
+
"search",
|
38
|
+
search_term: search_term,
|
39
|
+
resource_name: display_resource_name(page.resource_name)
|
40
|
+
) %>
|
41
|
+
<% end %>
|
42
|
+
|
43
|
+
<div>
|
44
|
+
<%= link_to(
|
45
|
+
t(
|
46
|
+
"administrate.actions.new_resource",
|
47
|
+
name: page.resource_name.titleize.downcase
|
48
|
+
),
|
49
|
+
[:new, namespace, page.resource_path],
|
50
|
+
class: "button",
|
51
|
+
) if valid_action?(:new) && show_action?(:new, new_resource) %>
|
52
|
+
</div>
|
53
|
+
</header>
|
54
|
+
|
55
|
+
<section class="main-content__body main-content__body--flush">
|
56
|
+
<%= render(
|
57
|
+
"collection",
|
58
|
+
collection_presenter: page,
|
59
|
+
collection_field_name: resource_name,
|
60
|
+
page: page,
|
61
|
+
resources: resources,
|
62
|
+
table_title: "page-title"
|
63
|
+
) %>
|
64
|
+
|
65
|
+
<%= paginate resources %>
|
66
|
+
</section>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= image_tag field.gravatar_url %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= image_tag field.gravatar_url %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= field.to_s %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= field.to_s %>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<%#
|
2
|
+
# Application Layout
|
3
|
+
|
4
|
+
This view template is used as the layout
|
5
|
+
for every page that Administrate generates.
|
6
|
+
|
7
|
+
By default, it renders:
|
8
|
+
- Navigation
|
9
|
+
- Content for a search bar
|
10
|
+
(if provided by a `content_for` block in a nested page)
|
11
|
+
- Flashes
|
12
|
+
- Links to stylesheets and JavaScripts
|
13
|
+
%>
|
14
|
+
|
15
|
+
<!DOCTYPE html>
|
16
|
+
<html lang="<%%= I18n.locale %>">
|
17
|
+
<head>
|
18
|
+
<meta charset="utf-8">
|
19
|
+
<meta name="robots" content="noodp, noydir, index, follow">
|
20
|
+
<meta name="viewport" content="initial-scale=1">
|
21
|
+
<%%- set_meta_tags title: (content_for(:title) || 'Dashboard') %>
|
22
|
+
<%%= display_meta_tags site: '<%= app_name.titleize %>' %>
|
23
|
+
<%- unless options[:skip_javascript] -%>
|
24
|
+
<%%= javascript_pack_tag 'administrate'<%= options[:skip_turbolinks] ? '' : ", 'data-turbolinks-track': 'reload'" -%> %>
|
25
|
+
<%- end -%>
|
26
|
+
<%%= render "stylesheet" %>
|
27
|
+
<%%= csrf_meta_tags %>
|
28
|
+
<%- unless options[:skip_turbolinks] || options[:skip_javascript] -%>
|
29
|
+
<meta name="turbolinks-root" content="/admin">
|
30
|
+
<%- end -%>
|
31
|
+
</head>
|
32
|
+
<body>
|
33
|
+
<div class="app-container">
|
34
|
+
<%%= render "navigation" -%>
|
35
|
+
<main class="main-content" role="main">
|
36
|
+
<%%= render "flashes" -%>
|
37
|
+
<%%= yield %>
|
38
|
+
</main>
|
39
|
+
</div>
|
40
|
+
<div style="display: none; width: 0; height: 0; overflow: hidden; position: absolute">
|
41
|
+
<%%= render "icons" %>
|
42
|
+
</div>
|
43
|
+
<%- unless options[:skip_javascript] -%>
|
44
|
+
<%%= render "javascript" %>
|
45
|
+
<%- end -%>
|
46
|
+
</body>
|
47
|
+
</html>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
5
|
+
<%= stylesheet_link_tag "application-mailer", media: "all" %>
|
6
|
+
</head>
|
7
|
+
<body class="bg-light">
|
8
|
+
<div class="container-fluid">
|
9
|
+
<div class="card card-body my-3">
|
10
|
+
<%= yield %>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<h2><%= title('Welcome') %></h2>
|
2
|
+
<p class="lead">
|
3
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
4
|
+
Mauris maximus vulputate gravida.
|
5
|
+
Integer eget mauris at orci molestie porta.
|
6
|
+
</p>
|
7
|
+
<p>
|
8
|
+
In scelerisque leo vitae lacus commodo, eu tincidunt nisi suscipit.
|
9
|
+
Pellentesque at sapien duiDuis sit amet arcu non ante sodales varius.
|
10
|
+
Fusce nibh nulla, mollis a dolor eu, iaculis pharetra lectus.
|
11
|
+
Sed turpis quam, imperdiet ut faucibus sit amet, efficitur ut nulla.
|
12
|
+
Duis non accumsan lacus.
|
13
|
+
Vestibulum nec diam quis dui convallis rhoncus quis sit amet massa.
|
14
|
+
Maecenas ac est purus. Aliquam erat volutpat.
|
15
|
+
Vestibulum tempus pharetra viverra.
|
16
|
+
Etiam vestibulum et elit in vulputate.
|
17
|
+
Quisque eget elementum purus, id volutpat augue.
|
18
|
+
</p>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
%h2 = title('Welcome')
|
2
|
+
%p.lead
|
3
|
+
| Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
4
|
+
| Mauris maximus vulputate gravida.
|
5
|
+
| Integer eget mauris at orci molestie porta.
|
6
|
+
|
7
|
+
%p
|
8
|
+
| In scelerisque leo vitae lacus commodo, eu tincidunt nisi suscipit.
|
9
|
+
| Pellentesque at sapien duiDuis sit amet arcu non ante sodales varius.
|
10
|
+
| Fusce nibh nulla, mollis a dolor eu, iaculis pharetra lectus.
|
11
|
+
| Sed turpis quam, imperdiet ut faucibus sit amet, efficitur ut nulla.
|
12
|
+
| Duis non accumsan lacus.
|
13
|
+
| Vestibulum nec diam quis dui convallis rhoncus quis sit amet massa.
|
14
|
+
| Maecenas ac est purus. Aliquam erat volutpat.
|
15
|
+
| Vestibulum tempus pharetra viverra.
|
16
|
+
| Etiam vestibulum et elit in vulputate.
|
17
|
+
| Quisque eget elementum purus, id volutpat augue.
|