Browse Source

Improve period and grace controls, allow up to 365 day periods

Fixes: #281
master
Pēteris Caune 3 years ago
parent
commit
e0d2f36928
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
9 changed files with 170 additions and 101 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +2
    -2
      hc/api/schemas.py
  3. +13
    -0
      hc/api/tests/test_update_check.py
  4. +2
    -2
      hc/front/forms.py
  5. +26
    -13
      static/css/my_checks.css
  6. +82
    -44
      static/js/update-timeout-modal.js
  7. +10
    -10
      templates/docs/api.html
  8. +10
    -10
      templates/docs/api.md
  9. +24
    -20
      templates/front/update_timeout_modal.html

+ 1
- 0
CHANGELOG.md View File

@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
- Change outgoing webhook timeout to 10s, but cap the total time to 20s
- Implement automatic `api_ping` and `api_notification` pruning (#556)
- Update Dockerfile to install apprise (#581)
- Improve period and grace controls, allow up to 365 day periods (#281)
### Bug Fixes
- Fix hc.api.views.ping to handle non-utf8 data in request body (#574)


+ 2
- 2
hc/api/schemas.py View File

@ -4,8 +4,8 @@ check = {
"name": {"type": "string", "maxLength": 100},
"desc": {"type": "string"},
"tags": {"type": "string", "maxLength": 500},
"timeout": {"type": "number", "minimum": 60, "maximum": 2592000},
"grace": {"type": "number", "minimum": 60, "maximum": 2592000},
"timeout": {"type": "number", "minimum": 60, "maximum": 31536000},
"grace": {"type": "number", "minimum": 60, "maximum": 31536000},
"schedule": {"type": "string", "format": "cron", "maxLength": 100},
"tz": {"type": "string", "format": "timezone", "maxLength": 36},
"channels": {"type": "string"},


+ 13
- 0
hc/api/tests/test_update_check.py View File

@ -321,3 +321,16 @@ class UpdateCheckTestCase(BaseTestCase):
def test_it_rejects_bad_methods_value(self):
r = self.post(self.check.code, {"api_key": "X" * 32, "methods": "bad-value"})
self.assertEqual(r.status_code, 400)
def test_it_accepts_60_days_timeout(self):
payload = {"api_key": "X" * 32, "timeout": 60 * 24 * 3600}
r = self.post(self.check.code, payload)
self.assertEqual(r.status_code, 200)
self.check.refresh_from_db()
self.assertEqual(self.check.timeout.total_seconds(), 60 * 24 * 3600)
def test_it_rejects_out_of_range_timeout(self):
payload = {"api_key": "X" * 32, "timeout": 500 * 24 * 3600}
r = self.post(self.check.code, payload)
self.assertEqual(r.status_code, 400)

+ 2
- 2
hc/front/forms.py View File

@ -96,8 +96,8 @@ class FilteringRulesForm(forms.Form):
class TimeoutForm(forms.Form):
timeout = forms.IntegerField(min_value=60, max_value=2592000)
grace = forms.IntegerField(min_value=60, max_value=2592000)
timeout = forms.IntegerField(min_value=60, max_value=31536000)
grace = forms.IntegerField(min_value=60, max_value=31536000)
def clean_timeout(self):
return td(seconds=self.cleaned_data["timeout"])


+ 26
- 13
static/css/my_checks.css View File

@ -34,7 +34,7 @@
@media (min-width: 992px) {
#update-timeout-modal .modal-dialog, #ping-details-modal .modal-dialog {
width: 800px;
width: 850px;
}
#update-name-modal .modal-dialog {
width: 650px;
@ -46,24 +46,37 @@
padding-top: 35px;
}
.update-timeout-info {
line-height: 22px;
.interval-controls {
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
}
.update-timeout-label {
position: relative;
right: 3px;
display: inline-block;
.interval-controls label {
font-weight: normal;
padding-right: 16px;
width: 120px;
text-align: right;
width: 100px;
}
.update-timeout-value {
font-size: 22px;
display: inline-block;
.interval-controls input {
width: 100px;
text-align: left;
white-space: nowrap;
text-align: center;
-moz-appearance: textfield;
}
.interval-controls input::-webkit-outer-spin-button,
.interval-controls input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
.interval-controls select.form-control {
width: auto;
padding-left: 8px;
margin-left: 8px;
/* Fix dropdown background in Chrome & dark mode */
background: var(--panel-bg);
}
.kind-simple, .kind-cron {


+ 82
- 44
static/js/update-timeout-modal.js View File

@ -1,5 +1,9 @@
$(function () {
var base = document.getElementById("base-url").getAttribute("href").slice(0, -1);
var period = document.getElementById("period-value");
var periodUnit = document.getElementById("period-unit");
var grace = document.getElementById("grace-value");
var graceUnit = document.getElementById("grace-unit");
$(".rw .timeout-grace").click(function() {
var code = $(this).closest("tr.checks-row").attr("id");
@ -12,9 +16,19 @@ $(function () {
$("#update-timeout-form").attr("action", url);
$("#update-cron-form").attr("action", url);
// Simple
// Simple, period
var parsed = secsToUnits(this.dataset.timeout);
period.value = parsed.value;
periodUnit.value = parsed.unit;
periodSlider.noUiSlider.set(this.dataset.timeout);
$("#update-timeout-timeout").val(this.dataset.timeout);
// Simple, grace
var parsed = secsToUnits(this.dataset.grace);
grace.value = parsed.value;
graceUnit.value = parsed.unit;
graceSlider.noUiSlider.set(this.dataset.grace);
$("#update-timeout-grace").val(this.dataset.grace);
// Cron
currentPreviewHash = "";
@ -30,35 +44,27 @@ $(function () {
return false;
});
var MINUTE = {name: "minute", nsecs: 60};
var HOUR = {name: "hour", nsecs: MINUTE.nsecs * 60};
var DAY = {name: "day", nsecs: HOUR.nsecs * 24};
var WEEK = {name: "week", nsecs: DAY.nsecs * 7};
var UNITS = [WEEK, DAY, HOUR, MINUTE];
var secsToText = function(total) {
var remainingSeconds = Math.floor(total);
var result = "";
for (var i=0, unit; unit=UNITS[i]; i++) {
if (unit === WEEK && remainingSeconds % unit.nsecs != 0) {
// Say "8 days" instead of "1 week 1 day"
continue
}
var count = Math.floor(remainingSeconds / unit.nsecs);
remainingSeconds = remainingSeconds % unit.nsecs;
if (count == 1) {
result += "1 " + unit.name + " ";
}
if (count > 1) {
result += count + " " + unit.name + "s ";
}
var secsToUnits = function(secs) {
if (secs % 86400 == 0) {
return {value: secs / 86400, unit: 86400}
}
if (secs % 3600 == 0) {
return {value: secs / 3600, unit: 3600}
}
return result;
};
return {value: Math.round(secs / 60), unit: 60}
}
var pipLabels = {
60: "1 minute",
1800: "30 minutes",
3600: "1 hour",
43200: "12 hours",
86400: "1 day",
604800: "1 week",
2592000: "30 days",
31536000: "365 days"
}
var periodSlider = document.getElementById("period-slider");
noUiSlider.create(periodSlider, {
@ -66,54 +72,86 @@ $(function () {
connect: "lower",
range: {
'min': [60, 60],
'33%': [3600, 3600],
'66%': [86400, 86400],
'83%': [604800, 604800],
'max': 2592000,
'30%': [3600, 3600],
'60%': [86400, 86400],
'75%': [604800, 86400],
'90%': [2592000, 2592000],
'max': 31536000
},
pips: {
mode: 'values',
values: [60, 1800, 3600, 43200, 86400, 604800, 2592000],
values: [60, 1800, 3600, 43200, 86400, 604800, 2592000, 31536000],
density: 4,
format: {
to: secsToText,
to: function(v) { return pipLabels[v] },
from: function() {}
}
}
});
periodSlider.noUiSlider.on("update", function(a, b, value) {
// Update inputs and the hidden field when user slides the period slider
periodSlider.noUiSlider.on("slide", function(a, b, value) {
var rounded = Math.round(value);
$("#period-slider-value").text(secsToText(rounded));
$("#update-timeout-timeout").val(rounded);
var parsed = secsToUnits(rounded);
period.value = parsed.value;
periodUnit.value = parsed.unit;
});
// Update the slider and the hidden field when user changes period inputs
$(".period-input").on("keyup change", function() {
var secs = Math.round(period.value * periodUnit.value);
period.setCustomValidity(secs <= 31536000 ? "" : "Must not exceed 365 days");
if (secs >= 60) {
periodSlider.noUiSlider.set(secs);
$("#update-timeout-timeout").val(secs);
}
})
var graceSlider = document.getElementById("grace-slider");
noUiSlider.create(graceSlider, {
start: [20],
connect: "lower",
range: {
'min': [60, 60],
'33%': [3600, 3600],
'66%': [86400, 86400],
'83%': [604800, 604800],
'max': 2592000,
'30%': [3600, 3600],
'60%': [86400, 86400],
'75%': [604800, 86400],
'90%': [2592000, 2592000],
'max': 31536000
},
pips: {
mode: 'values',
values: [60, 1800, 3600, 43200, 86400, 604800, 2592000],
values: [60, 1800, 3600, 43200, 86400, 604800, 2592000, 31536000],
density: 4,
format: {
to: secsToText,
to: function(v) { return pipLabels[v] },
from: function() {}
}
}
});
graceSlider.noUiSlider.on("update", function(a, b, value) {
// Update inputs and the hidden field when user slides the grace slider
graceSlider.noUiSlider.on("slide", function(a, b, value) {
var rounded = Math.round(value);
$("#grace-slider-value").text(secsToText(rounded));
$("#update-timeout-grace").val(rounded);
var parsed = secsToUnits(rounded);
grace.value = parsed.value;
graceUnit.value = parsed.unit;
});
// Update the slider and the hidden field when user changes grace inputs
$(".grace-input").on("keyup change", function() {
var secs = Math.round(grace.value * graceUnit.value);
grace.setCustomValidity(secs <= 31536000 ? "" : "Must not exceed 365 days");
if (secs >= 60) {
graceSlider.noUiSlider.set(secs);
$("#update-timeout-grace").val(secs);
}
});
function showSimple() {


+ 10
- 10
templates/docs/api.html View File

@ -302,16 +302,16 @@ Example:</p>
<dt>timeout</dt>
<dd>
<p>number, optional, default value: {{ default_timeout }}.</p>
<p>A number of seconds, the expected period of this check.</p>
<p>Minimum: 60 (one minute), maximum: 2592000 (30 days).</p>
<p>The expected period of this check in seconds.</p>
<p>Minimum: 60 (one minute), maximum: 31536000 (365 days).</p>
<p>Example for a 5-minute timeout:</p>
<p><pre>{"kind": "simple", "timeout": 300}</pre></p>
<p><pre>{"timeout": 300}</pre></p>
</dd>
<dt>grace</dt>
<dd>
<p>number, optional, default value: {{ default_grace }}.</p>
<p>A number of seconds, the grace period for this check.</p>
<p>Minimum: 60 (one minute), maximum: 2592000 (30 days).</p>
<p>The grace period for this check in seconds.</p>
<p>Minimum: 60 (one minute), maximum: 31536000 (365 days).</p>
</dd>
<dt>schedule</dt>
<dd>
@ -461,16 +461,16 @@ parameter, SITE_NAME will leave its value unchanged.</p>
<dt>timeout</dt>
<dd>
<p>number, optional.</p>
<p>A number of seconds, the expected period of this check.</p>
<p>Minimum: 60 (one minute), maximum: 2592000 (30 days).</p>
<p>The expected period of this check in seconds.</p>
<p>Minimum: 60 (one minute), maximum: 31536000 (365 days).</p>
<p>Example for a 5-minute timeout:</p>
<p><pre>{"kind": "simple", "timeout": 300}</pre></p>
<p><pre>{"timeout": 300}</pre></p>
</dd>
<dt>grace</dt>
<dd>
<p>number, optional.</p>
<p>A number of seconds, the grace period for this check.</p>
<p>Minimum: 60 (one minute), maximum: 2592000 (30 days).</p>
<p>The grace period for this check in seconds.</p>
<p>Minimum: 60 (one minute), maximum: 31536000 (365 days).</p>
</dd>
<dt>schedule</dt>
<dd>


+ 10
- 10
templates/docs/api.md View File

@ -306,20 +306,20 @@ desc
timeout
: number, optional, default value: {{ default_timeout }}.
A number of seconds, the expected period of this check.
The expected period of this check in seconds.
Minimum: 60 (one minute), maximum: 2592000 (30 days).
Minimum: 60 (one minute), maximum: 31536000 (365 days).
Example for a 5-minute timeout:
<pre>{"kind": "simple", "timeout": 300}</pre>
<pre>{"timeout": 300}</pre>
grace
: number, optional, default value: {{ default_grace }}.
A number of seconds, the grace period for this check.
The grace period for this check in seconds.
Minimum: 60 (one minute), maximum: 2592000 (30 days).
Minimum: 60 (one minute), maximum: 31536000 (365 days).
schedule
: string, optional, default value: "`* * * * *`".
@ -506,20 +506,20 @@ desc
timeout
: number, optional.
A number of seconds, the expected period of this check.
The expected period of this check in seconds.
Minimum: 60 (one minute), maximum: 2592000 (30 days).
Minimum: 60 (one minute), maximum: 31536000 (365 days).
Example for a 5-minute timeout:
<pre>{"kind": "simple", "timeout": 300}</pre>
<pre>{"timeout": 300}</pre>
grace
: number, optional.
A number of seconds, the grace period for this check.
The grace period for this check in seconds.
Minimum: 60 (one minute), maximum: 2592000 (30 days).
Minimum: 60 (one minute), maximum: 31536000 (365 days).
schedule
: string, optional.


+ 24
- 20
templates/front/update_timeout_modal.html View File

@ -8,30 +8,34 @@
<input type="hidden" name="grace" id="update-timeout-grace" />
<div class="modal-body">
<div class="update-timeout-info text-center">
<span class="update-timeout-label">
Period
</span>
<span
id="period-slider-value"
class="update-timeout-value">
1 day
</span>
<div class="interval-controls">
<label>Period</label>
<input
id="period-value"
type="number"
min="1"
class="period-input form-control input-lg" />
<select id="period-unit" class="period-input form-control input-lg">
<option value="60">minutes</option>
<option value="3600">hours</option>
<option value="86400">days</option>
</select>
</div>
<div id="period-slider"></div>
<div class="update-timeout-info text-center">
<span class="update-timeout-label">
Grace Time
</span>
<span
id="grace-slider-value"
class="update-timeout-value">
1 day
</span>
<div class="interval-controls">
<label>Grace Time</label>
<input
id="grace-value"
type="number"
min="1"
class="grace-input form-control input-lg" />
<select id="grace-unit" class="grace-input form-control input-lg">
<option value="60">minutes</option>
<option value="3600">hours</option>
<option value="86400">days</option>
</select>
</div>
<div id="grace-slider"></div>
<div class="update-timeout-terms">


Loading…
Cancel
Save