kowl 0.0.1
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/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.
|