mirror of https://github.com/Evolix/chexpire.git
commit
d83f618fe2
2
Gemfile
2
Gemfile
|
@ -42,6 +42,8 @@ gem 'whenever', require: false
|
|||
|
||||
|
||||
gem 'octicons'
|
||||
gem 'kaminari'
|
||||
gem 'has_scope'
|
||||
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem 'bootsnap', '>= 1.1.0', require: false
|
||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -131,6 +131,9 @@ GEM
|
|||
guard-minitest (2.4.6)
|
||||
guard-compat (~> 1.2)
|
||||
minitest (>= 3.0)
|
||||
has_scope (0.7.2)
|
||||
actionpack (>= 4.1)
|
||||
activesupport (>= 4.1)
|
||||
i18n (1.0.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-like (0.3.0)
|
||||
|
@ -138,6 +141,18 @@ GEM
|
|||
activesupport (>= 4.2.0)
|
||||
multi_json (>= 1.2)
|
||||
json (2.1.0)
|
||||
kaminari (1.1.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.1.1)
|
||||
kaminari-activerecord (= 1.1.1)
|
||||
kaminari-core (= 1.1.1)
|
||||
kaminari-actionview (1.1.1)
|
||||
actionview
|
||||
kaminari-core (= 1.1.1)
|
||||
kaminari-activerecord (1.1.1)
|
||||
activerecord
|
||||
kaminari-core (= 1.1.1)
|
||||
kaminari-core (1.1.1)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
letter_opener (1.6.0)
|
||||
|
@ -338,7 +353,9 @@ DEPENDENCIES
|
|||
factory_bot_rails
|
||||
guard
|
||||
guard-minitest
|
||||
has_scope
|
||||
jbuilder (~> 2.5)
|
||||
kaminari
|
||||
launchy
|
||||
letter_opener_web
|
||||
listen (>= 3.0.5, < 3.2)
|
||||
|
|
|
@ -20,4 +20,14 @@ class ApplicationController < ActionController::Base
|
|||
def set_locale
|
||||
I18n.locale = current_user.try(:locale) || I18n.default_locale
|
||||
end
|
||||
|
||||
def not_found
|
||||
fail ActionController::RoutingError, "Not Found"
|
||||
rescue StandardError
|
||||
render_404
|
||||
end
|
||||
|
||||
def render_404
|
||||
render file: "#{Rails.root}/public/404", status: :not_found
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,14 +4,24 @@ class ChecksController < ApplicationController
|
|||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
has_scope :kind
|
||||
has_scope :by_domain
|
||||
has_scope :recurrent_failures, type: :boolean
|
||||
|
||||
def index
|
||||
@checks = policy_scope(Check).order(:domain_expires_at)
|
||||
@checks = apply_scopes(policy_scope(Check)).order(Hash[*current_sort]).page(params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@check = Check.new
|
||||
build_empty_notification
|
||||
authorize @check
|
||||
|
||||
if params[:kind].present?
|
||||
return not_found unless Check.kinds.key?(params[:kind])
|
||||
@check.kind = params[:kind]
|
||||
end
|
||||
|
||||
build_empty_notification
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -20,10 +30,10 @@ class ChecksController < ApplicationController
|
|||
authorize @check
|
||||
|
||||
if @check.save
|
||||
flash[:notice] = "Your check has been saved."
|
||||
flash[:notice] = t(".saved")
|
||||
redirect_to checks_path
|
||||
else
|
||||
flash.now[:alert] = "An error occured."
|
||||
flash.now[:alert] = t(".invalid")
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
@ -73,4 +83,22 @@ class ChecksController < ApplicationController
|
|||
def build_empty_notification
|
||||
@check.notifications.build
|
||||
end
|
||||
|
||||
def current_sort
|
||||
@current_sort ||= clean_sort || Check.default_sort
|
||||
end
|
||||
helper_method :current_sort
|
||||
|
||||
def clean_sort
|
||||
return unless params[:sort].present?
|
||||
field, _, direction = params[:sort].rpartition("_").map(&:to_sym)
|
||||
|
||||
valid_fields = [:domain, :domain_expires_at]
|
||||
valid_directions = [:asc, :desc]
|
||||
|
||||
return unless valid_fields.include?(field)
|
||||
return unless valid_directions.include?(direction)
|
||||
|
||||
[field, direction]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,10 +8,18 @@
|
|||
// layout file, like app/views/layouts/application.html.erb
|
||||
|
||||
import Rails from 'rails-ujs';
|
||||
import Turbolinks from 'turbolinks';
|
||||
|
||||
import 'bootstrap/js/dist/collapse';
|
||||
import 'bootstrap/js/dist/dropdown';
|
||||
import 'bootstrap/js/dist/button';
|
||||
import 'bootstrap/js/dist/tooltip';
|
||||
|
||||
import '../scss';
|
||||
|
||||
Rails.start()
|
||||
Turbolinks.start()
|
||||
|
||||
document.addEventListener("turbolinks:load", () => {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
$input-placeholder-color: #adb5bd;
|
||||
$enable-rounded: false;
|
||||
$theme-colors: (
|
||||
"primary": #259EDB,
|
||||
"secondary": #565554,
|
||||
"success": #42935C,
|
||||
"warning": #F6AE2D,
|
||||
"danger": #E94F37,
|
||||
"info": #2E86AB,
|
||||
);
|
|
@ -1,4 +1,4 @@
|
|||
.table-checks {
|
||||
.checks-table {
|
||||
.action a {
|
||||
color: black;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@import '_variables';
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
@import 'layout';
|
||||
@import 'icons';
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
module ApplicationHelper
|
||||
def format_date(time, format: :long)
|
||||
l(time.utc.to_date, format: format)
|
||||
end
|
||||
|
||||
def format_utc(time, format: :default)
|
||||
l(time.utc, format: format)
|
||||
end
|
||||
|
|
|
@ -1,14 +1,78 @@
|
|||
module ChecksHelper
|
||||
def check_kind_label(check)
|
||||
check.kind.upcase
|
||||
end
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ChecksHelper
|
||||
def check_row_class(check)
|
||||
expiry_date = check.domain_expires_at
|
||||
|
||||
return unless expiry_date.present?
|
||||
|
||||
return "table-danger" if expiry_date <= 2.weeks.from_now
|
||||
return "table-warning" if expiry_date <= 30.days.from_now
|
||||
return "table-danger" if expiry_date <= 3.days.from_now
|
||||
return "table-warning" if expiry_date < 1.month.from_now
|
||||
end
|
||||
|
||||
def checks_sort_links(field)
|
||||
%i[asc desc].map { |direction|
|
||||
checks_sort_link(field, direction)
|
||||
}.join
|
||||
end
|
||||
|
||||
def checks_sort_link(field, direction)
|
||||
classes = "btn btn-light btn-sm mx-1 mx-1 px-1 py-0"
|
||||
sort = [field, direction]
|
||||
|
||||
icon = direction == :asc ? "chevron-up" : "chevron-down"
|
||||
html = Octicons::Octicon.new(icon).to_svg.html_safe
|
||||
|
||||
sort_path = checks_path(current_criterias.merge(sort: sort.join("_")))
|
||||
link_to_unless sort == current_sort, html, sort_path, class: classes do
|
||||
content_tag(:span, html, class: classes + " active")
|
||||
end
|
||||
end
|
||||
|
||||
def check_in_error(check)
|
||||
content_tag(
|
||||
:span,
|
||||
Octicons::Octicon.new("alert", class: "ml-1").to_svg.html_safe,
|
||||
class: "in-error text-danger",
|
||||
data: {
|
||||
toggle: "tooltip",
|
||||
placement: "bottom",
|
||||
title: check_last_success_title(check),
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
def current_criterias
|
||||
current_scopes.merge(sort: params[:sort])
|
||||
end
|
||||
|
||||
def scoped_with?(scope)
|
||||
name, value = scope.first
|
||||
scope_value = current_scopes[name]
|
||||
scope_value = scope_value.to_sym if scope_value.respond_to?(:to_sym)
|
||||
|
||||
scope_value == value
|
||||
end
|
||||
|
||||
def check_button_criterias(scope)
|
||||
if scoped_with?(scope)
|
||||
current_criterias.except(scope.keys.first)
|
||||
else
|
||||
current_criterias.merge(scope)
|
||||
end
|
||||
end
|
||||
|
||||
def check_button_scope_class(scope = nil)
|
||||
"btn btn-sm " + if scope && scoped_with?(scope)
|
||||
"btn-info active"
|
||||
else
|
||||
"btn-outline-info"
|
||||
end
|
||||
end
|
||||
|
||||
def check_last_success_title(check)
|
||||
return t(".never_succeeded") if check.last_success_at.nil?
|
||||
|
||||
t(".days_from_last_success", count: check.days_from_last_success)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
#
|
||||
|
||||
class Check < ApplicationRecord
|
||||
ERROR_DELAY_DAYS = 3
|
||||
|
||||
belongs_to :user
|
||||
has_many :logs, class_name: "CheckLog"
|
||||
has_many :notifications, validate: true, dependent: :destroy
|
||||
|
@ -58,6 +60,32 @@ class Check < ApplicationRecord
|
|||
OR (last_success_at <= DATE_SUB(last_run_at, INTERVAL 5 MINUTE))")
|
||||
}
|
||||
|
||||
scope :kind, ->(kind) { where(kind: kind) }
|
||||
scope :by_domain, ->(domain) { where("domain LIKE ?", "%#{domain}%") }
|
||||
scope :recurrent_failures, -> {
|
||||
interval = "INTERVAL #{ERROR_DELAY_DAYS} DAY"
|
||||
where("last_run_at IS NOT NULL AND created_at <= DATE_SUB(NOW(), #{interval})")
|
||||
.where("last_success_at IS NULL OR last_success_at <= DATE_SUB(last_run_at, #{interval})")
|
||||
}
|
||||
|
||||
def self.default_sort
|
||||
[:domain_expires_at, :asc]
|
||||
end
|
||||
|
||||
def in_error?
|
||||
return false if created_at > ERROR_DELAY_DAYS.days.ago
|
||||
return false if last_run_at.nil?
|
||||
return true if last_success_at.nil?
|
||||
|
||||
last_success_at < ERROR_DELAY_DAYS.days.ago
|
||||
end
|
||||
|
||||
def days_from_last_success
|
||||
return unless last_success_at.present?
|
||||
|
||||
(Date.today - last_success_at.to_date).to_i
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def domain_created_at_past
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<div class="row justify-content-md-end checks-filters">
|
||||
|
||||
<div class="col-md-6 mb-3 d-flex justify-content-between justify-content-md-start">
|
||||
<div class="btn-group mr-2">
|
||||
<% Check.kinds.keys.map(&:to_sym).each do |kind_name| %>
|
||||
<%= link_to t(".kind_#{kind_name}"),
|
||||
checks_path(check_button_criterias(kind: kind_name)),
|
||||
class: check_button_scope_class(kind: kind_name) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= link_to t(".with_error"),
|
||||
checks_path(check_button_criterias(recurrent_failures: true)),
|
||||
class: check_button_scope_class(recurrent_failures: true) %>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<%= form_tag(checks_path, method: :get) do %>
|
||||
<div class="form-row justify-content-around">
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<%= search_field_tag :by_domain, current_scopes[:by_domain], class: "form-control form-control-sm", placeholder: ".com, example.org, …" %>
|
||||
<div class="input-group-append">
|
||||
<%= button_tag Octicons::Octicon.new("search").to_svg.html_safe, class: "btn btn-sm btn-outline-secondary" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- current_criterias.except(:by_domain).compact.each_pair do |name, value| %>
|
||||
<%= hidden_field_tag name, value%>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<%= link_to Octicons::Octicon.new("x").to_svg.html_safe, checks_path, class: "btn btn-danger btn-sm btn-outline-danger" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,8 +1,11 @@
|
|||
<%= simple_form_for(check) do |f| %>
|
||||
<%= f.input :domain, autofocus: true, input_html: { autocapitalize: :none, autocorrect: :off } %>
|
||||
<%= f.input :domain,
|
||||
autofocus: true,
|
||||
input_html: { autocapitalize: :none, autocorrect: :off },
|
||||
label: t(".#{check.kind || "generic" }.domain") %>
|
||||
|
||||
<% if check.new_record? %>
|
||||
<%= f.input :kind, as: :radio_buttons, collection: Check.kinds.keys if check.new_record? %>
|
||||
<%= f.input :kind, as: check.kind.present? ? :hidden : :radio_buttons, collection: Check.kinds.keys %>
|
||||
<% end %>
|
||||
|
||||
<%= f.input :comment %>
|
||||
|
|
|
@ -1,26 +1,41 @@
|
|||
<div class="mb-4">
|
||||
<table class="table table-checks">
|
||||
<div class="mb-4 table-responsive">
|
||||
<table class="table checks-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">Domain</th>
|
||||
<th scope="col">Expiry date</th>
|
||||
<th scope="col">Edit</th>
|
||||
<th scope="col">
|
||||
<%= t(".th.domain") %>
|
||||
<span class="sort-links mx-sm-2 text-nowrap">
|
||||
<%== checks_sort_links(:domain) %>
|
||||
</span>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<span class="d-none d-sm-inline"><%= t(".th.expiry_date") %></span>
|
||||
<span class="d-inline d-sm-none"><%= t(".th.expiry_date_short") %></span>
|
||||
<span class="sort-links mx-sm-2 text-nowrap">
|
||||
<%== checks_sort_links(:domain_expires_at) %>
|
||||
</span>
|
||||
</th>
|
||||
<th scope="col" class="text-right"><%= t(".th.edit") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% checks.each do |check| %>
|
||||
<tr class="check-row <%= check_row_class(check) %>">
|
||||
<td>
|
||||
<span class="badge badge-secondary"><%= check_kind_label(check) %></span>
|
||||
<td class="kind">
|
||||
<span class="badge badge-info"><%= t(".kind_labels.#{check.kind}") %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%= check_in_error(check) if check.in_error? %>
|
||||
<strong><%= check.domain %></strong>
|
||||
</td>
|
||||
<td>
|
||||
<%= format_utc(check.domain_expires_at) if check.domain_expires_at.present? %>
|
||||
<% if check.domain_expires_at.present? %>
|
||||
<%= content_tag :span, format_date(check.domain_expires_at), class: "d-none d-md-inline" %>
|
||||
<%= content_tag :span, format_date(check.domain_expires_at, format: :short), class: "d-inline d-md-none" %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="action">
|
||||
<td class="action text-right">
|
||||
<%= link_to edit_check_path(check) do %>
|
||||
<%== Octicons::Octicon.new("pencil").to_svg %>
|
||||
<% end %>
|
||||
|
@ -30,3 +45,5 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<%= paginate @checks %>
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
<div class="container">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-10">
|
||||
<% if @checks.empty? %>
|
||||
<div class="col-12 col-lg-10 col-xl-9">
|
||||
<% if @checks.empty? && current_scopes.blank? %>
|
||||
<div class="alert alert-info">
|
||||
<%= t(".no_check_yet_html", new_domain_path: new_check_path, new_ssl_path: new_check_path) %>
|
||||
<%= t(".no_check_yet_html", new_domain_path: new_check_path(kind: :domain), new_ssl_path: new_check_path(kind: :ssl)) %>
|
||||
</div>
|
||||
<% else %>
|
||||
<h1>List of your checks</h1>
|
||||
<%= render "table", checks: @checks %>
|
||||
<h1 class="mb-3 mb-sm-5"><%= t(".title") %></h1>
|
||||
|
||||
<%= render "filters" %>
|
||||
|
||||
<% if @checks.any? %>
|
||||
<%= render "table", checks: @checks %>
|
||||
<% else %>
|
||||
<div class="alert alert-warning"><%= t(".no_matching_check") %></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-10">
|
||||
<h1>Create a new check</h1>
|
||||
<h1><%= t(".#{@check.kind}.title") %></h1>
|
||||
|
||||
<%= render "form", check: @check %>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<%# Link to the "First" page
|
||||
- available local variables
|
||||
url: url to the first page
|
||||
current_page: a page object for the currently displayed page
|
||||
total_pages: total number of pages
|
||||
per_page: number of items to fetch per page
|
||||
remote: data-remote
|
||||
-%>
|
||||
<li class="page-item <%= 'active' if current_page.first? %>">
|
||||
<%= link_to t('views.pagination.first').html_safe, url, remote: remote, class: "page-link" %>
|
||||
</li>
|
|
@ -0,0 +1,10 @@
|
|||
<%# Non-link tag that stands for skipped pages...
|
||||
- available local variables
|
||||
current_page: a page object for the currently displayed page
|
||||
total_pages: total number of pages
|
||||
per_page: number of items to fetch per page
|
||||
remote: data-remote
|
||||
-%>
|
||||
<li class="page-item">
|
||||
<span class="page-link"><%= t('views.pagination.truncate').html_safe %></span>
|
||||
</li>
|
|
@ -0,0 +1,11 @@
|
|||
<%# Link to the "Last" page
|
||||
- available local variables
|
||||
url: url to the last page
|
||||
current_page: a page object for the currently displayed page
|
||||
total_pages: total number of pages
|
||||
per_page: number of items to fetch per page
|
||||
remote: data-remote
|
||||
-%>
|
||||
<li class="page-item <%= 'active' if current_page.last? %>">
|
||||
<%= link_to t('views.pagination.last').html_safe, url, remote: remote, class: "page-link" %>
|
||||
</li>
|
|
@ -0,0 +1,11 @@
|
|||
<%# Link to the "Next" page
|
||||
- available local variables
|
||||
url: url to the next page
|
||||
current_page: a page object for the currently displayed page
|
||||
total_pages: total number of pages
|
||||
per_page: number of items to fetch per page
|
||||
remote: data-remote
|
||||
-%>
|
||||
<li class="page-item <%= 'active' if current_page.last? %>">
|
||||
<%= link_to t('views.pagination.next').html_safe, url, rel: 'next', remote: remote, class: "page-link" %>
|
||||
</li>
|
|
@ -0,0 +1,15 @@
|
|||
<%# Link showing page number
|
||||
- available local variables
|
||||
page: a page object for "this" page
|
||||
url: url to this page
|
||||
current_page: a page object for the currently displayed page
|
||||
total_pages: total number of pages
|
||||
per_page: number of items to fetch per page
|
||||
remote: data-remote
|
||||
-%>
|
||||
<li class="page-item <%= ' active' if page.current? %>">
|
||||
<%= link_to page, url, remote: remote, rel: page.rel, class: "page-link" %>
|
||||
<% if page.current? %>
|
||||
<span class="sr-only">(current)</span>
|
||||
<% end %>
|
||||
</li>
|
|
@ -0,0 +1,27 @@
|
|||
<%# The container tag
|
||||
- available local variables
|
||||
current_page: a page object for the currently displayed page
|
||||
total_pages: total number of pages
|
||||
per_page: number of items to fetch per page
|
||||
remote: data-remote
|
||||
paginator: the paginator that renders the pagination tags inside
|
||||
-%>
|
||||
<%= paginator.render do -%>
|
||||
<nav role="navigation" aria-label="pager">
|
||||
<ul class="pagination justify-content-center">
|
||||
<%= first_page_tag unless current_page.first? %>
|
||||
<%= prev_page_tag unless current_page.first? %>
|
||||
<% each_page do |page| -%>
|
||||
<% if page.display_tag? -%>
|
||||
<%= page_tag page %>
|
||||
<% elsif !page.was_truncated? -%>
|
||||
<%= gap_tag %>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
<% unless current_page.out_of_range? %>
|
||||
<%= next_page_tag unless current_page.last? %>
|
||||
<%= last_page_tag unless current_page.last? %>
|
||||
<% end %>
|
||||
</ul>
|
||||
</nav>
|
||||
<% end -%>
|
|
@ -0,0 +1,11 @@
|
|||
<%# Link to the "Previous" page
|
||||
- available local variables
|
||||
url: url to the previous page
|
||||
current_page: a page object for the currently displayed page
|
||||
total_pages: total number of pages
|
||||
per_page: number of items to fetch per page
|
||||
remote: data-remote
|
||||
-%>
|
||||
<li class="page-item <%= 'active' if current_page.first? %>">
|
||||
<%= link_to t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote, class: "page-link" %>
|
||||
</li>
|
|
@ -8,10 +8,13 @@
|
|||
<ul class="navbar-nav mr-auto">
|
||||
<% if user_signed_in? %>
|
||||
<li class="nav-item">
|
||||
<%= link_to("My checks", checks_path, class: "nav-link") %>
|
||||
<%= link_to(t(".my_checks"), checks_path, class: "nav-link") %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to("Add a check", new_check_path, class: "nav-link") %>
|
||||
<%= link_to(t(".new_domain_check"), new_check_path(kind: :domain), class: "nav-link") %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to(t(".new_ssl_check"), new_check_path(kind: :ssl), class: "nav-link") %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
# end
|
||||
|
||||
# These inflection rules are supported but not enabled by default:
|
||||
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
# inflect.acronym 'RESTful'
|
||||
# end
|
||||
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
inflect.acronym 'SSL'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
Kaminari.configure do |config|
|
||||
config.default_per_page = 20
|
||||
config.max_per_page = 200
|
||||
# config.window = 4
|
||||
# config.outer_window = 0
|
||||
# config.left = 0
|
||||
# config.right = 0
|
||||
# config.page_method_name = :page
|
||||
# config.param_name = :page
|
||||
# config.params_on_first_page = false
|
||||
end
|
|
@ -12,6 +12,11 @@ en:
|
|||
check:
|
||||
past: "can't be in the future"
|
||||
|
||||
date:
|
||||
formats:
|
||||
short: "%-d, %b %Y"
|
||||
long: "%A, %B %-d, %Y"
|
||||
|
||||
devise:
|
||||
registrations:
|
||||
new:
|
||||
|
@ -53,6 +58,9 @@ en:
|
|||
en: English
|
||||
fr: French
|
||||
navbar:
|
||||
my_checks: "My checks"
|
||||
new_domain_check: "New domain check"
|
||||
new_ssl_check: "New SSL check"
|
||||
sign_up: "Sign up"
|
||||
sign_in: "Sign in"
|
||||
sign_out: "Sign out"
|
||||
|
@ -60,11 +68,51 @@ en:
|
|||
|
||||
checks:
|
||||
index:
|
||||
title: List of your checks
|
||||
no_matching_check: "No checks match your filters."
|
||||
no_check_yet_html: |
|
||||
You have not set up a check yet.
|
||||
Please add a <a href="%{new_domain_path}">domain</a>
|
||||
or a <a href="%{new_ssl_path}">ssl</a> !
|
||||
|
||||
new:
|
||||
title: New check
|
||||
domain:
|
||||
title: New domain check
|
||||
ssl:
|
||||
title: New SSL check
|
||||
|
||||
create:
|
||||
saved: "Your check has been saved."
|
||||
invalid: "Please check the form."
|
||||
|
||||
filters:
|
||||
kind_domain: Domain
|
||||
kind_ssl: SSL
|
||||
with_error: With error
|
||||
|
||||
form:
|
||||
generic:
|
||||
domain: Domain
|
||||
domain:
|
||||
domain: Domain name
|
||||
ssl:
|
||||
domain: Hostname
|
||||
notifications_hint: |
|
||||
Receive notifications to warn you when our system detects that the
|
||||
expiration date is coming. The time is set in number of days.
|
||||
|
||||
table:
|
||||
th:
|
||||
domain: Name
|
||||
expiry_date: "Expiration date"
|
||||
expiry_date_short: "Exp."
|
||||
edit: Edit
|
||||
kind_labels:
|
||||
domain: Domain
|
||||
ssl: SSL
|
||||
never_succeeded: "Chexpire has never been able to perform a check."
|
||||
days_from_last_success:
|
||||
zero: "Last check successful: today"
|
||||
one: "Last check successful: yesterday"
|
||||
other: "Last check successful %{count} days ago"
|
||||
|
|
|
@ -2,8 +2,15 @@ fr:
|
|||
activerecord:
|
||||
attributes:
|
||||
check:
|
||||
domain: "Domaine"
|
||||
comment: "Commentaire"
|
||||
vendor: "Fournisseur"
|
||||
kind: Type
|
||||
domain_created_at: "Date de création"
|
||||
domain_updated_at: "Date de modification"
|
||||
notification:
|
||||
interval: Délai
|
||||
recipient: Destinataire
|
||||
user:
|
||||
tos_accepted: "Conditions d'utilisation"
|
||||
notifications_enabled: "Notifications activées"
|
||||
|
@ -11,6 +18,26 @@ fr:
|
|||
models:
|
||||
check:
|
||||
past: "ne peut être dans le futur"
|
||||
helpers:
|
||||
submit:
|
||||
check:
|
||||
create: "Créer"
|
||||
update: "Valider"
|
||||
page_entries_info:
|
||||
one_page:
|
||||
display_entries:
|
||||
zero: "Pas de %{entry_name} trouvé."
|
||||
one: "Affiche <b>1</b> %{entry_name}"
|
||||
other: "Affiche <b>les %{count}</b> %{entry_name}"
|
||||
more_pages:
|
||||
display_entries: "Affiche %{entry_name} <b>%{first} - %{last}</b> de <b>%{total}</b> au total"
|
||||
views:
|
||||
pagination:
|
||||
first: "« Début"
|
||||
last: "Fin »"
|
||||
previous: "‹ Préc"
|
||||
next: "Suiv ›"
|
||||
truncate: "…"
|
||||
|
||||
time:
|
||||
am: am
|
||||
|
@ -20,6 +47,11 @@ fr:
|
|||
short: "%d %b %H:%M"
|
||||
pm: pm
|
||||
|
||||
date:
|
||||
formats:
|
||||
short: "%d/%m/%Y"
|
||||
long: "%A %d %B %Y"
|
||||
|
||||
devise:
|
||||
registrations:
|
||||
new:
|
||||
|
@ -61,6 +93,9 @@ fr:
|
|||
en: Anglais
|
||||
fr: Français
|
||||
navbar:
|
||||
my_checks: "Mes vérifications"
|
||||
new_domain_check: "Nouveau nom de domaine"
|
||||
new_ssl_check: "Nouveau certificat SSL"
|
||||
sign_up: "Enregistrement"
|
||||
sign_in: "Connexion"
|
||||
sign_out: "Déconnexion"
|
||||
|
@ -68,11 +103,51 @@ fr:
|
|||
|
||||
checks:
|
||||
index:
|
||||
title: "Liste de vos vérifications"
|
||||
no_matching_check: "Aucune vérification ne correspond à vos critères."
|
||||
no_check_yet_html: |
|
||||
Vous n'avez pas encore créé de vérification.
|
||||
Vous pouvez en ajouter pour un <a href="%{new_domain_path}">domaine</a>
|
||||
ou un <a href="%{new_ssl_path}">SSL</a> !
|
||||
|
||||
new:
|
||||
title: Nouvelle vérification
|
||||
domain:
|
||||
title: Nouvelle vérification d'un nom de domaine
|
||||
ssl:
|
||||
title: Nouvelle vérification d'un certificat SSL
|
||||
|
||||
create:
|
||||
saved: La vérification est enregistrée.
|
||||
invalid: Veuillez vérifier le formulaire.
|
||||
|
||||
filters:
|
||||
kind_domain: Domaine
|
||||
kind_ssl: SSL
|
||||
with_error: En erreur
|
||||
|
||||
form:
|
||||
generic:
|
||||
domain: Domaine
|
||||
domain:
|
||||
domain: Nom de domaine
|
||||
ssl:
|
||||
domain: Nom d'hôte
|
||||
notifications_hint: |
|
||||
Recevez des notifications pour vous avertir lorsque notre système détecte
|
||||
que la date d'expiration approche. Le délais est indiqué ennombre de jours.
|
||||
que la date d'expiration approche. Le délai est indiqué ennombre de jours.
|
||||
|
||||
table:
|
||||
th:
|
||||
domain: Nom
|
||||
expiry_date: "Date d'expiration"
|
||||
expiry_date_short: "Exp."
|
||||
edit: Modifier
|
||||
kind_labels:
|
||||
domain: Domaine
|
||||
ssl: SSL
|
||||
never_succeeded: "Chexpire n'a jamais pu effectuer de vérification."
|
||||
days_from_last_success:
|
||||
zero: "Dernière vérification réussie : aujourd'hui"
|
||||
one: "Dernière vérification réussie : hier"
|
||||
other: "Dernière vérification réussie il y a %{count} jours"
|
||||
|
|
46
db/seeds.rb
46
db/seeds.rb
|
@ -7,9 +7,20 @@ user1 = User.create!(
|
|||
email: "colin@example.org",
|
||||
password: "password",
|
||||
tos_accepted: true,
|
||||
confirmed_at: Time.now
|
||||
confirmed_at: Time.now,
|
||||
locale: :fr,
|
||||
)
|
||||
|
||||
user2 = User.create!(
|
||||
email: "colin+en@example.org",
|
||||
password: "password",
|
||||
tos_accepted: true,
|
||||
confirmed_at: Time.now,
|
||||
locale: :en,
|
||||
)
|
||||
|
||||
users = [user1, user2]
|
||||
|
||||
check_chexpire_org = Check.create!(
|
||||
user: user1,
|
||||
kind: :domain,
|
||||
|
@ -24,14 +35,14 @@ check_chexpire_org = Check.create!(
|
|||
check_chexpire_org_error = Check.create!(
|
||||
user: user1,
|
||||
kind: :domain,
|
||||
domain: "chexpire.org",
|
||||
domain: "chexpire-error.org",
|
||||
domain_expires_at: 1.week.from_now,
|
||||
domain_updated_at: 6.months.ago,
|
||||
domain_created_at: Time.new(2016, 8, 4, 12, 15, 1),
|
||||
comment: "The date are fake, this is a seed !",
|
||||
vendor: "Some random registrar",
|
||||
last_run_at: 20.minutes.ago,
|
||||
last_success_at: 4.days.ago,
|
||||
created_at: 3.weeks.ago,
|
||||
)
|
||||
|
||||
ssl_check_chexpire_org = Check.create!(
|
||||
|
@ -48,7 +59,7 @@ ssl_check_chexpire_org = Check.create!(
|
|||
ssl_check_chexpire_org_error = Check.create!(
|
||||
user: user1,
|
||||
kind: :ssl,
|
||||
domain: "chexpire.org",
|
||||
domain: "chexpire-error.org",
|
||||
domain_expires_at: 1.week.from_now,
|
||||
domain_updated_at: 6.months.ago,
|
||||
domain_created_at: Time.new(2016, 8, 4, 12, 15, 1),
|
||||
|
@ -59,6 +70,33 @@ ssl_check_chexpire_org_error = Check.create!(
|
|||
)
|
||||
|
||||
|
||||
def check_factory(users)
|
||||
ext = %w[com net org fr].sample
|
||||
word = (0...rand(4..12)).map { (97 + rand(26)).chr }.join
|
||||
|
||||
Check.new(
|
||||
user: users.sample,
|
||||
kind: Check.kinds.keys.sample,
|
||||
domain: "#{word}.#{ext}",
|
||||
domain_expires_at: rand(8..300).days.from_now,
|
||||
domain_updated_at: rand(1..300).days.ago,
|
||||
domain_created_at: rand(301..3000).days.ago,
|
||||
)
|
||||
end
|
||||
|
||||
100.times do |i|
|
||||
check_factory(users).save!
|
||||
end
|
||||
|
||||
# checks with error
|
||||
10.times do |i|
|
||||
check_factory(users).update_attributes(
|
||||
created_at: rand(1..300).days.ago,
|
||||
last_run_at: 4.hours.ago,
|
||||
last_success_at: rand(10...100).days.ago,
|
||||
)
|
||||
end
|
||||
|
||||
Notification.create!(
|
||||
check: check_chexpire_org,
|
||||
interval: 15,
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
"exports-loader": "^0.7.0",
|
||||
"jquery": "^3.3.1",
|
||||
"popper.js": "^1.14.3",
|
||||
"rails-ujs": "^5.2.0"
|
||||
"rails-ujs": "^5.2.0",
|
||||
"turbolinks": "^5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-dev-server": "2.11.2"
|
||||
|
|
|
@ -4,7 +4,7 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
|||
driven_by :headless_chrome
|
||||
|
||||
def teardown
|
||||
Capybara.reset_sessions!
|
||||
Warden.test_reset!
|
||||
super
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
inherit_from: ../../.rubocop.yml
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
|
@ -1,7 +1,150 @@
|
|||
require "test_helper"
|
||||
|
||||
class ChecksControllerTest < ActionDispatch::IntegrationTest
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
setup do
|
||||
@user = create(:user)
|
||||
login_as(@user)
|
||||
end
|
||||
|
||||
test "no logged users are redirected to signin form" do
|
||||
logout
|
||||
get new_check_path
|
||||
assert_redirected_to new_user_session_path
|
||||
end
|
||||
|
||||
test "new without kind does not trigger an error" do
|
||||
get new_check_path
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "new with kind domain does not trigger an error" do
|
||||
get new_check_path(kind: :domain)
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "new with kind ssl does not trigger an error" do
|
||||
get new_check_path(kind: :ssl)
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "new with an invalid kind returns an error" do
|
||||
get new_check_path(kind: :invalid)
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "checks are ordered by default by expiry date sort" do
|
||||
c1 = create(:check, user: @user, domain_expires_at: 20.days.from_now)
|
||||
c2 = create(:check, user: @user, domain_expires_at: 10.days.from_now)
|
||||
c3 = create(:check, user: @user, domain_expires_at: 1.day.from_now)
|
||||
|
||||
get checks_path
|
||||
assert_equal [c3, c2, c1], current_checks
|
||||
end
|
||||
|
||||
test "checks are ordered by expiry date asc" do
|
||||
c1 = create(:check, user: @user, domain_expires_at: 20.days.from_now)
|
||||
c2 = create(:check, user: @user, domain_expires_at: 10.days.from_now)
|
||||
c3 = create(:check, user: @user, domain_expires_at: 1.day.from_now)
|
||||
|
||||
get checks_path(sort: :domain_expires_at_asc)
|
||||
assert_equal [c3, c2, c1], current_checks
|
||||
end
|
||||
|
||||
test "checks are ordered by reverse expiring date" do
|
||||
c1 = create(:check, user: @user, domain_expires_at: 1.day.from_now)
|
||||
c2 = create(:check, user: @user, domain_expires_at: 10.days.from_now)
|
||||
c3 = create(:check, user: @user, domain_expires_at: 20.days.from_now)
|
||||
|
||||
get checks_path(sort: :domain_expires_at_desc)
|
||||
assert_equal [c3, c2, c1], current_checks
|
||||
end
|
||||
|
||||
test "checks are ordered by domain name asc" do
|
||||
c1 = create(:check, user: @user, domain: "a")
|
||||
c2 = create(:check, user: @user, domain: "b")
|
||||
c3 = create(:check, user: @user, domain: "c")
|
||||
|
||||
get checks_path(sort: :domain_asc)
|
||||
assert_equal [c1, c2, c3], current_checks
|
||||
end
|
||||
|
||||
test "checks are ordered by domain name desc" do
|
||||
c1 = create(:check, user: @user, domain: "a")
|
||||
c2 = create(:check, user: @user, domain: "b")
|
||||
c3 = create(:check, user: @user, domain: "c")
|
||||
|
||||
get checks_path(sort: :domain_desc)
|
||||
assert_equal [c3, c2, c1], current_checks
|
||||
end
|
||||
|
||||
test "invalid sort fallback to default sort" do
|
||||
c1 = create(:check, user: @user, domain_expires_at: 20.days.from_now)
|
||||
c2 = create(:check, user: @user, domain_expires_at: 10.days.from_now)
|
||||
c3 = create(:check, user: @user, domain_expires_at: 1.day.from_now)
|
||||
|
||||
get checks_path(sort: :invalid_sort_asc)
|
||||
assert_equal [c3, c2, c1], current_checks
|
||||
end
|
||||
|
||||
test "checks are filtered by domain kind" do
|
||||
c1 = create(:check, :domain, user: @user)
|
||||
c2 = create(:check, :domain, user: @user)
|
||||
create(:check, :ssl, user: @user)
|
||||
|
||||
get checks_path(kind: :domain)
|
||||
assert_equal [c1, c2], current_checks
|
||||
end
|
||||
|
||||
test "checks are filtered by ssl kind" do
|
||||
create(:check, :domain, user: @user)
|
||||
create(:check, :domain, user: @user)
|
||||
c3 = create(:check, :ssl, user: @user)
|
||||
|
||||
get checks_path(kind: :ssl)
|
||||
assert_equal [c3], current_checks
|
||||
end
|
||||
|
||||
test "checks are filtered by domain name" do
|
||||
c1 = create(:check, user: @user, domain: "abc")
|
||||
c2 = create(:check, user: @user, domain: "bcde")
|
||||
create(:check, user: @user, domain: "hijk")
|
||||
|
||||
get checks_path(by_domain: "bc")
|
||||
assert_equal [c1, c2], current_checks
|
||||
|
||||
get checks_path(by_domain: "klm")
|
||||
assert_empty current_checks
|
||||
end
|
||||
|
||||
test "checks in error are filtered" do
|
||||
c1 = create(:check, :last_runs_failed, created_at: 1.week.ago, user: @user)
|
||||
create(:check, user: @user)
|
||||
|
||||
get checks_path(recurrent_failures: true)
|
||||
assert_equal [c1], current_checks
|
||||
end
|
||||
|
||||
test "checks are paginated" do
|
||||
create_list(:check, 40, user: @user)
|
||||
|
||||
get checks_path
|
||||
assert_equal 1, current_checks.current_page
|
||||
first_page = current_checks
|
||||
|
||||
get checks_path(page: 2)
|
||||
assert_equal 2, current_checks.current_page
|
||||
assert_not_equal first_page, current_checks
|
||||
end
|
||||
|
||||
test "checks are scoped to current user" do
|
||||
c1 = create(:check, user: @user)
|
||||
create(:check)
|
||||
|
||||
get checks_path
|
||||
assert_equal [c1], current_checks
|
||||
end
|
||||
|
||||
def current_checks
|
||||
@controller.instance_variable_get("@checks")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,4 +49,56 @@ class CheckTest < ActiveSupport::TestCase
|
|||
assert notification.pending?
|
||||
assert_nil notification.sent_at
|
||||
end
|
||||
|
||||
test "in_error? for recently added" do
|
||||
check = build(:check, created_at: 1.day.ago)
|
||||
refute check.in_error?
|
||||
|
||||
check = build(:check, created_at: 1.day.ago, last_run_at: 3.minutes.ago)
|
||||
refute check.in_error?
|
||||
|
||||
check = build(:check, created_at: 1.day.ago, last_success_at: 1.hour.ago)
|
||||
refute check.in_error?
|
||||
end
|
||||
|
||||
test "in_error? for never success check, with at least 1 run" do
|
||||
check = build(:check, created_at: 3.weeks.ago, last_run_at: 1.day.ago)
|
||||
assert check.in_error?
|
||||
|
||||
check = build(:check, created_at: 3.weeks.ago, last_run_at: 4.days.ago)
|
||||
assert check.in_error?
|
||||
end
|
||||
|
||||
test "in_error? ignore check without run" do
|
||||
check = build(:check, created_at: 3.weeks.ago)
|
||||
refute check.in_error?
|
||||
end
|
||||
|
||||
test "in_error? for last success a few days ago" do
|
||||
check = build(:check, created_at: 3.weeks.ago,
|
||||
last_success_at: 10.days.ago, last_run_at: 1.day.ago)
|
||||
assert check.in_error?
|
||||
|
||||
check = build(:check, created_at: 3.weeks.ago,
|
||||
last_success_at: 1.days.ago, last_run_at: 1.day.ago)
|
||||
refute check.in_error?
|
||||
end
|
||||
|
||||
test "days_from_last_success without any success" do
|
||||
check = build(:check)
|
||||
assert_nil check.days_from_last_success
|
||||
|
||||
check = build(:check, last_run_at: 1.day.ago)
|
||||
assert_nil check.days_from_last_success
|
||||
end
|
||||
|
||||
test "days_from_last_success" do
|
||||
check = build(:check, last_success_at: 10.days.ago - 1.hour)
|
||||
assert_equal 10, check.days_from_last_success
|
||||
end
|
||||
|
||||
test "days_from_last_success with a time" do
|
||||
check = build(:check, last_success_at: (10.1 * 24).hours.ago)
|
||||
assert_equal 10, check.days_from_last_success
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
inherit_from: ../../.rubocop.yml
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
|
@ -4,16 +4,263 @@ class ChecksTest < ApplicationSystemTestCase
|
|||
setup do
|
||||
@user = create(:user)
|
||||
login_as(@user)
|
||||
|
||||
@check = create(:check, :with_notifications, user: @user)
|
||||
end
|
||||
|
||||
test "create a check and a notification" do
|
||||
test "create a check and a notification without kind" do
|
||||
visit new_check_path
|
||||
|
||||
choose "domain"
|
||||
|
||||
fill_and_valid_new_check
|
||||
end
|
||||
|
||||
test "create a predefined domain check" do
|
||||
visit new_check_path(kind: :domain)
|
||||
|
||||
refute page.has_css? "domain[kind]"
|
||||
|
||||
fill_and_valid_new_check
|
||||
end
|
||||
|
||||
test "create a predefined ssl check" do
|
||||
visit new_check_path(kind: :ssl)
|
||||
|
||||
refute page.has_css? "domain[kind]"
|
||||
|
||||
fill_and_valid_new_check
|
||||
end
|
||||
|
||||
test "remove a notification" do
|
||||
check = create(:check, :with_notifications, domain: "dom-with-notif.net", user: @user)
|
||||
visit edit_check_path(check)
|
||||
notification = check.notifications.first
|
||||
|
||||
selector = "[data-notification-id=\"#{notification.id}\"]"
|
||||
|
||||
assert_difference "Notification.where(check_id: #{check.id}).count", -1 do
|
||||
within selector do
|
||||
find(".btn-danger").click
|
||||
end
|
||||
|
||||
page.has_no_content?(selector)
|
||||
end
|
||||
end
|
||||
|
||||
test "update a check" do
|
||||
check = create(:check, :with_notifications, domain: "dom-with-notif.net", user: @user)
|
||||
visit edit_check_path(check)
|
||||
|
||||
fill_in "check[comment]", with: "My comment"
|
||||
|
||||
click_button "Update Check"
|
||||
|
||||
assert_equal checks_path, page.current_path
|
||||
|
||||
assert page.has_css?(".alert-success")
|
||||
check.reload
|
||||
assert_equal "My comment", check.comment
|
||||
end
|
||||
|
||||
test "add a notification" do
|
||||
check = create(:check, :with_notifications, domain: "dom-with-notif.net", user: @user)
|
||||
visit edit_check_path(check)
|
||||
|
||||
recipient = "recipient2@example.org"
|
||||
fill_in("check[notifications_attributes][2][recipient]", with: recipient)
|
||||
fill_in("check[notifications_attributes][2][interval]", with: 55)
|
||||
|
||||
assert_difference "Notification.where(check_id: #{check.id}).count", +1 do
|
||||
click_button "Update Check"
|
||||
|
||||
assert_equal checks_path, page.current_path
|
||||
end
|
||||
|
||||
assert page.has_css?(".alert-success")
|
||||
|
||||
notification = Notification.last
|
||||
assert_equal recipient, notification.recipient
|
||||
assert_equal 55, notification.interval
|
||||
assert notification.email?
|
||||
assert notification.pending?
|
||||
end
|
||||
|
||||
test "list my checks" do
|
||||
create(:check, :domain, domain: "dom.com", domain_expires_at: Time.new(2018, 7, 5, 12), user: @user) # rubocop:disable Metrics/LineLength
|
||||
create(:check, :ssl, domain: "ssldom.com", user: @user)
|
||||
create(:check, :ssl, domain: "ssldom2.com", user: @user)
|
||||
|
||||
visit checks_path
|
||||
|
||||
within ".checks-table" do
|
||||
assert page.has_content?("SSL", count: 2)
|
||||
assert page.has_content?("Domain", count: 1)
|
||||
end
|
||||
|
||||
within ".check-row:first-of-type" do
|
||||
assert page.has_content?("Domain")
|
||||
assert page.has_content?("dom.com")
|
||||
assert page.has_content?("Thursday, July 5, 2018")
|
||||
end
|
||||
end
|
||||
|
||||
test "list filterable by domain and ssl" do
|
||||
create_list(:check, 2, :domain, domain: "mydom.fr", user: @user)
|
||||
create_list(:check, 1, :ssl, domain: "ssl.com", user: @user)
|
||||
|
||||
visit checks_path
|
||||
|
||||
assert page.has_css?(".check-row", count: 3)
|
||||
|
||||
within ".checks-filters" do
|
||||
click_on "Domain"
|
||||
assert find_link("Domain").matches_css? ".active"
|
||||
assert find_link("SSL").not_matches_css? ".active"
|
||||
end
|
||||
|
||||
within ".checks-table" do
|
||||
assert page.has_css?(".check-row", count: 2)
|
||||
assert page.has_content?("Domain", count: 2)
|
||||
end
|
||||
|
||||
within ".checks-filters" do
|
||||
click_on "SSL"
|
||||
assert find_link("SSL").matches_css? ".active"
|
||||
assert find_link("Domain").not_matches_css? ".active"
|
||||
end
|
||||
|
||||
within ".checks-table" do
|
||||
assert page.has_css?(".check-row", count: 1)
|
||||
assert page.has_content?("SSL", count: 1)
|
||||
assert page.has_content?("ssl.com")
|
||||
end
|
||||
end
|
||||
|
||||
test "list filterable by check in error" do
|
||||
create(:check, user: @user)
|
||||
create(:check, :last_runs_failed, created_at: 1.week.ago, user: @user)
|
||||
|
||||
visit checks_path
|
||||
|
||||
within ".checks-table" do
|
||||
assert page.has_css?(".check-row", count: 2)
|
||||
assert page.has_css?(".in-error", count: 1)
|
||||
end
|
||||
|
||||
within ".checks-filters" do
|
||||
click_on(I18n.t("checks.filters.with_error"))
|
||||
end
|
||||
|
||||
within ".checks-table" do
|
||||
assert page.has_css?(".check-row", count: 1)
|
||||
assert page.has_css?(".in-error", count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
test "list filterable by name string" do
|
||||
create(:check, user: @user)
|
||||
create(:check, domain: "chexpire.org", user: @user)
|
||||
create(:check, domain: "chexpire.net", user: @user)
|
||||
|
||||
visit checks_path
|
||||
|
||||
within ".checks-filters" do
|
||||
fill_in("by_domain", with: "chex")
|
||||
click_button
|
||||
end
|
||||
|
||||
within ".checks-table" do
|
||||
assert page.has_css?(".check-row", count: 2)
|
||||
assert page.has_content?("chexpire.", count: 2)
|
||||
end
|
||||
end
|
||||
|
||||
test "list is paginated" do
|
||||
create(:check, user: @user)
|
||||
|
||||
visit checks_path
|
||||
assert page.has_no_css?("ul.pagination")
|
||||
|
||||
create_list(:check, 50, user: @user)
|
||||
|
||||
visit checks_path
|
||||
assert page.has_css?("ul.pagination")
|
||||
end
|
||||
|
||||
test "list is sortable by name" do
|
||||
visit checks_path
|
||||
|
||||
create(:check, domain: "a.org", user: @user)
|
||||
create(:check, domain: "b.org", user: @user)
|
||||
|
||||
visit checks_path
|
||||
|
||||
within ".checks-table thead th:nth-of-type(2)" do
|
||||
find(".sort-links:first-child").click
|
||||
end
|
||||
|
||||
within ".check-row:first-of-type" do
|
||||
page.has_content? "a.org"
|
||||
end
|
||||
|
||||
within ".checks-table thead th:nth-of-type(2)" do
|
||||
find(".sort-links:last-child").click
|
||||
end
|
||||
|
||||
within ".check-row:first-of-type" do
|
||||
page.has_content? "b.org"
|
||||
end
|
||||
end
|
||||
|
||||
test "list is sorted by expiration date by default" do
|
||||
visit checks_path
|
||||
|
||||
create(:check, domain_expires_at: Time.new(2018, 7, 6, 12), user: @user)
|
||||
create(:check, domain_expires_at: Time.new(2018, 7, 5, 12), user: @user)
|
||||
|
||||
visit checks_path
|
||||
|
||||
within ".check-row:first-of-type" do
|
||||
page.has_content? "Thursday, July 5, 2018"
|
||||
end
|
||||
end
|
||||
|
||||
test "list is sortable by expiration date" do
|
||||
visit checks_path
|
||||
|
||||
create(:check, domain_expires_at: Time.new(2018, 7, 5, 12), user: @user)
|
||||
create(:check, domain_expires_at: Time.new(2018, 7, 6, 12), user: @user)
|
||||
|
||||
visit checks_path
|
||||
|
||||
within ".check-row:first-of-type" do
|
||||
page.has_content? "Thursday, July 5, 2018"
|
||||
end
|
||||
|
||||
# only a desc link because of default sort
|
||||
within ".checks-table thead th:nth-of-type(3)" do
|
||||
find(".sort-links a").click
|
||||
end
|
||||
|
||||
within ".check-row:first-of-type" do
|
||||
page.has_content? "Friday, July 6, 2018"
|
||||
end
|
||||
|
||||
within ".checks-table thead th:nth-of-type(3)" do
|
||||
find(".sort-links a").click
|
||||
end
|
||||
|
||||
within ".check-row:first-of-type" do
|
||||
page.has_content? "Thursday, July 5, 2018"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def fill_and_valid_new_check
|
||||
domain = "domain-test.fr"
|
||||
fill_in("check[domain]", with: domain)
|
||||
choose "domain"
|
||||
|
||||
recipient = "recipient@example.org"
|
||||
fill_in("check[notifications_attributes][0][recipient]", with: recipient)
|
||||
|
@ -32,55 +279,6 @@ class ChecksTest < ApplicationSystemTestCase
|
|||
assert notification.email?
|
||||
assert notification.pending?
|
||||
end
|
||||
|
||||
test "remove a notification" do
|
||||
visit edit_check_path(@check)
|
||||
notification = @check.notifications.first
|
||||
|
||||
selector = "[data-notification-id=\"#{notification.id}\"]"
|
||||
|
||||
assert_difference "Notification.where(check_id: #{@check.id}).count", -1 do
|
||||
within selector do
|
||||
find(".btn-danger").click
|
||||
end
|
||||
|
||||
page.has_no_content?(selector)
|
||||
end
|
||||
end
|
||||
|
||||
test "update a check" do
|
||||
visit edit_check_path(@check)
|
||||
|
||||
fill_in "check[comment]", with: "My comment"
|
||||
|
||||
click_button "Update Check"
|
||||
|
||||
assert_equal checks_path, page.current_path
|
||||
|
||||
assert page.has_css?(".alert-success")
|
||||
@check.reload
|
||||
assert_equal "My comment", @check.comment
|
||||
end
|
||||
|
||||
test "add a notification" do
|
||||
visit edit_check_path(@check)
|
||||
|
||||
recipient = "recipient2@example.org"
|
||||
fill_in("check[notifications_attributes][2][recipient]", with: recipient)
|
||||
fill_in("check[notifications_attributes][2][interval]", with: 55)
|
||||
|
||||
assert_difference "Notification.where(check_id: #{@check.id}).count", +1 do
|
||||
click_button "Update Check"
|
||||
|
||||
assert_equal checks_path, page.current_path
|
||||
end
|
||||
|
||||
assert page.has_css?(".alert-success")
|
||||
|
||||
notification = Notification.last
|
||||
assert_equal recipient, notification.recipient
|
||||
assert_equal 55, notification.interval
|
||||
assert notification.email?
|
||||
assert notification.pending?
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
end
|
||||
|
|
|
@ -42,6 +42,7 @@ Capybara.register_driver :headless_chrome do |app|
|
|||
end
|
||||
Capybara.save_path = Rails.root.join("tmp/capybara")
|
||||
Capybara.javascript_driver = :headless_chrome
|
||||
Capybara.default_driver = :headless_chrome
|
||||
|
||||
# Disable Open4 real system calls
|
||||
require "open4"
|
||||
|
|
|
@ -5598,6 +5598,10 @@ tunnel-agent@~0.4.1:
|
|||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
|
||||
|
||||
turbolinks@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/turbolinks/-/turbolinks-5.1.1.tgz#3d418a2d8172edbde5e787bf74cb7bef151ae43f"
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
|
|
Loading…
Reference in New Issue