"
-labels: [enhancement]
+#title: "[Feature] "
+labels: [feature-request]
body:
+ - type: checkboxes
+ id: no-duplicate-issues
+ attributes:
+ label: "⚠️ Please verify that this feature request has NOT been suggested before."
+ description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/uptime-kuma/issues?q=)"
+ options:
+ - label: "I checked and didn't find similar feature request"
+ required: true
- type: dropdown
id: feature-area
attributes:
@@ -49,11 +57,3 @@ body:
label: "📝 Additional Context"
description: "Add any other context or screenshots about the feature request here."
placeholder: "..."
- - type: checkboxes
- id: no-duplicate-issues
- attributes:
- label: "⚠️ Please verify that this feature request has NOT been suggested before."
- description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/uptime-kuma/issues?q=)"
- options:
- - label: "I checked and didn't find similar feature request"
- required: true
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index fa0aa883b..3be229315 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -24,3 +24,5 @@ Please delete options that are not relevant.
- [ ] My code needed automated testing. I have added them (this is optional task)
## Screenshots (if any)
+
+Please do not use any external image service. Instead, just paste in or drag and drop the image here and it will be uploaded automatically.
diff --git a/SECURITY.md b/SECURITY.md
index 7b9bfca41..3a11e8817 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,5 +1,11 @@
# Security Policy
+## Reporting a Vulnerability
+
+Please report security issues to uptime@kuma.pet.
+
+Do not use the issue tracker or discuss it in the public as it will cause more damage.
+
## Supported Versions
Use this section to tell people about which versions of your project are
@@ -23,9 +29,3 @@ currently being supported with security updates.
| debian | :white_check_mark: |
| alpine | :white_check_mark: |
| All other tags | ❌ |
-
-## Reporting a Vulnerability
-
-Please report security issues to uptime@kuma.pet.
-
-Do not use the issue tracker or discuss it in the public as it will cause more damage.
diff --git a/extra/healthcheck.js b/extra/healthcheck.js
index 99f748fb5..c6e8a349a 100644
--- a/extra/healthcheck.js
+++ b/extra/healthcheck.js
@@ -1,25 +1,41 @@
/*
* This script should be run after a period of time (180s), because the server may need some time to prepare.
*/
+const { FBSD } = require("../server/util-server");
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
let client;
-if (process.env.SSL_KEY && process.env.SSL_CERT) {
+const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
+const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
+
+if (sslKey && sslCert) {
client = require("https");
} else {
client = require("http");
}
+// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
+// Dual-stack support for (::)
+let hostname = process.env.UPTIME_KUMA_HOST;
+
+// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD
+if (!hostname && !FBSD) {
+ hostname = process.env.HOST;
+}
+
+const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || 3001);
+
let options = {
- host: process.env.HOST || "127.0.0.1",
- port: parseInt(process.env.PORT) || 3001,
+ host: hostname || "127.0.0.1",
+ port: port,
timeout: 28 * 1000,
};
let request = client.request(options, (res) => {
console.log(`Health Check OK [Res Code: ${res.statusCode}]`);
- if (res.statusCode === 200) {
+ if (res.statusCode === 302) {
process.exit(0);
} else {
process.exit(1);
diff --git a/package.json b/package.json
index 64be83ee1..c7ec3d8ee 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
- "version": "1.10.0",
+ "version": "1.10.2",
"license": "MIT",
"repository": {
"type": "git",
@@ -30,13 +30,13 @@
"build-docker": "npm run build-docker-debian && npm run build-docker-alpine",
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
- "build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.10.0-alpine --target release . --push",
- "build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.10.0 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.10.0-debian --target release . --push",
+ "build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.10.2-alpine --target release . --push",
+ "build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.10.2 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.10.2-debian --target release . --push",
"build-docker-nightly": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
- "setup": "git checkout 1.10.0 && npm ci --production && npm run download-dist",
+ "setup": "git checkout 1.10.2 && npm ci --production && npm run download-dist",
"download-dist": "node extra/download-dist.js",
"update-version": "node extra/update-version.js",
"mark-as-nightly": "node extra/mark-as-nightly.js",
@@ -61,7 +61,7 @@
"args-parser": "~1.3.0",
"axios": "~0.21.4",
"bcryptjs": "~2.4.3",
- "bootstrap": "~5.1.3",
+ "bootstrap": "5.1.3",
"bree": "~6.3.1",
"chardet": "^1.3.0",
"chart.js": "~3.6.0",
diff --git a/server/model/monitor.js b/server/model/monitor.js
index fc3292317..980e0ac69 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -141,6 +141,7 @@ class Monitor extends BeanModel {
// Do not do any queries/high loading things before the "bean.ping"
let startTime = dayjs().valueOf();
+ debug(`[${this.name}] Prepare Options for axios`);
const options = {
url: this.url,
method: (this.method || "get").toLowerCase(),
@@ -160,6 +161,8 @@ class Monitor extends BeanModel {
return checkStatusCode(status, this.getAcceptedStatuscodes());
},
};
+
+ debug(`[${this.name}] Axios Request`);
let res = await axios.request(options);
bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime;
@@ -167,12 +170,13 @@ class Monitor extends BeanModel {
// Check certificate if https is used
let certInfoStartTime = dayjs().valueOf();
if (this.getUrl()?.protocol === "https:") {
+ debug(`[${this.name}] Check cert`);
try {
let tlsInfoObject = checkCertificate(res);
tlsInfo = await this.updateTlsInfo(tlsInfoObject);
if (!this.getIgnoreTls()) {
- debug("call sendCertNotification");
+ debug(`[${this.name}] call sendCertNotification`);
await this.sendCertNotification(tlsInfoObject);
}
@@ -351,15 +355,19 @@ class Monitor extends BeanModel {
let beatInterval = this.interval;
+ debug(`[${this.name}] Check isImportant`);
let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status);
// Mark as important if status changed, ignore pending pings,
// Don't notify if disrupted changes to up
if (isImportant) {
bean.important = true;
+
+ debug(`[${this.name}] sendNotification`);
await Monitor.sendNotification(isFirstBeat, this, bean);
// Clear Status Page Cache
+ debug(`[${this.name}] apicache clear`);
apicache.clear();
} else {
@@ -377,10 +385,14 @@ class Monitor extends BeanModel {
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`);
}
+ debug(`[${this.name}] Send to socket`);
io.to(this.user_id).emit("heartbeat", bean.toJSON());
Monitor.sendStats(io, this.id, this.user_id);
+ debug(`[${this.name}] Store`);
await R.store(bean);
+
+ debug(`[${this.name}] prometheus.update`);
prometheus.update(bean, tlsInfo);
previousBeat = bean;
@@ -394,7 +406,10 @@ class Monitor extends BeanModel {
}
}
+ debug(`[${this.name}] SetTimeout for next check.`);
this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
+ } else {
+ console.log(`[${this.name}] isStop = true, no next check.`);
}
};
diff --git a/server/notification-providers/feishu.js b/server/notification-providers/feishu.js
index a3e340301..05fc9c186 100644
--- a/server/notification-providers/feishu.js
+++ b/server/notification-providers/feishu.js
@@ -27,7 +27,7 @@ class Feishu extends NotificationProvider {
content: {
post: {
zh_cn: {
- title: "UptimeKuma Alert: " + monitorJSON["name"],
+ title: "UptimeKuma Alert: [Down] " + monitorJSON["name"],
content: [
[
{
@@ -54,7 +54,7 @@ class Feishu extends NotificationProvider {
content: {
post: {
zh_cn: {
- title: "UptimeKuma Alert: " + monitorJSON["name"],
+ title: "UptimeKuma Alert: [Up] " + monitorJSON["name"],
content: [
[
{
diff --git a/server/routers/api-router.js b/server/routers/api-router.js
index fbe8136e5..79e828378 100644
--- a/server/routers/api-router.js
+++ b/server/routers/api-router.js
@@ -101,6 +101,10 @@ router.get("/api/status-page/config", async (_request, response) => {
config.statusPagePublished = true;
}
+ if (! config.statusPageTags) {
+ config.statusPageTags = false;
+ }
+
if (! config.title) {
config.title = "Uptime Kuma";
}
@@ -140,10 +144,25 @@ router.get("/api/status-page/monitor-list", cache("5 minutes"), async (_request,
try {
await checkPublished();
const publicGroupList = [];
- let list = await R.find("group", " public = 1 ORDER BY weight ");
-
+ const tagsVisible = (await getSettings("statusPage")).statusPageTags;
+ const list = await R.find("group", " public = 1 ORDER BY weight ");
for (let groupBean of list) {
- publicGroupList.push(await groupBean.toPublicJSON());
+ let monitorGroup = await groupBean.toPublicJSON();
+ if (tagsVisible) {
+ monitorGroup.monitorList = await Promise.all(monitorGroup.monitorList.map(async (monitor) => {
+ // Includes tags as an array in response, allows for tags to be displayed on public status page
+ const tags = await R.getAll(
+ `SELECT monitor_tag.monitor_id, monitor_tag.value, tag.name, tag.color
+ FROM monitor_tag
+ JOIN tag
+ ON monitor_tag.tag_id = tag.id
+ WHERE monitor_tag.monitor_id = ?`, [monitor.id]
+ );
+ return {...monitor, tags: tags}
+ }));
+ }
+
+ publicGroupList.push(monitorGroup);
}
response.json(publicGroupList);
diff --git a/server/server.js b/server/server.js
index 0af003d13..d1fd7ff29 100644
--- a/server/server.js
+++ b/server/server.js
@@ -186,6 +186,15 @@ exports.entryPage = "dashboard";
// Normal Router here
// ***************************
+ // Entry Page
+ app.get("/", async (_request, response) => {
+ if (exports.entryPage === "statusPage") {
+ response.redirect("/status");
+ } else {
+ response.redirect("/dashboard");
+ }
+ });
+
// Robots.txt
app.get("/robots.txt", async (_request, response) => {
let txt = "User-agent: *\nDisallow:";
diff --git a/server/util-server.js b/server/util-server.js
index 5d65f46d3..68f59f67f 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -201,8 +201,13 @@ const getDaysRemaining = (validFrom, validTo) => {
// param: info - the chain obtained from getPeerCertificate()
const parseCertificateInfo = function (info) {
let link = info;
+ let i = 0;
+
+ const existingList = {};
while (link) {
+ debug(`[${i}] ${link.fingerprint}`);
+
if (!link.valid_from || !link.valid_to) {
break;
}
@@ -210,15 +215,24 @@ const parseCertificateInfo = function (info) {
link.validFor = link.subjectaltname?.replace(/DNS:|IP Address:/g, "").split(", ");
link.daysRemaining = getDaysRemaining(new Date(), link.validTo);
+ existingList[link.fingerprint] = true;
+
// Move up the chain until loop is encountered
if (link.issuerCertificate == null) {
break;
- } else if (link.fingerprint == link.issuerCertificate.fingerprint) {
+ } else if (link.issuerCertificate.fingerprint in existingList) {
+ debug(`[Last] ${link.issuerCertificate.fingerprint}`);
link.issuerCertificate = null;
break;
} else {
link = link.issuerCertificate;
}
+
+ // Should be no use, but just in case.
+ if (i > 500) {
+ throw new Error("Dead loop occurred in parseCertificateInfo");
+ }
+ i++;
}
return info;
@@ -228,6 +242,7 @@ exports.checkCertificate = function (res) {
const info = res.request.res.socket.getPeerCertificate(true);
const valid = res.request.res.socket.authorized || false;
+ debug("Parsing Certificate Info");
const parsedInfo = parseCertificateInfo(info);
return {
diff --git a/src/assets/app.scss b/src/assets/app.scss
index e1a5d052d..5578946bd 100644
--- a/src/assets/app.scss
+++ b/src/assets/app.scss
@@ -189,7 +189,7 @@ textarea.form-control {
opacity: 1;
}
- .table-hover > tbody > tr:hover {
+ .table-hover > tbody > tr:hover > * {
--bs-table-accent-bg: #070a10;
color: $dark-font-color;
}
@@ -346,6 +346,10 @@ textarea.form-control {
&.active {
background-color: #cdf8f4;
}
+ .tags {
+ // Removes margin to line up tags list with uptime percentage
+ margin-left: -0.25rem;
+ }
}
}
diff --git a/src/components/PublicGroupList.vue b/src/components/PublicGroupList.vue
index 23d19e6cd..f30edcef5 100644
--- a/src/components/PublicGroupList.vue
+++ b/src/components/PublicGroupList.vue
@@ -41,6 +41,9 @@
{{ monitor.element.name }}
+
+
+
@@ -59,12 +62,14 @@
import Draggable from "vuedraggable";
import HeartbeatBar from "./HeartbeatBar.vue";
import Uptime from "./Uptime.vue";
+import Tag from "./Tag.vue";
export default {
components: {
Draggable,
HeartbeatBar,
Uptime,
+ Tag,
},
props: {
editMode: {
diff --git a/src/components/Uptime.vue b/src/components/Uptime.vue
index a4bf22f68..2717672c4 100644
--- a/src/components/Uptime.vue
+++ b/src/components/Uptime.vue
@@ -1,5 +1,5 @@
- {{ uptime }}
+ {{ uptime }}