diff --git a/Gemfile b/Gemfile index 8a77c1bf6..c866625c0 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,6 @@ gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'hiredis', '~> 0.6' gem 'redis-namespace', '~> 1.8' -gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b' gem 'htmlentities', '~> 4.3' gem 'http', '~> 4.4' gem 'http_accept_language', '~> 2.1' diff --git a/Gemfile.lock b/Gemfile.lock index 9fc70e175..1d6480a7b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -21,14 +21,6 @@ GIT sidekiq (>= 3.5) statsd-ruby (~> 1.4, >= 1.4.0) -GIT - remote: https://github.com/ianheggie/health_check - revision: 0b799ead604f900ed50685e9b2d469cd2befba5b - ref: 0b799ead604f900ed50685e9b2d469cd2befba5b - specs: - health_check (4.0.0.pre) - rails (>= 4.0) - GIT remote: https://github.com/nsommer/pluck_each revision: 73be0947c52fc54bf6d7085378db008358aac5eb @@ -567,7 +559,7 @@ GEM rspec-support (3.10.2) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.12.0) + rubocop (1.12.1) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) @@ -756,7 +748,6 @@ DEPENDENCIES fog-openstack (~> 0.3) fuubar (~> 2.5) hamlit-rails (~> 0.2) - health_check! hiredis (~> 0.6) htmlentities (~> 4.3) http (~> 4.4) diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index e87dd076f..9e921fb95 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -3,13 +3,8 @@ require 'sidekiq/api' module Admin class DashboardController < BaseController - SIDEKIQ_QUEUES = %w(default push mailers pull scheduler).freeze - def index - missing_queues = Sidekiq::ProcessSet.new.reduce(SIDEKIQ_QUEUES) { |queues, process| queues - process['queues'] } - - flash.now[:alert] = I18n.t('admin.dashboard.misconfigured_sidekiq_alert', queues: missing_queues.join(', ')) unless missing_queues.empty? - + @system_checks = Admin::SystemCheck.perform @users_count = User.count @pending_users_count = User.pending.count @registrations_week = Redis.current.get("activity:accounts:local:#{current_week}") || 0 diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb new file mode 100644 index 000000000..2a22a0557 --- /dev/null +++ b/app/controllers/health_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class HealthController < ActionController::Base + def show + render plain: 'OK' + end +end diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index b41de58d7..4ea7b48fe 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -90,7 +90,11 @@ class ColumnsArea extends ImmutablePureComponent { } if (this.mediaQuery) { - this.mediaQuery.addEventListener('change', this.handleLayoutChange); + if (this.mediaQuery.addEventListener) { + this.mediaQuery.addEventListener('change', this.handleLayoutChange); + } else { + this.mediaQuery.addListener(this.handleLayoutChange); + } this.setState({ renderComposePanel: !this.mediaQuery.matches }); } @@ -125,7 +129,11 @@ class ColumnsArea extends ImmutablePureComponent { } if (this.mediaQuery) { - this.mediaQuery.removeEventListener('change', this.handleLayoutChange); + if (this.mediaQuery.removeEventListener) { + this.mediaQuery.removeEventListener('change', this.handleLayoutChange); + } else { + this.mediaQuery.removeListener(this.handleLayouteChange); + } } } diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index a65ef4454..b93acd6cd 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -595,6 +595,12 @@ code { color: $valid-value-color; } + &.warning { + border: 1px solid rgba($gold-star, 0.5); + background: rgba($gold-star, 0.25); + color: $gold-star; + } + &.alert { border: 1px solid rgba($error-value-color, 0.5); background: rgba($error-value-color, 0.1); @@ -616,6 +622,19 @@ code { } } + &.warning a { + font-weight: 700; + color: inherit; + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + color: inherit; + } + } + p { margin-bottom: 15px; } @@ -672,6 +691,29 @@ code { } } +.flash-message-stack { + margin-bottom: 30px; + + .flash-message { + border-radius: 0; + margin-bottom: 0; + border-top-width: 0; + + &:first-child { + border-radius: 4px 4px 0 0; + border-top-width: 1px; + } + + &:last-child { + border-radius: 0 0 4px 4px; + + &:first-child { + border-radius: 4px; + } + } + } +} + .form-footer { margin-top: 30px; text-align: center; diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 85a92fc3a..270be2851 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -90,7 +90,11 @@ class ColumnsArea extends ImmutablePureComponent { } if (this.mediaQuery) { - this.mediaQuery.addEventListener('change', this.handleLayoutChange); + if (this.mediaQuery.addEventListener) { + this.mediaQuery.addEventListener('change', this.handleLayoutChange); + } else { + this.mediaQuery.addListener(this.handleLayoutChange); + } this.setState({ renderComposePanel: !this.mediaQuery.matches }); } @@ -125,7 +129,11 @@ class ColumnsArea extends ImmutablePureComponent { } if (this.mediaQuery) { - this.mediaQuery.removeEventListener('change', this.handleLayoutChange); + if (this.mediaQuery.removeEventListener) { + this.mediaQuery.removeEventListener('change', this.handleLayoutChange); + } else { + this.mediaQuery.removeListener(this.handleLayouteChange); + } } } diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index e0604303b..ef4a08c59 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -604,6 +604,12 @@ code { color: $valid-value-color; } + &.warning { + border: 1px solid rgba($gold-star, 0.5); + background: rgba($gold-star, 0.25); + color: $gold-star; + } + &.alert { border: 1px solid rgba($error-value-color, 0.5); background: rgba($error-value-color, 0.1); @@ -625,6 +631,19 @@ code { } } + &.warning a { + font-weight: 700; + color: inherit; + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + color: inherit; + } + } + p { margin-bottom: 15px; } @@ -681,6 +700,29 @@ code { } } +.flash-message-stack { + margin-bottom: 30px; + + .flash-message { + border-radius: 0; + margin-bottom: 0; + border-top-width: 0; + + &:first-child { + border-radius: 4px 4px 0 0; + border-top-width: 1px; + } + + &:last-child { + border-radius: 0 0 4px 4px; + + &:first-child { + border-radius: 4px; + } + } + } +} + .form-footer { margin-top: 30px; text-align: center; diff --git a/app/lib/admin/system_check.rb b/app/lib/admin/system_check.rb new file mode 100644 index 000000000..afb20cb47 --- /dev/null +++ b/app/lib/admin/system_check.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Admin::SystemCheck + ACTIVE_CHECKS = [ + Admin::SystemCheck::DatabaseSchemaCheck, + Admin::SystemCheck::SidekiqProcessCheck, + Admin::SystemCheck::RulesCheck, + ].freeze + + def self.perform + ACTIVE_CHECKS.each_with_object([]) do |klass, arr| + check = klass.new + + if check.pass? + arr + else + arr << check.message + end + end + end +end diff --git a/app/lib/admin/system_check/base_check.rb b/app/lib/admin/system_check/base_check.rb new file mode 100644 index 000000000..fcad8daca --- /dev/null +++ b/app/lib/admin/system_check/base_check.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Admin::SystemCheck::BaseCheck + def pass? + raise NotImplementedError + end + + def message + raise NotImplementedError + end +end diff --git a/app/lib/admin/system_check/database_schema_check.rb b/app/lib/admin/system_check/database_schema_check.rb new file mode 100644 index 000000000..b93d1954e --- /dev/null +++ b/app/lib/admin/system_check/database_schema_check.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Admin::SystemCheck::DatabaseSchemaCheck < Admin::SystemCheck::BaseCheck + def pass? + !ActiveRecord::Base.connection.migration_context.needs_migration? + end + + def message + Admin::SystemCheck::Message.new(:database_schema_check) + end +end diff --git a/app/lib/admin/system_check/message.rb b/app/lib/admin/system_check/message.rb new file mode 100644 index 000000000..bfcad3bf3 --- /dev/null +++ b/app/lib/admin/system_check/message.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Admin::SystemCheck::Message + attr_reader :key, :value, :action + + def initialize(key, value = nil, action = nil) + @key = key + @value = value + @action = action + end +end diff --git a/app/lib/admin/system_check/rules_check.rb b/app/lib/admin/system_check/rules_check.rb new file mode 100644 index 000000000..1fbdf955d --- /dev/null +++ b/app/lib/admin/system_check/rules_check.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Admin::SystemCheck::RulesCheck < Admin::SystemCheck::BaseCheck + include RoutingHelper + + def pass? + Rule.kept.exists? + end + + def message + Admin::SystemCheck::Message.new(:rules_check, nil, admin_rules_path) + end +end diff --git a/app/lib/admin/system_check/sidekiq_process_check.rb b/app/lib/admin/system_check/sidekiq_process_check.rb new file mode 100644 index 000000000..c44d86c44 --- /dev/null +++ b/app/lib/admin/system_check/sidekiq_process_check.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class Admin::SystemCheck::SidekiqProcessCheck < Admin::SystemCheck::BaseCheck + SIDEKIQ_QUEUES = %w( + default + push + mailers + pull + scheduler + ingress + ).freeze + + def pass? + missing_queues.empty? + end + + def message + Admin::SystemCheck::Message.new(:sidekiq_process_check, missing_queues.join(', ')) + end + + private + + def missing_queues + @missing_queues ||= Sidekiq::ProcessSet.new.reduce(SIDEKIQ_QUEUES) { |queues, process| queues - process['queues'] } + end +end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 249e12563..f2f0c813d 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,6 +1,14 @@ - content_for :page_title do = t('admin.dashboard.title') +- unless @system_checks.empty? + .flash-message-stack + - @system_checks.each do |message| + .flash-message.warning + = t("admin.system_checks.#{message.key}.message_html", message.value ? { value: content_tag(:strong, message.value) } : {}) + - if message.action + = link_to t("admin.system_checks.#{message.key}.action"), message.action + .dashboard__counters %div = link_to admin_accounts_url(local: 1, recent: 1) do diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 3daaf93a9..09826afb3 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -45,5 +45,5 @@ = content_for?(:content) ? yield(:content) : yield .logo-resources - = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') - = render file: Rails.root.join('app', 'javascript', 'images', 'logo_full.svg') + = raw render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') + = raw render file: Rails.root.join('app', 'javascript', 'images', 'logo_full.svg') diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml index 431bd260c..2a2996d28 100644 --- a/app/views/layouts/embedded.html.haml +++ b/app/views/layouts/embedded.html.haml @@ -25,4 +25,4 @@ = yield .logo-resources - = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') + = raw render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb deleted file mode 100644 index 6f1e78fed..000000000 --- a/config/initializers/health_check.rb +++ /dev/null @@ -1,8 +0,0 @@ -HealthCheck.setup do |config| - config.uri = 'health' - - config.standard_checks = %w(database migrations cache) - config.full_checks = %w(database migrations cache) - - config.include_error_in_response_body = false -end diff --git a/config/locales/en.yml b/config/locales/en.yml index b907d3882..182a8e985 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -367,7 +367,6 @@ en: feature_timeline_preview: Timeline preview features: Features hidden_service: Federation with hidden services - misconfigured_sidekiq_alert: 'No Sidekiq process seems to be handling the following queues: %{queues}. Please review your Sidekiq configuration.' open_reports: open reports pending_tags: hashtags waiting for review pending_users: users waiting for review @@ -661,6 +660,14 @@ en: no_status_selected: No statuses were changed as none were selected title: Account statuses with_media: With media + system_checks: + database_schema_check: + message_html: There are pending database migrations. Please run them to ensure the application behaves as expected + rules_check: + action: Manage server rules + message_html: You haven't defined any server rules. + sidekiq_process_check: + message_html: No Sidekiq process running for the %{value} queue(s). Please review your Sidekiq configuration tags: accounts_today: Unique uses today accounts_week: Unique uses this week diff --git a/config/routes.rb b/config/routes.rb index 6814dc61e..8ec67113b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,7 +10,7 @@ Rails.application.routes.draw do mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development? - health_check_routes + get 'health', to: 'health#show' authenticate :user, lambda { |u| u.admin? } do mount Sidekiq::Web, at: 'sidekiq', as: :sidekiq diff --git a/lib/mastodon/cli_helper.rb b/lib/mastodon/cli_helper.rb index ed22f44b2..aaee1fa91 100644 --- a/lib/mastodon/cli_helper.rb +++ b/lib/mastodon/cli_helper.rb @@ -25,7 +25,9 @@ module Mastodon exit(1) end - ActiveRecord::Base.configurations[Rails.env]['pool'] = options[:concurrency] + 1 + db_config = ActiveRecord::Base.configurations[Rails.env].dup + db_config['pool'] = options[:concurrency] + 1 + ActiveRecord::Base.establish_connection(db_config) progress = create_progress_bar(scope.count) pool = Concurrent::FixedThreadPool.new(options[:concurrency]) diff --git a/package.json b/package.json index 7337760bf..c5855f6af 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,7 @@ "twitter-text": "3.1.0", "uuid": "^8.3.1", "webpack": "^4.46.0", - "webpack-assets-manifest": "^4.0.1", + "webpack-assets-manifest": "^4.0.2", "webpack-bundle-analyzer": "^4.4.0", "webpack-cli": "^3.3.12", "webpack-merge": "^5.7.3", @@ -176,7 +176,7 @@ }, "devDependencies": { "@testing-library/jest-dom": "^5.11.10", - "@testing-library/react": "^11.2.5", + "@testing-library/react": "^11.2.6", "babel-eslint": "^10.1.0", "babel-jest": "^26.6.3", "eslint": "^7.23.0", diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_controller_spec.rb similarity index 60% rename from spec/controllers/health_check_controller_spec.rb rename to spec/controllers/health_controller_spec.rb index c00600c9b..1e41f6ed7 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_controller_spec.rb @@ -1,10 +1,10 @@ require 'rails_helper' -describe HealthCheck::HealthCheckController do +describe HealthController do render_views describe 'GET #show' do - subject(:response) { get :index, params: { format: :json } } + subject(:response) { get :show, params: { format: :json } } it 'returns the right response' do expect(response).to have_http_status 200 diff --git a/yarn.lock b/yarn.lock index 4d8a1a4a6..d0aa62307 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1405,10 +1405,10 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^11.2.5": - version "11.2.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" - integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== +"@testing-library/react@^11.2.6": + version "11.2.6" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" + integrity sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ== dependencies: "@babel/runtime" "^7.12.5" "@testing-library/dom" "^7.28.1" @@ -3696,7 +3696,7 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@^4.2.2: +deepmerge@^4.0, deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== @@ -6894,7 +6894,7 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lockfile@^1.0.4: +lockfile@^1.0: version "1.0.4" resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA== @@ -11213,14 +11213,14 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== -webpack-assets-manifest@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-4.0.1.tgz#918989c51a7800be6683aaa27b9f36bcc7a9afdc" - integrity sha512-NS7Bx2C3JsEj6a0MB/PPmPOD/BzDYjB3PaKcI7/r2fKXq0PuZ4YtcbZ5Og+q4gkmetGX9v21vejeAlbru/Fvhw== +webpack-assets-manifest@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-4.0.2.tgz#ead6e6dbdcd1c2af45d11a382246fcc79a286372" + integrity sha512-bBb9PvEGDOCFvW5/t6Yp9MEE0fymNJ0OvEud9nPvQegDbQEUZ/2WTeHnNoALwWMu1x3JHPyqHVYh8SwtYZ/dww== dependencies: chalk "^4.0" - deepmerge "^4.2.2" - lockfile "^1.0.4" + deepmerge "^4.0" + lockfile "^1.0" lodash.escaperegexp "^4.0" lodash.get "^4.0" lodash.has "^4.0"