mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-21 06:46:40 +08:00
Merge branch 'master' into 2.0.X
# Conflicts: # package-lock.json # server/database.js # server/util-server.js
This commit is contained in:
@@ -8,9 +8,9 @@
|
||||
<MonitorList :scrollbar="true" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-7 col-xl-8 mb-3">
|
||||
<div ref="container" class="col-12 col-md-7 col-xl-8 mb-3">
|
||||
<!-- Add :key to disable vue router re-use the same component -->
|
||||
<router-view :key="$route.fullPath" />
|
||||
<router-view :key="$route.fullPath" :calculatedHeight="height" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,7 +25,12 @@ export default {
|
||||
MonitorList,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
height: 0
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.height = this.$refs.container.offsetHeight;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<transition name="slide-fade" appear>
|
||||
<transition ref="tableContainer" name="slide-fade" appear>
|
||||
<div v-if="$route.name === 'DashboardHome'">
|
||||
<h1 class="mb-3">
|
||||
{{ $t("Quick Stats") }}
|
||||
@@ -81,10 +81,17 @@ export default {
|
||||
Status,
|
||||
Pagination,
|
||||
},
|
||||
props: {
|
||||
calculatedHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
initialPerPage: 25,
|
||||
heartBeatList: [],
|
||||
paginationConfig: {
|
||||
hideCount: true,
|
||||
@@ -134,6 +141,37 @@ export default {
|
||||
return this.heartBeatList.slice(startIndex, endIndex);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
importantHeartBeatList() {
|
||||
this.$nextTick(() => {
|
||||
this.updatePerPage();
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initialPerPage = this.perPage;
|
||||
|
||||
window.addEventListener("resize", this.updatePerPage);
|
||||
this.updatePerPage();
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.updatePerPage);
|
||||
},
|
||||
methods: {
|
||||
updatePerPage() {
|
||||
const tableContainer = this.$refs.tableContainer;
|
||||
const tableContainerHeight = tableContainer.offsetHeight;
|
||||
const availableHeight = window.innerHeight - tableContainerHeight;
|
||||
const additionalPerPage = Math.floor(availableHeight / 58);
|
||||
|
||||
if (additionalPerPage > 0) {
|
||||
this.perPage = Math.max(this.initialPerPage, this.perPage + additionalPerPage);
|
||||
} else {
|
||||
this.perPage = this.initialPerPage;
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@@ -102,11 +102,13 @@
|
||||
<!-- Parent Monitor -->
|
||||
<div class="my-3">
|
||||
<label for="parent" class="form-label">{{ $t("Monitor Group") }}</label>
|
||||
<select v-model="monitor.parent" class="form-select" :disabled="sortedMonitorList.length === 0">
|
||||
<option v-if="sortedMonitorList.length === 0" :value="null" selected>{{ $t("noGroupMonitorMsg") }}</option>
|
||||
<option v-else :value="null" selected>{{ $t("None") }}</option>
|
||||
<option v-for="parentMonitor in sortedMonitorList" :key="parentMonitor.id" :value="parentMonitor.id">{{ parentMonitor.pathName }}</option>
|
||||
</select>
|
||||
<ActionSelect
|
||||
v-model="monitor.parent"
|
||||
:options="parentMonitorOptionsList"
|
||||
:disabled="sortedGroupMonitorList.length === 0 && draftGroupName == null"
|
||||
:icon="'plus'"
|
||||
:action="() => $refs.createGroupDialog.show()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- URL -->
|
||||
@@ -373,49 +375,25 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- SQL Server / PostgreSQL / MySQL / Redis / MongoDB -->
|
||||
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql' || monitor.type === 'redis' || monitor.type === 'mongodb'">
|
||||
<div class="my-3">
|
||||
<label for="connectionString" class="form-label">{{ $t("Connection String") }}</label>
|
||||
<input id="connectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" required>
|
||||
</div>
|
||||
</template>
|
||||
<!-- SQL Server / PostgreSQL / MySQL -->
|
||||
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql'">
|
||||
<div class="my-3">
|
||||
<label for="sqlConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
||||
|
||||
<template v-if="monitor.type === 'sqlserver'">
|
||||
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
||||
</template>
|
||||
<template v-if="monitor.type === 'postgres'">
|
||||
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
||||
</template>
|
||||
<template v-if="monitor.type === 'mysql'">
|
||||
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
||||
</template>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label for="sqlQuery" class="form-label">{{ $t("Query") }}</label>
|
||||
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" placeholder="Example: select getdate()"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Redis -->
|
||||
<template v-if="monitor.type === 'redis'">
|
||||
<div class="my-3">
|
||||
<label for="redisConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
||||
<input id="redisConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- MongoDB -->
|
||||
<template v-if="monitor.type === 'mongodb'">
|
||||
<div class="my-3">
|
||||
<label for="sqlConnectionString" class="form-label">{{ $t("Connection String") }}</label>
|
||||
|
||||
<template v-if="monitor.type === 'mongodb'">
|
||||
<input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
|
||||
</template>
|
||||
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" :placeholder="$t('Example:', [ 'select getdate()' ])" required></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Interval -->
|
||||
<div class="my-3">
|
||||
<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 class="my-3">
|
||||
@@ -434,6 +412,12 @@
|
||||
<input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required :min="minInterval" step="1">
|
||||
</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">
|
||||
<label for="resend-interval" class="form-label">
|
||||
{{ $t("Resend Notification if Down X times consecutively") }}
|
||||
@@ -471,6 +455,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="monitor.type === 'gamedig'" class="my-3 form-check">
|
||||
<input id="gamedig-guess-port" v-model="monitor.gamedigGivenPortOnly" :true-value="false" :false-value="true" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="gamedig-guess-port">
|
||||
{{ $t("gamedigGuessPort") }}
|
||||
</label>
|
||||
<div class="form-text">
|
||||
{{ $t("gamedigGuessPortDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ping packet size -->
|
||||
<div v-if="monitor.type === 'ping'" class="my-3">
|
||||
<label for="packet-size" class="form-label">{{ $t("Packet Size") }}</label>
|
||||
@@ -689,6 +683,9 @@
|
||||
<option value="basic">
|
||||
{{ $t("HTTP Basic Auth") }}
|
||||
</option>
|
||||
<option value="oauth2-cc">
|
||||
{{ $t("OAuth2: Client Credentials") }}
|
||||
</option>
|
||||
<option value="ntlm">
|
||||
NTLM
|
||||
</option>
|
||||
@@ -712,6 +709,37 @@
|
||||
<textarea id="tls-ca" v-model="monitor.tlsCa" class="form-control" :placeholder="$t('Server CA')"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="monitor.authMethod === 'oauth2-cc' ">
|
||||
<div class="my-3">
|
||||
<label for="oauth_auth_method" class="form-label">{{ $t("Authentication Method") }}</label>
|
||||
<select id="oauth_auth_method" v-model="monitor.oauth_auth_method" class="form-select">
|
||||
<option value="client_secret_basic">
|
||||
{{ $t("Authorization Header") }}
|
||||
</option>
|
||||
<option value="client_secret_post">
|
||||
{{ $t("Form Data Body") }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label for="oauth_token_url" class="form-label">{{ $t("OAuth Token URL") }}</label>
|
||||
<input id="oauth_token_url" v-model="monitor.oauth_token_url" type="text" class="form-control" :placeholder="$t('OAuth Token URL')" required>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label for="oauth_client_id" class="form-label">{{ $t("Client ID") }}</label>
|
||||
<input id="oauth_client_id" v-model="monitor.oauth_client_id" type="text" class="form-control" :placeholder="$t('Client ID')" required>
|
||||
</div>
|
||||
<template v-if="monitor.oauth_auth_method === 'client_secret_post' || monitor.oauth_auth_method === 'client_secret_basic'">
|
||||
<div class="my-3">
|
||||
<label for="oauth_client_secret" class="form-label">{{ $t("Client Secret") }}</label>
|
||||
<input id="oauth_client_secret" v-model="monitor.oauth_client_secret" type="password" class="form-control" :placeholder="$t('Client Secret')" required>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label for="oauth_scopes" class="form-label">{{ $t("OAuth Scope") }}</label>
|
||||
<input id="oauth_scopes" v-model="monitor.oauth_scopes" type="text" class="form-control" :placeholder="$t('Optional: Space separated list of scopes')">
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="my-3">
|
||||
<label for="basicauth-user" class="form-label">{{ $t("Username") }}</label>
|
||||
@@ -797,6 +825,7 @@
|
||||
<NotificationDialog ref="notificationDialog" @added="addedNotification" />
|
||||
<DockerHostDialog ref="dockerHostDialog" @added="addedDockerHost" />
|
||||
<ProxyDialog ref="proxyDialog" @added="addedProxy" />
|
||||
<CreateGroupDialog ref="createGroupDialog" @added="addedDraftGroup" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
@@ -804,20 +833,62 @@
|
||||
<script>
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { useToast } from "vue-toastification";
|
||||
import ActionSelect from "../components/ActionSelect.vue";
|
||||
import CopyableInput from "../components/CopyableInput.vue";
|
||||
import CreateGroupDialog from "../components/CreateGroupDialog.vue";
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import DockerHostDialog from "../components/DockerHostDialog.vue";
|
||||
import ProxyDialog from "../components/ProxyDialog.vue";
|
||||
import TagsManager from "../components/TagsManager.vue";
|
||||
import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } from "../util.ts";
|
||||
import { hostNameRegexPattern } from "../util-frontend";
|
||||
import { sleep } from "../util";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const monitorDefaults = {
|
||||
type: "http",
|
||||
name: "",
|
||||
parent: null,
|
||||
url: "https://",
|
||||
method: "GET",
|
||||
interval: 60,
|
||||
retryInterval: 60,
|
||||
resendInterval: 0,
|
||||
maxretries: 1,
|
||||
timeout: 48,
|
||||
notificationIDList: {},
|
||||
ignoreTls: false,
|
||||
upsideDown: false,
|
||||
packetSize: 56,
|
||||
expiryNotification: false,
|
||||
maxredirects: 10,
|
||||
accepted_statuscodes: [ "200-299" ],
|
||||
dns_resolve_type: "A",
|
||||
dns_resolve_server: "1.1.1.1",
|
||||
docker_container: "",
|
||||
docker_host: null,
|
||||
proxyId: null,
|
||||
mqttUsername: "",
|
||||
mqttPassword: "",
|
||||
mqttTopic: "",
|
||||
mqttSuccessMessage: "",
|
||||
authMethod: null,
|
||||
oauth_auth_method: "client_secret_basic",
|
||||
httpBodyEncoding: "json",
|
||||
kafkaProducerBrokers: [],
|
||||
kafkaProducerSaslOptions: {
|
||||
mechanism: "None",
|
||||
},
|
||||
gamedigGivenPortOnly: true,
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActionSelect,
|
||||
ProxyDialog,
|
||||
CopyableInput,
|
||||
CreateGroupDialog,
|
||||
NotificationDialog,
|
||||
DockerHostDialog,
|
||||
TagsManager,
|
||||
@@ -845,7 +916,8 @@ export default {
|
||||
"mysql": "mysql://username:password@host:port/database",
|
||||
"redis": "redis://user:password@host:port",
|
||||
"mongodb": "mongodb://username:password@host:port/database",
|
||||
}
|
||||
},
|
||||
draftGroupName: null,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -956,7 +1028,7 @@ message HealthCheckResponse {
|
||||
|
||||
// Filter result by active state, weight and alphabetical
|
||||
// Only return groups which arent't itself and one of its decendants
|
||||
sortedMonitorList() {
|
||||
sortedGroupMonitorList() {
|
||||
let result = Object.values(this.$root.monitorList);
|
||||
|
||||
// Only groups, not itself, not a decendant
|
||||
@@ -995,6 +1067,45 @@ message HealthCheckResponse {
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates the parent monitor options list based on the sorted group monitor list and draft group name.
|
||||
*
|
||||
* @return {Array} The parent monitor options list.
|
||||
*/
|
||||
parentMonitorOptionsList() {
|
||||
let list = [];
|
||||
if (this.sortedGroupMonitorList.length === 0 && this.draftGroupName == null) {
|
||||
list = [
|
||||
{
|
||||
label: this.$t("noGroupMonitorMsg"),
|
||||
value: null
|
||||
}
|
||||
];
|
||||
} else {
|
||||
list = [
|
||||
{
|
||||
label: this.$t("None"),
|
||||
value: null
|
||||
},
|
||||
... this.sortedGroupMonitorList.map(monitor => {
|
||||
return {
|
||||
label: monitor.pathName,
|
||||
value: monitor.id,
|
||||
};
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
if (this.draftGroupName != null) {
|
||||
list = [{
|
||||
label: this.draftGroupName,
|
||||
value: -1,
|
||||
}].concat(list);
|
||||
}
|
||||
|
||||
return list;
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
"$root.proxyList"() {
|
||||
@@ -1020,6 +1131,13 @@ message HealthCheckResponse {
|
||||
}
|
||||
},
|
||||
|
||||
"monitor.timeout"(value, oldValue) {
|
||||
// keep timeout within 80% range
|
||||
if (value && value !== oldValue) {
|
||||
this.monitor.timeout = this.clampTimeout(value);
|
||||
}
|
||||
},
|
||||
|
||||
"monitor.type"() {
|
||||
if (this.monitor.type === "push") {
|
||||
if (! this.monitor.pushToken) {
|
||||
@@ -1121,37 +1239,7 @@ message HealthCheckResponse {
|
||||
if (this.isAdd) {
|
||||
|
||||
this.monitor = {
|
||||
type: "http",
|
||||
name: "",
|
||||
parent: null,
|
||||
url: "https://",
|
||||
method: "GET",
|
||||
interval: 60,
|
||||
retryInterval: this.interval,
|
||||
resendInterval: 0,
|
||||
maxretries: 1,
|
||||
notificationIDList: {},
|
||||
ignoreTls: false,
|
||||
upsideDown: false,
|
||||
packetSize: 56,
|
||||
expiryNotification: false,
|
||||
maxredirects: 10,
|
||||
accepted_statuscodes: [ "200-299" ],
|
||||
dns_resolve_type: "A",
|
||||
dns_resolve_server: "1.1.1.1",
|
||||
docker_container: "",
|
||||
docker_host: null,
|
||||
proxyId: null,
|
||||
mqttUsername: "",
|
||||
mqttPassword: "",
|
||||
mqttTopic: "",
|
||||
mqttSuccessMessage: "",
|
||||
authMethod: null,
|
||||
httpBodyEncoding: "json",
|
||||
kafkaProducerBrokers: [],
|
||||
kafkaProducerSaslOptions: {
|
||||
mechanism: "None",
|
||||
},
|
||||
...monitorDefaults
|
||||
};
|
||||
|
||||
if (this.$root.proxyList && !this.monitor.proxyId) {
|
||||
@@ -1211,12 +1299,18 @@ message HealthCheckResponse {
|
||||
if (this.monitor.retryInterval === 0) {
|
||||
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 {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.draftGroupName = null;
|
||||
|
||||
},
|
||||
|
||||
addKafkaProducerBroker(newBroker) {
|
||||
@@ -1265,7 +1359,8 @@ message HealthCheckResponse {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1281,16 +1376,46 @@ message HealthCheckResponse {
|
||||
this.monitor.url = this.monitor.url.trim();
|
||||
}
|
||||
|
||||
let createdNewParent = false;
|
||||
|
||||
if (this.draftGroupName && this.monitor.parent === -1) {
|
||||
// Create Monitor with name of draft group
|
||||
const res = await new Promise((resolve) => {
|
||||
this.$root.add({
|
||||
...monitorDefaults,
|
||||
type: "group",
|
||||
name: this.draftGroupName,
|
||||
interval: this.monitor.interval,
|
||||
active: false,
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
createdNewParent = true;
|
||||
this.monitor.parent = res.monitorID;
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isAdd || this.isClone) {
|
||||
this.$root.add(this.monitor, async (res) => {
|
||||
|
||||
if (res.ok) {
|
||||
await this.$refs.tagsManager.submit(res.monitorID);
|
||||
|
||||
// Start the new parent monitor after edit is done
|
||||
if (createdNewParent) {
|
||||
this.startParentGroupMonitor();
|
||||
}
|
||||
|
||||
toast.success(res.msg);
|
||||
this.processing = false;
|
||||
this.$root.getMonitorList();
|
||||
this.$router.push("/dashboard/" + res.monitorID);
|
||||
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.processing = false;
|
||||
@@ -1304,10 +1429,20 @@ message HealthCheckResponse {
|
||||
this.processing = false;
|
||||
this.$root.toastRes(res);
|
||||
this.init();
|
||||
|
||||
// Start the new parent monitor after edit is done
|
||||
if (createdNewParent) {
|
||||
this.startParentGroupMonitor();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async startParentGroupMonitor() {
|
||||
await sleep(2000);
|
||||
await this.$root.getSocket().emit("resumeMonitor", this.monitor.parent, () => {});
|
||||
},
|
||||
|
||||
/**
|
||||
* Added a Notification Event
|
||||
* Enable it if the notification is added in EditMonitor.vue
|
||||
@@ -1331,6 +1466,35 @@ message HealthCheckResponse {
|
||||
addedDockerHost(id) {
|
||||
this.monitor.docker_host = id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a draft group.
|
||||
*
|
||||
* @param {string} draftGroupName - The name of the draft group.
|
||||
*/
|
||||
addedDraftGroup(draftGroupName) {
|
||||
this.draftGroupName = draftGroupName;
|
||||
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>
|
||||
|
@@ -54,6 +54,12 @@
|
||||
<label class="form-check-label" for="show-powered-by">{{ $t("Show Powered By") }}</label>
|
||||
</div>
|
||||
|
||||
<!-- Show certificate expiry -->
|
||||
<div class="my-3 form-check form-switch">
|
||||
<input id="show-certificate-expiry" v-model="config.showCertificateExpiry" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="show-certificate-expiry">{{ $t("showCertificateExpiry") }}</label>
|
||||
</div>
|
||||
|
||||
<div v-if="false" class="my-3">
|
||||
<label for="password" class="form-label">{{ $t("Password") }} <sup>{{ $t("Coming Soon") }}</sup></label>
|
||||
<input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
|
||||
@@ -309,7 +315,7 @@
|
||||
👀 {{ $t("statusPageNothing") }}
|
||||
</div>
|
||||
|
||||
<PublicGroupList :edit-mode="enableEditMode" :show-tags="config.showTags" />
|
||||
<PublicGroupList :edit-mode="enableEditMode" :show-tags="config.showTags" :show-certificate-expiry="config.showCertificateExpiry" />
|
||||
</div>
|
||||
|
||||
<footer class="mt-5 mb-4">
|
||||
|
Reference in New Issue
Block a user