mirror of
https://github.com/Evolix/chexpire.git
synced 2024-04-27 06:20:50 +02:00
Merge pull request #90 from Evolix/unsupported-whois
Manual mode for unsupported TLDs
This commit is contained in:
commit
0f3571b3bb
|
@ -3,7 +3,7 @@
|
|||
|
||||
class ChecksController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_check, except: [:index, :new, :create]
|
||||
before_action :set_check, except: [:index, :new, :create, :supports]
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
|
@ -65,6 +65,11 @@ class ChecksController < ApplicationController
|
|||
redirect_to checks_path
|
||||
end
|
||||
|
||||
def supports
|
||||
@check = Check.new(new_check_params)
|
||||
authorize @check
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_check
|
||||
|
@ -82,7 +87,7 @@ class ChecksController < ApplicationController
|
|||
|
||||
def check_params(*others)
|
||||
params.require(:check)
|
||||
.permit(:domain, :domain_created_at, :comment, :vendor, :round_robin, *others,
|
||||
.permit(:domain, :domain_expires_at, :comment, :vendor, :round_robin, *others,
|
||||
notifications_attributes: [:id, :channel, :recipient, :interval])
|
||||
end
|
||||
|
||||
|
|
57
app/frontend/components/check_validation.js
Normal file
57
app/frontend/components/check_validation.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
|
||||
// License: GNU AGPL-3+ (see full text in LICENSE file)
|
||||
|
||||
function checkValidationInitialize() {
|
||||
const element = document.getElementById("check_domain");
|
||||
|
||||
if (element && element.dataset.kind == "domain") {
|
||||
addEventSupportListener(element);
|
||||
}
|
||||
}
|
||||
|
||||
function addEventSupportListener(element) {
|
||||
element.addEventListener("blur", event => {
|
||||
const request = $.ajax("/checks/supports.json", {
|
||||
method: "post",
|
||||
dataType: "json",
|
||||
data: {
|
||||
check: {
|
||||
domain: event.target.value,
|
||||
kind: element.dataset.kind,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
request.done(response => {
|
||||
const { supported } = response.check;
|
||||
|
||||
toggleUnsupportedContainers(supported);
|
||||
setFocus(supported);
|
||||
|
||||
// set normalized domain
|
||||
element.value = response.check.domain;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleUnsupportedContainers(supported) {
|
||||
const containerClass = supported ? "d-none" : "d-block";
|
||||
|
||||
document.getElementById("check_domain_expires_at_container").className = containerClass;
|
||||
|
||||
const domainHint = document.getElementById("check_domain_unsupported_container");
|
||||
domainHint.classList.remove("d-none");
|
||||
domainHint.classList.remove("d-block");
|
||||
domainHint.classList.add(containerClass);
|
||||
}
|
||||
|
||||
|
||||
function setFocus(supported) {
|
||||
if (supported) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("check_domain_expires_at").focus();
|
||||
}
|
||||
|
||||
export default checkValidationInitialize;
|
|
@ -20,9 +20,13 @@ import 'bootstrap/js/dist/tooltip';
|
|||
|
||||
import '../scss';
|
||||
|
||||
import checkValidationInitialize from '../components/check_validation';
|
||||
|
||||
Rails.start()
|
||||
Turbolinks.start()
|
||||
|
||||
document.addEventListener("turbolinks:load", () => {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
checkValidationInitialize();
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
|
||||
// License: GNU AGPL-3+ (see full text in LICENSE file)
|
||||
|
||||
$input-placeholder-color: #013d3a;
|
||||
$input-placeholder-color: #b9bbbb;
|
||||
$enable-rounded: false;
|
||||
$theme-colors: (
|
||||
"primary": #118b83, //light-green
|
||||
|
|
|
@ -20,9 +20,6 @@ class WhoisSyncJob < ApplicationJob
|
|||
return unless response.valid?
|
||||
|
||||
update_from_response(response)
|
||||
rescue Whois::DomainNotFoundError
|
||||
check.active = false
|
||||
check.save!
|
||||
end
|
||||
|
||||
def update_from_response(response)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# kind :integer not null
|
||||
# last_run_at :datetime
|
||||
# last_success_at :datetime
|
||||
# mode :integer default("auto"), not null
|
||||
# round_robin :boolean default(TRUE)
|
||||
# vendor :string(255)
|
||||
# created_at :datetime not null
|
||||
|
@ -39,6 +40,7 @@ class Check < ApplicationRecord
|
|||
reject_if: lambda { |at| at["recipient"].blank? && at["interval"].blank? }
|
||||
|
||||
enum kind: [:domain, :ssl]
|
||||
enum mode: [:auto, :manual]
|
||||
|
||||
self.skip_time_zone_conversion_for_attributes = [
|
||||
:domain_created_at,
|
||||
|
@ -50,10 +52,12 @@ class Check < ApplicationRecord
|
|||
validates :domain, presence: true
|
||||
validate :domain_created_at_past
|
||||
validate :domain_updated_at_past
|
||||
validates :domain_expires_at, presence: true, unless: :supported?
|
||||
validates :comment, length: { maximum: 255 }
|
||||
validates :vendor, length: { maximum: 255 }
|
||||
|
||||
before_save :reset_consecutive_failures
|
||||
before_save :set_mode
|
||||
after_update :reset_notifications
|
||||
after_save :enqueue_sync
|
||||
|
||||
|
@ -84,6 +88,20 @@ class Check < ApplicationRecord
|
|||
save!
|
||||
end
|
||||
|
||||
def supported?
|
||||
return true unless domain?
|
||||
return true if domain.blank?
|
||||
|
||||
begin
|
||||
Whois::Parser.for(domain)
|
||||
true
|
||||
rescue Whois::UnsupportedDomainError
|
||||
false
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def domain_created_at_past
|
||||
|
@ -113,4 +131,9 @@ class Check < ApplicationRecord
|
|||
|
||||
self.consecutive_failures = 0
|
||||
end
|
||||
|
||||
def set_mode
|
||||
return unless domain_changed?
|
||||
self.mode = supported? ? :auto : :manual
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,10 @@ class CheckPolicy < ApplicationPolicy
|
|||
owner?
|
||||
end
|
||||
|
||||
def supports?
|
||||
new?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def owner?
|
||||
|
|
|
@ -54,6 +54,7 @@ module CheckProcessor
|
|||
def base_scope
|
||||
Check
|
||||
.active
|
||||
.auto
|
||||
.where("last_run_at IS NULL OR last_run_at < DATE_SUB(NOW(), INTERVAL 12 HOUR)")
|
||||
end
|
||||
|
||||
|
|
|
@ -3,13 +3,33 @@
|
|||
<%= simple_form_for(check) do |f| %>
|
||||
<%= f.input :domain,
|
||||
autofocus: true,
|
||||
input_html: { autocapitalize: :none, autocorrect: :off },
|
||||
label: t(".#{check.kind || "generic" }.domain") %>
|
||||
input_html: { autocapitalize: :none, autocorrect: :off, data: { kind: check.kind } },
|
||||
label: t(".#{check.kind || "generic" }.domain"),
|
||||
hint: t(".#{check.kind || "generic" }.unsupported"),
|
||||
hint_html: {
|
||||
id: "check_domain_unsupported_container",
|
||||
class: "#{check.supported? && 'd-none'}",
|
||||
}
|
||||
%>
|
||||
|
||||
<% if check.new_record? %>
|
||||
<%= f.input :kind, as: check.kind.present? ? :hidden : :radio_buttons, collection: Check.kinds.keys %>
|
||||
<% end %>
|
||||
|
||||
<div id="check_domain_expires_at_container" class="<%= check.supported? ? "d-none" : "d-block" %>">
|
||||
<%= f.input :domain_expires_at,
|
||||
required: true,
|
||||
input_html: {
|
||||
type: :date,
|
||||
value: check.domain_expires_at&.to_date,
|
||||
min: Date.yesterday,
|
||||
max: 10.years.from_now.end_of_year.to_date
|
||||
},
|
||||
as: :string,
|
||||
placeholder: t(".domain_expires_at_placeholder")
|
||||
%>
|
||||
</div>
|
||||
|
||||
<%= f.input :comment %>
|
||||
<%= f.input :vendor %>
|
||||
|
||||
|
|
7
app/views/checks/supports.json.jbuilder
Normal file
7
app/views/checks/supports.json.jbuilder
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
|
||||
# License: GNU AGPL-3+ (see full text in LICENSE file)
|
||||
|
||||
json.check do
|
||||
json.supported @check.supported?
|
||||
json.domain normalize_domain(@check.domain)
|
||||
end
|
|
@ -119,6 +119,10 @@ en:
|
|||
domain: Domain
|
||||
domain:
|
||||
domain: Domain name
|
||||
unsupported: |
|
||||
This top-level domain isn't currently automatically supported.
|
||||
You'll have to fill and maintain yourself the expiry date.
|
||||
domain_expires_at_placeholder: YYYY-MM-DD.
|
||||
ssl:
|
||||
domain: Hostname
|
||||
notifications_hint: |
|
||||
|
|
|
@ -11,6 +11,7 @@ fr:
|
|||
kind: Type
|
||||
domain_created_at: "Date de création"
|
||||
domain_updated_at: "Date de modification"
|
||||
domain_expires_at: "Date d'expiration"
|
||||
notification:
|
||||
interval: Délai
|
||||
recipient: Destinataire
|
||||
|
@ -151,6 +152,10 @@ fr:
|
|||
domain: Domaine
|
||||
domain:
|
||||
domain: Nom de domaine
|
||||
unsupported: |
|
||||
Cette extension n'est pas supportée automatiquement actuellement.
|
||||
Vous devrez saisir et maintenir vous-même sa date d'expiration.
|
||||
domain_expires_at_placeholder: AAAA-MM-JJ
|
||||
ssl:
|
||||
domain: Nom d'hôte
|
||||
notifications_hint: |
|
||||
|
|
|
@ -1,10 +1,29 @@
|
|||
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
|
||||
# License: GNU AGPL-3+ (see full text in LICENSE file)
|
||||
|
||||
# In order to update the route map below,
|
||||
# run `bundle exec annotate -r` after modifying this file
|
||||
Rails.application.routes.draw do
|
||||
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
|
||||
|
||||
resources :checks, except: [:show] do
|
||||
resources :notifications, only: [:destroy]
|
||||
collection do
|
||||
post :supports, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
devise_for :users
|
||||
root to: "pages#home"
|
||||
|
||||
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
|
||||
end
|
||||
|
||||
# == Route Map
|
||||
#
|
||||
# Prefix Verb URI Pattern Controller#Action
|
||||
# check_notification DELETE /checks/:check_id/notifications/:id(.:format) notifications#destroy
|
||||
# supports_checks POST /checks/supports(.:format) checks#supports
|
||||
# checks GET /checks(.:format) checks#index
|
||||
# POST /checks(.:format) checks#create
|
||||
# new_check GET /checks/new(.:format) checks#new
|
||||
|
@ -44,18 +63,3 @@
|
|||
# letters GET / letter_opener_web/letters#index
|
||||
# letter GET /:id(/:style)(.:format) letter_opener_web/letters#show
|
||||
# GET /:id/attachments/:file(.:format) letter_opener_web/letters#attachment
|
||||
|
||||
# In order to update the route map above,
|
||||
# run `bundle exec annotate -r` after modifying this file
|
||||
Rails.application.routes.draw do
|
||||
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
|
||||
|
||||
resources :checks, except: [:show] do
|
||||
resources :notifications, only: [:destroy]
|
||||
end
|
||||
|
||||
devise_for :users
|
||||
root to: "pages#home"
|
||||
|
||||
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
|
||||
end
|
||||
|
|
5
db/migrate/20180829134404_add_mode_to_checks.rb
Normal file
5
db/migrate/20180829134404_add_mode_to_checks.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AddModeToChecks < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :checks, :mode, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2018_08_01_072038) do
|
||||
ActiveRecord::Schema.define(version: 2018_08_29_134404) do
|
||||
|
||||
create_table "check_logs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
|
||||
t.bigint "check_id"
|
||||
|
@ -40,6 +40,7 @@ ActiveRecord::Schema.define(version: 2018_08_01_072038) do
|
|||
t.datetime "updated_at", null: false
|
||||
t.boolean "round_robin", default: true
|
||||
t.integer "consecutive_failures", default: 0, null: false
|
||||
t.integer "mode", default: 0, null: false
|
||||
t.index ["user_id"], name: "index_checks_on_user_id"
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ if Rails.env.development?
|
|||
# same name.
|
||||
Annotate.set_defaults(
|
||||
'routes' => 'before',
|
||||
'position_in_routes' => 'before',
|
||||
'position_in_routes' => 'after',
|
||||
'position_in_class' => 'before',
|
||||
'position_in_test' => 'before',
|
||||
'position_in_fixture' => 'before',
|
||||
|
|
14
lib/tasks/one_shot.rake
Normal file
14
lib/tasks/one_shot.rake
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace :one_shot do
|
||||
desc "Set manual mode for unsupported checks"
|
||||
task reset_checks_modes: :environment do
|
||||
Check.domain.find_each do |check|
|
||||
check.mode = if check.supported?
|
||||
:auto
|
||||
else
|
||||
:manual
|
||||
end
|
||||
|
||||
check.save(validate: false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@
|
|||
# kind :integer not null
|
||||
# last_run_at :datetime
|
||||
# last_success_at :datetime
|
||||
# mode :integer default("auto"), not null
|
||||
# round_robin :boolean default(TRUE)
|
||||
# vendor :string(255)
|
||||
# created_at :datetime not null
|
||||
|
@ -44,6 +45,7 @@ FactoryBot.define do
|
|||
last_run_at nil
|
||||
last_success_at nil
|
||||
consecutive_failures 0
|
||||
mode :auto
|
||||
|
||||
trait :domain do
|
||||
kind :domain
|
||||
|
|
|
@ -24,7 +24,6 @@ class WhoisSyncJobTest < ActiveJob::TestCase
|
|||
|
||||
test "ignore invalid response (domain.fr)" do
|
||||
check = create(:check, :nil_dates, domain: "domain.fr")
|
||||
original_updated_at = check.updated_at
|
||||
|
||||
mock_system_command("whois", "domain.fr", stdout: "not a response") do
|
||||
WhoisSyncJob.perform_now(check.id)
|
||||
|
@ -34,7 +33,6 @@ class WhoisSyncJobTest < ActiveJob::TestCase
|
|||
|
||||
assert_just_now check.last_run_at
|
||||
assert_nil check.last_success_at
|
||||
assert_equal original_updated_at, check.updated_at
|
||||
assert check.active?
|
||||
assert_equal 1, check.consecutive_failures
|
||||
end
|
||||
|
@ -62,7 +60,7 @@ class WhoisSyncJobTest < ActiveJob::TestCase
|
|||
assert_equal 1, check.consecutive_failures
|
||||
end
|
||||
|
||||
test "disable check when whois responds domain not found" do
|
||||
test "increment consecutive failures when whois responds domain not found" do
|
||||
domain = "willneverexist.fr"
|
||||
check = create(:check, :nil_dates, domain: domain)
|
||||
|
||||
|
@ -72,7 +70,6 @@ class WhoisSyncJobTest < ActiveJob::TestCase
|
|||
|
||||
check.reload
|
||||
|
||||
refute check.active?
|
||||
assert_just_now check.last_run_at
|
||||
assert_nil check.last_success_at
|
||||
assert_equal 1, check.consecutive_failures
|
||||
|
|
|
@ -71,4 +71,34 @@ class CheckTest < ActiveSupport::TestCase
|
|||
check = build(:check, last_success_at: (10.1 * 24).hours.ago)
|
||||
assert_equal 10, check.days_from_last_success
|
||||
end
|
||||
|
||||
test "supported? for domain" do
|
||||
check = build(:check, :domain, domain: "domain.fr")
|
||||
assert check.supported?
|
||||
|
||||
check = build(:check, :domain, domain: "domain.cn")
|
||||
refute check.supported?
|
||||
|
||||
# an empty domain name is still considered as supported
|
||||
check = build(:check, :domain, domain: "")
|
||||
assert check.supported?
|
||||
end
|
||||
|
||||
test "supported? for SSL" do
|
||||
check = build(:check, :ssl)
|
||||
assert check.supported?
|
||||
|
||||
check = build(:check, :ssl, domain: "domain.cn")
|
||||
assert check.supported?
|
||||
end
|
||||
|
||||
test "set mode before saving" do
|
||||
check = build(:check, domain: "domain.fr")
|
||||
check.save!
|
||||
assert check.auto?
|
||||
|
||||
check.domain = "domain.xyz"
|
||||
check.save!
|
||||
assert check.mode?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -108,6 +108,16 @@ class CheckProcessorTest < ActiveSupport::TestCase
|
|||
assert_not_includes checks, c2
|
||||
end
|
||||
|
||||
test "resolvers does not include manual checks" do
|
||||
c1 = create(:check, :expires_next_week)
|
||||
c2 = create(:check, :expires_next_week, domain: "fff.wxyz")
|
||||
|
||||
checks = @processor.resolve_expire_short_term
|
||||
|
||||
assert_includes checks, c1
|
||||
assert_not_includes checks, c2
|
||||
end
|
||||
|
||||
test "#sync_dates respects the interval configuration between sends" do
|
||||
create_list(:check, 3, :expires_next_week)
|
||||
|
||||
|
|
|
@ -25,6 +25,26 @@ class ChecksTest < ApplicationSystemTestCase
|
|||
fill_and_valid_new_check
|
||||
end
|
||||
|
||||
test "create a manual domain check" do
|
||||
visit new_check_path(kind: :domain)
|
||||
|
||||
domain = "unsupported.wxyz"
|
||||
fill_in("check[domain]", with: domain)
|
||||
|
||||
page.find("body").click # simulate blur
|
||||
fill_in("check[domain_expires_at]", with: "2022-04-05")
|
||||
|
||||
click_button
|
||||
|
||||
assert_equal checks_path, page.current_path
|
||||
|
||||
assert page.has_css?(".alert-success")
|
||||
assert page.has_content?(domain)
|
||||
|
||||
check = Check.last
|
||||
assert_equal Date.new(2022, 4, 5), check.domain_expires_at
|
||||
end
|
||||
|
||||
test "create a predefined ssl check" do
|
||||
visit new_check_path(kind: :ssl)
|
||||
|
||||
|
|
Loading…
Reference in a new issue