mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 23:04:04 +00:00
Merge branch 'master' into feature/locale_on_status_page
This commit is contained in:
commit
722ac84f99
9 changed files with 123 additions and 45 deletions
6
db/patch-add-timeout-monitor.sql
Normal file
6
db/patch-add-timeout-monitor.sql
Normal 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;
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 인증서가 만료되면 알림을 활성화해요:",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue