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-->
|
||||
|
||||
## 📋 Related issues
|
||||
- Foobar was changed to FooFoo, because ...
|
||||
|
||||
<!--Please link any GitHub issues or tasks that this pull request addresses-->
|
||||
|
||||
- Relates to #issue-number <!--this links related the issue-->
|
||||
- Resolves #issue-number <!--this auto-closes the issue-->
|
||||
|
||||
## 📄 Checklist
|
||||
|
||||
<details>
|
||||
<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
|
||||
- [ ] 🧠 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.
|
||||
- [ ] 🔍 My code adheres to the style guidelines of this project.
|
||||
- [ ] ⚠️ My changes generate no new warnings.
|
||||
- [ ] 🛠️ I have reviewed and tested my code.
|
||||
- [ ] 🔍 Any UI changes adhere to visual style of this project.
|
||||
- [ ] 🛠️ I have self-reviewed and self-tested my code to ensure it works as expected.
|
||||
- [ ] 📝 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.
|
||||
- [ ] 📄 Documentation updates are included (if applicable).
|
||||
- [ ] 🔒 I have considered potential security impacts and mitigated risks.
|
||||
- [ ] 🧰 Dependency updates are listed and explained.
|
||||
- [ ] ⚠️ CI passes and is green.
|
||||
|
||||
</details>
|
||||
|
||||
## 📷 Screenshots or Visual Changes
|
||||
## Screenshots for Visual Changes
|
||||
|
||||
<!--
|
||||
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
|
||||
|
||||
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:
|
||||
[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)
|
||||
Pull requests are awesome.
|
||||
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
|
||||
|
||||
|
||||
@ -284,6 +284,14 @@ class Database {
|
||||
port: dbConfig.port,
|
||||
user: dbConfig.username,
|
||||
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`
|
||||
@ -309,6 +317,14 @@ class Database {
|
||||
}
|
||||
return next();
|
||||
},
|
||||
...(dbConfig.ssl
|
||||
? {
|
||||
ssl: {
|
||||
rejectUnauthorized: true,
|
||||
...(dbConfig.ca && dbConfig.ca.trim() !== "" ? { ca: [dbConfig.ca] } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
pool: mariadbPoolConfig,
|
||||
};
|
||||
|
||||
@ -108,14 +108,49 @@ class NotificationProvider {
|
||||
* @throws {any} The error specified
|
||||
*/
|
||||
throwGeneralAxiosError(error) {
|
||||
let msg = "Error: " + error + " ";
|
||||
let msg = error && error.message ? error.message : String(error);
|
||||
|
||||
if (error.response && error.response.data) {
|
||||
if (typeof error.response.data === "string") {
|
||||
msg += error.response.data;
|
||||
} else {
|
||||
msg += JSON.stringify(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") {
|
||||
msg += " " + error.response.data;
|
||||
} else {
|
||||
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);
|
||||
|
||||
@ -102,6 +102,8 @@ class SetupDatabase {
|
||||
dbConfig.dbName = process.env.UPTIME_KUMA_DB_NAME;
|
||||
dbConfig.username = getEnvOrFile("UPTIME_KUMA_DB_USERNAME");
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -239,6 +241,14 @@ class SetupDatabase {
|
||||
user: dbConfig.username,
|
||||
password: dbConfig.password,
|
||||
database: dbConfig.dbName,
|
||||
...(dbConfig.ssl
|
||||
? {
|
||||
ssl: {
|
||||
rejectUnauthorized: true,
|
||||
...(dbConfig.ca && dbConfig.ca.trim() !== "" ? { ca: [dbConfig.ca] } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
await connection.execute("SELECT 1");
|
||||
connection.end();
|
||||
|
||||
@ -157,6 +157,16 @@ exports.pingAsync = function (
|
||||
deadline = PING_GLOBAL_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) => {
|
||||
ping.promise
|
||||
.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.",
|
||||
"settingUpDatabaseMSG": "Setting up the database. It may take a while, please be patient.",
|
||||
"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",
|
||||
"Dashboard": "Dashboard",
|
||||
"Help": "Help",
|
||||
|
||||
@ -121,6 +121,42 @@
|
||||
<input id="floatingInput" v-model="dbConfig.dbName" type="text" class="form-control" required />
|
||||
<label for="floatingInput">{{ $t("dbName") }}</label>
|
||||
</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>
|
||||
|
||||
<button class="btn btn-primary mt-4 short" type="submit" :disabled="disabledButton">
|
||||
@ -148,6 +184,8 @@ export default {
|
||||
username: "",
|
||||
password: "",
|
||||
dbName: "kuma",
|
||||
ssl: false,
|
||||
ca: "",
|
||||
},
|
||||
info: {
|
||||
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 {
|
||||
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