From 4fc56efc6b3159e67975f6bea4619a190b6fbba0 Mon Sep 17 00:00:00 2001 From: "Michael J. Giarlo" Date: Thu, 8 Aug 2024 17:32:22 -0700 Subject: [PATCH 1/2] Set up ERB linting in CI and expand/update Rubocop --- .circleci/config.yml | 4 +-- .erb-lint.yml | 36 +++++++++++++++++++++++++ .rubocop.yml | 16 ++++++----- Capfile | 20 +++++++------- Gemfile | 21 ++++++++------- Gemfile.lock | 16 +++++++++++ Rakefile | 13 ++++++++- app/services/dashboard/audit_service.rb | 2 +- config.ru | 2 ++ config/environments/development.rb | 9 ++++--- config/environments/production.rb | 14 +++++----- config/environments/test.rb | 8 +++--- 12 files changed, 118 insertions(+), 43 deletions(-) create mode 100644 .erb-lint.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 903004814..f62a6b6dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,16 +7,14 @@ workflows: - ruby-rails/validate-api: name: validate - ruby-rails/lint: - context: dlss name: lint + - ruby-rails/lint-erb - ruby-rails/test-rails: name: test - context: dlss api-only: true db-prepare-command: db:reset executor: ruby-rails/ruby-postgres-redis - ruby-rails/docker-publish: - context: dlss name: publish-latest image: suldlss/preservation_catalog extra_build_args: --build-arg BUNDLE_GEMS__CONTRIBSYS__COM diff --git a/.erb-lint.yml b/.erb-lint.yml new file mode 100644 index 000000000..56481e931 --- /dev/null +++ b/.erb-lint.yml @@ -0,0 +1,36 @@ +--- +exclude: + - '**/vendor/**/*' +linters: + Rubocop: + enabled: true + rubocop_config: + inherit_from: + - .rubocop.yml + # Suggested by https://github.com/Shopify/erb-lint#rubocop + Layout/InitialIndentation: + Enabled: false + Layout/LineLength: + Enabled: false + Layout/TrailingEmptyLines: + Enabled: false + Style/FrozenStringLiteralComment: + Enabled: false + Naming/FileName: + Enabled: false + Lint/UselessAssignment: + Enabled: false + Rails/OutputSafety: # Already covered by ErbSafety linter + Enabled: false + Rails/LinkToBlank: + Enabled: false + ErbSafety: + enabled: true + PartialInstanceVariable: + enabled: false + DeprecatedClasses: + enabled: true + NoUnusedDisable: + enabled: true + RubocopText: + enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index 5b1ea4c97..144f287b3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,14 +14,8 @@ inherit_mode: AllCops: TargetRubyVersion: 3.3 DisplayCopNames: true - Include: - - './Rakefile' - - './config.ru' - - '**/*.rb' - - '**/*.rake' Exclude: - 'bin/**/*' - - 'config/environments/*.rb' - 'db/**/*' - 'vendor/**/*' @@ -650,3 +644,13 @@ RSpec/IsExpectedSpecify: # new in 2.27 Enabled: true RSpec/RepeatedSubjectCall: # new in 2.27 Enabled: true +Gemspec/AddRuntimeDependency: # new in 1.65 + Enabled: true +Style/MapIntoArray: # new in 1.63 + Enabled: true +Style/SendWithLiteralMethodName: # new in 1.64 + Enabled: true +Style/SuperArguments: # new in 1.64 + Enabled: true +Rails/WhereRange: # new in 2.25 + Enabled: true diff --git a/Capfile b/Capfile index 6a5128fc0..13789ab8a 100644 --- a/Capfile +++ b/Capfile @@ -1,18 +1,20 @@ +# frozen_string_literal: true + # Load DSL and set up stages -require "capistrano/setup" +require 'capistrano/setup' # Include default deployment tasks -require "capistrano/deploy" +require 'capistrano/deploy' -require "capistrano/scm/git" +require 'capistrano/scm/git' install_plugin Capistrano::SCM::Git -require "capistrano/bundler" -require "capistrano/passenger" -require "capistrano/rails" -require "capistrano/honeybadger" -require "dlss/capistrano" +require 'capistrano/bundler' +require 'capistrano/passenger' +require 'capistrano/rails' +require 'capistrano/honeybadger' +require 'dlss/capistrano' require 'whenever/capistrano' # Load custom tasks from `lib/capistrano/tasks` if you have any defined -Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } +Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } diff --git a/Gemfile b/Gemfile index 2fd026b18..df3ee7462 100644 --- a/Gemfile +++ b/Gemfile @@ -1,23 +1,27 @@ +# frozen_string_literal: true + source 'https://rubygems.org' -# general Ruby/Rails gems gem 'aws-sdk-s3', '~> 1.17' gem 'committee' # Validates HTTP requests/responses per OpenAPI specification -gem 'connection_pool' # Used for redis gem 'config' # Settings to manage configs on different instances +gem 'connection_pool' # Used for redis gem 'csv' # will be removed from standard library in Ruby 3.4 +gem 'dor-event-client' +gem 'dor-workflow-client' # audit errors are reported to the workflow service +gem 'druid-tools' # for druid validation and druid-tree parsing gem 'honeybadger' # for error reporting / tracking / notifications -gem "importmap-rails", "~> 1.2" +gem 'importmap-rails', '~> 1.2' gem 'jbuilder' # Build JSON APIs with ease. gem 'jwt' # for gating programmatic access to the application gem 'lograge' +gem 'moab-versioning', '~> 6.0' # work with Moab Objects gem 'okcomputer' # ReST endpoint with upness status gem 'pg' # postgres database gem 'postgresql_cursor' # for paging over large result sets efficiently gem 'propshaft', '~> 0.8.0' # asset pipeline -# pry is useful for debugging, even in prod -gem 'pry-byebug' # call 'binding.pry' anywhere in the code to stop execution and get a pry-byebug console gem 'pry' # make it possible to use pry for IRB +gem 'pry-byebug' # call 'binding.pry' anywhere in the code to stop execution and get a pry-byebug console gem 'puma' # app server gem 'rails', '~> 7.0.0' gem 'redis', '~> 5.0' @@ -31,13 +35,9 @@ source 'https://gems.contribsys.com/' do end # Stanford gems -gem 'dor-event-client' -gem 'dor-workflow-client' # audit errors are reported to the workflow service -gem 'druid-tools' # for druid validation and druid-tree parsing -gem 'moab-versioning', '~> 6.0' # work with Moab Objects group :development, :test do - gem 'rspec-rails' + gem 'erb_lint', require: false # Ruby static code analyzer https://rubocop.readthedocs.io/en/latest/ gem 'rubocop', '~> 1.0' gem 'rubocop-capybara' @@ -45,6 +45,7 @@ group :development, :test do gem 'rubocop-rails' gem 'rubocop-rspec' gem 'rubocop-rspec_rails' + gem 'rspec-rails' gem 'rspec_junit_formatter' # used by CircleCI end diff --git a/Gemfile.lock b/Gemfile.lock index d25b360d4..7d26ee2e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -98,6 +98,13 @@ GEM bcrypt_pbkdf (1.1.1) bcrypt_pbkdf (1.1.1-arm64-darwin) bcrypt_pbkdf (1.1.1-x86_64-darwin) + better_html (2.1.1) + actionview (>= 6.0) + activesupport (>= 6.0) + ast (~> 2.0) + erubi (~> 1.4) + parser (>= 2.4) + smart_properties bigdecimal (3.1.8) builder (3.3.0) bundler-audit (0.9.1) @@ -179,6 +186,13 @@ GEM zeitwerk (~> 2.1) druid-tools (3.0.0) ed25519 (1.3.0) + erb_lint (0.6.0) + activesupport + better_html (>= 2.0.1) + parser (>= 2.7.1.4) + rainbow + rubocop (>= 1) + smart_properties erubi (1.13.0) factory_bot (6.4.6) activesupport (>= 5.0.0) @@ -416,6 +430,7 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) + smart_properties (1.17.0) sorted_set (1.0.3) rbtree set (~> 1.0) @@ -476,6 +491,7 @@ DEPENDENCIES dor-event-client dor-workflow-client druid-tools + erb_lint factory_bot_rails honeybadger importmap-rails (~> 1.2) diff --git a/Rakefile b/Rakefile index 6395fdbc8..6615ad010 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. @@ -7,7 +9,16 @@ Rails.application.load_tasks require 'rubocop/rake_task' RuboCop::RakeTask.new +desc 'Run erblint against ERB files' +task erblint: :environment do + puts 'Running erblint...' + system('bundle exec erblint --lint-all --format compact') +end + +desc 'Run all configured linters' +task lint: %i[rubocop erblint] + # clear the default task injected by rspec task(:default).clear -task default: [:rubocop, :spec] +task default: [:lint, :spec] diff --git a/app/services/dashboard/audit_service.rb b/app/services/dashboard/audit_service.rb index 7aa414822..0f73ec3e1 100644 --- a/app/services/dashboard/audit_service.rb +++ b/app/services/dashboard/audit_service.rb @@ -56,7 +56,7 @@ def moab_audit_age_threshold def num_moab_audits_older_than_threshold # This is faster than MoabRecord.version_audit_expired(moab_audit_age_threshold).count MoabRecord.where(last_version_audit: nil).annotate(caller).count + \ - MoabRecord.where('last_version_audit < ?', moab_audit_age_threshold).annotate(caller).count + MoabRecord.where(last_version_audit: ...moab_audit_age_threshold).annotate(caller).count end def moab_audits_older_than_threshold? diff --git a/config.ru b/config.ru index f7ba0b527..842bccc34 100644 --- a/config.ru +++ b/config.ru @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file is used by Rack-based servers to start the application. require_relative 'config/environment' diff --git a/config/environments/development.rb b/config/environments/development.rb index 22ed1b5b7..0305d33b7 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -19,13 +21,13 @@ # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. - if Rails.root.join("tmp/caching-dev.txt").exist? + if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store config.public_file_server.headers = { - "Cache-Control" => "public, max-age=#{2.days.to_i}" + 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -48,7 +50,6 @@ # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true - # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/config/environments/production.rb b/config/environments/production.rb index 1ce5e2198..fe19d113e 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -22,7 +24,7 @@ # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. - config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" @@ -39,7 +41,7 @@ config.log_level = :info # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -57,14 +59,14 @@ config.active_support.report_deprecations = false # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new + config.log_formatter = Logger::Formatter.new # Use a different logger for distributed setups. # require "syslog/logger" # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") - if ENV["RAILS_LOG_TO_STDOUT"].present? - logger = ActiveSupport::Logger.new(STDOUT) + if ENV['RAILS_LOG_TO_STDOUT'].present? + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end diff --git a/config/environments/test.rb b/config/environments/test.rb index eb2f1716c..048e3d7ff 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -14,12 +16,12 @@ # Eager loading loads your whole application. When running a single test locally, # this probably isn't necessary. It's a good idea to do in a continuous integration # system, or in some way before deploying your code. - config.eager_load = ENV["CI"].present? + config.eager_load = ENV['CI'].present? # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - "Cache-Control" => "public, max-age=#{1.hour.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. From e1cf6bdb274088f5d64fdcd2c10b34492d530c57 Mon Sep 17 00:00:00 2001 From: "Michael J. Giarlo" Date: Thu, 8 Aug 2024 17:33:25 -0700 Subject: [PATCH 2/2] Apply ERB lint fixes --- .../audit_information_component.html.erb | 52 +++++++++---------- .../dashboard/audit_status_component.html.erb | 2 +- .../dashboard/moab_records_component.html.erb | 4 +- .../moab_storage_roots_component.html.erb | 8 +-- .../object_versions_component.html.erb | 2 +- .../replication_endpoints_component.html.erb | 6 +-- .../replication_files_component.html.erb | 1 - app/views/dashboard/_audit_info.html.erb | 2 +- .../dashboard/_moab_information.html.erb | 6 +-- .../_replication_information.html.erb | 6 +-- app/views/dashboard/index.html.erb | 2 +- 11 files changed, 45 insertions(+), 46 deletions(-) diff --git a/app/components/dashboard/audit_information_component.html.erb b/app/components/dashboard/audit_information_component.html.erb index a506ffc96..2c44a9e53 100644 --- a/app/components/dashboard/audit_information_component.html.erb +++ b/app/components/dashboard/audit_information_component.html.erb @@ -12,9 +12,9 @@ Note also: - - + @@ -23,50 +23,50 @@ Note also: - + - - + + - - + - - + + - - + - - + + - - + - - - + + + - + - - - - - + + + + + - +
+ redis queue nameexpected frequency
(from app/config/schedule.rb)
expected frequency
(from app/config/schedule.rb)
how many objects get queued objects with audit timestamps older than threshold objects with errors
Validate Moab
moab-versioning gem validation for well-formedness (not checksums) run for locally stored Moabs.
Validate Moab
moab-versioning gem validation for well-formedness (not checksums) run for locally stored Moabs.
validate_moab on demandone:
queued by preservation robot step after updating a Moab
Moab audits older than <%= moab_audit_age_threshold %>:
<%= num_moab_audits_older_than_threshold %>
one:
queued by preservation robot step after updating a Moab
Moab audits older than <%= moab_audit_age_threshold %>:
<%= num_moab_audits_older_than_threshold %>
<%= num_moab_record_not_ok %> +
Moab to Catalog
moab-versioning gem validation for well-formedness (not checksums) run for locally stored Moabs and verified against database info.
Moab to Catalog
moab-versioning gem validation for well-formedness (not checksums) run for locally stored Moabs and verified against database info.
m2c Monthly: 11 am on the 1st of every monthall:
queued by walking directories on MoabStorageRoot.storage_location
Moab audits older than <%= moab_audit_age_threshold %>:
<%= num_moab_audits_older_than_threshold %>
all:
queued by walking directories on MoabStorageRoot.storage_location
Moab audits older than <%= moab_audit_age_threshold %>:
<%= num_moab_audits_older_than_threshold %>
<%= num_moab_record_not_ok %> +
Catalog to Moab
Database info verified against locally stored Moabs and moab-versioning integrity checks for Moabs.
Catalog to Moab
Database info verified against locally stored Moabs and moab-versioning integrity checks for Moabs.
c2m Monthly: 11 am on the 15th of every monthall:
queued by associated MoabStorageRoot
Moab audits older than <%= moab_audit_age_threshold %>:
<%= num_moab_audits_older_than_threshold %>
all:
queued by associated MoabStorageRoot
Moab audits older than <%= moab_audit_age_threshold %>:
<%= num_moab_audits_older_than_threshold %>
<%= num_moab_record_not_ok %> +
Checksum Validation (CV)
Compare checksums in Moab metadata files on local storage with computed checksums for Moab metadata and content files.
Checksum Validation (CV)
Compare checksums in Moab metadata files on local storage with computed checksums for Moab metadata and content files.
checksum_validationWeekly: 1am on Sunday
for MoabRecords with expired checks
subset:
expired checksums queued by associated MoabStorageRoot
Moab audits older than <%= moab_audit_age_threshold %>:
<%= num_moab_audits_older_than_threshold %>
Weekly: 1am on Sunday
for MoabRecords with expired checks
subset:
expired checksums queued by associated MoabStorageRoot
Moab audits older than <%= moab_audit_age_threshold %>:
<%= num_moab_audits_older_than_threshold %>
<%= MoabRecord.invalid_checksum.count %><%= num_moab_expired_checksum_validation %>

(passing checks expire 90 days after they are run)
<%= num_moab_expired_checksum_validation %>

(passing checks expire 90 days after they are run)
Catalog to Archive
Confirm a PreservedObject has all versions/parts replicated for each of its target endpoints and attempts to backfill missing ones
moab_replication_audit
part_audit_aws_s3_east_1
part_audit_aws_s3_west_2
part_audit_ibm_us_south
Weekly: 12am on Wednesday
for PreservedObjects with expired checks
subset:
expired archive checks for PreservedObjects and their associated ZipParts are queued
PreservedObject replication audits older than <%= replication_audit_age_threshold %>:
<%= num_replication_audits_older_than_threshold %>
Catalog to Archive
Confirm a PreservedObject has all versions/parts replicated for each of its target endpoints and attempts to backfill missing ones
moab_replication_audit
part_audit_aws_s3_east_1
part_audit_aws_s3_west_2
part_audit_ibm_us_south
Weekly: 12am on Wednesday
for PreservedObjects with expired checks
subset:
expired archive checks for PreservedObjects and their associated ZipParts are queued
PreservedObject replication audits older than <%= replication_audit_age_threshold %>:
<%= num_replication_audits_older_than_threshold %>
<%= num_replication_errors %><%= PreservedObject.archive_check_expired.count %>

(passing checks expire 90 days after they are run)
<%= PreservedObject.archive_check_expired.count %>

(passing checks expire 90 days after they are run)
- \ No newline at end of file + diff --git a/app/components/dashboard/audit_status_component.html.erb b/app/components/dashboard/audit_status_component.html.erb index 1fc1921bc..e5d234dc5 100644 --- a/app/components/dashboard/audit_status_component.html.erb +++ b/app/components/dashboard/audit_status_component.html.erb @@ -57,4 +57,4 @@ - \ No newline at end of file + diff --git a/app/components/dashboard/moab_records_component.html.erb b/app/components/dashboard/moab_records_component.html.erb index e855e93d2..d41566ac9 100644 --- a/app/components/dashboard/moab_records_component.html.erb +++ b/app/components/dashboard/moab_records_component.html.erb @@ -7,8 +7,8 @@ count total size average size - <%status_labels.map do |status| %> - <%=status %> + <% status_labels.map do |status| %> + <%= status %> <% end %> expired checksum audit diff --git a/app/components/dashboard/moab_storage_roots_component.html.erb b/app/components/dashboard/moab_storage_roots_component.html.erb index 9c34cdd8e..eb847b903 100644 --- a/app/components/dashboard/moab_storage_roots_component.html.erb +++ b/app/components/dashboard/moab_storage_roots_component.html.erb @@ -13,7 +13,7 @@ average size moab count <% status_labels.map do |status| %> - <%=status %> + <%= status %> <% end %> expired checksum audit @@ -39,9 +39,9 @@ totals - - - + + + <%= storage_roots_moab_count %> <%= storage_roots_ok_count %> <%= storage_roots_invalid_moab_count %> diff --git a/app/components/dashboard/object_versions_component.html.erb b/app/components/dashboard/object_versions_component.html.erb index 95e0be902..296d65216 100644 --- a/app/components/dashboard/object_versions_component.html.erb +++ b/app/components/dashboard/object_versions_component.html.erb @@ -4,7 +4,7 @@ - diff --git a/app/components/dashboard/replication_endpoints_component.html.erb b/app/components/dashboard/replication_endpoints_component.html.erb index f6568b1b0..a9abec6fb 100644 --- a/app/components/dashboard/replication_endpoints_component.html.erb +++ b/app/components/dashboard/replication_endpoints_component.html.erb @@ -8,8 +8,8 @@ - - + + @@ -25,4 +25,4 @@
+ count object version count highest version
endpoint name ActiveJob class for replicationZippedMoabVersion replicated count
(according to database)
Number of Object Versions
(per PreservedObject)
ZippedMoabVersion replicated count
(according to database)
Number of Object Versions
(per PreservedObject)
- \ No newline at end of file + diff --git a/app/components/dashboard/replication_files_component.html.erb b/app/components/dashboard/replication_files_component.html.erb index a3719a055..2e81530ba 100644 --- a/app/components/dashboard/replication_files_component.html.erb +++ b/app/components/dashboard/replication_files_component.html.erb @@ -22,4 +22,3 @@ - diff --git a/app/views/dashboard/_audit_info.html.erb b/app/views/dashboard/_audit_info.html.erb index 490442e0e..075212df7 100644 --- a/app/views/dashboard/_audit_info.html.erb +++ b/app/views/dashboard/_audit_info.html.erb @@ -1,3 +1,3 @@ <%= render Dashboard::AuditInformationComponent.new %> - \ No newline at end of file + diff --git a/app/views/dashboard/_moab_information.html.erb b/app/views/dashboard/_moab_information.html.erb index f523eadf4..6de994ed3 100644 --- a/app/views/dashboard/_moab_information.html.erb +++ b/app/views/dashboard/_moab_information.html.erb @@ -1,15 +1,15 @@

Moabs on Storage - Status Information

PreservedObject records store the druid and links to db records about both the Moab on local storage and all the replicated instances in the cloud.

MoabRecord records are about the Moab on storage.

-
+
<%= render Spinner.new %> -
+
<%= render Spinner.new %> -
+
<%= render Spinner.new %> diff --git a/app/views/dashboard/_replication_information.html.erb b/app/views/dashboard/_replication_information.html.erb index 4974a3ff7..68264448c 100644 --- a/app/views/dashboard/_replication_information.html.erb +++ b/app/views/dashboard/_replication_information.html.erb @@ -6,12 +6,12 @@

ZippedMoabVersion records are about a single version of the Moab that is archived as zip file(s) on a particular cloud endpoint.

ZipPart records represent the individual zip segments for a ZippedMoabVersion (zips larger than <%= Replication::DruidVersionZip::ZIP_SPLIT_SIZE %> are split into segments) that are replicated to the particular cloud endpoint.

See Replication Flow wiki for details about the replication process.

-
+
<%= render Spinner.new %> -
+
<%= render Spinner.new %> -
+
diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb index 88f3435d1..1f16f2a00 100644 --- a/app/views/dashboard/index.html.erb +++ b/app/views/dashboard/index.html.erb @@ -20,7 +20,7 @@ -
+