Merge branch 'master' into feature/locale_on_status_page

This commit is contained in:
Wampie Driessen 2023-08-07 10:47:07 +02:00 committed by GitHub
commit 722ac84f99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 123 additions and 45 deletions

View file

@ -0,0 +1,6 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
ALTER TABLE monitor
ADD timeout DOUBLE default 0 not null;
COMMIT;

View file

@ -79,6 +79,7 @@ class Database {
"patch-added-kafka-producer.sql": true, "patch-added-kafka-producer.sql": true,
"patch-add-certificate-expiry-status-page.sql": true, "patch-add-certificate-expiry-status-page.sql": true,
"patch-monitor-oauth-cc.sql": true, "patch-monitor-oauth-cc.sql": true,
"patch-add-timeout-monitor.sql": true,
"patch-status-page-locale-selector.sql": true, "patch-status-page-locale-selector.sql": true,
}; };

View file

@ -102,6 +102,7 @@ class Monitor extends BeanModel {
active: await this.isActive(), active: await this.isActive(),
forceInactive: !await Monitor.isParentActive(this.id), forceInactive: !await Monitor.isParentActive(this.id),
type: this.type, type: this.type,
timeout: this.timeout,
interval: this.interval, interval: this.interval,
retryInterval: this.retryInterval, retryInterval: this.retryInterval,
resendInterval: this.resendInterval, resendInterval: this.resendInterval,
@ -428,7 +429,7 @@ class Monitor extends BeanModel {
const options = { const options = {
url: this.url, url: this.url,
method: (this.method || "get").toLowerCase(), method: (this.method || "get").toLowerCase(),
timeout: this.interval * 1000 * 0.8, timeout: this.timeout,
headers: { headers: {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"User-Agent": "Uptime-Kuma/" + version, "User-Agent": "Uptime-Kuma/" + version,
@ -648,7 +649,7 @@ class Monitor extends BeanModel {
} }
let res = await axios.get(steamApiUrl, { let res = await axios.get(steamApiUrl, {
timeout: this.interval * 1000 * 0.8, timeout: this.timeout,
headers: { headers: {
"Accept": "*/*", "Accept": "*/*",
"User-Agent": "Uptime-Kuma/" + version, "User-Agent": "Uptime-Kuma/" + version,

View file

@ -716,6 +716,7 @@ let needSetup = false;
bean.headers = monitor.headers; bean.headers = monitor.headers;
bean.basic_auth_user = monitor.basic_auth_user; bean.basic_auth_user = monitor.basic_auth_user;
bean.basic_auth_pass = monitor.basic_auth_pass; bean.basic_auth_pass = monitor.basic_auth_pass;
bean.timeout = monitor.timeout;
bean.oauth_client_id = monitor.oauth_client_id, bean.oauth_client_id = monitor.oauth_client_id,
bean.oauth_client_secret = monitor.oauth_client_secret, bean.oauth_client_secret = monitor.oauth_client_secret,
bean.oauth_auth_method = this.oauth_auth_method, bean.oauth_auth_method = this.oauth_auth_method,
@ -1372,6 +1373,7 @@ let needSetup = false;
// Define default values // Define default values
let retryInterval = 0; let retryInterval = 0;
let timeout = monitorListData[i].timeout || (monitorListData[i].interval * 0.8); // fallback to old value
/* /*
Only replace the default value with the backup file data for the specific version, where it appears the first time Only replace the default value with the backup file data for the specific version, where it appears the first time
@ -1397,6 +1399,7 @@ let needSetup = false;
basic_auth_pass: monitorListData[i].basic_auth_pass, basic_auth_pass: monitorListData[i].basic_auth_pass,
authWorkstation: monitorListData[i].authWorkstation, authWorkstation: monitorListData[i].authWorkstation,
authDomain: monitorListData[i].authDomain, authDomain: monitorListData[i].authDomain,
timeout,
interval: monitorListData[i].interval, interval: monitorListData[i].interval,
retryInterval: retryInterval, retryInterval: retryInterval,
resendInterval: monitorListData[i].resendInterval || 0, resendInterval: monitorListData[i].resendInterval || 0,

View file

@ -51,7 +51,7 @@
v-for="(item, index) in sortedMonitorList" v-for="(item, index) in sortedMonitorList"
:key="index" :key="index"
:monitor="item" :monitor="item"
:isSearch="searchText !== ''" :showPathName="filtersActive"
:isSelectMode="selectMode" :isSelectMode="selectMode"
:isSelected="isSelected" :isSelected="isSelected"
:select="select" :select="select"
@ -117,31 +117,68 @@ export default {
}, },
/**
* Returns a sorted list of monitors based on the applied filters and search text.
*
* @return {Array} The sorted list of monitors.
*/
sortedMonitorList() { sortedMonitorList() {
let result = Object.values(this.$root.monitorList); let result = Object.values(this.$root.monitorList);
// Simple filter by search text result = result.filter(monitor => {
// finds monitor name, tag name or tag value // filter by search text
if (this.searchText !== "") { // finds monitor name, tag name or tag value
const loweredSearchText = this.searchText.toLowerCase(); let searchTextMatch = true;
result = result.filter(monitor => { if (this.searchText !== "") {
return monitor.name.toLowerCase().includes(loweredSearchText) const loweredSearchText = this.searchText.toLowerCase();
searchTextMatch =
monitor.name.toLowerCase().includes(loweredSearchText)
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText) || monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|| tag.value?.toLowerCase().includes(loweredSearchText)); || tag.value?.toLowerCase().includes(loweredSearchText));
}); }
} else {
result = result.filter(monitor => monitor.parent === null); // filter by status
} let statusMatch = true;
if (this.filterState.status != null && this.filterState.status.length > 0) {
if (monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[monitor.id]) {
monitor.status = this.$root.lastHeartbeatList[monitor.id].status;
}
statusMatch = this.filterState.status.includes(monitor.status);
}
// filter by active
let activeMatch = true;
if (this.filterState.active != null && this.filterState.active.length > 0) {
activeMatch = this.filterState.active.includes(monitor.active);
}
// filter by tags
let tagsMatch = true;
if (this.filterState.tags != null && this.filterState.tags.length > 0) {
tagsMatch = monitor.tags.map(tag => tag.tag_id) // convert to array of tag IDs
.filter(monitorTagId => this.filterState.tags.includes(monitorTagId)) // perform Array Intersaction between filter and monitor's tags
.length > 0;
}
// Hide children if not filtering
let showChild = true;
if (this.filterState.status == null && this.filterState.active == null && this.filterState.tags == null && this.searchText === "") {
if (monitor.parent !== null) {
showChild = false;
}
}
return searchTextMatch && statusMatch && activeMatch && tagsMatch && showChild;
});
// Filter result by active state, weight and alphabetical // Filter result by active state, weight and alphabetical
result.sort((m1, m2) => { result.sort((m1, m2) => {
if (m1.active !== m2.active) { if (m1.active !== m2.active) {
if (m1.active === 0) { if (m1.active === false) {
return 1; return 1;
} }
if (m2.active === 0) { if (m2.active === false) {
return -1; return -1;
} }
} }
@ -159,27 +196,6 @@ export default {
return m1.name.localeCompare(m2.name); return m1.name.localeCompare(m2.name);
}); });
if (this.filterState.status != null && this.filterState.status.length > 0) {
result.map(monitor => {
if (monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[monitor.id]) {
monitor.status = this.$root.lastHeartbeatList[monitor.id].status;
}
});
result = result.filter(monitor => this.filterState.status.includes(monitor.status));
}
if (this.filterState.active != null && this.filterState.active.length > 0) {
result = result.filter(monitor => this.filterState.active.includes(monitor.active));
}
if (this.filterState.tags != null && this.filterState.tags.length > 0) {
result = result.filter(monitor => {
return monitor.tags.map(tag => tag.tag_id) // convert to array of tag IDs
.filter(monitorTagId => this.filterState.tags.includes(monitorTagId)) // perform Array Intersaction between filter and monitor's tags
.length > 0;
});
}
return result; return result;
}, },
@ -202,6 +218,15 @@ export default {
selectedMonitorCount() { selectedMonitorCount() {
return Object.keys(this.selectedMonitors).length; return Object.keys(this.selectedMonitors).length;
}, },
/**
* Determines if any filters are active.
*
* @return {boolean} True if any filter is active, false otherwise.
*/
filtersActive() {
return this.filterState.status != null || this.filterState.active != null || this.filterState.tags != null || this.searchText !== "";
}
}, },
watch: { watch: {
searchText() { searchText() {
@ -233,7 +258,7 @@ export default {
this.selectAll = false; this.selectAll = false;
this.selectedMonitors = {}; this.selectedMonitors = {};
} }
} },
}, },
mounted() { mounted() {
window.addEventListener("scroll", this.onScroll); window.addEventListener("scroll", this.onScroll);

View file

@ -44,7 +44,7 @@
<MonitorListItem <MonitorListItem
v-for="(item, index) in sortedChildMonitorList" v-for="(item, index) in sortedChildMonitorList"
:key="index" :monitor="item" :key="index" :monitor="item"
:isSearch="isSearch" :showPathName="showPathName"
:isSelectMode="isSelectMode" :isSelectMode="isSelectMode"
:isSelected="isSelected" :isSelected="isSelected"
:select="select" :select="select"
@ -75,8 +75,8 @@ export default {
type: Object, type: Object,
default: null, default: null,
}, },
/** If the user is currently searching */ /** Should the monitor name show it's parent */
isSearch: { showPathName: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
@ -153,7 +153,7 @@ export default {
}; };
}, },
monitorName() { monitorName() {
if (this.isSearch) { if (this.showPathName) {
return this.monitor.pathName; return this.monitor.pathName;
} else { } else {
return this.monitor.name; return this.monitor.name;

View file

@ -59,6 +59,8 @@
"Hostname": "Hostname", "Hostname": "Hostname",
"Port": "Port", "Port": "Port",
"Heartbeat Interval": "Heartbeat Interval", "Heartbeat Interval": "Heartbeat Interval",
"Request Timeout": "Request Timeout",
"timeoutAfter": "Timeout after {0} seconds",
"Retries": "Retries", "Retries": "Retries",
"Heartbeat Retry Interval": "Heartbeat Retry Interval", "Heartbeat Retry Interval": "Heartbeat Retry Interval",
"Resend Notification if Down X times consecutively": "Resend Notification if Down X times consecutively", "Resend Notification if Down X times consecutively": "Resend Notification if Down X times consecutively",

View file

@ -589,6 +589,8 @@
"RadiusCalledStationIdDescription": "접속 스테이션의 식별자", "RadiusCalledStationIdDescription": "접속 스테이션의 식별자",
"RadiusCallingStationId": "접속 요청 스테이션의 Id", "RadiusCallingStationId": "접속 요청 스테이션의 Id",
"RadiusCallingStationIdDescription": "접속 요청 스테이션의 식별자", "RadiusCallingStationIdDescription": "접속 요청 스테이션의 식별자",
"timeoutAfter": "{0}초 후 타임아웃",
"Request Timeout": "요청 타임아웃",
"Query": "쿼리", "Query": "쿼리",
"settingsCertificateExpiry": "TLS 인증서 만료", "settingsCertificateExpiry": "TLS 인증서 만료",
"certificationExpiryDescription": "HTTPS 모니터링 TLS 인증서가 만료되면 알림을 활성화해요:", "certificationExpiryDescription": "HTTPS 모니터링 TLS 인증서가 만료되면 알림을 활성화해요:",

View file

@ -393,7 +393,7 @@
<!-- Interval --> <!-- Interval -->
<div class="my-3"> <div class="my-3">
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label> <label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
<input id="interval" v-model="monitor.interval" type="number" class="form-control" required :min="minInterval" step="1" :max="maxInterval"> <input id="interval" v-model="monitor.interval" type="number" class="form-control" required :min="minInterval" step="1" :max="maxInterval" @blur="finishUpdateInterval">
</div> </div>
<div class="my-3"> <div class="my-3">
@ -412,6 +412,12 @@
<input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required :min="minInterval" step="1"> <input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required :min="minInterval" step="1">
</div> </div>
<!-- Timeout: HTTP / Keyword only -->
<div v-if="monitor.type === 'http' || monitor.type === 'keyword'" class="my-3">
<label for="timeout" class="form-label">{{ $t("Request Timeout") }} ({{ $t("timeoutAfter", [ monitor.timeout || clampTimeout(monitor.interval) ]) }})</label>
<input id="timeout" v-model="monitor.timeout" type="number" class="form-control" required min="0" step="0.1">
</div>
<div class="my-3"> <div class="my-3">
<label for="resend-interval" class="form-label"> <label for="resend-interval" class="form-label">
{{ $t("Resend Notification if Down X times consecutively") }} {{ $t("Resend Notification if Down X times consecutively") }}
@ -840,6 +846,7 @@ const monitorDefaults = {
retryInterval: 60, retryInterval: 60,
resendInterval: 0, resendInterval: 0,
maxretries: 1, maxretries: 1,
timeout: 48,
notificationIDList: {}, notificationIDList: {},
ignoreTls: false, ignoreTls: false,
upsideDown: false, upsideDown: false,
@ -1113,6 +1120,13 @@ message HealthCheckResponse {
} }
}, },
"monitor.timeout"(value, oldValue) {
// keep timeout within 80% range
if (value && value !== oldValue) {
this.monitor.timeout = this.clampTimeout(value);
}
},
"monitor.type"() { "monitor.type"() {
if (this.monitor.type === "push") { if (this.monitor.type === "push") {
if (! this.monitor.pushToken) { if (! this.monitor.pushToken) {
@ -1274,6 +1288,10 @@ message HealthCheckResponse {
if (this.monitor.retryInterval === 0) { if (this.monitor.retryInterval === 0) {
this.monitor.retryInterval = this.monitor.interval; this.monitor.retryInterval = this.monitor.interval;
} }
// Handling for monitors that are missing/zeroed timeout
if (!this.monitor.timeout) {
this.monitor.timeout = ~~(this.monitor.interval * 8) / 10;
}
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }
@ -1330,7 +1348,8 @@ message HealthCheckResponse {
this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4); this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4);
} }
if (this.monitor.type && this.monitor.type !== "http" && (this.monitor.type !== "keyword" || this.monitor.type !== "json-query")) { const monitorTypesWithEncodingAllowed = [ "http", "keyword", "json-query" ];
if (this.monitor.type && !monitorTypesWithEncodingAllowed.includes(this.monitor.type)) {
this.monitor.httpBodyEncoding = null; this.monitor.httpBodyEncoding = null;
} }
@ -1445,7 +1464,26 @@ message HealthCheckResponse {
addedDraftGroup(draftGroupName) { addedDraftGroup(draftGroupName) {
this.draftGroupName = draftGroupName; this.draftGroupName = draftGroupName;
this.monitor.parent = -1; this.monitor.parent = -1;
} },
// Clamp timeout
clampTimeout(timeout) {
// limit to 80% of interval, narrowly avoiding epsilon bug
const maxTimeout = ~~(this.monitor.interval * 8 ) / 10;
const clamped = Math.max(0, Math.min(timeout, maxTimeout));
// 0 will be treated as 80% of interval
return Number.isFinite(clamped) ? clamped : maxTimeout;
},
finishUpdateInterval() {
// Update timeout if it is greater than the clamp timeout
let clampedValue = this.clampTimeout(this.monitor.interval);
if (this.monitor.timeout > clampedValue) {
this.monitor.timeout = clampedValue;
}
},
}, },
}; };
</script> </script>