Merge branch 'master' into copilot/update-float-fields-precision
This commit is contained in:
commit
5e4eec3f10
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,36 +1,31 @@
|
|||||||
<sub>ℹ️ To keep reviews fast and effective, please make sure you’ve [read our pull request guidelines](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma)</sub>
|
# Summary
|
||||||
|
|
||||||
## 📝 Summary of changes done and why they are done
|
In this pull request, the following changes are made:
|
||||||
|
|
||||||
<!-- Provide a clear summary of the purpose and scope of this pull request-->
|
- Foobar was changed to FooFoo, because ...
|
||||||
|
|
||||||
## 📋 Related issues
|
|
||||||
|
|
||||||
<!--Please link any GitHub issues or tasks that this pull request addresses-->
|
<!--Please link any GitHub issues or tasks that this pull request addresses-->
|
||||||
|
|
||||||
- Relates to #issue-number <!--this links related the issue-->
|
- Relates to #issue-number <!--this links related the issue-->
|
||||||
- Resolves #issue-number <!--this auto-closes the issue-->
|
- Resolves #issue-number <!--this auto-closes the issue-->
|
||||||
|
|
||||||
## 📄 Checklist
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Please follow this checklist to avoid unnecessary back and forth (click to expand)</summary>
|
<summary>Please follow this checklist to avoid unnecessary back and forth (click to expand)</summary>
|
||||||
|
|
||||||
- [ ] ⚠️ If there are Breaking change (a fix or feature that alters existing functionality in a way that could cause issues) I have called them out
|
- [ ] ⚠️ If there are Breaking change (a fix or feature that alters existing functionality in a way that could cause issues) I have called them out
|
||||||
- [ ] 🧠 I have disclosed any use of LLMs/AI in this contribution and reviewed all generated content.
|
- [ ] 🧠 I have disclosed any use of LLMs/AI in this contribution and reviewed all generated content.
|
||||||
I understand that I am responsible for and able to explain every line of code I submit.
|
I understand that I am responsible for and able to explain every line of code I submit.
|
||||||
- [ ] 🔍 My code adheres to the style guidelines of this project.
|
- [ ] 🔍 Any UI changes adhere to visual style of this project.
|
||||||
- [ ] ⚠️ My changes generate no new warnings.
|
- [ ] 🛠️ I have self-reviewed and self-tested my code to ensure it works as expected.
|
||||||
- [ ] 🛠️ I have reviewed and tested my code.
|
|
||||||
- [ ] 📝 I have commented my code, especially in hard-to-understand areas (e.g., using JSDoc for methods).
|
- [ ] 📝 I have commented my code, especially in hard-to-understand areas (e.g., using JSDoc for methods).
|
||||||
- [ ] 🤖 I added or updated automated tests where appropriate.
|
- [ ] 🤖 I added or updated automated tests where appropriate.
|
||||||
- [ ] 📄 Documentation updates are included (if applicable).
|
- [ ] 📄 Documentation updates are included (if applicable).
|
||||||
- [ ] 🔒 I have considered potential security impacts and mitigated risks.
|
|
||||||
- [ ] 🧰 Dependency updates are listed and explained.
|
- [ ] 🧰 Dependency updates are listed and explained.
|
||||||
|
- [ ] ⚠️ CI passes and is green.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 📷 Screenshots or Visual Changes
|
## Screenshots for Visual Changes
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
If this pull request introduces visual changes, please provide the following details.
|
If this pull request introduces visual changes, please provide the following details.
|
||||||
|
|||||||
38
.github/workflows/new_contributor_pr.yml
vendored
Normal file
38
.github/workflows/new_contributor_pr.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
name: New contributor message
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Safety
|
||||||
|
# This workflow uses pull_request_target so it can run with write permissions on first-time contributor PRs.
|
||||||
|
# It is safe because it does not check out or execute any code from the pull request and
|
||||||
|
# only uses the pinned, trusted actions/first-interaction action
|
||||||
|
pull_request_target: # zizmor: ignore[dangerous-triggers]
|
||||||
|
types: [opened]
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: github.repository == 'louislam/uptime-kuma'
|
||||||
|
name: Hello new contributor
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- uses: actions/first-interaction@1c4688942c71f71d4f5502a26ea67c331730fa4d # v3.1.0
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
pr-message: |
|
||||||
|
Hello! Thank you for your contribution 👋
|
||||||
|
|
||||||
|
As this is your first contribution, please be sure to check out our
|
||||||
|
[Pull Request guidelines](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma).
|
||||||
|
|
||||||
|
In particular:
|
||||||
|
- Mark your PR as Draft while you’re still making changes
|
||||||
|
- Mark it as Ready for review once it’s fully ready 🟢
|
||||||
|
|
||||||
|
If you have any design or process questions, feel free to ask them right here in this pull request - unclear documentation is a bug too.
|
||||||
|
|
||||||
|
Thanks for lending a paw to Uptime Kuma 🐻
|
||||||
@ -175,8 +175,8 @@ You can mention me if you ask a question on the subreddit.
|
|||||||
|
|
||||||
### Create Pull Requests
|
### Create Pull Requests
|
||||||
|
|
||||||
We DO NOT accept all types of pull requests and do not want to waste your time. Please be sure that you have read and follow pull request rules:
|
Pull requests are awesome.
|
||||||
[CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma)
|
To keep reviews fast and effective, please make sure you’ve [read our pull request guidelines](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma).
|
||||||
|
|
||||||
### Test Pull Requests
|
### Test Pull Requests
|
||||||
|
|
||||||
|
|||||||
@ -284,6 +284,14 @@ class Database {
|
|||||||
port: dbConfig.port,
|
port: dbConfig.port,
|
||||||
user: dbConfig.username,
|
user: dbConfig.username,
|
||||||
password: dbConfig.password,
|
password: dbConfig.password,
|
||||||
|
...(dbConfig.ssl
|
||||||
|
? {
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
...(dbConfig.ca && dbConfig.ca.trim() !== "" ? { ca: [dbConfig.ca] } : {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set to true, so for example "uptime.kuma", becomes `uptime.kuma`, not `uptime`.`kuma`
|
// Set to true, so for example "uptime.kuma", becomes `uptime.kuma`, not `uptime`.`kuma`
|
||||||
@ -309,6 +317,14 @@ class Database {
|
|||||||
}
|
}
|
||||||
return next();
|
return next();
|
||||||
},
|
},
|
||||||
|
...(dbConfig.ssl
|
||||||
|
? {
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
...(dbConfig.ca && dbConfig.ca.trim() !== "" ? { ca: [dbConfig.ca] } : {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
pool: mariadbPoolConfig,
|
pool: mariadbPoolConfig,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -108,16 +108,51 @@ class NotificationProvider {
|
|||||||
* @throws {any} The error specified
|
* @throws {any} The error specified
|
||||||
*/
|
*/
|
||||||
throwGeneralAxiosError(error) {
|
throwGeneralAxiosError(error) {
|
||||||
let msg = "Error: " + error + " ";
|
let msg = error && error.message ? error.message : String(error);
|
||||||
|
|
||||||
if (error.response && error.response.data) {
|
if (error && error.code) {
|
||||||
|
msg += ` (code=${error.code})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error && error.response && error.response.status) {
|
||||||
|
msg += ` (HTTP ${error.response.status}${error.response.statusText ? " " + error.response.statusText : ""})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error && error.response && error.response.data) {
|
||||||
if (typeof error.response.data === "string") {
|
if (typeof error.response.data === "string") {
|
||||||
msg += error.response.data;
|
msg += " " + error.response.data;
|
||||||
} else {
|
} else {
|
||||||
msg += JSON.stringify(error.response.data);
|
try {
|
||||||
|
msg += " " + JSON.stringify(error.response.data);
|
||||||
|
} catch (e) {
|
||||||
|
msg += " " + String(error.response.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expand AggregateError to show underlying causes
|
||||||
|
let agg = null;
|
||||||
|
if (error && error.name === "AggregateError" && Array.isArray(error.errors)) {
|
||||||
|
agg = error;
|
||||||
|
} else if (error && error.cause && error.cause.name === "AggregateError" && Array.isArray(error.cause.errors)) {
|
||||||
|
agg = error.cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agg) {
|
||||||
|
let causes = agg.errors
|
||||||
|
.map((e) => {
|
||||||
|
let m = e && e.message ? e.message : String(e);
|
||||||
|
if (e && e.code) {
|
||||||
|
m += ` (code=${e.code})`;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
})
|
||||||
|
.join("; ");
|
||||||
|
msg += " - caused by: " + causes;
|
||||||
|
} else if (error && error.cause && error.cause.message) {
|
||||||
|
msg += " - cause: " + error.cause.message;
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -102,6 +102,8 @@ class SetupDatabase {
|
|||||||
dbConfig.dbName = process.env.UPTIME_KUMA_DB_NAME;
|
dbConfig.dbName = process.env.UPTIME_KUMA_DB_NAME;
|
||||||
dbConfig.username = getEnvOrFile("UPTIME_KUMA_DB_USERNAME");
|
dbConfig.username = getEnvOrFile("UPTIME_KUMA_DB_USERNAME");
|
||||||
dbConfig.password = getEnvOrFile("UPTIME_KUMA_DB_PASSWORD");
|
dbConfig.password = getEnvOrFile("UPTIME_KUMA_DB_PASSWORD");
|
||||||
|
dbConfig.ssl = getEnvOrFile("UPTIME_KUMA_DB_SSL")?.toLowerCase() === "true";
|
||||||
|
dbConfig.ca = getEnvOrFile("UPTIME_KUMA_DB_CA");
|
||||||
Database.writeDBConfig(dbConfig);
|
Database.writeDBConfig(dbConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,6 +241,14 @@ class SetupDatabase {
|
|||||||
user: dbConfig.username,
|
user: dbConfig.username,
|
||||||
password: dbConfig.password,
|
password: dbConfig.password,
|
||||||
database: dbConfig.dbName,
|
database: dbConfig.dbName,
|
||||||
|
...(dbConfig.ssl
|
||||||
|
? {
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
...(dbConfig.ca && dbConfig.ca.trim() !== "" ? { ca: [dbConfig.ca] } : {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
});
|
});
|
||||||
await connection.execute("SELECT 1");
|
await connection.execute("SELECT 1");
|
||||||
connection.end();
|
connection.end();
|
||||||
|
|||||||
@ -157,6 +157,16 @@ exports.pingAsync = function (
|
|||||||
deadline = PING_GLOBAL_TIMEOUT_DEFAULT,
|
deadline = PING_GLOBAL_TIMEOUT_DEFAULT,
|
||||||
timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT
|
timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT
|
||||||
) {
|
) {
|
||||||
|
try {
|
||||||
|
const url = new URL(`http://${destAddr}`);
|
||||||
|
destAddr = url.hostname;
|
||||||
|
if (destAddr.startsWith("[") && destAddr.endsWith("]")) {
|
||||||
|
destAddr = destAddr.slice(1, -1);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ping.promise
|
ping.promise
|
||||||
.probe(destAddr, {
|
.probe(destAddr, {
|
||||||
|
|||||||
@ -6,6 +6,10 @@
|
|||||||
"setupDatabaseSQLite": "A simple database file, recommended for small-scale deployments. Prior to v2.0.0, Uptime Kuma used SQLite as the default database.",
|
"setupDatabaseSQLite": "A simple database file, recommended for small-scale deployments. Prior to v2.0.0, Uptime Kuma used SQLite as the default database.",
|
||||||
"settingUpDatabaseMSG": "Setting up the database. It may take a while, please be patient.",
|
"settingUpDatabaseMSG": "Setting up the database. It may take a while, please be patient.",
|
||||||
"dbName": "Database Name",
|
"dbName": "Database Name",
|
||||||
|
"enableSSL": "Enable SSL/TLS",
|
||||||
|
"mariadbUseSSLHelptext": "Enable to use a encrypted connection to your database. Required for most cloud databases.",
|
||||||
|
"mariadbCaCertificateLabel": "CA Certificate",
|
||||||
|
"mariadbCaCertificateHelptext": "Paste the CA Cert in PEM format to use with self-signed certificates. Leave blank if your database uses a certificate signed by a public CA.",
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
"Dashboard": "Dashboard",
|
"Dashboard": "Dashboard",
|
||||||
"Help": "Help",
|
"Help": "Help",
|
||||||
|
|||||||
@ -121,6 +121,42 @@
|
|||||||
<input id="floatingInput" v-model="dbConfig.dbName" type="text" class="form-control" required />
|
<input id="floatingInput" v-model="dbConfig.dbName" type="text" class="form-control" required />
|
||||||
<label for="floatingInput">{{ $t("dbName") }}</label>
|
<label for="floatingInput">{{ $t("dbName") }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 short text-start">
|
||||||
|
<div class="form-check form-switch ps-0" style="height: auto; display: block; padding: 0">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<input
|
||||||
|
id="sslCheck"
|
||||||
|
v-model="dbConfig.ssl"
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
class="form-check-input ms-0 me-2"
|
||||||
|
style="float: none"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label fw-bold" for="sslCheck">
|
||||||
|
{{ $t("enableSSL") }}
|
||||||
|
<span class="fw-normal text-muted" style="font-size: 0.9em">
|
||||||
|
({{ $t("Optional") }})
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text mt-1">
|
||||||
|
{{ $t("mariadbUseSSLHelptext") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="dbConfig.ssl" class="form-floating mt-3 short">
|
||||||
|
<textarea
|
||||||
|
id="caInput"
|
||||||
|
v-model="dbConfig.ca"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="-----BEGIN CERTIFICATE-----"
|
||||||
|
style="height: 120px"
|
||||||
|
></textarea>
|
||||||
|
<label for="caInput">{{ $t("mariadbCaCertificateLabel") }}</label>
|
||||||
|
<div class="form-text">{{ $t("mariadbCaCertificateHelptext") }}</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<button class="btn btn-primary mt-4 short" type="submit" :disabled="disabledButton">
|
<button class="btn btn-primary mt-4 short" type="submit" :disabled="disabledButton">
|
||||||
@ -148,6 +184,8 @@ export default {
|
|||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
dbName: "kuma",
|
dbName: "kuma",
|
||||||
|
ssl: false,
|
||||||
|
ca: "",
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
needSetup: false,
|
needSetup: false,
|
||||||
@ -253,6 +291,14 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-check {
|
||||||
|
height: calc(3.5rem + 2px);
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
.short {
|
.short {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
const { describe, test } = require("node:test");
|
||||||
|
const assert = require("node:assert");
|
||||||
|
|
||||||
|
const NotificationProvider = require("../../../server/notification-providers/notification-provider");
|
||||||
|
|
||||||
|
describe("NotificationProvider.throwGeneralAxiosError()", () => {
|
||||||
|
const provider = new NotificationProvider();
|
||||||
|
|
||||||
|
test("expands AggregateError causes", () => {
|
||||||
|
let err1 = new Error("connect ECONNREFUSED 127.0.0.1:443");
|
||||||
|
err1.code = "ECONNREFUSED";
|
||||||
|
let err2 = new Error("connect ECONNREFUSED ::1:443");
|
||||||
|
err2.code = "ECONNREFUSED";
|
||||||
|
|
||||||
|
let aggErr = new AggregateError([err1, err2], "AggregateError");
|
||||||
|
|
||||||
|
assert.throws(() => provider.throwGeneralAxiosError(aggErr), {
|
||||||
|
message: /^AggregateError - caused by: .+/,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("expands AggregateError wrapped in error.cause", () => {
|
||||||
|
let innerErr = new Error("connect ETIMEDOUT 10.0.0.1:443");
|
||||||
|
innerErr.code = "ETIMEDOUT";
|
||||||
|
|
||||||
|
let aggErr = new AggregateError([innerErr], "AggregateError");
|
||||||
|
let outerErr = new Error("Request failed");
|
||||||
|
outerErr.cause = aggErr;
|
||||||
|
|
||||||
|
assert.throws(() => provider.throwGeneralAxiosError(outerErr), {
|
||||||
|
message: /^Request failed - caused by: .+/,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
57
test/backend-test/test-util-server.js
Normal file
57
test/backend-test/test-util-server.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
const { describe, test } = require("node:test");
|
||||||
|
const assert = require("node:assert");
|
||||||
|
const { pingAsync } = require("../../server/util-server");
|
||||||
|
|
||||||
|
describe("Server Utilities: pingAsync", () => {
|
||||||
|
test("should convert IDN domains to Punycode before pinging", async () => {
|
||||||
|
const idnDomain = "münchen.de";
|
||||||
|
const punycodeDomain = "xn--mnchen-3ya.de";
|
||||||
|
|
||||||
|
await assert.rejects(pingAsync(idnDomain, false, 1, "", true, 56, 1, 1), (err) => {
|
||||||
|
if (err.message.includes("Parameter string not correctly encoded")) {
|
||||||
|
assert.fail("Ping failed with encoding error: IDN was not converted");
|
||||||
|
}
|
||||||
|
assert.ok(
|
||||||
|
err.message.includes(punycodeDomain),
|
||||||
|
`Error message should contain the Punycode domain "${punycodeDomain}". Got: ${err.message}`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should strip brackets from IPv6 addresses before pinging", async () => {
|
||||||
|
const ipv6WithBrackets = "[2606:4700:4700::1111]";
|
||||||
|
const ipv6Raw = "2606:4700:4700::1111";
|
||||||
|
|
||||||
|
await assert.rejects(pingAsync(ipv6WithBrackets, true, 1, "", true, 56, 1, 1), (err) => {
|
||||||
|
assert.strictEqual(
|
||||||
|
err.message.includes(ipv6WithBrackets),
|
||||||
|
false,
|
||||||
|
"Error message should not contain brackets"
|
||||||
|
);
|
||||||
|
// Allow either the IP in the message (local) OR "Network is unreachable"
|
||||||
|
const containsIP = err.message.includes(ipv6Raw);
|
||||||
|
const isUnreachable =
|
||||||
|
err.message.includes("Network is unreachable") || err.message.includes("Network unreachable");
|
||||||
|
// macOS error when IPv6 stack is missing
|
||||||
|
const isMacOSError = err.message.includes("nodename nor servname provided");
|
||||||
|
assert.ok(
|
||||||
|
containsIP || isUnreachable || isMacOSError,
|
||||||
|
`Ping failed correctly, but error message format was unexpected.\nGot: "${err.message}"\nExpected to contain IP "${ipv6Raw}" OR be a standard network error.`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle standard ASCII domains correctly", async () => {
|
||||||
|
const domain = "invalid-domain.test";
|
||||||
|
await assert.rejects(pingAsync(domain, false, 1, "", true, 56, 1, 1), (err) => {
|
||||||
|
assert.strictEqual(err.message.includes("Parameter string not correctly encoded"), false);
|
||||||
|
assert.ok(
|
||||||
|
err.message.includes(domain),
|
||||||
|
`Error message should contain the domain "${domain}". Got: ${err.message}`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user