mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-11 13:36:55 +08:00
Compare commits
171 Commits
node22
...
release-2.
Author | SHA1 | Date | |
---|---|---|---|
|
74448d39e8 | ||
|
049005d2d2 | ||
|
ad444cdbcf | ||
|
325bec42ee | ||
|
95780656df | ||
|
45ab84e1f9 | ||
|
71d0e7c15a | ||
|
5c68c2b5d9 | ||
|
c800638ea1 | ||
|
fb3031edf0 | ||
|
f46379b48a | ||
|
084617ff4d | ||
|
c5c21668a2 | ||
|
eb9fc9a68d | ||
|
937177177e | ||
|
061406d7d1 | ||
|
f563f49bf0 | ||
|
93bcfe76da | ||
|
35dfd5c684 | ||
|
dbec3fcc13 | ||
|
b71c44efa0 | ||
|
b0d1d913bc | ||
|
da8a697b51 | ||
|
bb7c0d9e09 | ||
|
db835a0309 | ||
|
44db422128 | ||
|
30f82b9cb4 | ||
|
10a518e72e | ||
|
a7d1b99719 | ||
|
f50e26edd6 | ||
|
b0610c02ac | ||
|
6f8f8f955f | ||
|
9857770cc7 | ||
|
fce824f5a8 | ||
|
cf44d39e1b | ||
|
230832c382 | ||
|
c7d7fdd632 | ||
|
0fc6998a31 | ||
|
bfd4b84a19 | ||
|
eef742fe93 | ||
|
4307f7f79b | ||
|
1bf98aa114 | ||
|
d03de1fca4 | ||
|
681530dc1a | ||
|
86362a802f | ||
|
de8e42b748 | ||
|
196c9cae57 | ||
|
79ffb6e159 | ||
|
d8418a8949 | ||
|
e496f37b15 | ||
|
457626c715 | ||
|
17f3d5214a | ||
|
d7c76abff7 | ||
|
1dd8c16fb2 | ||
|
f932531320 | ||
|
cbb2ae7139 | ||
|
084efe3d17 | ||
|
db112d9c47 | ||
|
cec6b2e79a | ||
|
a8ccc730b1 | ||
|
5bd3b02a4e | ||
|
24d76489a7 | ||
|
9afac23029 | ||
|
cef81ecc5a | ||
|
1e297e2867 | ||
|
efce9072f6 | ||
|
46b3bd314f | ||
|
427cf2a7ac | ||
|
e5af814ab3 | ||
|
a0ab37554f | ||
|
468f7c2aeb | ||
|
ae202ae272 | ||
|
fa6f717a5e | ||
|
d57e1d315f | ||
|
25b224ed19 | ||
|
0e9020d0e3 | ||
|
8b774da885 | ||
|
d14ccb0a14 | ||
|
09f4ac57f2 | ||
|
5c87b3c78a | ||
|
27041b337f | ||
|
7d0f095bb4 | ||
|
65e6f10820 | ||
|
b45dc6787d | ||
|
7482d5824d | ||
|
44f5a896d0 | ||
|
784ac9ca35 | ||
|
20820f5a5a | ||
|
66908c7055 | ||
|
7a9191761d | ||
|
7dc6191b0a | ||
|
03beef8006 | ||
|
223cde831f | ||
|
be2faf64ce | ||
|
0b76e19401 | ||
|
97cbaac5d9 | ||
|
fd55659c2b | ||
|
20da4d29ab | ||
|
845c6e1189 | ||
|
46f771e9a2 | ||
|
ccede11e1c | ||
|
c872929b8a | ||
|
4d16575599 | ||
|
5bb329fa0e | ||
|
09dedc07fb | ||
|
6cfae01a0d | ||
|
489b73a7a6 | ||
|
1a7a738418 | ||
|
4a25be875e | ||
|
5edd22ffbe | ||
|
ba1031327e | ||
|
de141f1f26 | ||
|
3114db18e9 | ||
|
a6e46c6ed7 | ||
|
71a361c86c | ||
|
e5ed32c9cb | ||
|
340a5c8d5e | ||
|
35f68decb2 | ||
|
1417983b9c | ||
|
9dec2cee26 | ||
|
8631c8da07 | ||
|
6aa09fb76a | ||
|
a89e1735b8 | ||
|
d4e51fe557 | ||
|
7a710b4ab7 | ||
|
1dfbce3961 | ||
|
2f5453017e | ||
|
14df207d55 | ||
|
2ecd7af89b | ||
|
efdffca06c | ||
|
c0fe669cd8 | ||
|
cdb8ad321d | ||
|
4228dd0a29 | ||
|
8a432ac937 | ||
|
778363a948 | ||
|
6899603eb7 | ||
|
13ea190298 | ||
|
a7407a1b65 | ||
|
5bcde56a0f | ||
|
5864c6dd88 | ||
|
595b35fb15 | ||
|
0254e72177 | ||
|
06a272c119 | ||
|
93cf63cb06 | ||
|
03bdaf70ca | ||
|
65663e201b | ||
|
351a48c6a4 | ||
|
88b312c882 | ||
|
186f150cba | ||
|
121b235056 | ||
|
3df37151d8 | ||
|
d6d13936f7 | ||
|
6786466b65 | ||
|
7ab7000253 | ||
|
1a75267138 | ||
|
fea02d0512 | ||
|
c79a3ef794 | ||
|
fed6e4bdb3 | ||
|
e99fbf1ab7 | ||
|
be6e5211e5 | ||
|
5ee986c58e | ||
|
ca094296f2 | ||
|
277d6fe0ce | ||
|
46d8744fa4 | ||
|
7d8dc55dbe | ||
|
459fb138f2 | ||
|
8f950a5145 | ||
|
4d779cfc69 | ||
|
2470451f6d | ||
|
32dc76a085 | ||
|
c6d6061a9f |
@@ -32,7 +32,6 @@ tsconfig.json
|
||||
/extra/healthcheck.exe
|
||||
/extra/healthcheck
|
||||
/extra/exe-builder
|
||||
/extra/push-examples
|
||||
/extra/uptime-kuma-push
|
||||
|
||||
# Comment the following line if you want to rebuild the healthcheck binary
|
||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
2
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
blank_issues_enabled: false
|
17
.github/ISSUE_TEMPLATE/security.md
vendored
17
.github/ISSUE_TEMPLATE/security.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
|
||||
name: "Security Issue"
|
||||
about: "Just for alerting @louislam, do not provide any details here"
|
||||
title: "Security Issue"
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- security
|
||||
|
||||
---
|
||||
|
||||
DO NOT PROVIDE ANY DETAILS HERE. Please privately report to https://github.com/louislam/uptime-kuma/security/advisories/new.
|
||||
|
||||
Why need this issue? It is because GitHub Advisory do not send a notification to @louislam, it is a workaround to do so.
|
||||
|
||||
Your GitHub Advisory URL:
|
45
.github/ISSUE_TEMPLATE/security_issue.yml
vendored
Normal file
45
.github/ISSUE_TEMPLATE/security_issue.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: "🛡️ Security Issue"
|
||||
description: |
|
||||
Notify Louis Lam about a security concern. Please do NOT include any sensitive details in this issue.
|
||||
# title: "Security Issue"
|
||||
labels: [security]
|
||||
assignees: [louislam]
|
||||
body:
|
||||
- type: "markdown"
|
||||
attributes:
|
||||
value: |
|
||||
## **⚠️ Report a Security Vulnerability**
|
||||
|
||||
### **IMPORTANT: DO NOT SHARE VULNERABILITY DETAILS HERE**
|
||||
|
||||
If you have discovered a security vulnerability, please report it securely using the GitHub Security Advisory.
|
||||
|
||||
**Note**: This issue is only for notifying the maintainers of the repository, as the GitHub Security Advisory does not automatically send notifications.
|
||||
|
||||
- **Confidentiality**: The information you provide in the GitHub Security Advisory will initially remain confidential. However, once the vulnerability is addressed, the advisory will be publicly disclosed on GitHub.
|
||||
- **Access and Visibility**: Until the advisory is published, it will only be visible to the maintainers of the repository and invited collaborators.
|
||||
- **Credit**: You will be automatically credited as a contributor for identifying and reporting the vulnerability. Your contribution will be reflected in the MITRE Credit System.
|
||||
- **Important Reminder**: **Do not include any sensitive or detailed vulnerability information in this issue.** This issue is only for sharing the advisory URL to notify the maintainers of the repository, not for discussing the vulnerability itself.
|
||||
|
||||
**Thank you for helping us keep Uptime Kuma secure!**
|
||||
|
||||
## **Step 1: Submit a GitHub Security Advisory**
|
||||
|
||||
Right-click the link below and select `Open link in new tab` to access the page. This will keep the security issue open, allowing you to easily return and paste the Advisory URL here later.
|
||||
|
||||
➡️ [Create a New Security Advisory](https://github.com/louislam/uptime-kuma/security/advisories/new)
|
||||
|
||||
## **Step 2: Share the Advisory URL**
|
||||
|
||||
Once you've created your advisory, please share the URL below. This will notify Louis Lam and enable them to take the appropriate action.
|
||||
|
||||
- type: "textarea"
|
||||
id: github-advisory-url
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "GitHub Advisory URL for @louislam"
|
||||
placeholder: |
|
||||
Please paste the GitHub Advisory URL here. Only the URL is required.
|
||||
Example: https://github.com/louislam/uptime-kuma/security/advisories/GHSA-8h5r-7t6l-q3kz
|
4
.github/workflows/auto-test.yml
vendored
4
.github/workflows/auto-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
||||
node: [ 18, 20, 22 ]
|
||||
node: [ 18, 20 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
|
||||
e2e-test:
|
||||
needs: [ ]
|
||||
runs-on: e2e
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v4
|
||||
|
@@ -1,4 +1,4 @@
|
||||
name: json-yaml-validate
|
||||
name: validate
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -26,7 +26,8 @@ jobs:
|
||||
comment: "true" # enable comment mode
|
||||
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
||||
|
||||
check-lang-json:
|
||||
# General validations
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -34,4 +35,9 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: node ./extra/check-lang-json.js
|
||||
|
||||
- name: Validate language JSON files
|
||||
run: node ./extra/check-lang-json.js
|
||||
|
||||
- name: Validate knex migrations filename
|
||||
run: node ./extra/check-knex-filenames.mjs
|
@@ -62,6 +62,7 @@ Requirements:
|
||||
- Platform
|
||||
- ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc.
|
||||
- ✅ Windows 10 (x64), Windows Server 2012 R2 (x64) or higher
|
||||
- ❌ FreeBSD / OpenBSD / NetBSD
|
||||
- ❌ Replit / Heroku
|
||||
- [Node.js](https://nodejs.org/en/download/) 18 / 20.4
|
||||
- [npm](https://docs.npmjs.com/cli/) 9
|
||||
|
7
db/knex_migrations/2024-10-31-0000-fix-snmp-monitor.js
Normal file
7
db/knex_migrations/2024-10-31-0000-fix-snmp-monitor.js
Normal file
@@ -0,0 +1,7 @@
|
||||
exports.up = function (knex) {
|
||||
return knex("monitor").whereNull("json_path_operator").update("json_path_operator", "==");
|
||||
};
|
||||
exports.down = function (knex) {
|
||||
// changing the json_path_operator back to null for all "==" is not possible anymore
|
||||
// we have lost the context which fields have been set explicitely in >= v2.0 and which would need to be reverted
|
||||
};
|
@@ -0,0 +1,13 @@
|
||||
// Update info_json column to LONGTEXT mainly for MariaDB
|
||||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor_tls_info", function (table) {
|
||||
table.text("info_json", "longtext").alter();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("monitor_tls_info", function (table) {
|
||||
table.text("info_json", "text").alter();
|
||||
});
|
||||
};
|
13
db/knex_migrations/2025-03-25-0127-fix-5721.js
Normal file
13
db/knex_migrations/2025-03-25-0127-fix-5721.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// Fix #5721: Change proxy port column type to integer to support larger port numbers
|
||||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("proxy", function (table) {
|
||||
table.integer("port").alter();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("proxy", function (table) {
|
||||
table.smallint("port").alter();
|
||||
});
|
||||
};
|
@@ -1,5 +1,15 @@
|
||||
# Download Apprise deb package
|
||||
FROM node:20-bookworm-slim AS download-apprise
|
||||
WORKDIR /app
|
||||
COPY ./extra/download-apprise.mjs ./download-apprise.mjs
|
||||
RUN apt update && \
|
||||
apt --yes --no-install-recommends install curl && \
|
||||
npm install cheerio semver && \
|
||||
node ./download-apprise.mjs
|
||||
|
||||
# Base Image (Slim)
|
||||
# If the image changed, the second stage image should be changed too
|
||||
FROM node:22-bookworm-slim AS base2-slim
|
||||
FROM node:20-bookworm-slim AS base2-slim
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!
|
||||
@@ -27,8 +37,9 @@ RUN apt update && \
|
||||
# apprise = for notifications (Install from the deb package, as the stable one is too old) (workaround for #4867)
|
||||
# Switching to testing repo is no longer working, as the testing repo is not bookworm anymore.
|
||||
# python3-paho-mqtt (#4859)
|
||||
RUN curl http://ftp.debian.org/debian/pool/main/a/apprise/apprise_1.8.0-2_all.deb --output apprise.deb && \
|
||||
apt update && \
|
||||
# TODO: no idea how to delete the deb file after installation as it becomes a layer already
|
||||
COPY --from=download-apprise /app/apprise.deb ./apprise.deb
|
||||
RUN apt update && \
|
||||
apt --yes --no-install-recommends install ./apprise.deb python3-paho-mqtt && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
rm -f apprise.deb && \
|
||||
|
@@ -27,7 +27,6 @@ RUN mkdir ./data
|
||||
# ⭐ Main Image
|
||||
############################################
|
||||
FROM $BASE_IMAGE AS release
|
||||
USER node
|
||||
WORKDIR /app
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/louislam/uptime-kuma"
|
||||
@@ -46,6 +45,7 @@ CMD ["node", "server/server.js"]
|
||||
# Rootless Image
|
||||
############################################
|
||||
FROM release AS rootless
|
||||
USER node
|
||||
|
||||
############################################
|
||||
# Mark as Nightly
|
||||
|
@@ -5,7 +5,7 @@ const util = require("../../src/util");
|
||||
|
||||
util.polyfill();
|
||||
|
||||
const version = process.env.VERSION;
|
||||
const version = process.env.RELEASE_BETA_VERSION;
|
||||
|
||||
console.log("Beta Version: " + version);
|
||||
|
||||
|
72
extra/check-knex-filenames.mjs
Normal file
72
extra/check-knex-filenames.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
import fs from "fs";
|
||||
const dir = "./db/knex_migrations";
|
||||
|
||||
// Get the file list (ending with .js) from the directory
|
||||
const files = fs.readdirSync(dir).filter((file) => file !== "README.md");
|
||||
|
||||
// They are wrong, but they had been merged, so allowed.
|
||||
const exceptionList = [
|
||||
"2024-08-24-000-add-cache-bust.js",
|
||||
"2024-10-1315-rabbitmq-monitor.js",
|
||||
];
|
||||
|
||||
// Correct format: YYYY-MM-DD-HHmm-description.js
|
||||
|
||||
for (const file of files) {
|
||||
if (exceptionList.includes(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check ending with .js
|
||||
if (!file.endsWith(".js")) {
|
||||
console.error(`It should end with .js: ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const parts = file.split("-");
|
||||
|
||||
// Should be at least 5 parts
|
||||
if (parts.length < 5) {
|
||||
console.error(`Invalid format: ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// First part should be a year >= 2024
|
||||
const year = parseInt(parts[0], 10);
|
||||
if (isNaN(year) || year < 2023) {
|
||||
console.error(`Invalid year: ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Second part should be a month
|
||||
const month = parseInt(parts[1], 10);
|
||||
if (isNaN(month) || month < 1 || month > 12) {
|
||||
console.error(`Invalid month: ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Third part should be a day
|
||||
const day = parseInt(parts[2], 10);
|
||||
if (isNaN(day) || day < 1 || day > 31) {
|
||||
console.error(`Invalid day: ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Fourth part should be HHmm
|
||||
const time = parts[3];
|
||||
|
||||
// Check length is 4
|
||||
if (time.length !== 4) {
|
||||
console.error(`Invalid time: ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hour = parseInt(time.substring(0, 2), 10);
|
||||
const minute = parseInt(time.substring(2), 10);
|
||||
if (isNaN(hour) || hour < 0 || hour > 23 || isNaN(minute) || minute < 0 || minute > 59) {
|
||||
console.error(`Invalid time: ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("All knex filenames are correct.");
|
@@ -4,8 +4,11 @@
|
||||
// Target file: the latest version of Apprise, which the format is apprise_{VERSION}_all.deb
|
||||
|
||||
import * as cheerio from "cheerio";
|
||||
import semver from "semver";
|
||||
import * as childProcess from "child_process";
|
||||
|
||||
const response = await fetch("http://ftp.debian.org/debian/pool/main/a/apprise/");
|
||||
const baseURL = "http://ftp.debian.org/debian/pool/main/a/apprise/";
|
||||
const response = await fetch(baseURL);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch page of Apprise Debian repository.");
|
||||
@@ -20,13 +23,35 @@ const linkElements = $("a");
|
||||
|
||||
// Filter the links which match apprise_{VERSION}_all.deb
|
||||
const links = [];
|
||||
const pattern = /apprise_(.*?)_all.deb/;
|
||||
|
||||
for (let i = 0; i < linkElements.length; i++) {
|
||||
const link = linkElements[i];
|
||||
if (link.attribs.href.match(/apprise_(.*?)_all.deb/) && !link.attribs.href.includes("~")) {
|
||||
links.push(link.attribs.href);
|
||||
if (link.attribs.href.match(pattern) && !link.attribs.href.includes("~")) {
|
||||
links.push({
|
||||
filename: link.attribs.href,
|
||||
version: link.attribs.href.match(pattern)[1],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(links);
|
||||
// TODO: semver compare and download?
|
||||
|
||||
// semver compare and download
|
||||
let latestLink = {
|
||||
filename: "",
|
||||
version: "0.0.0",
|
||||
};
|
||||
|
||||
for (const link of links) {
|
||||
if (semver.gt(link.version, latestLink.version)) {
|
||||
latestLink = link;
|
||||
}
|
||||
}
|
||||
|
||||
const downloadURL = baseURL + latestLink.filename;
|
||||
console.log(`Downloading ${downloadURL}...`);
|
||||
let result = childProcess.spawnSync("curl", [ downloadURL, "--output", "apprise.deb" ]);
|
||||
console.log(result.stdout?.toString());
|
||||
console.error(result.stderr?.toString());
|
||||
process.exit(result.status !== null ? result.status : 1);
|
||||
|
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const childProcess = require("child_process");
|
||||
let env = process.env;
|
||||
|
||||
let cmd = process.argv[2];
|
||||
let args = process.argv.slice(3);
|
||||
let replacedArgs = [];
|
||||
|
||||
for (let arg of args) {
|
||||
for (let key in env) {
|
||||
arg = arg.replaceAll(`$${key}`, env[key]);
|
||||
}
|
||||
replacedArgs.push(arg);
|
||||
}
|
||||
|
||||
let child = childProcess.spawn(cmd, replacedArgs);
|
||||
child.stdout.pipe(process.stdout);
|
||||
child.stderr.pipe(process.stderr);
|
1
extra/exe-builder/.gitignore
vendored
1
extra/exe-builder/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
packages/
|
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||
</startup>
|
||||
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.Tracing" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.1.1" newVersion="4.1.1.1" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
84
extra/exe-builder/DownloadForm.Designer.cs
generated
84
extra/exe-builder/DownloadForm.Designer.cs
generated
@@ -1,84 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace UptimeKuma {
|
||||
partial class DownloadForm {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DownloadForm));
|
||||
this.progressBar = new System.Windows.Forms.ProgressBar();
|
||||
this.label = new System.Windows.Forms.Label();
|
||||
this.labelData = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// progressBar
|
||||
//
|
||||
this.progressBar.Location = new System.Drawing.Point(12, 12);
|
||||
this.progressBar.Name = "progressBar";
|
||||
this.progressBar.Size = new System.Drawing.Size(472, 41);
|
||||
this.progressBar.TabIndex = 0;
|
||||
//
|
||||
// label
|
||||
//
|
||||
this.label.Location = new System.Drawing.Point(12, 59);
|
||||
this.label.Name = "label";
|
||||
this.label.Size = new System.Drawing.Size(472, 23);
|
||||
this.label.TabIndex = 1;
|
||||
this.label.Text = "Preparing...";
|
||||
//
|
||||
// labelData
|
||||
//
|
||||
this.labelData.Location = new System.Drawing.Point(12, 82);
|
||||
this.labelData.Name = "labelData";
|
||||
this.labelData.Size = new System.Drawing.Size(472, 23);
|
||||
this.labelData.TabIndex = 2;
|
||||
//
|
||||
// DownloadForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(496, 117);
|
||||
this.Controls.Add(this.labelData);
|
||||
this.Controls.Add(this.label);
|
||||
this.Controls.Add(this.progressBar);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MaximizeBox = false;
|
||||
this.Name = "DownloadForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Uptime Kuma";
|
||||
this.Load += new System.EventHandler(this.DownloadForm_Load);
|
||||
this.ResumeLayout(false);
|
||||
}
|
||||
|
||||
private System.Windows.Forms.Label labelData;
|
||||
|
||||
private System.Windows.Forms.Label label;
|
||||
|
||||
private System.Windows.Forms.ProgressBar progressBar;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@@ -1,204 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace UptimeKuma {
|
||||
public partial class DownloadForm : Form {
|
||||
private readonly Queue<DownloadItem> downloadQueue = new();
|
||||
private readonly WebClient webClient = new();
|
||||
private DownloadItem currentDownloadItem;
|
||||
|
||||
public DownloadForm() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void DownloadForm_Load(object sender, EventArgs e) {
|
||||
webClient.DownloadProgressChanged += DownloadProgressChanged;
|
||||
webClient.DownloadFileCompleted += DownloadFileCompleted;
|
||||
|
||||
label.Text = "Reading latest version...";
|
||||
|
||||
// Read json from https://uptime.kuma.pet/version
|
||||
var versionJson = new WebClient().DownloadString("https://uptime.kuma.pet/version");
|
||||
var versionObj = JsonConvert.DeserializeObject<Version>(versionJson);
|
||||
|
||||
var nodeVersion = versionObj.nodejs;
|
||||
var uptimeKumaVersion = versionObj.latest;
|
||||
var hasUpdateFile = File.Exists("update");
|
||||
|
||||
if (!Directory.Exists("node")) {
|
||||
downloadQueue.Enqueue(new DownloadItem {
|
||||
URL = $"https://nodejs.org/dist/v{nodeVersion}/node-v{nodeVersion}-win-x64.zip",
|
||||
Filename = "node.zip",
|
||||
TargetFolder = "node"
|
||||
});
|
||||
}
|
||||
|
||||
if (!Directory.Exists("core") || hasUpdateFile) {
|
||||
|
||||
// It is update, rename the core folder to core.old
|
||||
if (Directory.Exists("core")) {
|
||||
// Remove the old core.old folder
|
||||
if (Directory.Exists("core.old")) {
|
||||
Directory.Delete("core.old", true);
|
||||
}
|
||||
|
||||
Directory.Move("core", "core.old");
|
||||
}
|
||||
|
||||
downloadQueue.Enqueue(new DownloadItem {
|
||||
URL = $"https://github.com/louislam/uptime-kuma/archive/refs/tags/{uptimeKumaVersion}.zip",
|
||||
Filename = "core.zip",
|
||||
TargetFolder = "core"
|
||||
});
|
||||
|
||||
File.WriteAllText("version.json", versionJson);
|
||||
|
||||
// Delete the update file
|
||||
if (hasUpdateFile) {
|
||||
File.Delete("update");
|
||||
}
|
||||
}
|
||||
|
||||
DownloadNextFile();
|
||||
}
|
||||
|
||||
void DownloadNextFile() {
|
||||
if (downloadQueue.Count > 0) {
|
||||
var item = downloadQueue.Dequeue();
|
||||
|
||||
currentDownloadItem = item;
|
||||
|
||||
// Download if the zip file is not existing
|
||||
if (!File.Exists(item.Filename)) {
|
||||
label.Text = item.URL;
|
||||
webClient.DownloadFileAsync(new Uri(item.URL), item.Filename);
|
||||
} else {
|
||||
progressBar.Value = 100;
|
||||
label.Text = "Use local " + item.Filename;
|
||||
DownloadFileCompleted(null, null);
|
||||
}
|
||||
} else {
|
||||
npmSetup();
|
||||
}
|
||||
}
|
||||
|
||||
void npmSetup() {
|
||||
labelData.Text = "";
|
||||
|
||||
var npm = "..\\node\\npm.cmd";
|
||||
var cmd = $"{npm} ci --production & {npm} run download-dist & exit";
|
||||
|
||||
var startInfo = new ProcessStartInfo {
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/k \"{cmd}\"",
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
RedirectStandardInput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = false,
|
||||
WorkingDirectory = "core"
|
||||
};
|
||||
|
||||
var process = new Process();
|
||||
process.StartInfo = startInfo;
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Exited += (_, e) => {
|
||||
progressBar.Value = 100;
|
||||
|
||||
if (process.ExitCode == 0) {
|
||||
Task.Delay(2000).ContinueWith(_ => {
|
||||
Application.Restart();
|
||||
});
|
||||
label.Text = "Done";
|
||||
} else {
|
||||
label.Text = "Failed, exit code: " + process.ExitCode;
|
||||
}
|
||||
|
||||
};
|
||||
process.Start();
|
||||
label.Text = "Installing dependencies and download dist files";
|
||||
progressBar.Value = 50;
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) {
|
||||
progressBar.Value = e.ProgressPercentage;
|
||||
var total = e.TotalBytesToReceive / 1024;
|
||||
var current = e.BytesReceived / 1024;
|
||||
|
||||
if (total > 0) {
|
||||
labelData.Text = $"{current}KB/{total}KB";
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) {
|
||||
Extract(currentDownloadItem);
|
||||
DownloadNextFile();
|
||||
}
|
||||
|
||||
void Extract(DownloadItem item) {
|
||||
if (Directory.Exists(item.TargetFolder)) {
|
||||
var dir = new DirectoryInfo(item.TargetFolder);
|
||||
dir.Delete(true);
|
||||
}
|
||||
|
||||
if (Directory.Exists("temp")) {
|
||||
var dir = new DirectoryInfo("temp");
|
||||
dir.Delete(true);
|
||||
}
|
||||
|
||||
labelData.Text = $"Extracting {item.Filename}...";
|
||||
|
||||
ZipFile.ExtractToDirectory(item.Filename, "temp");
|
||||
|
||||
string[] dirList;
|
||||
|
||||
// Move to the correct level
|
||||
dirList = Directory.GetDirectories("temp");
|
||||
|
||||
|
||||
|
||||
if (dirList.Length > 0) {
|
||||
var dir = dirList[0];
|
||||
|
||||
// As sometime ExtractToDirectory is still locking the directory, loop until ok
|
||||
while (true) {
|
||||
try {
|
||||
Directory.Move(dir, item.TargetFolder);
|
||||
break;
|
||||
} catch (Exception exception) {
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
MessageBox.Show("Unexcepted Error: Cannot move extracted files, folder not found.");
|
||||
}
|
||||
|
||||
labelData.Text = $"Extracted";
|
||||
|
||||
if (Directory.Exists("temp")) {
|
||||
var dir = new DirectoryInfo("temp");
|
||||
dir.Delete(true);
|
||||
}
|
||||
|
||||
File.Delete(item.Filename);
|
||||
}
|
||||
}
|
||||
|
||||
public class DownloadItem {
|
||||
public string URL { get; set; }
|
||||
public string Filename { get; set; }
|
||||
public string TargetFolder { get; set; }
|
||||
}
|
||||
}
|
||||
|
@@ -1,377 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
|
||||
AABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA////BPT09Bfu7u4e8fHxJPPz8yv19fUy9fX1M/Pz8yvx8fEk9vb2HPPz8xXMzMwFAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//
|
||||
/wHv7+8f7u7uPPPz81Tx8fFs8fHxgPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGB8fHxcfHx8V3x8fFI9PT0MOvr6w0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AADy8vIU8fHxS/Dw8Hbx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fFr9PT0R/Dw8CIAAAABAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA8vLyFPHx8Vnx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fFs9fX1Mb+/vwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAICAgALy8vI88fHxfvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy8nby8vI8gICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAzMzMBfHx8Vrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyYf///wwAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8vLyYPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8W/z8/MWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+9R8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLw8PB26urqDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLy8ijx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu7w7Ifj79ud2u7PtNLrw83P677dzeu85c3r
|
||||
u+rM67rwzOu68c7rverQ68Dj0uvD3NbuyM3b7c+64u7apujv5ZPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxXgAAAAEAAAAAAAAAAAAAAAAAAAAA4+PjCfDw
|
||||
8Hfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLd7tSmzeu92MbqsvvG6bH/xumy/8fq
|
||||
s//H6rP/yOq0/8jqtf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//Q7MDx1u7Kz9/t
|
||||
163s8OuJ8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu/v7y8AAAAAAAAAAAAA
|
||||
AAAAAAAA7u7uPfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC5PDdl8jqtuTE6a7/xOmv/8Xp
|
||||
sP/G6bH/xumx/8bpsv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zr
|
||||
u//N67v/zey8/87svf/P67742e3Mx+jv5ZLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw
|
||||
8HWAgIACAAAAAAAAAACqqqoD8vLyc/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLf7degxOiu+cPo
|
||||
rf/D6a7/xOmu/8Xpr//F6bD/xumx/8bpsf/G6bL/x+qz/8fqs//I6rT/yOq1/8nqtv/J6rb/yuu3/8rr
|
||||
uP/L67j/y+u5/8zruv/M67v/zeu7/83svP/O7L3/zuy9/87svfzc7tK28fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fEkAAAAAAAAAADz8/Mq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgunv
|
||||
5o3D6a/0wuis/8Lorf/D6K3/xOmu/8Tprv/F6a//xemw/8bpsf/G6bH/xumy/8fqs//H6rP/yOq0/8jq
|
||||
tf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/87svf/O7L3/3e/TtPHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAADy8vJM8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgszqutDB6Kv/weir/8LorP/D6K3/w+it/8Tprv/E6a7/xemv/8XpsP/G6bH/xumx/8bp
|
||||
sv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zru//N67v/zey8/87s
|
||||
vf/O7L3/zuy++u3w6Yzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJ1AAAAAAAAAADx8fFr8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC6O/kjsDoqvzA6Kr/weir/8Loq//C6Kz/w+it/8Porf/E6a7/xOmu/8Xp
|
||||
r//F6bD/xumx/8bpsf/G6bL/x+qz/8fqtP/I6rT/yOq1/8nqtv/J6rb/yuu3/8rruP/L67n/y+u5/8zr
|
||||
uv/M67v/zeu7/83svP/O7L3/zuy9/93u07Xx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC////Bv//
|
||||
/wfx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1ezJsr/nqf/A56n/weiq/8Hoq//C6Kv/wuis/8Po
|
||||
rf/D6K3/xOmu/8Pprv+856T/uOed/7bmmv+05Zf/teWZ/7jnnf+86KP/wOio/8fqs//J6rb/yeq2/8rr
|
||||
t//K67j/y+u5/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/9buyNLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8vLyE/Ly8hPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCy+q6zr/nqP/A56n/wOep/8Ho
|
||||
qv/B6Kv/wuir/8LorP+u5Y//neF2/5bgav+V4Gr/luBr/5fhbP+Y4W7/meFv/5rhcf+b4nL/nOJ0/53i
|
||||
dv+j5H//reaM/7nnnf/E6q//y+y4/8vruf/L67n/zOu6/8zru//N67v/zey8/9Lsxd/x8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC7+/vIPb29hzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/n
|
||||
qP+/56j/wOep/8Dnqf/B6Kr/weir/7nmn/+R32T/kt9l/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nh
|
||||
b/+a4XH/m+Jy/5zidP+d4nX/nuN3/5/jeP+f4nn/weqq/8rruP/L67n/y+u5/8zruv/M67v/zeu7/9Ls
|
||||
w+Lx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwI/Hx8SXx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGCxeix5L/nqP+/56j/v+eo/8Dnqf/A56n/weiq/7Pllv+Q3mP/kd9k/5LfZf+T32f/lOBo/5Xg
|
||||
av+W4Gv/l+Ft/5jhbv+Z4W//muFx/5vicv+c4nT/neJ1/57jd/+f43j/xOmu/8rrt//K67j/y+u5/8vr
|
||||
uf/M67r/zOu7/9Tsxtfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC9PT0GO/v7yDx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGCx+m037/nqP+/56j/v+eo/7/nqP/A56n/wOip/7TmmP+P3mH/kN5j/5Hf
|
||||
ZP+S32b/k99n/5TgaP+V4Gr/luBr/5fhbf+Y4W7/meFw/5rhcf+b4nL/nOJ0/53idf+h5Hz/yuu2/8nq
|
||||
t//K67f/yuu4/8vruf/L67n/zOu6/9ftysrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7e3tDvT0
|
||||
9Bfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCyOq117/nqP+/56j/v+eo/7/nqP+/56j/wOep/7vn
|
||||
of+O3mD/j95h/5DeY/+R32T/kt9m/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nhcP+a4nH/m+Jy/5zi
|
||||
dP+r5Yr/yOq1/8nqtv/J6rf/yuu3/8rruP/L67n/y+u5/9zu1LHx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLz8/OA////A+7u7g/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCz+q+xb/nqP+/56j/v+eo/7/n
|
||||
qP+/56j/v+eo/8Dnqf+S4Gb/jt5g/4/eYf+Q3mP/kd9k/5LfZv+T32f/lOBo/5Xgav+W4Gv/l+Ft/5jh
|
||||
bv+Z4XD/muJx/5vic/+4553/yOq0/8jqtf/J6rb/yeq3/8rrt//K67j/y+u5/+bw4Zfx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fFrAAAAAP///wHz8/N88fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1+zMrr/n
|
||||
qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+f4Xn/jd5f/47eYP+P3mH/kN5j/5HfZP+S32b/k99n/5Tg
|
||||
af+V4Gr/luBr/5fhbf+Y4W7/meFw/5vic//F6rD/x+q0/8jqtP/I6rX/yeq2/8nqt//K67f/zOu88u/x
|
||||
74Px8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLv7+9QAAAAAAAAAADw8PBm8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC5e7gk7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//jN1d/43eX/+O3mD/j95h/5De
|
||||
Y/+R32T/kt9m/5PfZ/+U4Gn/leBq/5bga/+X4W3/mOFu/6rliP/G6rL/x+qz/8fqtP/I6rT/yOq1/8nq
|
||||
tv/J6rf/1OzGy/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YL19fUzAAAAAAAAAADy8vJO8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgsPoru2/56j/v+eo/7/nqP+/56j/v+eo/7/nqP++6Kf/j95i/4zd
|
||||
Xf+N3l//jt5g/4/eYv+Q3mP/kd9k/5LfZv+T32f/lOBp/5Xgav+W4Gz/l+Ft/7voov/G6bL/xuqy/8fq
|
||||
s//H6rT/yOq1/8jqtf/J6rb/4e/Zo/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PARAAAAAAAA
|
||||
AADu7u4u8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgszpvMm/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||
qP+/56j/q+SL/4vdXP+M3V3/jd5f/47eYP+P3mL/kN9j/5HfZP+S32b/k99n/5Tgaf+V4Gr/qOOH/8Xp
|
||||
sP/G6bH/xumy/8bqsv/H6rP/x+q0/8jqtf/K67jy8PHwhPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8WoAAAAAAAAAAAAAAADo6OgL8fHxgfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguDv2J2/56j/v+eo/7/n
|
||||
qP+/56j/v+eo/7/nqP+/56j/v+eo/6Xjgv+L3Vz/jN1d/43eX/+O3mD/j95i/5DfY/+R32T/kt9m/5Pf
|
||||
Z/+k44D/xOmu/8XpsP/F6bD/xumx/8bpsv/G6rL/x+qz/8fqtP/W7cnB8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvPz80AAAAAAAAAAAAAAAAAAAAAA8PDwZ/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLD6K/rv+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//kt5n/4zdXf+N3l//jt5g/4/e
|
||||
Yv+Q32P/luFs/67kj//D6K3/xOmu/8Tpr//F6bD/xemw/8bpsf/G6bL/xuqy/8fqtP7o7+WR8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xYAAAAAAAAAAAAAAAAAAAAA8vLyPPHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLV7ci0v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOio/7Xl
|
||||
mv+u5I7/rOSM/67kj/+35pz/wumr/8Lorf/D6K3/w+it/8Tprv/E6a//xemw/8XpsP/G6bH/xumy/9Ds
|
||||
wNPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyZQAAAAAAAAAAAAAAAAAAAAAAAAAA////DPHx
|
||||
8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||
qP+/56j/v+eo/7/nqP+/56j/wOep/8Doqv/B6Kr/weir/8LorP/C6K3/w+it/8Porv/E6a7/xOmv/8Xp
|
||||
sP/F6bD/yOq18uvw6Yvx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7+/vMQAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAPHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6O/ij8LorPG/56j/v+eo/7/n
|
||||
qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weiq/8Hoq//C6Kz/wuit/8Po
|
||||
rf/D6K7/xOmu/8Tpr//F6bH74u/anvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PB6////BQAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPz8yrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguHu
|
||||
2pnB56v2v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/wOiq/8Ho
|
||||
q//B6Kv/wuis/8Lorf/D6K3/w+mu/8Tprv3b7dKq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fFJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHy8vJf8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLi7tyXwumt8L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||
qP+/56j/wOep/8Doqv/B6Kv/weir/8LorP/C6K3/xOiv+d7u1aTx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvLy8nb///8KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+8Q8/Pze/Hx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6/Dpiszqu82/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||
qP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weir/8Hoq//H6bTj5e7elfHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8yoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA9fX1MvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLe7tShx+mz3r/n
|
||||
qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/xumy5drtz6rv8e+D8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyTgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPHx8Unx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgubv45DU68e2y+q6z8XoseTD6a7uweir9MPpru7F6bHly+q50tLsxLrl796U8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJh////AwAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wHx8fFZ8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8Wzf398IAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8D8/PzVfHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwZujo
|
||||
6AsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA////AfHx8Ujx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fFa////BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/Mp8vLydvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8/PzfPHx8TcAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CvLy8lDz8/N/8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvPz84Hx8fFa8PDwEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AADw8PAR8vLyTvHx8X3x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fF/8/PzVvT09BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wXz8/Mq8/PzU/Hx8XDx8fGB8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLy8vJz8fHxWO/v7y////8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8G7e3tHfLy
|
||||
8ifu7u4u8PDwNPT09C/y8vIo7+/vH+Pj4wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAP///////wAA////////AAD///////8AAP//gAf//wAA//gAAD//AAD/wAAAB/8AAP+A
|
||||
AAAB/wAA/gAAAAB/AAD8AAAAAD8AAPgAAAAAHwAA8AAAAAAPAADwAAAAAAcAAOAAAAAABwAA4AAAAAAD
|
||||
AADAAAAAAAMAAMAAAAAAAwAAwAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA
|
||||
AAAAAQAAgAAAAAABAACAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAABwAAwAAAAAAH
|
||||
AADgAAAAAAcAAOAAAAAADwAA4AAAAAAPAADwAAAAAB8AAPAAAAAAHwAA+AAAAAA/AAD8AAAAAD8AAPwA
|
||||
AAAAfwAA/gAAAAD/AAD/AAAAAf8AAP+AAAAD/wAA/8AAAAf/AAD/8AAAH/8AAP/8AAA//wAA//8AAf//
|
||||
AAD//+AP//8AAP///////wAA////////AAD///////8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAgICAAu/v7xD09PQX7u7uHvDw8CP29vYb8vLyFOrq6gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICA
|
||||
gALy8vIm7+/vT/Pz82fz8/N98fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw8Hrw8PBm7+/vUPT0
|
||||
9C3o6OgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOPj
|
||||
4wnz8/NC8vLydPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YHy8vJj8/PzKoCAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AADx8fEl8vLydfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxcfHx8SUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA9PT0LfHx8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8/PzgPLy8j0AAAABAAAAAAAA
|
||||
AAAAAAAAAAAAAO3t7Rzx8fGA8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLr8OmM5O7emeTv
|
||||
3Z7h79mj5fDem+nv45Tu8u6H8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy
|
||||
8joAAAAAAAAAAAAAAAD///8E8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC7vDshtns0K7N67zayeq288fq
|
||||
s//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/P7L7w0+zF29vv0Lrn8OKX8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8/PzfvPz8xUAAAAAAAAAAPX19TLx8fGC8fHxgvHx8YLx8fGC8fHxgt3u1KXF6rHzxOmv/8Xp
|
||||
sP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/M67v/zey8/87svf/S7MPj4u7Zp/Hx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8/PzVQAAAAAAAAAA8fHxavHx8YLx8fGC8fHxgvHx8YLf7defwuis/cPo
|
||||
rf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruv/M67v/zey8/87s
|
||||
vf/N67z/3e7SufHx8YLx8fGC8fHxgvHx8YLz8/N8////Bf///w3x8fGC8fHxgvHx8YLx8fGC8fHxgsXp
|
||||
sOnB6Kv/wuis/8Porf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vr
|
||||
uv/M67v/zey8/87svf/O67z96/Hoj/Hx8YLx8fGC8fHxgvHx8YLy8vIm8/PzK/Hx8YLx8fGC8fHxgvHx
|
||||
8YLg79icwOep/8Hoqv/B6Kv/wuis/8Porf/E6a7/wuit/73opP+76KL/u+eh/77opv/D6a3/yeu1/8nq
|
||||
tv/K67f/y+u5/8zruv/M67v/zey8/87svf/d7tSz8fHxgvHx8YLx8fGC8fHxgvHx8Tby8vI68fHxgvHx
|
||||
8YLx8fGC8fHxgtTrxre/56j/wOep/8Hoqv/B6Kv/uOad/53idv+V4Gn/leBq/5fhbP+Y4W//muFx/5vi
|
||||
c/+e4Xb/puWD/7PmlP/D6a3/y+u5/8zruv/M67v/zey8/9rtzsHx8fGC8fHxgvHx8YLx8fGC8/PzQfPz
|
||||
80Lx8fGC8fHxgvHx8YLx8fGC0OvAwr/nqP+/56j/wOep/8Hoqv+o44b/kd9k/5LfZv+U4Gj/leBq/5fh
|
||||
bf+Y4W//muFx/5vic/+d4nX/n+N3/7fnm//K67j/y+u5/8zruv/M67v/2u3QvPHx8YLx8fGC8fHxgvHx
|
||||
8YLy8vI98/PzP/Hx8YLx8fGC8fHxgvHx8YLQ6sK/v+eo/7/nqP+/56j/wOep/6jjhv+P3mL/kd9k/5Lf
|
||||
Zv+U4Gj/leBr/5fhbf+Y4W//muFx/5zic/+d4nX/v+mm/8nqt//K67j/y+u5/8zruv/f79au8fHxgvHx
|
||||
8YLx8fGC8fHxgvX19TLx8fE38fHxgvHx8YLx8fGC8fHxgtTrybO/56j/v+eo/7/nqP+/56j/sOSS/47e
|
||||
YP+P3mL/kd9k/5LfZv+U4Gj/leBr/5fhbf+Z4W//muJx/5/jd//H6bP/yeq2/8nqt//K67j/y+u5/+nv
|
||||
45Tx8fGC8fHxgvHx8YLx8fGC7+/vIPHx8SXx8fGC8fHxgvHx8YLx8fGC4e/Zm7/nqP+/56j/v+eo/7/n
|
||||
qP+956X/jt5h/47eYP+P3mL/kd9k/5LfZv+U4Gn/luBr/5fhbf+Z4W//q+aK/8fqs//I6rT/yeq2/8nq
|
||||
t//N7Lvw8fHxgvHx8YLx8fGC8fHxgvPz84D///8G6+vrDfHx8YLx8fGC8fHxgvHx8YLv8e+Dweis87/n
|
||||
qP+/56j/v+eo/7/nqP+d4XX/jN1e/47eYP+P3mL/kd9k/5PfZ/+U4Gn/luBr/5fhbf+86KP/xuqy/8fq
|
||||
s//I6rX/yeq2/9Tsx8nx8fGC8fHxgvHx8YLx8fGC8PDwaAAAAAAAAAAA8fHxbPHx8YLx8fGC8fHxgvHx
|
||||
8YLM6rrMv+eo/7/nqP+/56j/v+eo/7blmv+N3V//jN1e/47eYP+Q3mL/kd9k/5PfZ/+U4Gn/qeSH/8Xp
|
||||
sP/G6bH/xuqy/8fqs//I6rX/5fDem/Hx8YLx8fGC8fHxgvHx8YLz8/M/AAAAAAAAAADz8/NB8fHxgvHx
|
||||
8YLx8fGC8fHxgt3s06O/56j/v+eo/7/nqP+/56j/v+eo/7Xmmf+U32n/jN1e/47eYP+Q3mL/k99o/6zk
|
||||
i//D6a7/xemv/8XpsP/G6bH/xuqy/8vqu+jx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xUAAAAAAAAAAPT0
|
||||
9Bfx8fGC8fHxgvHx8YLx8fGC8fHvg8Tpsee/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+35pz/suWV/7Xm
|
||||
mf/A6Kj/wuit/8Porf/E6a7/xemv/8XpsP/G6bH/3e3UqvHx8YLx8fGC8fHxgvHx8YLw8PBmAAAAAAAA
|
||||
AAAAAAAAAAAAAPHx8W7x8fGC8fHxgvHx8YLx8fGC4u7cmMHnqvm/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||
qP+/56j/wOep/8Hoqv/C6Kz/wuit/8Porf/E6a7/xemv/9Hrwszx8fGC8fHxgvHx8YLx8fGC8fHxgvX1
|
||||
9TEAAAAAAAAAAAAAAAAAAAAA7u7uO/Hx8YLx8fGC8fHxgvHx8YLx8fGC3e7SpMHoqfq/56j/v+eo/7/n
|
||||
qP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz/wuit/8Porf/O67zV8PHwhPHx8YLx8fGC8fHxgvHx
|
||||
8YLy8vJ2////BQAAAAAAAAAAAAAAAAAAAACqqqoD8PDwafHx8YLx8fGC8fHxgvHx8YLx8fGC4O/YnMTo
|
||||
ruy/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz90uvEwe/x74Px8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvPz8ykAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/MW8fHxfPHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8PLuhdXtyLXF6bHlv+eo/7/nqP+/56j/v+eo/7/nqP/B6Kv0zeq8zOXv4JTx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADy8vIm8fHxgPHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLs8OmJ4e/Zm93u06Pf7def5+/hkvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxXf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AADy8vIo8/PzffHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8VnMzMwFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAD29vYb8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz83/v7+9BgICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8/PzQPLy8nnx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz84Hx8fFc9PT0GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////B/X19TLx8fFc8PDwevHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgPHx8Wv09PRE9PT0FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA7+/vEPb29hvw8PAj7+/vH/T09Be/v78EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////8B///wAA//wAAD/wAAAP4AAAB+AA
|
||||
AAfAAAADwAAAA4AAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAADwAAAA8AAAAPAAAAH4AAAB+AA
|
||||
AA/wAAAP+AAAH/gAAD/+AAB//wAB///AA///+B////////////8oAAAAEAAAACAAAAABACAAAAAAAAAE
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CfDw8BH///8GAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAAu7u7i7x8fFe8PDwevHx8YLx8fGC8fHxgvDw
|
||||
8Hvx8fFs7+/vT/Dw8CMAAAABAAAAAAAAAAAAAAAA5ubmCvLy8l/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||
8YLx8fGC8fHxgvHx8YLx8fGC8/PzZu7u7g8AAAAAAAAAAPHx8V3x8fGC8fHxgunv5o7Z7c200+vFytTs
|
||||
xc7W7cnH2+7QueLu2qbu8OyH8fHxgvHx8YLx8fFu////BfHx8STx8fGC8fHxgtrtzq3D6a/8xemw/8bp
|
||||
sv/I6rT/yeq2/8vruP/M67v/z+u++Nzu0bjx8fGC8fHxgu/v7zDx8fFI8fHxguzw6ojC56z3wuis/8Tp
|
||||
rv/E6q3/weiq/8fqsv/J6rb/y+u5/8zru//N67z/6/HpjfHx8YLy8vJN8fHxXPHx8YLg79icv+eo/8Ho
|
||||
qv+k4n//lOBo/5fhbf+a4XH/n+J5/7Pmlv/L67n/zOu7/+Xw353x8fGC8fHxXvHx8Vrx8fGC4O3Zm7/n
|
||||
qP+/56j/nuF3/5HfZP+U4Gj/l+Ft/5ricf+x5pL/yeq3/8vruf/r8emN8fHxgu/v70/x8fFK8fHxguzw
|
||||
6ojA6Kn8v+eo/6njiP+O3mD/kd9k/5Tgaf+X4W3/vuim/8jqtP/N67zr8fHxgvHx8YLy8vI68/PzK/Hx
|
||||
8YLx8fGCx+m03L/nqP++6Kb/meBw/47eYP+S32X/q+SL/8XpsP/G6rL/1+zLvvHx8YLz8/OB8PDwEdXV
|
||||
1Qbx8fF98fHxgt/t1Z/A56j9v+eo/7/nqP+656H/vuim/8Lorf/E6a7/yOq18Ovw6Yvx8fGC8vLyYwAA
|
||||
AAAAAAAA8fHxR/Hx8YLx8fGC2O3NrMDnqfq/56j/v+eo/7/nqP/B6Kv/xumy7OTu3Zfx8fGC8/PzgfLy
|
||||
8icAAAAAAAAAAP///wPz8/Nm8fHxgvHx8YLo7+SO0+zFuczquszM6bzJ1+zMru7w7Ibx8fGC8fHxgvHx
|
||||
8UcAAAAAAAAAAAAAAAAAAAAA4+PjCfHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgfPz
|
||||
80D///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8/PzK/Ly8mDz8/N+8fHxgvHx8YLy8vJ68vLyUezs
|
||||
7BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAevr6w3j4+MJAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD8fwAA4AcAAMADAACAAQAAgAEAAIABAACAAQAAgAEAAIAB
|
||||
AADAAwAAwAMAAOAHAADwDwAA/n8AAP//AAA=
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
@@ -1,3 +0,0 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura DisableCompression='true' IncludeDebugSymbols='false' />
|
||||
</Weavers>
|
@@ -1,141 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCompression" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCleanup" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
@@ -1,262 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using UptimeKuma.Properties;
|
||||
|
||||
namespace UptimeKuma {
|
||||
static class Program {
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main(string[] args) {
|
||||
var cwd = Path.GetDirectoryName(Application.ExecutablePath);
|
||||
|
||||
if (cwd != null) {
|
||||
Environment.CurrentDirectory = cwd;
|
||||
}
|
||||
|
||||
bool isIntranet = args.Contains("--intranet");
|
||||
|
||||
if (isIntranet) {
|
||||
Console.WriteLine("The --intranet argument was provided, so we will not try to access the internet. The first time this application runs you'll need to run it without the --intranet param or copy the result from another machine to the intranet server.");
|
||||
}
|
||||
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new UptimeKumaApplicationContext(isIntranet));
|
||||
}
|
||||
}
|
||||
|
||||
public class UptimeKumaApplicationContext : ApplicationContext
|
||||
{
|
||||
private static Mutex mutex = null;
|
||||
|
||||
const string appName = "Uptime Kuma";
|
||||
|
||||
private NotifyIcon trayIcon;
|
||||
private Process process;
|
||||
|
||||
private MenuItem statusMenuItem;
|
||||
private MenuItem runWhenStarts;
|
||||
private MenuItem openMenuItem;
|
||||
|
||||
private RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
|
||||
|
||||
private readonly bool intranetOnly;
|
||||
|
||||
public UptimeKumaApplicationContext(bool intranetOnly) {
|
||||
|
||||
// Single instance only
|
||||
bool createdNew;
|
||||
mutex = new Mutex(true, appName, out createdNew);
|
||||
if (!createdNew) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.intranetOnly = intranetOnly;
|
||||
|
||||
var startingText = "Starting server...";
|
||||
trayIcon = new NotifyIcon();
|
||||
trayIcon.Text = startingText;
|
||||
|
||||
runWhenStarts = new MenuItem("Run when system starts", RunWhenStarts);
|
||||
runWhenStarts.Checked = registryKey.GetValue(appName) != null;
|
||||
|
||||
statusMenuItem = new MenuItem(startingText);
|
||||
statusMenuItem.Enabled = false;
|
||||
|
||||
openMenuItem = new MenuItem("Open", Open);
|
||||
openMenuItem.Enabled = false;
|
||||
|
||||
trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
|
||||
trayIcon.ContextMenu = new ContextMenu(new MenuItem[] {
|
||||
statusMenuItem,
|
||||
openMenuItem,
|
||||
//new("Debug Console", DebugConsole),
|
||||
runWhenStarts,
|
||||
new("Check for Update...", CheckForUpdate),
|
||||
new("Visit GitHub...", VisitGitHub),
|
||||
new("About", About),
|
||||
new("Exit", Exit),
|
||||
});
|
||||
|
||||
trayIcon.MouseDoubleClick += new MouseEventHandler(Open);
|
||||
trayIcon.Visible = true;
|
||||
|
||||
var hasUpdateFile = File.Exists("update");
|
||||
|
||||
if (!hasUpdateFile && Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules") && Directory.Exists("core/dist")) {
|
||||
// Go go go
|
||||
StartProcess();
|
||||
} else {
|
||||
DownloadFiles();
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadFiles() {
|
||||
if (intranetOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
var form = new DownloadForm();
|
||||
form.Closed += Exit;
|
||||
form.Show();
|
||||
}
|
||||
|
||||
private void RunWhenStarts(object sender, EventArgs e) {
|
||||
if (registryKey == null) {
|
||||
MessageBox.Show("Error: Unable to set startup registry key.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (runWhenStarts.Checked) {
|
||||
registryKey.DeleteValue(appName, false);
|
||||
runWhenStarts.Checked = false;
|
||||
} else {
|
||||
registryKey.SetValue(appName, Application.ExecutablePath);
|
||||
runWhenStarts.Checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
void StartProcess() {
|
||||
var startInfo = new ProcessStartInfo {
|
||||
FileName = "node/node.exe",
|
||||
Arguments = "server/server.js --data-dir=\"../data/\"",
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = "core"
|
||||
};
|
||||
|
||||
process = new Process();
|
||||
process.StartInfo = startInfo;
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Exited += ProcessExited;
|
||||
|
||||
try {
|
||||
process.Start();
|
||||
//Open(null, null);
|
||||
|
||||
// Async task to check if the server is ready
|
||||
Task.Run(() => {
|
||||
var runningText = "Server is running";
|
||||
using TcpClient tcpClient = new TcpClient();
|
||||
while (true) {
|
||||
try {
|
||||
tcpClient.Connect("127.0.0.1", 3001);
|
||||
statusMenuItem.Text = runningText;
|
||||
openMenuItem.Enabled = true;
|
||||
trayIcon.Text = runningText;
|
||||
break;
|
||||
} catch (Exception) {
|
||||
System.Threading.Thread.Sleep(2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error");
|
||||
}
|
||||
}
|
||||
|
||||
void StopProcess() {
|
||||
process?.Kill();
|
||||
}
|
||||
|
||||
void Open(object sender, EventArgs e) {
|
||||
Process.Start("http://localhost:3001");
|
||||
}
|
||||
|
||||
void DebugConsole(object sender, EventArgs e) {
|
||||
|
||||
}
|
||||
|
||||
void CheckForUpdate(object sender, EventArgs e) {
|
||||
if (intranetOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check version.json exists
|
||||
if (File.Exists("version.json")) {
|
||||
// Load version.json and compare with the latest version from GitHub
|
||||
var currentVersionObj = JsonConvert.DeserializeObject<Version>(File.ReadAllText("version.json"));
|
||||
|
||||
var versionJson = new WebClient().DownloadString("https://uptime.kuma.pet/version");
|
||||
var latestVersionObj = JsonConvert.DeserializeObject<Version>(versionJson);
|
||||
|
||||
// Compare version, if the latest version is newer, then update
|
||||
if (new System.Version(latestVersionObj.latest).CompareTo(new System.Version(currentVersionObj.latest)) > 0) {
|
||||
var result = MessageBox.Show("A new version is available. Do you want to update?", "Update", MessageBoxButtons.YesNo);
|
||||
if (result == DialogResult.Yes) {
|
||||
// Create a empty file `update`, so the app will download the core files again at startup
|
||||
File.Create("update").Close();
|
||||
|
||||
trayIcon.Visible = false;
|
||||
process?.Kill();
|
||||
|
||||
// Restart the app, it will download the core files again at startup
|
||||
Application.Restart();
|
||||
}
|
||||
} else {
|
||||
MessageBox.Show("You are using the latest version.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void VisitGitHub(object sender, EventArgs e) {
|
||||
if (intranetOnly) {
|
||||
MessageBox.Show("You have parsed in --intranet so we will not try to access the internet or visit github.com, please go to https://github.com/louislam/uptime-kuma if you want to visit github.");
|
||||
return;
|
||||
}
|
||||
|
||||
Process.Start("https://github.com/louislam/uptime-kuma");
|
||||
}
|
||||
|
||||
void About(object sender, EventArgs e)
|
||||
{
|
||||
MessageBox.Show("Uptime Kuma Windows Runtime v1.0.0" + Environment.NewLine + "© 2023 Louis Lam", "Info");
|
||||
}
|
||||
|
||||
void Exit(object sender, EventArgs e)
|
||||
{
|
||||
// Hide tray icon, otherwise it will remain shown until user mouses over it
|
||||
trayIcon.Visible = false;
|
||||
process?.Kill();
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
void ProcessExited(object sender, EventArgs e) {
|
||||
|
||||
if (process.ExitCode != 0) {
|
||||
var line = "";
|
||||
while (!process.StandardOutput.EndOfStream)
|
||||
{
|
||||
line += process.StandardOutput.ReadLine();
|
||||
}
|
||||
|
||||
MessageBox.Show("Uptime Kuma exited unexpectedly. Exit code: " + process.ExitCode + " " + line);
|
||||
}
|
||||
|
||||
trayIcon.Visible = false;
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Uptime Kuma")]
|
||||
[assembly: AssemblyDescription("A portable executable for running Uptime Kuma")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Uptime Kuma")]
|
||||
[assembly: AssemblyProduct("Uptime Kuma")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2023 Louis Lam")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("86B40AFB-61FC-433D-8C31-650B0F32EA8F")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.2.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.2.0")]
|
62
extra/exe-builder/Properties/Resources.Designer.cs
generated
62
extra/exe-builder/Properties/Resources.Designer.cs
generated
@@ -1,62 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace UptimeKuma.Properties {
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder",
|
||||
"4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance",
|
||||
"CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
|
||||
.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if ((resourceMan == null)) {
|
||||
global::System.Resources.ResourceManager temp =
|
||||
new global::System.Resources.ResourceManager("UptimeKuma.Properties.Resources",
|
||||
typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
|
||||
.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get { return resourceCulture; }
|
||||
set { resourceCulture = value; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,117 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
23
extra/exe-builder/Properties/Settings.Designer.cs
generated
23
extra/exe-builder/Properties/Settings.Designer.cs
generated
@@ -1,23 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace UptimeKuma.Properties {
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(
|
||||
"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
private static Settings defaultInstance =
|
||||
((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get { return defaultInstance; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
@@ -1,203 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>UptimeKuma</RootNamespace>
|
||||
<AssemblyName>uptime-kuma</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<ApplicationIcon>..\..\public\favicon.ico</ApplicationIcon>
|
||||
<LangVersion>9</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\"</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.AppContext, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Console, Version=4.0.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Console.4.3.1\lib\net46\System.Console.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Diagnostics.DiagnosticSource, Version=7.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Diagnostics.DiagnosticSource.7.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.Tracing, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Linq.4.3.0\lib\net463\System.Linq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq.Expressions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http, Version=4.1.1.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Reflection, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Extensions, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.InteropServices, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Text.RegularExpressions.4.3.1\lib\net463\System.Text.RegularExpressions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Xml.ReaderWriter.4.3.1\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DownloadForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="DownloadForm.Designer.cs">
|
||||
<DependentUpon>DownloadForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Version.cs" />
|
||||
<EmbeddedResource Include="DownloadForm.resx">
|
||||
<DependentUpon>DownloadForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<None Include="..\..\public\favicon.ico">
|
||||
<Link>favicon.ico</Link>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include=".gitignore" />
|
||||
<Content Include="app.manifest" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets'))" />
|
||||
</Target>
|
||||
<Import Project="packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" />
|
||||
</Project>
|
@@ -1,16 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UptimeKuma", "UptimeKuma.csproj", "{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@@ -1,3 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=UptimeKuma_002FProperties_002FResources/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>
|
@@ -1,9 +0,0 @@
|
||||
namespace UptimeKuma {
|
||||
public class Version {
|
||||
public string latest { get; set; }
|
||||
public string slow { get; set; }
|
||||
public string beta { get; set; }
|
||||
public string nodejs { get; set; }
|
||||
public string exe { get; set; }
|
||||
}
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC Manifest Options
|
||||
If you want to change the Windows User Account Control level replace the
|
||||
requestedExecutionLevel node with one of the following.
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
Specifying requestedExecutionLevel element will disable file and registry virtualization.
|
||||
Remove this element if your application requires this virtualization for backwards
|
||||
compatibility.
|
||||
-->
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
@@ -1,54 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.NETCore.Platforms" version="7.0.0" targetFramework="net472" />
|
||||
<package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net472" />
|
||||
<package id="NETStandard.Library" version="2.0.3" targetFramework="net472" />
|
||||
<package id="Newtonsoft.Json" version="13.0.2" targetFramework="net472" />
|
||||
<package id="System.AppContext" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Console" version="4.3.1" targetFramework="net472" />
|
||||
<package id="System.Diagnostics.DiagnosticSource" version="7.0.1" targetFramework="net472" />
|
||||
<package id="System.Net.Http" version="4.3.4" targetFramework="net472" />
|
||||
<package id="System.Runtime.Extensions" version="4.3.1" targetFramework="net472" />
|
||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.1" targetFramework="net472" />
|
||||
<package id="System.Security.Cryptography.X509Certificates" version="4.3.2" targetFramework="net472" />
|
||||
<package id="System.Text.RegularExpressions" version="4.3.1" targetFramework="net472" />
|
||||
<package id="System.Xml.ReaderWriter" version="4.3.1" targetFramework="net472" />
|
||||
<package id="System.Memory" version="4.5.5" targetFramework="net472" />
|
||||
<package id="System.Net.Primitives" version="4.3.1" targetFramework="net472" />
|
||||
<package id="System.Runtime" version="4.3.1" targetFramework="net472" />
|
||||
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
|
||||
<package id="System.Collections" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Globalization" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.IO" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.IO.Compression" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Linq" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Net.Sockets" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
|
||||
<package id="System.ObjectModel" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Reflection" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.Handles" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Threading" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Threading.Timer" version="4.3.0" targetFramework="net472" />
|
||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net472" />
|
||||
</packages>
|
@@ -15,7 +15,6 @@ if (newVersion) {
|
||||
// Process package.json
|
||||
pkg.version = newVersion;
|
||||
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion);
|
||||
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion);
|
||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
||||
|
||||
// Process README.md
|
||||
|
@@ -1,6 +0,0 @@
|
||||
console.log("Git Push and Publish the release note on github, then press any key to continue");
|
||||
|
||||
process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
process.stdin.on("data", process.exit.bind(process, 0));
|
||||
|
64
extra/release/beta.mjs
Normal file
64
extra/release/beta.mjs
Normal file
@@ -0,0 +1,64 @@
|
||||
import "dotenv/config";
|
||||
import {
|
||||
ver,
|
||||
buildDist,
|
||||
buildImage,
|
||||
checkDocker,
|
||||
checkTagExists,
|
||||
checkVersionFormat,
|
||||
getRepoNames,
|
||||
pressAnyKey,
|
||||
execSync, uploadArtifacts,
|
||||
} from "./lib.mjs";
|
||||
import semver from "semver";
|
||||
|
||||
const repoNames = getRepoNames();
|
||||
const version = process.env.RELEASE_BETA_VERSION;
|
||||
const githubToken = process.env.RELEASE_GITHUB_TOKEN;
|
||||
|
||||
console.log("RELEASE_BETA_VERSION:", version);
|
||||
|
||||
if (!githubToken) {
|
||||
console.error("GITHUB_TOKEN is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if the version is a valid semver
|
||||
checkVersionFormat(version);
|
||||
|
||||
// Check if the semver identifier is "beta"
|
||||
const semverIdentifier = semver.prerelease(version);
|
||||
console.log("Semver identifier:", semverIdentifier);
|
||||
if (semverIdentifier[0] !== "beta") {
|
||||
console.error("VERSION should have a semver identifier of 'beta'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if docker is running
|
||||
checkDocker();
|
||||
|
||||
// Check if the tag exists
|
||||
await checkTagExists(repoNames, version);
|
||||
|
||||
// node extra/beta/update-version.js
|
||||
execSync("node ./extra/beta/update-version.js");
|
||||
|
||||
// Build frontend dist
|
||||
buildDist();
|
||||
|
||||
// Build slim image (rootless)
|
||||
buildImage(repoNames, [ "beta-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
|
||||
|
||||
// Build full image (rootless)
|
||||
buildImage(repoNames, [ "beta-rootless", ver(version, "rootless") ], "rootless");
|
||||
|
||||
// Build slim image
|
||||
buildImage(repoNames, [ "beta-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
|
||||
|
||||
// Build full image
|
||||
buildImage(repoNames, [ "beta", version ], "release");
|
||||
|
||||
await pressAnyKey();
|
||||
|
||||
// npm run upload-artifacts
|
||||
uploadArtifacts(version, githubToken);
|
57
extra/release/final.mjs
Normal file
57
extra/release/final.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
import "dotenv/config";
|
||||
import {
|
||||
ver,
|
||||
buildDist,
|
||||
buildImage,
|
||||
checkDocker,
|
||||
checkTagExists,
|
||||
checkVersionFormat,
|
||||
getRepoNames,
|
||||
pressAnyKey, execSync, uploadArtifacts
|
||||
} from "./lib.mjs";
|
||||
|
||||
const repoNames = getRepoNames();
|
||||
const version = process.env.RELEASE_VERSION;
|
||||
const githubToken = process.env.RELEASE_GITHUB_TOKEN;
|
||||
|
||||
console.log("RELEASE_VERSION:", version);
|
||||
|
||||
if (!githubToken) {
|
||||
console.error("GITHUB_TOKEN is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if the version is a valid semver
|
||||
checkVersionFormat(version);
|
||||
|
||||
// Check if docker is running
|
||||
checkDocker();
|
||||
|
||||
// Check if the tag exists
|
||||
await checkTagExists(repoNames, version);
|
||||
|
||||
// node extra/beta/update-version.js
|
||||
execSync("node extra/update-version.js");
|
||||
|
||||
// Build frontend dist
|
||||
buildDist();
|
||||
|
||||
// Build slim image (rootless)
|
||||
buildImage(repoNames, [ "2-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
|
||||
|
||||
// Build full image (rootless)
|
||||
buildImage(repoNames, [ "2-rootless", ver(version, "rootless") ], "rootless");
|
||||
|
||||
// Build slim image
|
||||
buildImage(repoNames, [ "next-slim", "2-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
|
||||
|
||||
// Build full image
|
||||
buildImage(repoNames, [ "next", "2", version ], "release");
|
||||
|
||||
await pressAnyKey();
|
||||
|
||||
// npm run upload-artifacts
|
||||
uploadArtifacts(version, githubToken);
|
||||
|
||||
// node extra/update-wiki-version.js
|
||||
execSync("node extra/update-wiki-version.js");
|
251
extra/release/lib.mjs
Normal file
251
extra/release/lib.mjs
Normal file
@@ -0,0 +1,251 @@
|
||||
import "dotenv/config";
|
||||
import * as childProcess from "child_process";
|
||||
import semver from "semver";
|
||||
|
||||
export const dryRun = process.env.RELEASE_DRY_RUN === "1";
|
||||
|
||||
if (dryRun) {
|
||||
console.info("Dry run enabled.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if docker is running
|
||||
* @returns {void}
|
||||
*/
|
||||
export function checkDocker() {
|
||||
try {
|
||||
childProcess.execSync("docker ps");
|
||||
} catch (error) {
|
||||
console.error("Docker is not running. Please start docker and try again.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Docker Hub repository name
|
||||
*/
|
||||
export function getRepoNames() {
|
||||
if (process.env.RELEASE_REPO_NAMES) {
|
||||
// Split by comma
|
||||
return process.env.RELEASE_REPO_NAMES.split(",").map((name) => name.trim());
|
||||
}
|
||||
return [
|
||||
"louislam/uptime-kuma",
|
||||
"ghcr.io/louislam/uptime-kuma",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build frontend dist
|
||||
* @returns {void}
|
||||
*/
|
||||
export function buildDist() {
|
||||
if (!dryRun) {
|
||||
childProcess.execSync("npm run build", { stdio: "inherit" });
|
||||
} else {
|
||||
console.info("[DRY RUN] npm run build");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build docker image and push to Docker Hub
|
||||
* @param {string[]} repoNames Docker Hub repository names
|
||||
* @param {string[]} tags Docker image tags
|
||||
* @param {string} target Dockerfile's target name
|
||||
* @param {string} buildArgs Docker build args
|
||||
* @param {string} dockerfile Path to Dockerfile
|
||||
* @param {string} platform Build platform
|
||||
* @returns {void}
|
||||
*/
|
||||
export function buildImage(repoNames, tags, target, buildArgs = "", dockerfile = "docker/dockerfile", platform = "linux/amd64,linux/arm64,linux/arm/v7") {
|
||||
let args = [
|
||||
"buildx",
|
||||
"build",
|
||||
"-f",
|
||||
dockerfile,
|
||||
"--platform",
|
||||
platform,
|
||||
];
|
||||
|
||||
for (let repoName of repoNames) {
|
||||
// Add tags
|
||||
for (let tag of tags) {
|
||||
args.push("-t", `${repoName}:${tag}`);
|
||||
}
|
||||
}
|
||||
|
||||
args = [
|
||||
...args,
|
||||
"--target",
|
||||
target,
|
||||
];
|
||||
|
||||
// Add build args
|
||||
if (buildArgs) {
|
||||
args.push("--build-arg", buildArgs);
|
||||
}
|
||||
|
||||
args = [
|
||||
...args,
|
||||
".",
|
||||
"--push",
|
||||
];
|
||||
|
||||
if (!dryRun) {
|
||||
childProcess.spawnSync("docker", args, { stdio: "inherit" });
|
||||
} else {
|
||||
console.log(`[DRY RUN] docker ${args.join(" ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the version already exists on Docker Hub
|
||||
* TODO: use semver to compare versions if it is greater than the previous?
|
||||
* @param {string[]} repoNames repository name (Only check the name with single slash)
|
||||
* @param {string} version Version to check
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function checkTagExists(repoNames, version) {
|
||||
// Skip if the tag is not on Docker Hub
|
||||
// louislam/uptime-kuma
|
||||
let dockerHubRepoNames = repoNames.filter((name) => {
|
||||
return name.split("/").length === 2;
|
||||
});
|
||||
|
||||
for (let repoName of dockerHubRepoNames) {
|
||||
await checkTagExistsSingle(repoName, version);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the version already exists on Docker Hub
|
||||
* @param {string} repoName repository name
|
||||
* @param {string} version Version to check
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function checkTagExistsSingle(repoName, version) {
|
||||
console.log(`Checking if version ${version} exists on Docker Hub:`, repoName);
|
||||
|
||||
// Get a list of tags from the Docker Hub repository
|
||||
let tags = [];
|
||||
|
||||
// It is mainly to check my careless mistake that I forgot to update the release version in .env, so `page_size` is set to 100 is enough, I think.
|
||||
const response = await fetch(`https://hub.docker.com/v2/repositories/${repoName}/tags/?page_size=100`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
tags = data.results.map((tag) => tag.name);
|
||||
} else {
|
||||
console.error("Failed to get tags from Docker Hub");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if the version already exists
|
||||
if (tags.includes(version)) {
|
||||
console.error(`Version ${version} already exists`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the version format
|
||||
* @param {string} version Version to check
|
||||
* @returns {void}
|
||||
*/
|
||||
export function checkVersionFormat(version) {
|
||||
if (!version) {
|
||||
console.error("VERSION is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check the version format, it should be a semver and must be like this: "2.0.0-beta.0"
|
||||
if (!semver.valid(version)) {
|
||||
console.error("VERSION is not a valid semver version");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Press any key to continue
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function pressAnyKey() {
|
||||
console.log("Git Push and Publish the release note on github, then press any key to continue");
|
||||
process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
return new Promise(resolve => process.stdin.once("data", data => {
|
||||
process.stdin.setRawMode(false);
|
||||
process.stdin.pause();
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append version identifier
|
||||
* @param {string} version Version
|
||||
* @param {string} identifier Identifier
|
||||
* @returns {string} Version with identifier
|
||||
*/
|
||||
export function ver(version, identifier) {
|
||||
const obj = semver.parse(version);
|
||||
|
||||
if (obj.prerelease.length === 0) {
|
||||
obj.prerelease = [ identifier ];
|
||||
} else {
|
||||
obj.prerelease[0] = [ obj.prerelease[0], identifier ].join("-");
|
||||
}
|
||||
return obj.format();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload artifacts to GitHub
|
||||
* docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain
|
||||
* @param {string} version Version
|
||||
* @param {string} githubToken GitHub token
|
||||
* @returns {void}
|
||||
*/
|
||||
export function uploadArtifacts(version, githubToken) {
|
||||
let args = [
|
||||
"buildx",
|
||||
"build",
|
||||
"-f",
|
||||
"docker/dockerfile",
|
||||
"--platform",
|
||||
"linux/amd64",
|
||||
"-t",
|
||||
"louislam/uptime-kuma:upload-artifact",
|
||||
"--build-arg",
|
||||
`VERSION=${version}`,
|
||||
"--build-arg",
|
||||
"GITHUB_TOKEN",
|
||||
"--target",
|
||||
"upload-artifact",
|
||||
".",
|
||||
"--progress",
|
||||
"plain",
|
||||
];
|
||||
|
||||
if (!dryRun) {
|
||||
childProcess.spawnSync("docker", args, {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
GITHUB_TOKEN: githubToken,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log(`[DRY RUN] docker ${args.join(" ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command
|
||||
* @param {string} cmd Command to execute
|
||||
* @returns {void}
|
||||
*/
|
||||
export function execSync(cmd) {
|
||||
if (!dryRun) {
|
||||
childProcess.execSync(cmd, { stdio: "inherit" });
|
||||
} else {
|
||||
console.info(`[DRY RUN] ${cmd}`);
|
||||
}
|
||||
}
|
16
extra/release/nightly.mjs
Normal file
16
extra/release/nightly.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
import { buildDist, buildImage, checkDocker, getRepoNames } from "./lib.mjs";
|
||||
|
||||
// Docker Hub repository name
|
||||
const repoNames = getRepoNames();
|
||||
|
||||
// Check if docker is running
|
||||
checkDocker();
|
||||
|
||||
// Build frontend dist (it will build on the host machine, TODO: build on a container?)
|
||||
buildDist();
|
||||
|
||||
// Build full image (rootless)
|
||||
buildImage(repoNames, [ "nightly2-rootless" ], "nightly-rootless");
|
||||
|
||||
// Build full image
|
||||
buildImage(repoNames, [ "nightly2" ], "nightly");
|
6
extra/release/upload-artifacts-beta.mjs
Normal file
6
extra/release/upload-artifacts-beta.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
import { uploadArtifacts } from "./lib.mjs";
|
||||
|
||||
const version = process.env.RELEASE_BETA_VERSION;
|
||||
const githubToken = process.env.RELEASE_GITHUB_TOKEN;
|
||||
|
||||
uploadArtifacts(version, githubToken);
|
6
extra/release/upload-artifacts.mjs
Normal file
6
extra/release/upload-artifacts.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
import { uploadArtifacts } from "./lib.mjs";
|
||||
|
||||
const version = process.env.RELEASE_VERSION;
|
||||
const githubToken = process.env.RELEASE_GITHUB_TOKEN;
|
||||
|
||||
uploadArtifacts(version, githubToken);
|
24
extra/reset-migrate-aggregate-table-state.js
Normal file
24
extra/reset-migrate-aggregate-table-state.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const { R } = require("redbean-node");
|
||||
const Database = require("../server/database");
|
||||
const args = require("args-parser")(process.argv);
|
||||
const { Settings } = require("../server/settings");
|
||||
|
||||
const main = async () => {
|
||||
console.log("Connecting the database");
|
||||
Database.initDataDir(args);
|
||||
await Database.connect(false, false, true);
|
||||
|
||||
console.log("Deleting all data from aggregate tables");
|
||||
await R.exec("DELETE FROM stat_minutely");
|
||||
await R.exec("DELETE FROM stat_hourly");
|
||||
await R.exec("DELETE FROM stat_daily");
|
||||
|
||||
console.log("Resetting the aggregate table state");
|
||||
await Settings.set("migrateAggregateTableState", "");
|
||||
|
||||
await Database.close();
|
||||
console.log("Done");
|
||||
};
|
||||
|
||||
main();
|
||||
|
@@ -1,12 +0,0 @@
|
||||
// If Node.js >= 22, run `npm run test-backend-node22`, otherwise run `npm run test-backend-node20`
|
||||
import * as childProcess from "child_process";
|
||||
|
||||
const version = parseInt(process.version.slice(1));
|
||||
|
||||
console.log(`Node.js version: ${version}`);
|
||||
|
||||
if (version >= 22) {
|
||||
childProcess.execSync("npm run test-backend-node22", { stdio: "inherit" });
|
||||
} else {
|
||||
childProcess.execSync("npm run test-backend-node20", { stdio: "inherit" });
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
// Check if docker is running
|
||||
const { exec } = require("child_process");
|
||||
|
||||
exec("docker ps", (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.error("Docker is not running. Please start docker and try again.");
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
@@ -5,7 +5,7 @@ const util = require("../src/util");
|
||||
|
||||
util.polyfill();
|
||||
|
||||
const newVersion = process.env.VERSION;
|
||||
const newVersion = process.env.RELEASE_VERSION;
|
||||
|
||||
console.log("New Version: " + newVersion);
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const childProcess = require("child_process");
|
||||
const fs = require("fs");
|
||||
|
||||
const newVersion = process.env.VERSION;
|
||||
const newVersion = process.env.RELEASE_VERSION;
|
||||
|
||||
if (!newVersion) {
|
||||
console.log("Missing version");
|
||||
|
7313
package-lock.json
generated
7313
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "2.0.0-dev",
|
||||
"version": "2.0.0-beta.2",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,29 +27,21 @@
|
||||
"build": "vite build --config ./config/vite.config.js",
|
||||
"test": "npm run test-backend && npm run test-e2e",
|
||||
"test-with-build": "npm run build && npm test",
|
||||
"test-backend": "node extra/test-backend.mjs",
|
||||
"test-backend-node20": "cross-env TEST_BACKEND=1 node --test test/backend-test",
|
||||
"test-backend-node22": "cross-env TEST_BACKEND=1 node --test test/backend-test/test-*.js && node --test test/backend-test/**/test-*.js",
|
||||
"test-backend": "cross-env TEST_BACKEND=1 node --test test/backend-test",
|
||||
"test-e2e": "playwright test --config ./config/playwright.config.js",
|
||||
"test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
|
||||
"playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",
|
||||
"playwright-show-report": "playwright show-report ./private/playwright-report",
|
||||
"tsc": "tsc",
|
||||
"vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
|
||||
"build-docker": "npm run build && npm run build-docker-full && npm run build-docker-slim",
|
||||
"build-docker-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2 --target base2 . --push",
|
||||
"build-docker-base-slim": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2-slim --target base2-slim . --push",
|
||||
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
||||
"build-docker-slim": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-slim -t louislam/uptime-kuma:$VERSION-slim --target release --build-arg BASE_IMAGE=louislam/uptime-kuma:base2-slim . --push",
|
||||
"build-docker-full": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2 -t louislam/uptime-kuma:$VERSION --target release . --push",
|
||||
"build-docker-nightly": "node ./extra/test-docker.js && npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly2 --target nightly . --push",
|
||||
"build-docker-slim-rootless": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-slim-rootless -t louislam/uptime-kuma:$VERSION-slim-rootless --target rootless --build-arg BASE_IMAGE=louislam/uptime-kuma:base2-slim . --push",
|
||||
"build-docker-full-rootless": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-rootless -t louislam/uptime-kuma:$VERSION-rootless --target rootless . --push",
|
||||
"build-docker-nightly-rootless": "node ./extra/test-docker.js && npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly2-rootless --target nightly-rootless . --push",
|
||||
"build-docker-nightly-local": "npm run build && docker build -f docker/dockerfile -t louislam/uptime-kuma:nightly2 --target nightly .",
|
||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test2 --target pr-test2 . --push",
|
||||
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||
"setup": "git checkout 1.23.14 && npm ci --production && npm run download-dist",
|
||||
"upload-artifacts": "node extra/release/upload-artifacts.mjs",
|
||||
"upload-artifacts-beta": "node extra/release/upload-artifacts-beta.mjs",
|
||||
"setup": "git checkout 1.23.16 && npm ci --omit dev && npm run download-dist",
|
||||
"download-dist": "node extra/download-dist.js",
|
||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||
"reset-password": "node extra/reset-password.js",
|
||||
@@ -60,8 +52,9 @@
|
||||
"simple-postgres": "docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres",
|
||||
"simple-mariadb": "docker run --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mariadb# mariadb",
|
||||
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
|
||||
"release-final": "node ./extra/test-docker.js && node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
||||
"release-beta": "node ./extra/test-docker.js && node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
||||
"release-final": "node ./extra/release/final.mjs",
|
||||
"release-beta": "node ./extra/release/beta.mjs",
|
||||
"release-nightly": "node ./extra/release/nightly.mjs",
|
||||
"git-remove-tag": "git tag -d",
|
||||
"build-dist-and-restart": "npm run build && npm run start-server-dev",
|
||||
"start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev",
|
||||
@@ -70,7 +63,8 @@
|
||||
"sort-contributors": "node extra/sort-contributors.js",
|
||||
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly2",
|
||||
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
|
||||
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X"
|
||||
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X",
|
||||
"reset-migrate-aggregate-table-state": "node extra/reset-migrate-aggregate-table-state.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "~1.8.22",
|
||||
@@ -78,7 +72,7 @@
|
||||
"@louislam/sqlite3": "15.1.6",
|
||||
"@vvo/tzdb": "^6.125.0",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.28.1",
|
||||
"axios": "~0.29.0",
|
||||
"badge-maker": "~3.3.1",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"chardet": "~1.4.0",
|
||||
@@ -101,8 +95,8 @@
|
||||
"html-escaper": "^3.0.3",
|
||||
"http-cookie-agent": "~5.0.4",
|
||||
"http-graceful-shutdown": "~3.1.7",
|
||||
"http-proxy-agent": "~5.0.0",
|
||||
"https-proxy-agent": "~5.0.1",
|
||||
"http-proxy-agent": "~7.0.2",
|
||||
"https-proxy-agent": "~7.0.6",
|
||||
"iconv-lite": "~0.6.3",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"jsesc": "~3.0.2",
|
||||
@@ -110,7 +104,7 @@
|
||||
"jsonwebtoken": "~9.0.0",
|
||||
"jwt-decode": "~3.1.2",
|
||||
"kafkajs": "^2.2.4",
|
||||
"knex": "^2.4.2",
|
||||
"knex": "~3.1.0",
|
||||
"limiter": "~2.1.0",
|
||||
"liquidjs": "^10.7.0",
|
||||
"marked": "^14.0.0",
|
||||
@@ -118,13 +112,13 @@
|
||||
"mongodb": "~4.17.1",
|
||||
"mqtt": "~4.3.7",
|
||||
"mssql": "~11.0.0",
|
||||
"mysql2": "~3.9.6",
|
||||
"mysql2": "~3.11.3",
|
||||
"nanoid": "~3.3.4",
|
||||
"net-snmp": "^3.11.2",
|
||||
"node-cloudflared-tunnel": "~1.0.9",
|
||||
"node-radius-client": "~1.0.0",
|
||||
"nodemailer": "~6.9.13",
|
||||
"nostr-tools": "^1.13.1",
|
||||
"nostr-tools": "^2.10.4",
|
||||
"notp": "~2.0.3",
|
||||
"openid-client": "^5.4.2",
|
||||
"password-hash": "~1.2.2",
|
||||
@@ -141,7 +135,7 @@
|
||||
"semver": "~7.5.4",
|
||||
"socket.io": "~4.8.0",
|
||||
"socket.io-client": "~4.8.0",
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"socks-proxy-agent": "~8.0.5",
|
||||
"tar": "~6.2.1",
|
||||
"tcp-ping": "~0.1.1",
|
||||
"thirty-two": "~1.0.2",
|
||||
@@ -149,7 +143,7 @@
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "~5.1.1",
|
||||
"@actions/github": "~6.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "~1.2.36",
|
||||
"@fortawesome/free-regular-svg-icons": "~5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "~5.15.4",
|
||||
@@ -175,7 +169,7 @@
|
||||
"cross-env": "~7.0.3",
|
||||
"delay": "^5.0.0",
|
||||
"dns2": "~2.0.1",
|
||||
"dompurify": "~3.1.7",
|
||||
"dompurify": "~3.2.4",
|
||||
"eslint": "~8.14.0",
|
||||
"eslint-plugin-jsdoc": "~46.4.6",
|
||||
"eslint-plugin-vue": "~8.7.1",
|
||||
@@ -185,7 +179,7 @@
|
||||
"postcss-html": "~1.5.0",
|
||||
"postcss-rtlcss": "~3.7.2",
|
||||
"postcss-scss": "~4.0.4",
|
||||
"prismjs": "~1.29.0",
|
||||
"prismjs": "~1.30.0",
|
||||
"qrcode": "~1.5.0",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"sass": "~1.42.1",
|
||||
@@ -196,14 +190,14 @@
|
||||
"testcontainers": "^10.13.1",
|
||||
"typescript": "~4.4.4",
|
||||
"v-pagination-3": "~0.1.7",
|
||||
"vite": "~5.2.8",
|
||||
"vite": "~5.4.15",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-vue-devtools": "^7.0.15",
|
||||
"vue": "~3.4.2",
|
||||
"vue-chartjs": "~5.2.0",
|
||||
"vue-confirm-dialog": "~1.0.2",
|
||||
"vue-contenteditable": "~3.0.4",
|
||||
"vue-i18n": "~9.2.2",
|
||||
"vue-i18n": "~9.14.3",
|
||||
"vue-image-crop-upload": "~3.0.3",
|
||||
"vue-multiselect": "~3.0.0-alpha.2",
|
||||
"vue-prism-editor": "~2.0.0-alpha.2",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 10 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 30 KiB |
@@ -6,6 +6,11 @@ const knex = require("knex");
|
||||
const path = require("path");
|
||||
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
||||
const mysql = require("mysql2/promise");
|
||||
const { Settings } = require("./settings");
|
||||
const { UptimeCalculator } = require("./uptime-calculator");
|
||||
const dayjs = require("dayjs");
|
||||
const { SimpleMigrationServer } = require("./utils/simple-migration-server");
|
||||
const KumaColumnCompiler = require("./utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler");
|
||||
|
||||
/**
|
||||
* Database & App Data Folder
|
||||
@@ -194,6 +199,14 @@ class Database {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async connect(testMode = false, autoloadModels = true, noLog = false) {
|
||||
// Patch "mysql2" knex client
|
||||
// Workaround: Tried extending the ColumnCompiler class, but it didn't work for unknown reasons, so I override the function via prototype
|
||||
const { getDialectByNameOrAlias } = require("knex/lib/dialects");
|
||||
const mysql2 = getDialectByNameOrAlias("mysql2");
|
||||
mysql2.prototype.columnCompiler = function () {
|
||||
return new KumaColumnCompiler(this, ...arguments);
|
||||
};
|
||||
|
||||
const acquireConnectionTimeout = 120 * 1000;
|
||||
let dbConfig;
|
||||
try {
|
||||
@@ -283,7 +296,7 @@ class Database {
|
||||
client: "mysql2",
|
||||
connection: {
|
||||
socketPath: embeddedMariaDB.socketPath,
|
||||
user: "node",
|
||||
user: embeddedMariaDB.username,
|
||||
database: "kuma",
|
||||
timezone: "Z",
|
||||
typeCast: function (field, next) {
|
||||
@@ -379,9 +392,11 @@ class Database {
|
||||
|
||||
/**
|
||||
* Patch the database
|
||||
* @param {number} port Start the migration server for aggregate tables on this port if provided
|
||||
* @param {string} hostname Start the migration server for aggregate tables on this hostname if provided
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async patch() {
|
||||
static async patch(port = undefined, hostname = undefined) {
|
||||
// Still need to keep this for old versions of Uptime Kuma
|
||||
if (Database.dbConfig.type === "sqlite") {
|
||||
await this.patchSqlite();
|
||||
@@ -391,9 +406,23 @@ class Database {
|
||||
// https://knexjs.org/guide/migrations.html
|
||||
// https://gist.github.com/NigelEarle/70db130cc040cc2868555b29a0278261
|
||||
try {
|
||||
// Disable foreign key check for SQLite
|
||||
// Known issue of knex: https://github.com/drizzle-team/drizzle-orm/issues/1813
|
||||
if (Database.dbConfig.type === "sqlite") {
|
||||
await R.exec("PRAGMA foreign_keys = OFF");
|
||||
}
|
||||
|
||||
await R.knex.migrate.latest({
|
||||
directory: Database.knexMigrationsPath,
|
||||
});
|
||||
|
||||
// Enable foreign key check for SQLite
|
||||
if (Database.dbConfig.type === "sqlite") {
|
||||
await R.exec("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
|
||||
await this.migrateAggregateTable(port, hostname);
|
||||
|
||||
} catch (e) {
|
||||
// Allow missing patch files for downgrade or testing pr.
|
||||
if (e.message.includes("the following files are missing:")) {
|
||||
@@ -711,6 +740,175 @@ class Database {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the old data in the heartbeat table to the new format (stat_daily, stat_hourly, stat_minutely)
|
||||
* It should be run once while upgrading V1 to V2
|
||||
*
|
||||
* Normally, it should be in transaction, but UptimeCalculator wasn't designed to be in transaction before that.
|
||||
* I don't want to heavily modify the UptimeCalculator, so it is not in transaction.
|
||||
* Run `npm run reset-migrate-aggregate-table-state` to reset, in case the migration is interrupted.
|
||||
* @param {number} port Start the migration server on this port if provided
|
||||
* @param {string} hostname Start the migration server on this hostname if provided
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async migrateAggregateTable(port, hostname = undefined) {
|
||||
log.debug("db", "Enter Migrate Aggregate Table function");
|
||||
|
||||
// Add a setting for 2.0.0-dev users to skip this migration
|
||||
if (process.env.SET_MIGRATE_AGGREGATE_TABLE_TO_TRUE === "1") {
|
||||
log.warn("db", "SET_MIGRATE_AGGREGATE_TABLE_TO_TRUE is set to 1, skipping aggregate table migration forever (for 2.0.0-dev users)");
|
||||
await Settings.set("migrateAggregateTableState", "migrated");
|
||||
}
|
||||
|
||||
let migrateState = await Settings.get("migrateAggregateTableState");
|
||||
|
||||
// Skip if already migrated
|
||||
// If it is migrating, it possibly means the migration was interrupted, or the migration is in progress
|
||||
if (migrateState === "migrated") {
|
||||
log.debug("db", "Migrated aggregate table already, skip");
|
||||
return;
|
||||
} else if (migrateState === "migrating") {
|
||||
log.warn("db", "Aggregate table migration is already in progress, or it was interrupted");
|
||||
throw new Error("Aggregate table migration is already in progress");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start migration server for displaying the migration status
|
||||
* @type {SimpleMigrationServer}
|
||||
*/
|
||||
let migrationServer;
|
||||
let msg;
|
||||
|
||||
if (port) {
|
||||
migrationServer = new SimpleMigrationServer();
|
||||
await migrationServer.start(port, hostname);
|
||||
}
|
||||
|
||||
log.info("db", "Migrating Aggregate Table");
|
||||
|
||||
log.info("db", "Getting list of unique monitors");
|
||||
|
||||
// Get a list of unique monitors from the heartbeat table, using raw sql
|
||||
let monitors = await R.getAll(`
|
||||
SELECT DISTINCT monitor_id
|
||||
FROM heartbeat
|
||||
ORDER BY monitor_id ASC
|
||||
`);
|
||||
|
||||
// Stop if stat_* tables are not empty
|
||||
for (let table of [ "stat_minutely", "stat_hourly", "stat_daily" ]) {
|
||||
let countResult = await R.getRow(`SELECT COUNT(*) AS count FROM ${table}`);
|
||||
let count = countResult.count;
|
||||
if (count > 0) {
|
||||
log.warn("db", `Aggregate table ${table} is not empty, migration will not be started (Maybe you were using 2.0.0-dev?)`);
|
||||
await migrationServer?.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await Settings.set("migrateAggregateTableState", "migrating");
|
||||
|
||||
let progressPercent = 0;
|
||||
let part = 100 / monitors.length;
|
||||
let i = 1;
|
||||
for (let monitor of monitors) {
|
||||
// Get a list of unique dates from the heartbeat table, using raw sql
|
||||
let dates = await R.getAll(`
|
||||
SELECT DISTINCT DATE(time) AS date
|
||||
FROM heartbeat
|
||||
WHERE monitor_id = ?
|
||||
ORDER BY date ASC
|
||||
`, [
|
||||
monitor.monitor_id
|
||||
]);
|
||||
|
||||
for (let date of dates) {
|
||||
// New Uptime Calculator
|
||||
let calculator = new UptimeCalculator();
|
||||
calculator.monitorID = monitor.monitor_id;
|
||||
calculator.setMigrationMode(true);
|
||||
|
||||
// Get all the heartbeats for this monitor and date
|
||||
let heartbeats = await R.getAll(`
|
||||
SELECT status, ping, time
|
||||
FROM heartbeat
|
||||
WHERE monitor_id = ?
|
||||
AND DATE(time) = ?
|
||||
ORDER BY time ASC
|
||||
`, [ monitor.monitor_id, date.date ]);
|
||||
|
||||
if (heartbeats.length > 0) {
|
||||
msg = `[DON'T STOP] Migrating monitor data ${monitor.monitor_id} - ${date.date} [${progressPercent.toFixed(2)}%][${i}/${monitors.length}]`;
|
||||
log.info("db", msg);
|
||||
migrationServer?.update(msg);
|
||||
}
|
||||
|
||||
for (let heartbeat of heartbeats) {
|
||||
await calculator.update(heartbeat.status, parseFloat(heartbeat.ping), dayjs(heartbeat.time));
|
||||
}
|
||||
|
||||
progressPercent += (Math.round(part / dates.length * 100) / 100);
|
||||
|
||||
// Lazy to fix the floating point issue, it is acceptable since it is just a progress bar
|
||||
if (progressPercent > 100) {
|
||||
progressPercent = 100;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
msg = "Clearing non-important heartbeats";
|
||||
log.info("db", msg);
|
||||
migrationServer?.update(msg);
|
||||
|
||||
await Database.clearHeartbeatData(true);
|
||||
await Settings.set("migrateAggregateTableState", "migrated");
|
||||
await migrationServer?.stop();
|
||||
|
||||
if (monitors.length > 0) {
|
||||
log.info("db", "Aggregate Table Migration Completed");
|
||||
} else {
|
||||
log.info("db", "No data to migrate");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all non-important heartbeats from heartbeat table, keep last 24-hour or {KEEP_LAST_ROWS} rows for each monitor
|
||||
* @param {boolean} detailedLog Log detailed information
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async clearHeartbeatData(detailedLog = false) {
|
||||
let monitors = await R.getAll("SELECT id FROM monitor");
|
||||
const sqlHourOffset = Database.sqlHourOffset();
|
||||
|
||||
for (let monitor of monitors) {
|
||||
if (detailedLog) {
|
||||
log.info("db", "Deleting non-important heartbeats for monitor " + monitor.id);
|
||||
}
|
||||
await R.exec(`
|
||||
DELETE FROM heartbeat
|
||||
WHERE monitor_id = ?
|
||||
AND important = 0
|
||||
AND time < ${sqlHourOffset}
|
||||
AND id NOT IN (
|
||||
SELECT id FROM ( -- written this way for Maria's support
|
||||
SELECT id
|
||||
FROM heartbeat
|
||||
WHERE monitor_id = ?
|
||||
ORDER BY time DESC
|
||||
LIMIT ?
|
||||
) AS limited_ids
|
||||
)
|
||||
`, [
|
||||
monitor.id,
|
||||
-24,
|
||||
monitor.id,
|
||||
100,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Database;
|
||||
|
@@ -14,9 +14,15 @@ class EmbeddedMariaDB {
|
||||
|
||||
mariadbDataDir = "/app/data/mariadb";
|
||||
|
||||
runDir = "/app/data/run/mariadb";
|
||||
runDir = "/app/data/run";
|
||||
|
||||
socketPath = this.runDir + "/mysqld.sock";
|
||||
socketPath = this.runDir + "/mariadb.sock";
|
||||
|
||||
/**
|
||||
* The username to connect to the MariaDB
|
||||
* @type {string}
|
||||
*/
|
||||
username = null;
|
||||
|
||||
/**
|
||||
* @type {ChildProcessWithoutNullStreams}
|
||||
@@ -46,16 +52,42 @@ class EmbeddedMariaDB {
|
||||
|
||||
/**
|
||||
* Start the embedded MariaDB
|
||||
* @throws {Error} If the current user is not "node" or "root"
|
||||
* @returns {Promise<void>|void} A promise that resolves when the MariaDB is started or void if it is already started
|
||||
*/
|
||||
start() {
|
||||
// Check if the current user is "node" or "root"
|
||||
this.username = require("os").userInfo().username;
|
||||
if (this.username !== "node" && this.username !== "root") {
|
||||
throw new Error("Embedded Mariadb supports only 'node' or 'root' user, but the current user is: " + this.username);
|
||||
}
|
||||
|
||||
this.initDB();
|
||||
|
||||
this.startChildProcess();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let interval = setInterval(() => {
|
||||
if (this.started) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
} else {
|
||||
log.info("mariadb", "Waiting for Embedded MariaDB to start...");
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the child process
|
||||
* @returns {void}
|
||||
*/
|
||||
startChildProcess() {
|
||||
if (this.childProcess) {
|
||||
log.info("mariadb", "Already started");
|
||||
return;
|
||||
}
|
||||
|
||||
this.initDB();
|
||||
|
||||
this.running = true;
|
||||
log.info("mariadb", "Starting Embedded MariaDB");
|
||||
this.childProcess = childProcess.spawn(this.exec, [
|
||||
@@ -63,6 +95,8 @@ class EmbeddedMariaDB {
|
||||
"--datadir=" + this.mariadbDataDir,
|
||||
`--socket=${this.socketPath}`,
|
||||
`--pid-file=${this.runDir}/mysqld.pid`,
|
||||
// Don't add the following option, the mariadb will not report message to the console, which affects initDBAfterStarted()
|
||||
// "--log-error=" + `${this.mariadbDataDir}/mariadb-error.log`,
|
||||
]);
|
||||
|
||||
this.childProcess.on("close", (code) => {
|
||||
@@ -72,8 +106,8 @@ class EmbeddedMariaDB {
|
||||
log.info("mariadb", "Stopped Embedded MariaDB: " + code);
|
||||
|
||||
if (code !== 0) {
|
||||
log.info("mariadb", "Try to restart Embedded MariaDB as it is not stopped by user");
|
||||
this.start();
|
||||
log.error("mariadb", "Try to restart Embedded MariaDB as it is not stopped by user");
|
||||
this.startChildProcess();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -86,7 +120,7 @@ class EmbeddedMariaDB {
|
||||
});
|
||||
|
||||
let handler = (data) => {
|
||||
log.debug("mariadb", data.toString("utf-8"));
|
||||
log.info("mariadb", data.toString("utf-8"));
|
||||
if (data.toString("utf-8").includes("ready for connections")) {
|
||||
this.initDBAfterStarted();
|
||||
}
|
||||
@@ -94,17 +128,6 @@ class EmbeddedMariaDB {
|
||||
|
||||
this.childProcess.stdout.on("data", handler);
|
||||
this.childProcess.stderr.on("data", handler);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let interval = setInterval(() => {
|
||||
if (this.started) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
} else {
|
||||
log.info("mariadb", "Waiting for Embedded MariaDB to start...");
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,9 +152,11 @@ class EmbeddedMariaDB {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
let result = childProcess.spawnSync("mysql_install_db", [
|
||||
let result = childProcess.spawnSync("mariadb-install-db", [
|
||||
"--user=node",
|
||||
"--ldata=" + this.mariadbDataDir,
|
||||
"--auth-root-socket-user=node",
|
||||
"--datadir=" + this.mariadbDataDir,
|
||||
"--auth-root-authentication-method=socket",
|
||||
]);
|
||||
|
||||
if (result.status !== 0) {
|
||||
@@ -143,6 +168,17 @@ class EmbeddedMariaDB {
|
||||
}
|
||||
}
|
||||
|
||||
// Check the owner of the mariadb directory, and change it if necessary
|
||||
let stat = fs.statSync(this.mariadbDataDir);
|
||||
if (stat.uid !== 1000 || stat.gid !== 1000) {
|
||||
fs.chownSync(this.mariadbDataDir, 1000, 1000);
|
||||
}
|
||||
|
||||
// Check the permission of the mariadb directory, and change it if it is not 755
|
||||
if (stat.mode !== 0o755) {
|
||||
fs.chmodSync(this.mariadbDataDir, 0o755);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(this.runDir)) {
|
||||
log.info("mariadb", `Embedded MariaDB: ${this.runDir} is not found, create one now.`);
|
||||
fs.mkdirSync(this.runDir, {
|
||||
@@ -150,6 +186,13 @@ class EmbeddedMariaDB {
|
||||
});
|
||||
}
|
||||
|
||||
stat = fs.statSync(this.runDir);
|
||||
if (stat.uid !== 1000 || stat.gid !== 1000) {
|
||||
fs.chownSync(this.runDir, 1000, 1000);
|
||||
}
|
||||
if (stat.mode !== 0o755) {
|
||||
fs.chmodSync(this.runDir, 0o755);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +202,7 @@ class EmbeddedMariaDB {
|
||||
async initDBAfterStarted() {
|
||||
const connection = mysql.createConnection({
|
||||
socketPath: this.socketPath,
|
||||
user: "node",
|
||||
user: this.username,
|
||||
});
|
||||
|
||||
let result = await connection.execute("CREATE DATABASE IF NOT EXISTS `kuma`");
|
||||
|
@@ -1,21 +1,22 @@
|
||||
const { R } = require("redbean-node");
|
||||
const { log } = require("../../src/util");
|
||||
const { setSetting, setting } = require("../util-server");
|
||||
const Database = require("../database");
|
||||
const { Settings } = require("../settings");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
const DEFAULT_KEEP_PERIOD = 180;
|
||||
const DEFAULT_KEEP_PERIOD = 365;
|
||||
|
||||
/**
|
||||
* Clears old data from the heartbeat table of the database.
|
||||
* Clears old data from the heartbeat table and the stat_daily of the database.
|
||||
* @returns {Promise<void>} A promise that resolves when the data has been cleared.
|
||||
*/
|
||||
|
||||
const clearOldData = async () => {
|
||||
let period = await setting("keepDataPeriodDays");
|
||||
await Database.clearHeartbeatData();
|
||||
let period = await Settings.get("keepDataPeriodDays");
|
||||
|
||||
// Set Default Period
|
||||
if (period == null) {
|
||||
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
||||
await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
||||
period = DEFAULT_KEEP_PERIOD;
|
||||
}
|
||||
|
||||
@@ -25,23 +26,28 @@ const clearOldData = async () => {
|
||||
parsedPeriod = parseInt(period);
|
||||
} catch (_) {
|
||||
log.warn("clearOldData", "Failed to parse setting, resetting to default..");
|
||||
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
||||
await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
||||
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
||||
}
|
||||
|
||||
if (parsedPeriod < 1) {
|
||||
log.info("clearOldData", `Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
|
||||
} else {
|
||||
|
||||
log.debug("clearOldData", `Clearing Data older than ${parsedPeriod} days...`);
|
||||
|
||||
const sqlHourOffset = Database.sqlHourOffset();
|
||||
|
||||
try {
|
||||
await R.exec(
|
||||
"DELETE FROM heartbeat WHERE time < " + sqlHourOffset,
|
||||
[ parsedPeriod * -24 ]
|
||||
);
|
||||
// Heartbeat
|
||||
await R.exec("DELETE FROM heartbeat WHERE time < " + sqlHourOffset, [
|
||||
parsedPeriod * -24,
|
||||
]);
|
||||
|
||||
let timestamp = dayjs().subtract(parsedPeriod, "day").utc().startOf("day").unix();
|
||||
|
||||
// stat_daily
|
||||
await R.exec("DELETE FROM stat_daily WHERE timestamp < ? ", [
|
||||
timestamp,
|
||||
]);
|
||||
|
||||
if (Database.dbConfig.type === "sqlite") {
|
||||
await R.exec("PRAGMA optimize;");
|
||||
@@ -50,6 +56,8 @@ const clearOldData = async () => {
|
||||
log.error("clearOldData", `Failed to clear old data: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("clearOldData", "Data cleared.");
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
@@ -1434,7 +1434,7 @@ class Monitor extends BeanModel {
|
||||
for (let notification of notificationList) {
|
||||
try {
|
||||
log.debug("monitor", "Sending to " + notification.name);
|
||||
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will be expired in ${daysRemaining} days`);
|
||||
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will expire in ${daysRemaining} days`);
|
||||
sent = true;
|
||||
} catch (e) {
|
||||
log.error("monitor", "Cannot send cert notification to " + notification.name);
|
||||
@@ -1522,7 +1522,7 @@ class Monitor extends BeanModel {
|
||||
*/
|
||||
static async getMonitorTag(monitorIDs) {
|
||||
return await R.getAll(`
|
||||
SELECT monitor_tag.monitor_id, monitor_tag.tag_id, tag.name, tag.color
|
||||
SELECT monitor_tag.monitor_id, monitor_tag.tag_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 IN (${monitorIDs.map((_) => "?").join(",")})
|
||||
@@ -1567,6 +1567,8 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
tagsMap.get(row.monitor_id).push({
|
||||
tag_id: row.tag_id,
|
||||
monitor_id: row.monitor_id,
|
||||
value: row.value,
|
||||
name: row.name,
|
||||
color: row.color
|
||||
});
|
||||
|
@@ -485,7 +485,7 @@ function ApiCache() {
|
||||
}
|
||||
|
||||
if (typeof duration === "string") {
|
||||
let split = duration.match(/^([\d\.,]+)\s?(\w+)$/);
|
||||
let split = duration.match(/^([\d\.,]+)\s?([a-zA-Z]+)$/);
|
||||
|
||||
if (split.length === 3) {
|
||||
let len = parseFloat(split[1]);
|
||||
|
@@ -240,6 +240,14 @@ class RealBrowserMonitorType extends MonitorType {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
// Prevent Local File Inclusion
|
||||
// Accept only http:// and https://
|
||||
// https://github.com/louislam/uptime-kuma/security/advisories/GHSA-2qgm-m29m-cj2h
|
||||
let url = new URL(monitor.url);
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
||||
throw new Error("Invalid url protocol, only http and https are allowed.");
|
||||
}
|
||||
|
||||
const res = await page.goto(monitor.url, {
|
||||
waitUntil: "networkidle",
|
||||
timeout: monitor.interval * 1000 * 0.8,
|
||||
|
@@ -17,7 +17,7 @@ class AliyunSMS extends NotificationProvider {
|
||||
if (heartbeatJSON != null) {
|
||||
let msgBody = JSON.stringify({
|
||||
name: monitorJSON["name"],
|
||||
time: heartbeatJSON["time"],
|
||||
time: heartbeatJSON["localDateTime"],
|
||||
status: this.statusToString(heartbeatJSON["status"]),
|
||||
msg: heartbeatJSON["msg"],
|
||||
});
|
||||
|
@@ -72,6 +72,7 @@ class GoogleChat extends NotificationProvider {
|
||||
|
||||
// construct json data
|
||||
let data = {
|
||||
fallbackText: chatHeader["title"],
|
||||
cardsV2: [
|
||||
{
|
||||
card: {
|
||||
|
@@ -1,11 +1,10 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const {
|
||||
relayInit,
|
||||
getPublicKey,
|
||||
getEventHash,
|
||||
getSignature,
|
||||
finalizeEvent,
|
||||
Relay,
|
||||
kinds,
|
||||
nip04,
|
||||
nip19
|
||||
nip19,
|
||||
} = require("nostr-tools");
|
||||
|
||||
// polyfills for node versions
|
||||
@@ -31,7 +30,6 @@ class Nostr extends NotificationProvider {
|
||||
const createdAt = Math.floor(Date.now() / 1000);
|
||||
|
||||
const senderPrivateKey = await this.getPrivateKey(notification.sender);
|
||||
const senderPublicKey = getPublicKey(senderPrivateKey);
|
||||
const recipientsPublicKeys = await this.getPublicKeys(notification.recipients);
|
||||
|
||||
// Create NIP-04 encrypted direct message event for each recipient
|
||||
@@ -39,34 +37,41 @@ class Nostr extends NotificationProvider {
|
||||
for (const recipientPublicKey of recipientsPublicKeys) {
|
||||
const ciphertext = await nip04.encrypt(senderPrivateKey, recipientPublicKey, msg);
|
||||
let event = {
|
||||
kind: 4,
|
||||
pubkey: senderPublicKey,
|
||||
kind: kinds.EncryptedDirectMessage,
|
||||
created_at: createdAt,
|
||||
tags: [[ "p", recipientPublicKey ]],
|
||||
content: ciphertext,
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, senderPrivateKey);
|
||||
events.push(event);
|
||||
const signedEvent = finalizeEvent(event, senderPrivateKey);
|
||||
events.push(signedEvent);
|
||||
}
|
||||
|
||||
// Publish events to each relay
|
||||
const relays = notification.relays.split("\n");
|
||||
let successfulRelays = 0;
|
||||
|
||||
// Connect to each relay
|
||||
for (const relayUrl of relays) {
|
||||
const relay = relayInit(relayUrl);
|
||||
try {
|
||||
await relay.connect();
|
||||
successfulRelays++;
|
||||
const relay = await Relay.connect(relayUrl);
|
||||
let eventIndex = 0;
|
||||
|
||||
// Publish events
|
||||
for (const event of events) {
|
||||
relay.publish(event);
|
||||
}
|
||||
// Authenticate to the relay, if required
|
||||
try {
|
||||
await relay.publish(events[0]);
|
||||
eventIndex = 1;
|
||||
} catch (error) {
|
||||
continue;
|
||||
if (relay.challenge) {
|
||||
await relay.auth(async (evt) => {
|
||||
return finalizeEvent(evt, senderPrivateKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
for (let i = eventIndex; i < events.length; i++) {
|
||||
await relay.publish(events[i]);
|
||||
}
|
||||
successfulRelays++;
|
||||
} catch (error) {
|
||||
console.error(`Failed to publish event to ${relayUrl}:`, error);
|
||||
} finally {
|
||||
relay.close();
|
||||
}
|
||||
@@ -90,7 +95,7 @@ class Nostr extends NotificationProvider {
|
||||
const { data } = senderDecodeResult;
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get private key: ${error.message}`);
|
||||
throw new Error(`Failed to decode private key for sender ${sender}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,10 +114,10 @@ class Nostr extends NotificationProvider {
|
||||
if (type === "npub") {
|
||||
publicKeys.push(data);
|
||||
} else {
|
||||
throw new Error("not an npub");
|
||||
throw new Error(`Recipient ${recipient} is not an npub`);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Error decoding recipient: ${error}`);
|
||||
throw new Error(`Error decoding recipient ${recipient}: ${error}`);
|
||||
}
|
||||
}
|
||||
return publicKeys;
|
||||
|
@@ -1,3 +1,6 @@
|
||||
const { Liquid } = require("liquidjs");
|
||||
const { DOWN } = require("../../src/util");
|
||||
|
||||
class NotificationProvider {
|
||||
|
||||
/**
|
||||
@@ -49,6 +52,50 @@ class NotificationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a message template with notification context
|
||||
* @param {string} template the template
|
||||
* @param {string} msg the message that will be included in the context
|
||||
* @param {?object} monitorJSON Monitor details (For Up/Down/Cert-Expiry only)
|
||||
* @param {?object} heartbeatJSON Heartbeat details (For Up/Down only)
|
||||
* @returns {Promise<string>} rendered template
|
||||
*/
|
||||
async renderTemplate(template, msg, monitorJSON, heartbeatJSON) {
|
||||
const engine = new Liquid();
|
||||
const parsedTpl = engine.parse(template);
|
||||
|
||||
// Let's start with dummy values to simplify code
|
||||
let monitorName = "Monitor Name not available";
|
||||
let monitorHostnameOrURL = "testing.hostname";
|
||||
|
||||
if (monitorJSON !== null) {
|
||||
monitorName = monitorJSON["name"];
|
||||
monitorHostnameOrURL = this.extractAddress(monitorJSON);
|
||||
}
|
||||
|
||||
let serviceStatus = "⚠️ Test";
|
||||
if (heartbeatJSON !== null) {
|
||||
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
|
||||
}
|
||||
|
||||
const context = {
|
||||
// for v1 compatibility, to be removed in v3
|
||||
"STATUS": serviceStatus,
|
||||
"NAME": monitorName,
|
||||
"HOSTNAME_OR_URL": monitorHostnameOrURL,
|
||||
|
||||
// variables which are officially supported
|
||||
"status": serviceStatus,
|
||||
"name": monitorName,
|
||||
"hostnameOrURL": monitorHostnameOrURL,
|
||||
monitorJSON,
|
||||
heartbeatJSON,
|
||||
msg,
|
||||
};
|
||||
|
||||
return engine.render(parsedTpl, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error
|
||||
* @param {any} error The error to throw
|
||||
|
@@ -11,7 +11,8 @@ class PushDeer extends NotificationProvider {
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const serverUrl = notification.pushdeerServer || "https://api2.pushdeer.com";
|
||||
const url = `${serverUrl.trim().replace(/\/*$/, "")}/message/push`;
|
||||
// capture group below is nessesary to prevent an ReDOS-attack
|
||||
const url = `${serverUrl.trim().replace(/([^/])\/+$/, "$1")}/message/push`;
|
||||
|
||||
let valid = msg != null && monitorJSON != null && heartbeatJSON != null;
|
||||
|
||||
|
56
server/notification-providers/pushplus.js
Normal file
56
server/notification-providers/pushplus.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class PushPlus extends NotificationProvider {
|
||||
name = "PushPlus";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @param {BeanModel} notification Notification object
|
||||
* @param {string} msg Message content
|
||||
* @param {?object} monitorJSON Monitor details
|
||||
* @param {?object} heartbeatJSON Heartbeat details
|
||||
* @returns {Promise<string>} Success message
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://www.pushplus.plus/send";
|
||||
try {
|
||||
const config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
const params = {
|
||||
"token": notification.pushPlusSendKey,
|
||||
"title": this.checkStatus(heartbeatJSON, monitorJSON),
|
||||
"content": msg,
|
||||
"template": "html"
|
||||
};
|
||||
await axios.post(url, params, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted title for message
|
||||
* @param {?object} heartbeatJSON Heartbeat details (For Up/Down only)
|
||||
* @param {?object} monitorJSON Monitor details (For Up/Down only)
|
||||
* @returns {string} Formatted title
|
||||
*/
|
||||
checkStatus(heartbeatJSON, monitorJSON) {
|
||||
let title = "UptimeKuma Message";
|
||||
if (heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
||||
title = "UptimeKuma Monitor Up " + monitorJSON["name"];
|
||||
}
|
||||
if (heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
|
||||
title = "UptimeKuma Monitor Down " + monitorJSON["name"];
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PushPlus;
|
@@ -1,7 +1,7 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { setSettings, setting } = require("../util-server");
|
||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||
const { getMonitorRelativeURL, UP, log } = require("../../src/util");
|
||||
|
||||
class Slack extends NotificationProvider {
|
||||
name = "slack";
|
||||
@@ -50,15 +50,20 @@ class Slack extends NotificationProvider {
|
||||
|
||||
const address = this.extractAddress(monitorJSON);
|
||||
if (address) {
|
||||
actions.push({
|
||||
"type": "button",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": "Visit site",
|
||||
},
|
||||
"value": "Site",
|
||||
"url": address,
|
||||
});
|
||||
try {
|
||||
actions.push({
|
||||
"type": "button",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": "Visit site",
|
||||
},
|
||||
"value": "Site",
|
||||
"url": new URL(address),
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
log.debug("slack", `Failed to parse address ${address} as URL`);
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
const nodemailer = require("nodemailer");
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const { DOWN } = require("../../src/util");
|
||||
const { Liquid } = require("liquidjs");
|
||||
|
||||
class SMTP extends NotificationProvider {
|
||||
name = "smtp";
|
||||
@@ -53,15 +51,11 @@ class SMTP extends NotificationProvider {
|
||||
const customSubject = notification.customSubject?.trim() || "";
|
||||
const customBody = notification.customBody?.trim() || "";
|
||||
|
||||
const context = this.generateContext(msg, monitorJSON, heartbeatJSON);
|
||||
const engine = new Liquid();
|
||||
if (customSubject !== "") {
|
||||
const tpl = engine.parse(customSubject);
|
||||
subject = await engine.render(tpl, context);
|
||||
subject = await this.renderTemplate(customSubject, msg, monitorJSON, heartbeatJSON);
|
||||
}
|
||||
if (customBody !== "") {
|
||||
const tpl = engine.parse(customBody);
|
||||
body = await engine.render(tpl, context);
|
||||
body = await this.renderTemplate(customBody, msg, monitorJSON, heartbeatJSON);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,43 +72,6 @@ class SMTP extends NotificationProvider {
|
||||
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate context for LiquidJS
|
||||
* @param {string} msg the message that will be included in the context
|
||||
* @param {?object} monitorJSON Monitor details (For Up/Down/Cert-Expiry only)
|
||||
* @param {?object} heartbeatJSON Heartbeat details (For Up/Down only)
|
||||
* @returns {{STATUS: string, status: string, HOSTNAME_OR_URL: string, hostnameOrUrl: string, NAME: string, name: string, monitorJSON: ?object, heartbeatJSON: ?object, msg: string}} the context
|
||||
*/
|
||||
generateContext(msg, monitorJSON, heartbeatJSON) {
|
||||
// Let's start with dummy values to simplify code
|
||||
let monitorName = "Monitor Name not available";
|
||||
let monitorHostnameOrURL = "testing.hostname";
|
||||
|
||||
if (monitorJSON !== null) {
|
||||
monitorName = monitorJSON["name"];
|
||||
monitorHostnameOrURL = this.extractAddress(monitorJSON);
|
||||
}
|
||||
|
||||
let serviceStatus = "⚠️ Test";
|
||||
if (heartbeatJSON !== null) {
|
||||
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
|
||||
}
|
||||
return {
|
||||
// for v1 compatibility, to be removed in v3
|
||||
"STATUS": serviceStatus,
|
||||
"NAME": monitorName,
|
||||
"HOSTNAME_OR_URL": monitorHostnameOrURL,
|
||||
|
||||
// variables which are officially supported
|
||||
"status": serviceStatus,
|
||||
"name": monitorName,
|
||||
"hostnameOrURL": monitorHostnameOrURL,
|
||||
monitorJSON,
|
||||
heartbeatJSON,
|
||||
msg,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SMTP;
|
||||
|
@@ -9,7 +9,7 @@ class Telegram extends NotificationProvider {
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://api.telegram.org";
|
||||
const url = notification.telegramServerUrl ?? "https://api.telegram.org";
|
||||
|
||||
try {
|
||||
let params = {
|
||||
@@ -22,6 +22,14 @@ class Telegram extends NotificationProvider {
|
||||
params.message_thread_id = notification.telegramMessageThreadID;
|
||||
}
|
||||
|
||||
if (notification.telegramUseTemplate) {
|
||||
params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON);
|
||||
|
||||
if (notification.telegramTemplateParseMode !== "plain") {
|
||||
params.parse_mode = notification.telegramTemplateParseMode;
|
||||
}
|
||||
}
|
||||
|
||||
await axios.get(`${url}/bot${notification.telegramBotToken}/sendMessage`, {
|
||||
params: params,
|
||||
});
|
||||
|
40
server/notification-providers/waha.js
Normal file
40
server/notification-providers/waha.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class WAHA extends NotificationProvider {
|
||||
name = "waha";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
const config = {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": notification.wahaApiKey,
|
||||
}
|
||||
};
|
||||
|
||||
let data = {
|
||||
"session": notification.wahaSession,
|
||||
"chatId": notification.wahaChatId,
|
||||
"text": msg,
|
||||
};
|
||||
|
||||
let url = notification.wahaApiUrl.replace(/([^/])\/+$/, "$1") + "/api/sendText";
|
||||
|
||||
await axios.post(url, data, config);
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = WAHA;
|
@@ -1,7 +1,6 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const FormData = require("form-data");
|
||||
const { Liquid } = require("liquidjs");
|
||||
|
||||
class Webhook extends NotificationProvider {
|
||||
name = "webhook";
|
||||
@@ -28,17 +27,7 @@ class Webhook extends NotificationProvider {
|
||||
config.headers = formData.getHeaders();
|
||||
data = formData;
|
||||
} else if (notification.webhookContentType === "custom") {
|
||||
// Initialize LiquidJS and parse the custom Body Template
|
||||
const engine = new Liquid();
|
||||
const tpl = engine.parse(notification.webhookCustomBody);
|
||||
|
||||
// Insert templated values into Body
|
||||
data = await engine.render(tpl,
|
||||
{
|
||||
msg,
|
||||
heartbeatJSON,
|
||||
monitorJSON
|
||||
});
|
||||
data = await this.renderTemplate(notification.webhookCustomBody, msg, monitorJSON, heartbeatJSON);
|
||||
}
|
||||
|
||||
if (notification.webhookAdditionalHeaders) {
|
||||
|
@@ -24,7 +24,7 @@ class Whapi extends NotificationProvider {
|
||||
"body": msg,
|
||||
};
|
||||
|
||||
let url = (notification.whapiApiUrl || "https://gate.whapi.cloud/").replace(/\/+$/, "") + "/messages/text";
|
||||
let url = (notification.whapiApiUrl || "https://gate.whapi.cloud/").replace(/([^/])\/+$/, "$1") + "/messages/text";
|
||||
|
||||
await axios.post(url, data, config);
|
||||
|
||||
|
57
server/notification-providers/yzj.js
Normal file
57
server/notification-providers/yzj.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
const { default: axios } = require("axios");
|
||||
|
||||
class YZJ extends NotificationProvider {
|
||||
name = "YZJ";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
if (heartbeatJSON !== null) {
|
||||
msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
||||
}
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
const params = {
|
||||
content: msg
|
||||
};
|
||||
// yzjtype=0 => general robot
|
||||
const url = `${notification.yzjWebHookUrl}?yzjtype=0&yzjtoken=${notification.yzjToken}`;
|
||||
|
||||
const result = await axios.post(url, params, config);
|
||||
if (!result.data?.success) {
|
||||
throw new Error(result.data?.errmsg ?? "yzj's server did not respond with the expected result");
|
||||
}
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert status constant to string
|
||||
* @param {string} status The status constant
|
||||
* @returns {string} status
|
||||
*/
|
||||
statusToString(status) {
|
||||
switch (status) {
|
||||
case DOWN:
|
||||
return "❌";
|
||||
case UP:
|
||||
return "✅";
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = YZJ;
|
@@ -39,6 +39,7 @@ const PromoSMS = require("./notification-providers/promosms");
|
||||
const Pushbullet = require("./notification-providers/pushbullet");
|
||||
const PushDeer = require("./notification-providers/pushdeer");
|
||||
const Pushover = require("./notification-providers/pushover");
|
||||
const PushPlus = require("./notification-providers/pushplus");
|
||||
const Pushy = require("./notification-providers/pushy");
|
||||
const RocketChat = require("./notification-providers/rocket-chat");
|
||||
const SerwerSMS = require("./notification-providers/serwersms");
|
||||
@@ -64,11 +65,13 @@ const ServerChan = require("./notification-providers/serverchan");
|
||||
const ZohoCliq = require("./notification-providers/zoho-cliq");
|
||||
const SevenIO = require("./notification-providers/sevenio");
|
||||
const Whapi = require("./notification-providers/whapi");
|
||||
const WAHA = require("./notification-providers/waha");
|
||||
const GtxMessaging = require("./notification-providers/gtx-messaging");
|
||||
const Cellsynt = require("./notification-providers/cellsynt");
|
||||
const Onesender = require("./notification-providers/onesender");
|
||||
const Wpush = require("./notification-providers/wpush");
|
||||
const SendGrid = require("./notification-providers/send-grid");
|
||||
const YZJ = require("./notification-providers/yzj");
|
||||
|
||||
class Notification {
|
||||
|
||||
@@ -126,6 +129,7 @@ class Notification {
|
||||
new Pushbullet(),
|
||||
new PushDeer(),
|
||||
new Pushover(),
|
||||
new PushPlus(),
|
||||
new Pushy(),
|
||||
new RocketChat(),
|
||||
new ServerChan(),
|
||||
@@ -151,10 +155,12 @@ class Notification {
|
||||
new ZohoCliq(),
|
||||
new SevenIO(),
|
||||
new Whapi(),
|
||||
new WAHA(),
|
||||
new GtxMessaging(),
|
||||
new Cellsynt(),
|
||||
new Wpush(),
|
||||
new SendGrid()
|
||||
new SendGrid(),
|
||||
new YZJ()
|
||||
];
|
||||
for (let item of list) {
|
||||
if (! item.name) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const { R } = require("redbean-node");
|
||||
const HttpProxyAgent = require("http-proxy-agent");
|
||||
const HttpsProxyAgent = require("https-proxy-agent");
|
||||
const SocksProxyAgent = require("socks-proxy-agent");
|
||||
const { HttpProxyAgent } = require("http-proxy-agent");
|
||||
const { HttpsProxyAgent } = require("https-proxy-agent");
|
||||
const { SocksProxyAgent } = require("socks-proxy-agent");
|
||||
const { debug } = require("../src/util");
|
||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||
const { CookieJar } = require("tough-cookie");
|
||||
@@ -100,17 +100,17 @@ class Proxy {
|
||||
let jar = new CookieJar();
|
||||
|
||||
const proxyOptions = {
|
||||
protocol: proxy.protocol,
|
||||
host: proxy.host,
|
||||
port: proxy.port,
|
||||
cookies: { jar },
|
||||
};
|
||||
|
||||
const proxyUrl = new URL(`${proxy.protocol}://${proxy.host}:${proxy.port}`);
|
||||
|
||||
if (proxy.auth) {
|
||||
proxyOptions.auth = `${proxy.username}:${proxy.password}`;
|
||||
proxyUrl.username = proxy.username;
|
||||
proxyUrl.password = proxy.password;
|
||||
}
|
||||
|
||||
debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`);
|
||||
debug(`Proxy URL: ${proxyUrl.toString()}`);
|
||||
debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
|
||||
debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
|
||||
|
||||
@@ -122,15 +122,15 @@ class Proxy {
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const HttpsCookieProxyAgent = createCookieAgent(HttpsProxyAgent);
|
||||
|
||||
httpAgent = new HttpCookieProxyAgent({
|
||||
...httpAgentOptions || {},
|
||||
httpAgent = new HttpCookieProxyAgent(proxyUrl.toString(), {
|
||||
...(httpAgentOptions || {}),
|
||||
...proxyOptions,
|
||||
});
|
||||
httpsAgent = new HttpsCookieProxyAgent(proxyUrl.toString(), {
|
||||
...(httpsAgentOptions || {}),
|
||||
...proxyOptions,
|
||||
});
|
||||
|
||||
httpsAgent = new HttpsCookieProxyAgent({
|
||||
...httpsAgentOptions || {},
|
||||
...proxyOptions,
|
||||
});
|
||||
break;
|
||||
case "socks":
|
||||
case "socks5":
|
||||
@@ -138,10 +138,9 @@ class Proxy {
|
||||
case "socks4":
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const SocksCookieProxyAgent = createCookieAgent(SocksProxyAgent);
|
||||
agent = new SocksCookieProxyAgent({
|
||||
agent = new SocksCookieProxyAgent(proxyUrl.toString(), {
|
||||
...httpAgentOptions,
|
||||
...httpsAgentOptions,
|
||||
...proxyOptions,
|
||||
tls: {
|
||||
rejectUnauthorized: httpsAgentOptions.rejectUnauthorized,
|
||||
},
|
||||
|
@@ -15,11 +15,13 @@ const server = UptimeKumaServer.getInstance();
|
||||
|
||||
router.get("/status/:slug", cache("5 minutes"), async (request, response) => {
|
||||
let slug = request.params.slug;
|
||||
slug = slug.toLowerCase();
|
||||
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
|
||||
});
|
||||
|
||||
router.get("/status/:slug/rss", cache("5 minutes"), async (request, response) => {
|
||||
let slug = request.params.slug;
|
||||
slug = slug.toLowerCase();
|
||||
await StatusPage.handleStatusPageRSSResponse(response, slug);
|
||||
});
|
||||
|
||||
@@ -37,6 +39,7 @@ router.get("/status-page", cache("5 minutes"), async (request, response) => {
|
||||
router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => {
|
||||
allowDevAllOrigin(response);
|
||||
let slug = request.params.slug;
|
||||
slug = slug.toLowerCase();
|
||||
|
||||
try {
|
||||
// Get Status Page
|
||||
@@ -69,6 +72,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
|
||||
let uptimeList = {};
|
||||
|
||||
let slug = request.params.slug;
|
||||
slug = slug.toLowerCase();
|
||||
let statusPageID = await StatusPage.slugToID(slug);
|
||||
|
||||
let monitorIDList = await R.getCol(`
|
||||
@@ -111,6 +115,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
|
||||
router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async (request, response) => {
|
||||
allowDevAllOrigin(response);
|
||||
let slug = request.params.slug;
|
||||
slug = slug.toLowerCase();
|
||||
|
||||
try {
|
||||
// Get Status Page
|
||||
@@ -145,7 +150,8 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async
|
||||
// overall status-page status badge
|
||||
router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, response) => {
|
||||
allowDevAllOrigin(response);
|
||||
const slug = request.params.slug;
|
||||
let slug = request.params.slug;
|
||||
slug = slug.toLowerCase();
|
||||
const statusPageID = await StatusPage.slugToID(slug);
|
||||
const {
|
||||
label,
|
||||
|
@@ -1604,18 +1604,20 @@ let needSetup = false;
|
||||
|
||||
await server.start();
|
||||
|
||||
server.httpServer.listen(port, hostname, () => {
|
||||
server.httpServer.listen(port, hostname, async () => {
|
||||
if (hostname) {
|
||||
log.info("server", `Listening on ${hostname}:${port}`);
|
||||
} else {
|
||||
log.info("server", `Listening on ${port}`);
|
||||
}
|
||||
startMonitors();
|
||||
await startMonitors();
|
||||
|
||||
// Put this here. Start background jobs after the db and server is ready to prevent clear up during db migration.
|
||||
await initBackgroundJobs();
|
||||
|
||||
checkVersion.startInterval();
|
||||
});
|
||||
|
||||
await initBackgroundJobs();
|
||||
|
||||
// Start cloudflared at the end if configured
|
||||
await cloudflaredAutoStart(cloudflaredToken);
|
||||
|
||||
@@ -1714,7 +1716,7 @@ async function initDatabase(testMode = false) {
|
||||
log.info("server", "Connected to the database");
|
||||
|
||||
// Patch the database
|
||||
await Database.patch();
|
||||
await Database.patch(port, hostname);
|
||||
|
||||
let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [
|
||||
"jwtSecret",
|
||||
@@ -1809,7 +1811,11 @@ async function startMonitors() {
|
||||
}
|
||||
|
||||
for (let monitor of list) {
|
||||
await monitor.start(io);
|
||||
try {
|
||||
await monitor.start(io);
|
||||
} catch (e) {
|
||||
log.error("monitor", e);
|
||||
}
|
||||
// Give some delays, so all monitors won't make request at the same moment when just start the server.
|
||||
await sleep(getRandomInt(300, 1000));
|
||||
}
|
||||
|
@@ -220,13 +220,17 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||
|
||||
// Delete groups that are not in the list
|
||||
log.debug("socket", "Delete groups that are not in the list");
|
||||
const slots = groupIDList.map(() => "?").join(",");
|
||||
if (groupIDList.length === 0) {
|
||||
await R.exec("DELETE FROM `group` WHERE status_page_id = ?", [ statusPage.id ]);
|
||||
} else {
|
||||
const slots = groupIDList.map(() => "?").join(",");
|
||||
|
||||
const data = [
|
||||
...groupIDList,
|
||||
statusPage.id
|
||||
];
|
||||
await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots}) AND status_page_id = ?`, data);
|
||||
const data = [
|
||||
...groupIDList,
|
||||
statusPage.id
|
||||
];
|
||||
await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots}) AND status_page_id = ?`, data);
|
||||
}
|
||||
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
|
||||
@@ -288,6 +292,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||
ok: true,
|
||||
msg: "successAdded",
|
||||
msgi18n: true,
|
||||
slug: slug
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
@@ -12,7 +12,6 @@ class UptimeCalculator {
|
||||
* @private
|
||||
* @type {{string:UptimeCalculator}}
|
||||
*/
|
||||
|
||||
static list = {};
|
||||
|
||||
/**
|
||||
@@ -55,6 +54,15 @@ class UptimeCalculator {
|
||||
lastHourlyStatBean = null;
|
||||
lastMinutelyStatBean = null;
|
||||
|
||||
/**
|
||||
* For migration purposes.
|
||||
* @type {boolean}
|
||||
*/
|
||||
migrationMode = false;
|
||||
|
||||
statMinutelyKeepHour = 24;
|
||||
statHourlyKeepDay = 30;
|
||||
|
||||
/**
|
||||
* Get the uptime calculator for a monitor
|
||||
* Initializes and returns the monitor if it does not exist
|
||||
@@ -189,16 +197,19 @@ class UptimeCalculator {
|
||||
/**
|
||||
* @param {number} status status
|
||||
* @param {number} ping Ping
|
||||
* @param {dayjs.Dayjs} date Date (Only for migration)
|
||||
* @returns {dayjs.Dayjs} date
|
||||
* @throws {Error} Invalid status
|
||||
*/
|
||||
async update(status, ping = 0) {
|
||||
let date = this.getCurrentDate();
|
||||
async update(status, ping = 0, date) {
|
||||
if (!date) {
|
||||
date = this.getCurrentDate();
|
||||
}
|
||||
|
||||
let flatStatus = this.flatStatus(status);
|
||||
|
||||
if (flatStatus === DOWN && ping > 0) {
|
||||
log.warn("uptime-calc", "The ping is not effective when the status is DOWN");
|
||||
log.debug("uptime-calc", "The ping is not effective when the status is DOWN");
|
||||
}
|
||||
|
||||
let divisionKey = this.getMinutelyKey(date);
|
||||
@@ -297,47 +308,61 @@ class UptimeCalculator {
|
||||
}
|
||||
await R.store(dailyStatBean);
|
||||
|
||||
let hourlyStatBean = await this.getHourlyStatBean(hourlyKey);
|
||||
hourlyStatBean.up = hourlyData.up;
|
||||
hourlyStatBean.down = hourlyData.down;
|
||||
hourlyStatBean.ping = hourlyData.avgPing;
|
||||
hourlyStatBean.pingMin = hourlyData.minPing;
|
||||
hourlyStatBean.pingMax = hourlyData.maxPing;
|
||||
{
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = hourlyData;
|
||||
if (Object.keys(extras).length > 0) {
|
||||
hourlyStatBean.extras = JSON.stringify(extras);
|
||||
let currentDate = this.getCurrentDate();
|
||||
|
||||
// For migration mode, we don't need to store old hourly and minutely data, but we need 30-day's hourly data
|
||||
// Run anyway for non-migration mode
|
||||
if (!this.migrationMode || date.isAfter(currentDate.subtract(this.statHourlyKeepDay, "day"))) {
|
||||
let hourlyStatBean = await this.getHourlyStatBean(hourlyKey);
|
||||
hourlyStatBean.up = hourlyData.up;
|
||||
hourlyStatBean.down = hourlyData.down;
|
||||
hourlyStatBean.ping = hourlyData.avgPing;
|
||||
hourlyStatBean.pingMin = hourlyData.minPing;
|
||||
hourlyStatBean.pingMax = hourlyData.maxPing;
|
||||
{
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = hourlyData;
|
||||
if (Object.keys(extras).length > 0) {
|
||||
hourlyStatBean.extras = JSON.stringify(extras);
|
||||
}
|
||||
}
|
||||
await R.store(hourlyStatBean);
|
||||
}
|
||||
await R.store(hourlyStatBean);
|
||||
|
||||
let minutelyStatBean = await this.getMinutelyStatBean(divisionKey);
|
||||
minutelyStatBean.up = minutelyData.up;
|
||||
minutelyStatBean.down = minutelyData.down;
|
||||
minutelyStatBean.ping = minutelyData.avgPing;
|
||||
minutelyStatBean.pingMin = minutelyData.minPing;
|
||||
minutelyStatBean.pingMax = minutelyData.maxPing;
|
||||
{
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = minutelyData;
|
||||
if (Object.keys(extras).length > 0) {
|
||||
minutelyStatBean.extras = JSON.stringify(extras);
|
||||
// For migration mode, we don't need to store old hourly and minutely data, but we need 24-hour's minutely data
|
||||
// Run anyway for non-migration mode
|
||||
if (!this.migrationMode || date.isAfter(currentDate.subtract(this.statMinutelyKeepHour, "hour"))) {
|
||||
let minutelyStatBean = await this.getMinutelyStatBean(divisionKey);
|
||||
minutelyStatBean.up = minutelyData.up;
|
||||
minutelyStatBean.down = minutelyData.down;
|
||||
minutelyStatBean.ping = minutelyData.avgPing;
|
||||
minutelyStatBean.pingMin = minutelyData.minPing;
|
||||
minutelyStatBean.pingMax = minutelyData.maxPing;
|
||||
{
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = minutelyData;
|
||||
if (Object.keys(extras).length > 0) {
|
||||
minutelyStatBean.extras = JSON.stringify(extras);
|
||||
}
|
||||
}
|
||||
await R.store(minutelyStatBean);
|
||||
}
|
||||
await R.store(minutelyStatBean);
|
||||
|
||||
// Remove the old data
|
||||
log.debug("uptime-calc", "Remove old data");
|
||||
await R.exec("DELETE FROM stat_minutely WHERE monitor_id = ? AND timestamp < ?", [
|
||||
this.monitorID,
|
||||
this.getMinutelyKey(date.subtract(24, "hour")),
|
||||
]);
|
||||
// No need to remove old data in migration mode
|
||||
if (!this.migrationMode) {
|
||||
// Remove the old data
|
||||
// TODO: Improvement: Convert it to a job?
|
||||
log.debug("uptime-calc", "Remove old data");
|
||||
await R.exec("DELETE FROM stat_minutely WHERE monitor_id = ? AND timestamp < ?", [
|
||||
this.monitorID,
|
||||
this.getMinutelyKey(currentDate.subtract(this.statMinutelyKeepHour, "hour")),
|
||||
]);
|
||||
|
||||
await R.exec("DELETE FROM stat_hourly WHERE monitor_id = ? AND timestamp < ?", [
|
||||
this.monitorID,
|
||||
this.getHourlyKey(date.subtract(30, "day")),
|
||||
]);
|
||||
await R.exec("DELETE FROM stat_hourly WHERE monitor_id = ? AND timestamp < ?", [
|
||||
this.monitorID,
|
||||
this.getHourlyKey(currentDate.subtract(this.statHourlyKeepDay, "day")),
|
||||
]);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
@@ -812,6 +837,14 @@ class UptimeCalculator {
|
||||
return dayjs.utc();
|
||||
}
|
||||
|
||||
/**
|
||||
* For migration purposes.
|
||||
* @param {boolean} value Migration mode on/off
|
||||
* @returns {void}
|
||||
*/
|
||||
setMigrationMode(value) {
|
||||
this.migrationMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
class UptimeDataResult {
|
||||
|
@@ -0,0 +1,22 @@
|
||||
const ColumnCompilerMySQL = require("knex/lib/dialects/mysql/schema/mysql-columncompiler");
|
||||
const { formatDefault } = require("knex/lib/formatter/formatterUtils");
|
||||
const { log } = require("../../../../../../../src/util");
|
||||
|
||||
class KumaColumnCompiler extends ColumnCompilerMySQL {
|
||||
/**
|
||||
* Override defaultTo method to handle default value for TEXT fields
|
||||
* @param {any} value Value
|
||||
* @returns {string|void} Default value (Don't understand why it can return void or string, but it's the original code, lol)
|
||||
*/
|
||||
defaultTo(value) {
|
||||
if (this.type === "text" && typeof value === "string") {
|
||||
log.debug("defaultTo", `${this.args[0]}: ${this.type} ${value} ${typeof value}`);
|
||||
// MySQL 8.0 is required and only if the value is written as an expression: https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html
|
||||
// MariaDB 10.2 is required: https://mariadb.com/kb/en/text/
|
||||
return `default (${formatDefault(value, this.type, this.client)})`;
|
||||
}
|
||||
return super.defaultTo.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = KumaColumnCompiler;
|
84
server/utils/simple-migration-server.js
Normal file
84
server/utils/simple-migration-server.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const express = require("express");
|
||||
const http = require("node:http");
|
||||
const { log } = require("../../src/util");
|
||||
|
||||
/**
|
||||
* SimpleMigrationServer
|
||||
* For displaying the migration status of the server
|
||||
* Also, it is used to let Docker healthcheck know the status of the server, as the main server is not started yet, healthcheck will think the server is down incorrectly.
|
||||
*/
|
||||
class SimpleMigrationServer {
|
||||
/**
|
||||
* Express app instance
|
||||
* @type {?Express}
|
||||
*/
|
||||
app;
|
||||
|
||||
/**
|
||||
* Server instance
|
||||
* @type {?Server}
|
||||
*/
|
||||
server;
|
||||
|
||||
/**
|
||||
* Response object
|
||||
* @type {?Response}
|
||||
*/
|
||||
response;
|
||||
|
||||
/**
|
||||
* Start the server
|
||||
* @param {number} port Port
|
||||
* @param {string} hostname Hostname
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
start(port, hostname) {
|
||||
this.app = express();
|
||||
this.server = http.createServer(this.app);
|
||||
|
||||
this.app.get("/", (req, res) => {
|
||||
res.set("Content-Type", "text/plain");
|
||||
res.write("Migration is in progress, listening message...\n");
|
||||
if (this.response) {
|
||||
this.response.write("Disconnected\n");
|
||||
this.response.end();
|
||||
}
|
||||
this.response = res;
|
||||
// never ending response
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.server.listen(port, hostname, () => {
|
||||
if (hostname) {
|
||||
log.info("migration", `Migration server is running on http://${hostname}:${port}`);
|
||||
} else {
|
||||
log.info("migration", `Migration server is running on http://localhost:${port}`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the message
|
||||
* @param {string} msg Message to update
|
||||
* @returns {void}
|
||||
*/
|
||||
update(msg) {
|
||||
this.response?.write(msg + "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the server
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async stop() {
|
||||
this.response?.write("Finished, please refresh this page.\n");
|
||||
this.response?.end();
|
||||
await this.server?.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SimpleMigrationServer,
|
||||
};
|
@@ -619,7 +619,7 @@ $shadow-box-padding: 20px;
|
||||
bottom: 0;
|
||||
margin-left: -$shadow-box-padding;
|
||||
margin-right: -$shadow-box-padding;
|
||||
z-index: 100;
|
||||
z-index: 10;
|
||||
background-color: rgba(white, 0.2);
|
||||
backdrop-filter: blur(2px);
|
||||
border-radius: 0 0 10px 10px;
|
||||
|
@@ -73,12 +73,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.model.length === 0) {
|
||||
this.addCondition();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getNewGroup() {
|
||||
return {
|
||||
|
@@ -163,6 +163,7 @@ export default {
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"SevenIO": "SevenIO",
|
||||
"whapi": "WhatsApp (Whapi)",
|
||||
"waha": "WhatsApp (WAHA)",
|
||||
"gtxmessaging": "GtxMessaging",
|
||||
"Cellsynt": "Cellsynt",
|
||||
"SendGrid": "SendGrid"
|
||||
@@ -181,8 +182,10 @@ export default {
|
||||
"SMSManager": "SmsManager (smsmanager.cz)",
|
||||
"WeCom": "WeCom (企业微信群机器人)",
|
||||
"ServerChan": "ServerChan (Server酱)",
|
||||
"PushPlus": "PushPlus (推送加)",
|
||||
"smsc": "SMSC",
|
||||
"WPush": "WPush(wpush.cn)",
|
||||
"YZJ": "YZJ (云之家自定义机器人)"
|
||||
};
|
||||
|
||||
// Sort by notification name
|
||||
|
@@ -197,7 +197,7 @@ export default {
|
||||
return tagOptions;
|
||||
},
|
||||
selectedTags() {
|
||||
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.id === tag.id));
|
||||
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.tag_id === tag.tag_id));
|
||||
},
|
||||
colorOptions() {
|
||||
return colorOptions(this);
|
||||
|
75
src/components/TemplatedInput.vue
Normal file
75
src/components/TemplatedInput.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="form-text mb-2">
|
||||
<i18n-t tag="div" keypath="liquidIntroduction">
|
||||
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
|
||||
</i18n-t>
|
||||
|
||||
<code v-pre>{{ msg }}</code>: {{ $t("templateMsg") }}<br />
|
||||
<code v-pre>{{ name }}</code>: {{ $t("templateServiceName") }}<br />
|
||||
<code v-pre>{{ status }}</code>: {{ $t("templateStatus") }}<br />
|
||||
<code v-pre>{{ hostnameOrURL }}</code>: {{ $t("templateHostnameOrURL") }}<br />
|
||||
<code v-pre>{{ heartbeatJSON }}</code>: {{ $t("templateHeartbeatJSON") }} <b>({{ $t("templateLimitedToUpDownNotifications") }})</b><br />
|
||||
<code v-pre>{{ monitorJSON }}</code>: {{ $t("templateMonitorJSON") }} <b>({{ $t("templateLimitedToUpDownCertNotifications") }})</b><br />
|
||||
</div>
|
||||
|
||||
<input
|
||||
:id="id"
|
||||
ref="templatedInput"
|
||||
v-model="model"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
autocomplete="false"
|
||||
>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
/**
|
||||
* The value of the templated input.
|
||||
*/
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
/**
|
||||
* id for the templated input.
|
||||
*/
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* Whether the templated input is required.
|
||||
* @example true
|
||||
*/
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* Placeholder text for the templated input.
|
||||
*/
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
},
|
||||
emits: [ "update:modelValue" ],
|
||||
computed: {
|
||||
/**
|
||||
* Send value update to parent on change.
|
||||
*/
|
||||
model: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
80
src/components/TemplatedTextarea.vue
Normal file
80
src/components/TemplatedTextarea.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="form-text mb-2">
|
||||
<i18n-t tag="div" keypath="liquidIntroduction">
|
||||
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
|
||||
</i18n-t>
|
||||
|
||||
<code v-pre>{{ msg }}</code>: {{ $t("templateMsg") }}<br />
|
||||
<code v-pre>{{ name }}</code>: {{ $t("templateServiceName") }}<br />
|
||||
<code v-pre>{{ status }}</code>: {{ $t("templateStatus") }}<br />
|
||||
<code v-pre>{{ hostnameOrURL }}</code>: {{ $t("templateHostnameOrURL") }}<br />
|
||||
<code v-pre>{{ heartbeatJSON }}</code>: {{ $t("templateHeartbeatJSON") }} <b>({{ $t("templateLimitedToUpDownNotifications") }})</b><br />
|
||||
<code v-pre>{{ monitorJSON }}</code>: {{ $t("templateMonitorJSON") }} <b>({{ $t("templateLimitedToUpDownCertNotifications") }})</b><br />
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
:id="id"
|
||||
ref="templatedTextarea"
|
||||
v-model="model"
|
||||
class="form-control"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
autocomplete="false"
|
||||
></textarea>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
/**
|
||||
* The value of the templated textarea.
|
||||
*/
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
/**
|
||||
* id for the templated textarea.
|
||||
*/
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* Whether the templated textarea is required.
|
||||
* @example true
|
||||
*/
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* Placeholder text for the templated textarea.
|
||||
*/
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
},
|
||||
emits: [ "update:modelValue" ],
|
||||
computed: {
|
||||
/**
|
||||
* Send value update to parent on change.
|
||||
*/
|
||||
model: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
textarea {
|
||||
min-height: 150px;
|
||||
}
|
||||
</style>
|
19
src/components/notifications/PushPlus.vue
Normal file
19
src/components/notifications/PushPlus.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="pushPlus-sendkey" class="form-label">{{ $t("SendKey") }}</label>
|
||||
<HiddenInput id="pushPlus-sendkey" v-model="$parent.notification.pushPlusSendKey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
</div>
|
||||
<i18n-t tag="div" keypath="More info on:" class="mb-3 form-text">
|
||||
<a href="https://www.pushplus.plus/" target="_blank">https://www.pushplus.plus/</a>
|
||||
</i18n-t>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
@@ -67,25 +67,15 @@
|
||||
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
|
||||
</div>
|
||||
|
||||
<p class="form-text">
|
||||
<i18n-t tag="div" keypath="smtpLiquidIntroduction" class="form-text mb-3">
|
||||
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
|
||||
</i18n-t>
|
||||
<code v-pre>{{name}}</code>: {{ $t("emailTemplateServiceName") }}<br />
|
||||
<code v-pre>{{msg}}</code>: {{ $t("emailTemplateMsg") }}<br />
|
||||
<code v-pre>{{status}}</code>: {{ $t("emailTemplateStatus") }}<br />
|
||||
<code v-pre>{{heartbeatJSON}}</code>: {{ $t("emailTemplateHeartbeatJSON") }}<b>{{ $t("emailTemplateLimitedToUpDownNotification") }}</b><br />
|
||||
<code v-pre>{{monitorJSON}}</code>: {{ $t("emailTemplateMonitorJSON") }} <b>{{ $t("emailTemplateLimitedToUpDownNotification") }}</b><br />
|
||||
<code v-pre>{{hostnameOrURL}}</code>: {{ $t("emailTemplateHostnameOrURL") }}<br />
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label for="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
|
||||
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
|
||||
<TemplatedInput id="subject-email" v-model="$parent.notification.customSubject" :required="false" placeholder=""></TemplatedInput>
|
||||
<div class="form-text">{{ $t("leave blank for default subject") }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="body-email" class="form-label">{{ $t("emailCustomBody") }}</label>
|
||||
<textarea id="body-email" v-model="$parent.notification.customBody" type="text" class="form-control" autocomplete="false" placeholder=""></textarea>
|
||||
<TemplatedTextarea id="body-email" v-model="$parent.notification.customBody" :required="false" placeholder=""></TemplatedTextarea>
|
||||
<div class="form-text">{{ $t("leave blank for default body") }}</div>
|
||||
</div>
|
||||
|
||||
@@ -124,11 +114,15 @@
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
import TemplatedInput from "../TemplatedInput.vue";
|
||||
import TemplatedTextarea from "../TemplatedTextarea.vue";
|
||||
import ToggleSection from "../ToggleSection.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
TemplatedInput,
|
||||
TemplatedTextarea,
|
||||
ToggleSection,
|
||||
},
|
||||
computed: {
|
||||
|
@@ -5,20 +5,20 @@
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="sendgrid-from-email" class="form-label">{{ $t("From Email") }}</label>
|
||||
<input id="sendgrid-from-email" v-model="$parent.notification.sendgridFromEmail" type="email" class="form-control" required>
|
||||
<input id="sendgrid-from-email" v-model="$parent.notification.sendgridFromEmail" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="sendgrid-to-email" class="form-label">{{ $t("To Email") }}</label>
|
||||
<input id="sendgrid-to-email" v-model="$parent.notification.sendgridToEmail" type="email" class="form-control" required>
|
||||
<input id="sendgrid-to-email" v-model="$parent.notification.sendgridToEmail" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="sendgrid-cc-email" class="form-label">{{ $t("smtpCC") }}</label>
|
||||
<input id="sendgrid-cc-email" v-model="$parent.notification.sendgridCcEmail" type="email" class="form-control">
|
||||
<input id="sendgrid-cc-email" v-model="$parent.notification.sendgridCcEmail" type="text" class="form-control">
|
||||
<div class="form-text">{{ $t("Separate multiple email addresses with commas") }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="sendgrid-bcc-email" class="form-label">{{ $t("smtpBCC") }}</label>
|
||||
<input id="sendgrid-bcc-email" v-model="$parent.notification.sendgridBccEmail" type="email" class="form-control">
|
||||
<input id="sendgrid-bcc-email" v-model="$parent.notification.sendgridBccEmail" type="text" class="form-control">
|
||||
<small class="form-text text-muted">{{ $t("Separate multiple email addresses with commas") }}</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
|
@@ -4,6 +4,9 @@
|
||||
<input id="slack-webhook-url" v-model="$parent.notification.slackwebhookURL" type="text" class="form-control" required>
|
||||
<label for="slack-username" class="form-label">{{ $t("Username") }}</label>
|
||||
<input id="slack-username" v-model="$parent.notification.slackusername" type="text" class="form-control">
|
||||
<div class="form-text">
|
||||
{{ $t("aboutSlackUsername") }}
|
||||
</div>
|
||||
<label for="slack-iconemo" class="form-label">{{ $t("Icon Emoji") }}</label>
|
||||
<input id="slack-iconemo" v-model="$parent.notification.slackiconemo" type="text" class="form-control">
|
||||
<label for="slack-channel" class="form-label">{{ $t("Channel Name") }}</label>
|
||||
|
@@ -33,6 +33,56 @@
|
||||
<input id="message_thread_id" v-model="$parent.notification.telegramMessageThreadID" type="text" class="form-control">
|
||||
<p class="form-text">{{ $t("telegramMessageThreadIDDescription") }}</p>
|
||||
|
||||
<label for="server_url" class="form-label">{{ $t("telegramServerUrl") }}</label>
|
||||
<input id="server_url" v-model="$parent.notification.telegramServerUrl" type="text" class="form-control">
|
||||
<div class="form-text">
|
||||
<i18n-t keypath="telegramServerUrlDescription">
|
||||
<a
|
||||
href="https://core.telegram.org/bots/api#using-a-local-bot-api-server"
|
||||
target="_blank"
|
||||
>{{ $t("here") }}</a>
|
||||
<a
|
||||
href="https://api.telegram.org"
|
||||
target="_blank"
|
||||
>https://api.telegram.org</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input v-model="$parent.notification.telegramUseTemplate" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label">{{ $t("telegramUseTemplate") }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-text">
|
||||
{{ $t("telegramUseTemplateDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="$parent.notification.telegramUseTemplate">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="message_parse_mode">{{ $t("Message Format") }}</label>
|
||||
<select
|
||||
id="message_parse_mode"
|
||||
v-model="$parent.notification.telegramTemplateParseMode"
|
||||
class="form-select"
|
||||
required
|
||||
>
|
||||
<option value="plain">{{ $t("Plain Text") }}</option>
|
||||
<option value="HTML">HTML</option>
|
||||
<option value="MarkdownV2">MarkdownV2</option>
|
||||
</select>
|
||||
<i18n-t tag="p" keypath="telegramTemplateFormatDescription" class="form-text">
|
||||
<a href="https://core.telegram.org/bots/api#formatting-options" target="_blank">{{ $t("documentation") }}</a>
|
||||
</i18n-t>
|
||||
|
||||
<label class="form-label" for="message_template">{{ $t('Message Template') }}</label>
|
||||
<TemplatedTextarea id="message_template" v-model="$parent.notification.telegramTemplate" :required="true" :placeholder="telegramTemplatedTextareaPlaceholder"></TemplatedTextarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input v-model="$parent.notification.telegramSendSilently" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label">{{ $t("telegramSendSilently") }}</label>
|
||||
@@ -57,11 +107,27 @@
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
import TemplatedTextarea from "../TemplatedTextarea.vue";
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
TemplatedTextarea,
|
||||
},
|
||||
computed: {
|
||||
telegramTemplatedTextareaPlaceholder() {
|
||||
return this.$t("Example:", [
|
||||
`
|
||||
Uptime Kuma Alert{% if monitorJSON %} - {{ monitorJSON['name'] }}{% endif %}
|
||||
|
||||
{{ msg }}
|
||||
`,
|
||||
]);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$parent.notification.telegramServerUrl ||= "https://api.telegram.org";
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
@@ -80,7 +146,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
return `https://api.telegram.org/bot${token}/getUpdates`;
|
||||
return `${this.$parent.notification.telegramServerUrl}/bot${token}/getUpdates`;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -115,3 +181,9 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
textarea {
|
||||
min-height: 150px;
|
||||
}
|
||||
</style>
|
||||
|
38
src/components/notifications/WAHA.vue
Normal file
38
src/components/notifications/WAHA.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="waha-api-url" class="form-label">{{ $t("API URL") }}</label>
|
||||
<input id="waha-api-url" v-model="$parent.notification.wahaApiUrl" placeholder="http://localhost:3000/" type="url" class="form-control" required>
|
||||
<div class="form-text">{{ $t("wayToGetWahaApiUrl") }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="waha-api-key" class="form-label">{{ $t("API Key") }}</label>
|
||||
<HiddenInput id="waha-api-key" v-model="$parent.notification.wahaApiKey" :required="false" autocomplete="new-password"></HiddenInput>
|
||||
<div class="form-text">{{ $t("wayToGetWahaApiKey") }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="waha-session" class="form-label">{{ $t("wahaSession") }}</label>
|
||||
<input id="waha-session" v-model="$parent.notification.wahaSession" type="text" placeholder="default" class="form-control" required>
|
||||
<div class="form-text">{{ $t("wayToGetWahaSession") }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="waha-chat-id" class="form-label">{{ $t("wahaChatId") }}</label>
|
||||
<input id="waha-chat-id" v-model="$parent.notification.wahaChatId" type="text" pattern="^[\d-]{10,31}$" class="form-control" required>
|
||||
<div class="form-text">{{ $t("wayToWriteWahaChatId", ["00117612345678", "00117612345678@c.us", "123456789012345678@g.us"]) }}</div>
|
||||
</div>
|
||||
|
||||
<i18n-t tag="div" keypath="More info on:" class="mb-3 form-text">
|
||||
<a href="https://waha.devlike.pro/" target="_blank">https://waha.devlike.pro/</a>
|
||||
</i18n-t>
|
||||
</template>
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -32,20 +32,7 @@
|
||||
</template>
|
||||
</i18n-t>
|
||||
<template v-else-if="$parent.notification.webhookContentType == 'custom'">
|
||||
<i18n-t tag="div" keypath="liquidIntroduction" class="form-text">
|
||||
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
|
||||
</i18n-t>
|
||||
<code v-pre>{{msg}}</code>: {{ $t("templateMsg") }}<br />
|
||||
<code v-pre>{{heartbeatJSON}}</code>: {{ $t("templateHeartbeatJSON") }} <b>({{ $t("templateLimitedToUpDownNotifications") }})</b><br />
|
||||
<code v-pre>{{monitorJSON}}</code>: {{ $t("templateMonitorJSON") }} <b>({{ $t("templateLimitedToUpDownCertNotifications") }})</b><br />
|
||||
|
||||
<textarea
|
||||
id="customBody"
|
||||
v-model="$parent.notification.webhookCustomBody"
|
||||
class="form-control"
|
||||
:placeholder="customBodyPlaceholder"
|
||||
required
|
||||
></textarea>
|
||||
<TemplatedTextarea id="customBody" v-model="$parent.notification.webhookCustomBody" :required="true" :placeholder="customBodyPlaceholder"></TemplatedTextarea>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +54,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TemplatedTextarea from "../TemplatedTextarea.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TemplatedTextarea,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAdditionalHeadersField: this.$parent.notification.webhookAdditionalHeaders != null,
|
||||
|
19
src/components/notifications/YZJ.vue
Normal file
19
src/components/notifications/YZJ.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="url" class="form-control" required />
|
||||
<i18n-t class="form-text" keypath="wayToGetTeamsURL">
|
||||
<a href="https://www.yunzhijia.com/opendocs/docs.html#/tutorial/index/robot" target="_blank">{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="yzjToken" class="form-label">{{ $t("YZJ Robot Token") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<HiddenInput id="yzjToken" v-model="$parent.notification.yzjToken" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
<i18n-t class="form-text" keypath="wayToGetLineNotifyToken">
|
||||
<a href="https://www.yunzhijia.com/opendocs/docs.html#/server-api/im/index?id=%e6%8e%a5%e5%8f%a3%e5%9c%b0%e5%9d%80%e5%92%8c%e6%8e%88%e6%9d%83%e7%a0%81" target="_blank">{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
</script>
|
@@ -39,6 +39,7 @@ import PromoSMS from "./PromoSMS.vue";
|
||||
import Pushbullet from "./Pushbullet.vue";
|
||||
import PushDeer from "./PushDeer.vue";
|
||||
import Pushover from "./Pushover.vue";
|
||||
import PushPlus from "./PushPlus.vue";
|
||||
import Pushy from "./Pushy.vue";
|
||||
import RocketChat from "./RocketChat.vue";
|
||||
import ServerChan from "./ServerChan.vue";
|
||||
@@ -63,10 +64,12 @@ import ZohoCliq from "./ZohoCliq.vue";
|
||||
import Splunk from "./Splunk.vue";
|
||||
import SevenIO from "./SevenIO.vue";
|
||||
import Whapi from "./Whapi.vue";
|
||||
import WAHA from "./WAHA.vue";
|
||||
import Cellsynt from "./Cellsynt.vue";
|
||||
import WPush from "./WPush.vue";
|
||||
import SIGNL4 from "./SIGNL4.vue";
|
||||
import SendGrid from "./SendGrid.vue";
|
||||
import YZJ from "./YZJ.vue";
|
||||
|
||||
/**
|
||||
* Manage all notification form.
|
||||
@@ -114,6 +117,7 @@ const NotificationFormList = {
|
||||
"PushByTechulus": TechulusPush,
|
||||
"PushDeer": PushDeer,
|
||||
"pushover": Pushover,
|
||||
"PushPlus": PushPlus,
|
||||
"pushy": Pushy,
|
||||
"rocket.chat": RocketChat,
|
||||
"serwersms": SerwerSMS,
|
||||
@@ -138,10 +142,12 @@ const NotificationFormList = {
|
||||
"ZohoCliq": ZohoCliq,
|
||||
"SevenIO": SevenIO,
|
||||
"whapi": Whapi,
|
||||
"waha": WAHA,
|
||||
"gtxmessaging": GtxMessaging,
|
||||
"Cellsynt": Cellsynt,
|
||||
"WPush": WPush,
|
||||
"SendGrid": SendGrid,
|
||||
"YZJ": YZJ,
|
||||
};
|
||||
|
||||
export default NotificationFormList;
|
||||
|
@@ -31,6 +31,7 @@ const languageList = {
|
||||
"sv-SE": "Svenska",
|
||||
"tr-TR": "Türkçe",
|
||||
"ko-KR": "한국어",
|
||||
"lt": "Lietuvių",
|
||||
"ru-RU": "Русский",
|
||||
"zh-CN": "简体中文",
|
||||
"pl": "Polski",
|
||||
|
@@ -733,5 +733,12 @@
|
||||
"Remove the expiry notification": "إزالة تنبيه يوم تاريخ الإنتهاء",
|
||||
"tailscalePingWarning": "من أجل استخدام مراقبة Tailscale Ping، تحتاج إلى تثبيت Uptime Kuma بدون Docker وكذلك تثبيت عميل Tailscale على الخادم الخاص بك.",
|
||||
"Clone Monitor": "نسخ المراقبة",
|
||||
"telegramMessageThreadIDDescription": "معرف فريد اختياري لسلسلة الرسائل المستهدفة (الموضوع) في المنتدى؛ لمجموعات المنتدى الكبرى فقط"
|
||||
"telegramMessageThreadIDDescription": "معرف فريد اختياري لسلسلة الرسائل المستهدفة (الموضوع) في المنتدى؛ لمجموعات المنتدى الكبرى فقط",
|
||||
"emailCustomBody": "نص مخصص",
|
||||
"emailTemplateStatus": "الحالة",
|
||||
"leave blank for default subject": "اترك فارغاً ليتم تعيين الموضوع تلقائياً",
|
||||
"leave blank for default body": "اترك فارغاً ليتم تعيين النص تلقائياً",
|
||||
"emailTemplateServiceName": "اسم الخدمة",
|
||||
"emailTemplateHostnameOrURL": "اسم المضيف أو عنوان URL",
|
||||
"smspartnerPhoneNumber": "رقم الهاتف"
|
||||
}
|
||||
|
@@ -1088,5 +1088,34 @@
|
||||
"From": "От",
|
||||
"Can be found on:": "Можте да се откриете на: {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Телефонният номер на получателя във формат E.164.",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Идентификационен номер на подателя на текста или телефонен номер във формат E.164, в случай, че желаете да получавате отговори."
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Идентификационен номер на подателя на текста или телефонен номер във формат E.164, в случай, че желаете да получавате отговори.",
|
||||
"rabbitmqNodesRequired": "Моля, задайте възлите за този монитор.",
|
||||
"rabbitmqNodesInvalid": "Моля, използвайте пълния URL адрес (започващ с 'http') за RabbitMQ възли.",
|
||||
"RabbitMQ Password": "RabbitMQ парола",
|
||||
"RabbitMQ Username": "RabbitMQ потребител",
|
||||
"SendGrid API Key": "SendGrid API ключ",
|
||||
"Separate multiple email addresses with commas": "Разделяйте отделните имейл адреси със запетаи",
|
||||
"RabbitMQ Nodes": "Възли за управление на RabbitMQ",
|
||||
"rabbitmqNodesDescription": "Въведете URL адреса на възлите за управление на RabbitMQ, включително протокол и порт. Пример: {0}",
|
||||
"rabbitmqHelpText": "За да използвате монитора, ще трябва да активирате добавката за управление във вашата настройка на RabbitMQ. За повече информация моля, вижте {rabitmq_documentation}.",
|
||||
"aboutSlackUsername": "Променя показваното име на подателя на съобщението. Ако желаете да споменете някого, вместо това го включете в приятелското име.",
|
||||
"YZJ Robot Token": "YZJ Robot токен код",
|
||||
"YZJ Webhook URL": "YZJ Уеб кука URL адрес",
|
||||
"templateServiceName": "име на услугата",
|
||||
"templateHostnameOrURL": "име на хоста или URL адрес",
|
||||
"Plain Text": "Обикновен текст",
|
||||
"Message Template": "Шаблон за съобщение",
|
||||
"templateStatus": "статус",
|
||||
"telegramUseTemplate": "Използвай персонализиран шаблон за съобщение",
|
||||
"telegramUseTemplateDescription": "Ако е активирано, съобщението ще бъде изпратено чрез персонализиран шаблон.",
|
||||
"telegramTemplateFormatDescription": "Telegram позволява използването на различни \"markup\" езици за съобщенията. Вижте Telegram {0} за детайли.",
|
||||
"Template Format": "Формат на шаблона",
|
||||
"wayToGetWahaApiUrl": "Вашият WAHA URL адрес.",
|
||||
"wahaSession": "Сесия",
|
||||
"wahaChatId": "Чат ID (телефонен номер / ID на контакт / ID на група)",
|
||||
"wayToGetWahaApiKey": "API ключът, е стойността на променливата WHATSAPP_API_KEY, която сте използвали за стартиране на WAHA.",
|
||||
"wayToWriteWahaChatId": "Телефонният номер с международния префикс, но без знака плюс в началото ({0}), ID на контакта ({1}) или ID на групата ({2}). Известията се изпращат до това чат ID от WAHA сесия.",
|
||||
"wayToGetWahaSession": "От тази сесия WAHA изпраща известия до чат ID. Можете да го намерите в таблото за управление на WAHA.",
|
||||
"telegramServerUrlDescription": "За премахване на API бот ограниченията за Telegram или за получаване на достъп в блокирани зони (Китай, Иран и др.). За повече информация щракнете върху {0}. По подразбиране: {1}",
|
||||
"telegramServerUrl": "(По избор) URL адрес на сървъра"
|
||||
}
|
||||
|
219
src/lang/bn.json
219
src/lang/bn.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"setupDatabaseChooseDatabase": "আপনি কোন ডাটাবেজটি ব্যবহার করতে চান?",
|
||||
"setupDatabaseEmbeddedMariaDB": "আপনাকে কিছু নিযুক্ত করতে হবে না। এই ডকার ইমেজটি (Docker image) স্বয়ংক্রিয়ভাবে আপনার জন্য মারিয়া ডিবি (MariaDB) বসিয়েছে এবং প্রস্তুত করেছে।Uptime Kuma ইউনিক্স সকেটের (Unix Socket) মাধ্যমে এই ডাটাবেসের সাথে সংযুক্ত হবে।",
|
||||
"setupDatabaseMariaDB": "একটি বহিরাগত মারিয়া ডিবি (MariaDB) ডাটাবেসের সাথে সংযোগ করুন। আপনাকে ডাটাবেস সংযোগ তথ্য নিযুক্ত করতে হবে।",
|
||||
"setupDatabaseEmbeddedMariaDB": "আপনাকে কিছু সেটআপ করার প্রয়োজন নেই। এই ডকার ইমেজে স্বয়ংক্রিয়ভাবে MariaDB এম্বেড এবং কনফিগার করা হয়েছে। Uptime Kuma এই ডাটাবেজের সাথে ইউনিক্স সকেটের মাধ্যমে সংযুক্ত হবে।",
|
||||
"setupDatabaseMariaDB": "বহিরাগত MariaDB ডাটাবেজের সাথে সংযোগ করতে হবে। আপনাকে ডাটাবেজের সংযোগ তথ্য সেট করতে হবে।",
|
||||
"Add": "সংযোগ করুন",
|
||||
"dbName": "ডাটাবেজের নাম",
|
||||
"languageName": "ইংরেজি",
|
||||
"languageName": "বাংলা",
|
||||
"Settings": "সেটিংস",
|
||||
"Dashboard": "ড্যাশবোর্ড",
|
||||
"Help": "সাহায্য",
|
||||
@@ -14,5 +14,216 @@
|
||||
"Check Update On GitHub": "GitHub-এ আপডেট চেক করুন",
|
||||
"List": "তালিকা",
|
||||
"General": "সাধারণ",
|
||||
"Game": "খেলা"
|
||||
"Game": "খেলা",
|
||||
"disable authentication": "প্রমাণীকরণ বন্ধ করুন",
|
||||
"pauseDashboardHome": "থামুন",
|
||||
"disableauth.message2": "এটি Uptime Kuma এর সামনে {intendThirdPartyAuth} এর মতো পরিস্থিতির জন্য ডিজাইন করা হয়েছে, যেমন Cloudflare Access, Authelia অথবা অন্যান্য প্রমাণীকরণ ব্যবস্থা।",
|
||||
"I understand, please disable": "আমি বুঝতে পারছি, দয়া করে অক্ষম করুন",
|
||||
"Certificate Info": "সার্টিফিকেট তথ্য",
|
||||
"Create your admin account": "আপনার অ্যাডমিন অ্যাকাউন্ট তৈরি করুন",
|
||||
"Import": "ইমপোর্ট",
|
||||
"Default enabled": "ডিফল্ট সক্রিয়",
|
||||
"Heartbeats": "হার্টবিট",
|
||||
"Affected Monitors": "প্রভাবিত মনিটরগুলো",
|
||||
"All Status Pages": "সমস্ত স্ট্যাটাস পেজ",
|
||||
"alertNoFile": "অনুগ্রহ করে আমদানির জন্য একটি ফাইল নির্বাচন করুন।",
|
||||
"alertWrongFileType": "অনুগ্রহ করে একটি JSON ফাইল নির্বাচন করুন।",
|
||||
"Clear all statistics": "সব পরিসংখ্যান মুছে ফেলুন",
|
||||
"Setup 2FA": "2FA সেট আপ করুন",
|
||||
"Two Factor Authentication": "দুই ফ্যাক্টর প্রমাণীকরণ",
|
||||
"Add New Tag": "নতুন ট্যাগ যোগ করুন",
|
||||
"Tag with this name already exist.": "এই নামের ট্যাগ আগে থেকেই আছে।",
|
||||
"Pink": "গোলাপী",
|
||||
"Search monitored sites": "পর্যবেক্ষণ করা সাইট অনুসন্ধান",
|
||||
"statusPageNothing": "এখানে কিছুই নেই, অনুগ্রহ করে একটি গ্রুপ বা মনিটর যোগ করুন।",
|
||||
"Degraded Service": "অধঃপতন সেবা",
|
||||
"Go to Dashboard": "ড্যাশবোর্ডে যান",
|
||||
"defaultNotificationName": "আমার {বিজ্ঞপ্তি} সতর্কতা ({number})",
|
||||
"webhookJsonDesc": "{0} যেকোনো আধুনিক HTTP সার্ভার যেমন Express.js-এর জন্য ভালো",
|
||||
"liquidIntroduction": "টেম্পলেটবিলিটি লিকুইড টেমপ্লেটিং ভাষার মাধ্যমে অর্জন করা হয়। ব্যবহারের নির্দেশাবলীর জন্য অনুগ্রহ করে {0} দেখুন। এই উপলব্ধ ভেরিয়েবল হল:",
|
||||
"Notifications": "নোটিফিকেশন",
|
||||
"Setup Notification": "নোটিফিকেশন সেটআপ করুন",
|
||||
"Light": "আলো",
|
||||
"Auto": "স্বয়ংক্রিয়",
|
||||
"Theme - Heartbeat Bar": "থিম - হার্টবিট বার",
|
||||
"styleElapsedTime": "হার্টবিট বারের নিচে ব্যবহৃত সময়",
|
||||
"styleElapsedTimeShowNoLine": "শোর করুন (কোনো লাইন নেই)",
|
||||
"styleElapsedTimeShowWithLine": "শো করুন (লাইন সহ)",
|
||||
"Normal": "স্বাভাবিক",
|
||||
"Bottom": "নীচে",
|
||||
"Timezone": "টাইমজোন",
|
||||
"Search Engine Visibility": "সার্চ ইঞ্জিন দৃশ্যমানতা",
|
||||
"Allow indexing": "ইন্ডেক্সিং অনুমোদন করুন",
|
||||
"Discourage search engines from indexing site": "সার্চ ইঞ্জিনগুলোকে সাইটের ইন্ডেক্সিং থেকে বিরত রাখুন",
|
||||
"Change Password": "পাসওয়ার্ড পরিবর্তন করুন",
|
||||
"Current Password": "বর্তমান পাসওয়ার্ড",
|
||||
"New Password": "নতুন পাসওয়ার্ড",
|
||||
"Repeat New Password": "নতুন পাসওয়ার্ডটি আবার দিন",
|
||||
"Update Password": "পাসওয়ার্ড আপডেট করুন",
|
||||
"Enable Auth": "প্রমাণীকরণ চালু করুন",
|
||||
"disableauth.message1": "আপনি কি সত্যিই {disableAuth} করতে চান?",
|
||||
"where you intend to implement third-party authentication": "যেখানে আপনি তৃতীয় পক্ষের প্রমাণীকরণ বাস্তবায়ন করতে চান",
|
||||
"Please use this option carefully!": "দয়া করে এই অপশনটি সাবধানে ব্যবহার করুন!",
|
||||
"Logout": "লগ আউট",
|
||||
"Leave": "লিভ",
|
||||
"Confirm": "নিশ্চিত",
|
||||
"Yes": "হ্যাঁ",
|
||||
"No": "না",
|
||||
"Username": "ইউজার নেম",
|
||||
"Password": "পাসওয়ার্ড",
|
||||
"Remember me": "আমাকে মনে রাখুন",
|
||||
"Login": "লগ ইন",
|
||||
"add one": "একটি যোগ করুন",
|
||||
"Notification Type": "নোটিফিকেশনের ধরন",
|
||||
"Email": "ইমেইল",
|
||||
"Test": "পরীক্ষা",
|
||||
"Resolver Server": "সমাধানকারী সার্ভার",
|
||||
"Resource Record Type": "রিসোর্স রেকর্ড টাইপ",
|
||||
"Last Result": "শেষ ফলাফল",
|
||||
"Repeat Password": "পাসওয়ার্ড পুনরায় দিন",
|
||||
"Import Backup": "ইমপোর্ট ব্যাকআপ",
|
||||
"Export Backup": "এক্সপোর্ট ব্যাকআপ",
|
||||
"Export": "এক্সপোর্ট",
|
||||
"respTime": "প্রতিক্রিয়া সময় (ms)",
|
||||
"notAvailableShort": "প্রযোজ্য নয়",
|
||||
"Apply on all existing monitors": "সমস্ত বিদ্যমান মনিটরে প্রয়োগ করুন",
|
||||
"Create": "তৈরি",
|
||||
"Clear Data": "ডেটা সাফ",
|
||||
"Events": "ইভেন্ট সমূহ",
|
||||
"Auto Get": "অটো গ্রহণ",
|
||||
"Schedule maintenance": "রক্ষণাবেক্ষণ সূচী করুন",
|
||||
"Pick Affected Monitors...": "প্রভাবিত মনিটর নির্বাচন করুন…",
|
||||
"Start of maintenance": "রক্ষণাবেক্ষণের শুরু",
|
||||
"Select status pages...": "স্ট্যাটাস পেজগুলো নির্বাচন করুন…",
|
||||
"Skip existing": "বিদ্যমান এড়িয়ে যান",
|
||||
"Overwrite": "ওভাররাইট",
|
||||
"Options": "অপশন",
|
||||
"Keep both": "দুটোই রাখুন",
|
||||
"Verify Token": "টোকেন যাচাই করুন",
|
||||
"Enable 2FA": "2FA সক্ষম করুন",
|
||||
"Disable 2FA": "2FA নিষ্ক্রিয় করুন",
|
||||
"2FA Settings": "2FA সেটিংস",
|
||||
"filterActive": "সক্রিয়",
|
||||
"filterActivePaused": "বিরতি",
|
||||
"Active": "সক্রিয়",
|
||||
"Inactive": "নিষ্ক্রিয়",
|
||||
"Token": "টোকেন",
|
||||
"Show URI": "URI দেখান",
|
||||
"Tags": "ট্যাগ",
|
||||
"Add New below or Select...": "নীচে নতুন যোগ করুন বা নির্বাচন করুন…",
|
||||
"Tag with this value already exist.": "এই মান সহ ট্যাগ ইতিমধ্যেই বিদ্যমান।",
|
||||
"color": "রং",
|
||||
"value (optional)": "মান (ঐচ্ছিক)",
|
||||
"Red": "লাল",
|
||||
"Orange": "কমলা",
|
||||
"Green": "সবুজ",
|
||||
"Blue": "নীল",
|
||||
"Indigo": "নীল",
|
||||
"Purple": "বেগুনি",
|
||||
"Custom": "কাস্টম",
|
||||
"Search...": "অনুসন্ধান…",
|
||||
"Avg. Ping": "গড় পিং",
|
||||
"Avg. Response": "গড় প্রতিক্রিয়া",
|
||||
"Entry Page": "প্রবেশ পৃষ্ঠা",
|
||||
"statusPageRefreshIn": "রিফ্রেশ হবে: {0}",
|
||||
"No Services": "কোনো পরিষেবা নেই",
|
||||
"All Systems Operational": "সমস্ত সিস্টেম অপারেশনাল",
|
||||
"Partially Degraded Service": "আংশিকভাবে অবনমিত পরিষেবা",
|
||||
"Add Group": "গ্রুপ যোগ করুন",
|
||||
"Add a monitor": "একটি মনিটর যোগ করুন",
|
||||
"Edit Status Page": "এডিট স্টেটাস পেজ",
|
||||
"Status Page": "স্ট্যাটাস পেজ",
|
||||
"Status Pages": "স্ট্যাটাস পেজ সমূহ",
|
||||
"here": "এখানে",
|
||||
"Required": "প্রয়োজন",
|
||||
"Post URL": "পোস্ট URL",
|
||||
"Content Type": "বিষয়বস্তুর প্রকার",
|
||||
"webhookFormDataDesc": "{multipart} PHP এর জন্য ভালো। JSON-কে {decodeFunction} দিয়ে পার্স করতে হবে",
|
||||
"templateMsg": "বিজ্ঞপ্তির বার্তা",
|
||||
"templateHeartbeatJSON": "হৃদস্পন্দন বর্ণনাকারী বস্তু",
|
||||
"templateMonitorJSON": "মনিটরের বর্ণনাকারী বস্তু",
|
||||
"templateLimitedToUpDownNotifications": "শুধুমাত্র UP/DOWN বিজ্ঞপ্তির জন্য উপলব্ধ",
|
||||
"Gray": "ধূসর",
|
||||
"Add New Monitor": "নতুন আপটাইম মনিটর যোগ করুন",
|
||||
"statusMaintenance": "রক্ষণাবেক্ষণ",
|
||||
"Friendly Name": "বন্ধুত্বপূর্ণ নাম",
|
||||
"URL": "ইউআরএল",
|
||||
"settingUpDatabaseMSG": "ডাটাবেস সেটআপ করা হচ্ছে। এটি কিছুটা সময় নিতে পারে, অনুগ্রহ করে ধৈর্য ধরুন।",
|
||||
"Response": "রেসপন্স",
|
||||
"Ping": "পিং",
|
||||
"Keyword": "কীওয়ার্ড",
|
||||
"Invert Keyword": "ইনভার্ট কীওয়ার্ড",
|
||||
"setupDatabaseSQLite": "একটি সাধারণ ডাটাবেস ফাইল, যা ছোট পরিসরের ডিপ্লয়মেন্টের জন্য সুপারিশ করা হয়। v2.0.0 এর পূর্বে, Uptime Kuma ডিফল্ট ডাটাবেস হিসেবে SQLite ব্যবহার করত।",
|
||||
"Unknown": "অজানা",
|
||||
"Cannot connect to the socket server": "সকেট সার্ভারের সাথে সংযোগ স্থাপন করা যাচ্ছে না",
|
||||
"Reconnecting...": "পুনরায় সংযোগ স্থাপন করা হচ্ছে...",
|
||||
"Passive Monitor Type": "নিষ্ক্রিয় মনিটরের ধরন",
|
||||
"markdownSupported": "মার্কডাউন সিনট্যাক্স সাপোর্ট",
|
||||
"Pause": "থামুন",
|
||||
"-day": "-দিন",
|
||||
"hour": "ঘন্টা",
|
||||
"Host URL": "হোস্ট ইউআরএল",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "আপনি যে সার্ভারে সংযোগ করতে চান তার হোস্টনেম প্রবেশ করুন অথবা যদি আপনি একটি {local_mta} ব্যবহার করার পরিকল্পনা করেন, তাহলে {localhost} লিখুন",
|
||||
"ignoreTLSErrorGeneral": "সংযোগের জন্য TLS/SSL ত্রুটি উপেক্ষা করুন",
|
||||
"Upside Down Mode": "আপসাইড ডাউন মোড",
|
||||
"Pending": "প্রক্রিয়াধীন",
|
||||
"Push URL": "পুশ URL",
|
||||
"needPushEvery": "আপনাকে এই URL টি প্রতি {0} সেকেন্ডে কল করতে হবে।",
|
||||
"Dark": "অন্ধকার",
|
||||
"Up": "আপ",
|
||||
"webhookAdditionalHeadersTitle": "অতিরিক্ত শিরোনাম",
|
||||
"pushOthers": "অন্যান্য",
|
||||
"programmingLanguages": "প্রোগ্রামিং ভাষাসমূহ",
|
||||
"Not available, please setup.": "উপলব্ধ নয়, অনুগ্রহ করে সেটআপ করুন।",
|
||||
"Down": "ডাউন",
|
||||
"Monitor Type": "মনিটরের ধরন",
|
||||
"Expected Value": "প্রত্যাশিত মান",
|
||||
"Home": "হোম",
|
||||
"Maintenance": "রক্ষণাবেক্ষণ",
|
||||
"General Monitor Type": "সাধারণ মনিটরের ধরন",
|
||||
"Specific Monitor Type": "নির্দিষ্ট মনিটরের ধরন",
|
||||
"Monitor": "মনিটর | মনিটরগুলো",
|
||||
"day": "দিন | দিনগুলো",
|
||||
"-hour": "-ঘন্টা",
|
||||
"Hostname": "হোস্টনেম",
|
||||
"locally configured mail transfer agent": "লোকালি কনফিগারড মেইল ট্রান্সফার এজেন্ট",
|
||||
"Port": "পোর্ট",
|
||||
"Heartbeat Interval": "হার্টবিট ইন্টারভাল",
|
||||
"Max. Redirects": "সর্বোচ্চ রিডাইরেক্ট",
|
||||
"Primary Base URL": "প্রাথমিক বেস URL",
|
||||
"Message": "বার্তা",
|
||||
"Resume": "পুনরায় শুরু",
|
||||
"Delete": "মুছে ফেলুন",
|
||||
"now": "এখন",
|
||||
"time ago": "{০} বয়স",
|
||||
"-year": "-বছর",
|
||||
"Json Query Expression": "JSON কোয়েরি এক্সপ্রেশন",
|
||||
"timeoutAfter": "{0} সেকেন্ড পর টাইমআউট",
|
||||
"Retries": "পুনরায় চেষ্টা করে",
|
||||
"checkEverySecond": "প্রতিটি {0} সেকেন্ডে চেক করুন",
|
||||
"retryCheckEverySecond": "প্রতিটি {0} সেকেন্ডে পুনরায় চেষ্টা করুন",
|
||||
"resendEveryXTimes": "প্রতিটি {0} বার পুনরায় পাঠান",
|
||||
"pushOptionalParams": "ঐচ্ছিক প্যারামিটারসমূহ: {0}",
|
||||
"Save": "সংরক্ষণ",
|
||||
"ignoredTLSError": "TLS/SSL ত্রুটিগুলি উপেক্ষা করা হয়েছে",
|
||||
"Accepted Status Codes": "গ্রহণযোগ্য স্ট্যাটাস কোডসমূহ",
|
||||
"Disable Auth": "প্রমাণীকরণ বন্ধ করুন",
|
||||
"Theme": "থিম",
|
||||
"Name": "নাম",
|
||||
"Status": "অবস্থা",
|
||||
"DateTime": "তারিখ ও সময়",
|
||||
"No important events": "কোনো গুরুত্বপূর্ণ ইভেন্ট নেই",
|
||||
"Edit": "সম্পাদনা",
|
||||
"Current": "বর্তমান",
|
||||
"Uptime": "আপটাইম",
|
||||
"Request Timeout": "রিকোয়েস্ট টাইমআউট",
|
||||
"Heartbeat Retry Interval": "হার্টবিট রিট্রাই ইন্টারভাল",
|
||||
"Advanced": "অ্যাডভান্স",
|
||||
"retriesDescription": "সার্ভিসটি ডাউন হিসেবে চিহ্নিত হওয়ার আগে এবং একটি নোটিফিকেশন পাঠানোর জন্য সর্বোচ্চ পুনরায় চেষ্টা করার সংখ্যা",
|
||||
"upsideDownModeDescription": "স্ট্যাটাসটি উল্টো করে দিন। যদি সার্ভিসটি পৌঁছানো যায়, তবে এটি DOWN হবে।",
|
||||
"maxRedirectDescription": "অনুসরণ করার জন্য সর্বোচ্চ রিডাইরেক্ট সংখ্যা। রিডাইরেক্ট নিষ্ক্রিয় করতে 0 সেট করুন।",
|
||||
"ignoreTLSError": "HTTPS ওয়েবসাইটগুলির জন্য TLS/SSL ত্রুটিগুলি উপেক্ষা করুন",
|
||||
"pushViewCode": "পুশ মনিটর কীভাবে ব্যবহার করবেন? (কোড দেখুন)",
|
||||
"Appearance": "দেখানোর ধরন",
|
||||
"Quick Stats": "তাৎক্ষণিক পরিসংখ্যান"
|
||||
}
|
||||
|
353
src/lang/ca.json
353
src/lang/ca.json
@@ -49,7 +49,7 @@
|
||||
"Delete": "Eliminar",
|
||||
"Current": "Actual",
|
||||
"Uptime": "Temps actiu",
|
||||
"Cert Exp.": "Caducitat del certificat",
|
||||
"Cert Exp.": "Caducitat del certificat.",
|
||||
"Monitor": "Monitor | Monitors",
|
||||
"day": "dia | dies",
|
||||
"-day": "-dia",
|
||||
@@ -65,7 +65,7 @@
|
||||
"URL": "URL",
|
||||
"Hostname": "Nom del servidor",
|
||||
"Port": "Port",
|
||||
"timeoutAfter": "¡Timeout' després de {0",
|
||||
"timeoutAfter": "¡Timeout' després de {0} segons",
|
||||
"locally configured mail transfer agent": "Agent de transferència de correu configurat localment",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Introduïu el nom del servidor al qual voleu connectar-vos o {localhost} si voleu utilitzar un {local_mta}",
|
||||
"Host URL": "URL del servidor",
|
||||
@@ -213,5 +213,352 @@
|
||||
"Email": "Correu",
|
||||
"Last Result": "Darrer Resultat",
|
||||
"Add New Tag": "Afegir nova etiqueta",
|
||||
"Tag with this value already exist.": "Ja existeix una etiqueta amb aquest valor."
|
||||
"Tag with this value already exist.": "Ja existeix una etiqueta amb aquest valor.",
|
||||
"defaultNotificationName": "La meva {notification} Alerta ({number})",
|
||||
"Required": "Obligatori",
|
||||
"Post URL": "Posar URL",
|
||||
"Content Type": "Content Type",
|
||||
"Json Query Expression": "Expressió de consulta Json",
|
||||
"now": "ara",
|
||||
"-year": "-any",
|
||||
"Status Pages": "Pàgines d'estat",
|
||||
"here": "aquí",
|
||||
"time ago": "fa {0}",
|
||||
"ignoredTLSError": "Errors TLS/SSL ignorats",
|
||||
"webhookFormDataDesc": "{multipart} es bo per PHP. El JSON haurà d'analitzar-se amb {decodeFunction}",
|
||||
"webhookJsonDesc": "{0} es bo per qualsevol servidor HTTP modern com Express.js",
|
||||
"templateMsg": "missatge de la notificació",
|
||||
"webhookAdditionalHeadersTitle": "Capçaleres addicionals",
|
||||
"Application Token": "Testimoni d'aplicació",
|
||||
"Server URL": "URL del servidor",
|
||||
"Priority": "Prioritat",
|
||||
"Webhook URL": "URL del Webhook",
|
||||
"emojiCheatSheet": "Full de trampa d'emoji: {0}",
|
||||
"Read more": "Llegeix més",
|
||||
"appriseInstalled": "S'ha instal·lat l'apèndix.",
|
||||
"templateHeartbeatJSON": "objecte que descriu el batec del cor",
|
||||
"templateMonitorJSON": "objecte que descriu el monitor",
|
||||
"templateLimitedToUpDownCertNotifications": "només disponible per a notificacions de venciment UP/DOWN/Certificate",
|
||||
"templateLimitedToUpDownNotifications": "només disponible per a notificacions UP/DOWN",
|
||||
"webhookAdditionalHeadersDesc": "Estableix les capçaleres addicionals enviades amb el webhook. Cada capçalera s'ha de definir com una clau/valor JSON.",
|
||||
"webhookBodyPresetOption": "Predefinit - {0}",
|
||||
"webhookBodyCustomOption": "Cos personalitzat",
|
||||
"Headers": "Capçaleres",
|
||||
"Monitor History": "Historial del monitor",
|
||||
"PasswordsDoNotMatch": "Les contrasenyes no coincideixen.",
|
||||
"records": "registres",
|
||||
"One record": "Un registre",
|
||||
"Current User": "Usuari actual",
|
||||
"topic": "Tema",
|
||||
"topicExplanation": "tema MQTT a monitorar",
|
||||
"successKeyword": "Paraula clau d'èxit",
|
||||
"successKeywordExplanation": "Paraula clau MQTT que es considerarà un èxit",
|
||||
"recent": "Recent",
|
||||
"Reset Token": "Restableix el testimoni",
|
||||
"Done": "Fet",
|
||||
"Info": "Info",
|
||||
"Steam API Key": "Clau API de Steam",
|
||||
"Shrink Database": "Redueix la base de dades",
|
||||
"Pick a RR-Type...": "Trieu un tipus RR…",
|
||||
"Default": "Per defecte",
|
||||
"HTTP Options": "Opcions HTTP",
|
||||
"Create Incident": "Crear Incident",
|
||||
"Title": "Títol",
|
||||
"Content": "Contingut",
|
||||
"Style": "Estil",
|
||||
"info": "info",
|
||||
"warning": "avís",
|
||||
"danger": "perill",
|
||||
"primary": "primària",
|
||||
"light": "lleuger",
|
||||
"dark": "fosc",
|
||||
"Post": "Post",
|
||||
"Created": "Creat",
|
||||
"Last Updated": "Darrera actualització",
|
||||
"Switch to Light Theme": "Canvia a tema clar",
|
||||
"Switch to Dark Theme": "Canviar a tema fosc",
|
||||
"Show Tags": "Mostra les etiquetes",
|
||||
"Hide Tags": "Amaga les etiquetes",
|
||||
"Description": "Descripció",
|
||||
"No monitors available.": "No hi ha monitors disponibles.",
|
||||
"Add one": "Afegeix-ne un",
|
||||
"No Monitors": "Sense monitors",
|
||||
"Untitled Group": "Grup sense títol",
|
||||
"Services": "Serveis",
|
||||
"Discard": "Descarta",
|
||||
"Cancel": "Canel·la",
|
||||
"Select": "Selecciona",
|
||||
"Check/Uncheck": "Comprova/desmarca",
|
||||
"Powered by": "Funciona amb",
|
||||
"Customize": "Personalitza",
|
||||
"Custom Footer": "Peu de pàgina personalitzat",
|
||||
"Custom CSS": "CSS personalitzat",
|
||||
"default": "Per defecte",
|
||||
"enabled": "Habilitat",
|
||||
"setAsDefault": "Estableix com a predeterminat",
|
||||
"deleteProxyMsg": "Esteu segur que voleu suprimir aquest servidor intermediari per a tots els monitors?",
|
||||
"setAsDefaultProxyDescription": "Aquest servidor intermediari s'habilitarà de manera predeterminada per als monitors nous. Encara podeu desactivar el servidor intermediari per separat per a cada monitor.",
|
||||
"Certificate Chain": "Cadena de certificats",
|
||||
"Valid": "Vàlid",
|
||||
"Invalid": "Invàlid",
|
||||
"User": "Usuari",
|
||||
"Installed": "Instal·lat",
|
||||
"Not installed": "No instal·lat",
|
||||
"Running": "En execució",
|
||||
"Not running": "No en execució",
|
||||
"Remove Token": "Elimina el testimoni",
|
||||
"Start": "Inicia",
|
||||
"Stop": "Aturar",
|
||||
"Add New Status Page": "Afegeix una pàgina d'estat nova",
|
||||
"Slug": "Àlies",
|
||||
"Accept characters:": "Accepta caràcters:",
|
||||
"startOrEndWithOnly": "Inicia o acaba només amb {0}",
|
||||
"No consecutive dashes": "Sense guions consecutius",
|
||||
"Next": "Següent",
|
||||
"The slug is already taken. Please choose another slug.": "L'àlias ja està agafat. Si us plau, escolliu un altre àlies.",
|
||||
"No Proxy": "Sense servidor intermediari",
|
||||
"Proxies": "Servidors intermediaris",
|
||||
"HTTP Basic Auth": "Autenticació bàsica HTTP",
|
||||
"New Status Page": "Pàgina d'estat nova",
|
||||
"Reverse Proxy": "Servidor intermediari invertit",
|
||||
"Backup": "Còpia de seguretat",
|
||||
"About": "Quant a",
|
||||
"wayToGetCloudflaredURL": "(Descarrega de cloudfared des de {0})",
|
||||
"cloudflareWebsite": "Lloc web de Cloudflare",
|
||||
"Message:": "Missatge:",
|
||||
"HTTP Headers": "Capçaleres HTTP",
|
||||
"Trust Proxy": "Confia en el servidor intermediari",
|
||||
"Other Software": "Un altre programari",
|
||||
"For example: nginx, Apache and Traefik.": "Per exemple: nginx, Apache i Traefik.",
|
||||
"Please read": "Llegiu",
|
||||
"Subject:": "Assumpte:",
|
||||
"Valid To:": "Vàlid per a:",
|
||||
"Days Remaining:": "Dies restants:",
|
||||
"Issuer:": "Emissor:",
|
||||
"Fingerprint:": "Empremta digital:",
|
||||
"No status pages": "Sense pàgines d'estat",
|
||||
"Domain Name Expiry Notification": "Notificació de venciment del nom de domini",
|
||||
"Add a new expiry notification day": "Notificació de venciment del nom de domini",
|
||||
"Remove the expiry notification": "Elimina el dia de notificació de venciment",
|
||||
"Proxy": "Servidor intermediari",
|
||||
"Date Created": "Data de creació",
|
||||
"Footer Text": "Text del peu de pàgina",
|
||||
"Refresh Interval Description": "La pàgina d'estat farà una actualització completa del lloc cada {0} segons",
|
||||
"Show Powered By": "Mostra Impulsat per",
|
||||
"Domain Names": "Noms de domini",
|
||||
"signedInDisp": "Sessió iniciada com a {0}",
|
||||
"RadiusSecret": "Secret de Radius",
|
||||
"RadiusSecretDescription": "Secret compartit entre client i servidor",
|
||||
"RadiusCalledStationId": "Id de l'estació cridada",
|
||||
"RadiusCallingStationId": "Id de l'estació de trucada",
|
||||
"RadiusCallingStationIdDescription": "Identificador del dispositiu de crida",
|
||||
"Certificate Expiry Notification": "Notificació de venciment del certificat",
|
||||
"API Username": "Nom d'usuari API",
|
||||
"API Key": "Clau API",
|
||||
"Show update if available": "Mostra l'actualització si està disponible",
|
||||
"Using a Reverse Proxy?": "Usar un servidor intermediari invers?",
|
||||
"Check how to config it for WebSocket": "Comprova com configurar-lo per a WebSocket",
|
||||
"Steam Game Server": "Servidor de jocs Steam",
|
||||
"Most likely causes:": "Causes més probables:",
|
||||
"There might be a typing error in the address.": "Pot haver-hi un error d'escriptura a l'adreça.",
|
||||
"What you can try:": "Què podeu provar:",
|
||||
"Retype the address.": "Torneu a teclejar l'adreça.",
|
||||
"Go back to the previous page.": "Torna a la pàgina anterior.",
|
||||
"Coming Soon": "Properament",
|
||||
"Connection String": "Cadena de connexió",
|
||||
"Query": "Consulta",
|
||||
"settingsCertificateExpiry": "Caducitat del certificat TLS",
|
||||
"Setup Docker Host": "Configura l'amfitrió Docker",
|
||||
"Connection Type": "Tipus de connexió",
|
||||
"Docker Daemon": "Dimoni Docker",
|
||||
"noDockerHostMsg": "No disponible. Primer configureu un amfitrió Docker.",
|
||||
"DockerHostRequired": "Establiu l'amfitrió Docker per a aquest monitor.",
|
||||
"socket": "Sòcol",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Docker Container": "Contenidor Docker",
|
||||
"Container Name / ID": "Nom del contenidor / ID",
|
||||
"Docker Host": "Amfitrió Docker",
|
||||
"Docker Hosts": "Amfitrions Docker",
|
||||
"Domain": "Domini",
|
||||
"Workstation": "Estació de treball",
|
||||
"Packet Size": "Mida del paquet",
|
||||
"Bot Token": "Testimoni de bot",
|
||||
"wayToGetTelegramToken": "Podeu obtenir un testimoni de {0}.",
|
||||
"Chat ID": "ID de xat",
|
||||
"telegramMessageThreadID": "(Opcional) ID del fil del missatge",
|
||||
"telegramMessageThreadIDDescription": "Identificador únic opcional per al fil del missatge de destinació (tema) del fòrum; només per als supergrups del fòrum",
|
||||
"telegramSendSilently": "Envia silenciosament",
|
||||
"telegramProtectContent": "Protegeix la reenviament/desament",
|
||||
"supportTelegramChatID": "Admet xat directe / grup / ID de xat del canal",
|
||||
"YOUR BOT TOKEN HERE": "EL VOSTRE TESTIMONI DE BOT AQUÍ",
|
||||
"chatIDNotFound": "No s'ha trobat l'ID del xat; primer envieu un missatge a aquest bot",
|
||||
"disableCloudflaredNoAuthMsg": "Esteu en mode No Auth, no cal una contrasenya.",
|
||||
"wayToGetLineNotifyToken": "Podeu obtenir un testimoni d'accés des de {0}",
|
||||
"Examples": "Exemples",
|
||||
"Home Assistant URL": "URL de l'assistent d'inici",
|
||||
"Long-Lived Access Token": "Testimoni d'accés viu",
|
||||
"Notification Service": "Servei de notificacions",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Les automatitzacions es poden activar opcionalment a l'assistent d'inici:",
|
||||
"Trigger type:": "Tipus d'activador:",
|
||||
"Event type:": "Tipus d'esdeveniment:",
|
||||
"Event data:": "Dades de l'esdeveniment:",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "A continuació, trieu una acció, per exemple, canvieu l'escena a on una llum RGB és vermella.",
|
||||
"Frontend Version": "Versió del frontal",
|
||||
"Frontend Version do not match backend version!": "La versió frontal no coincideix amb la versió del dorsal!",
|
||||
"backupRecommend": "Si us plau, feu una còpia de seguretat del volum o de la carpeta de dades (./data/) directament.",
|
||||
"Optional": "Opcional",
|
||||
"and": "i",
|
||||
"startDateTime": "Data/hora d'inici",
|
||||
"endDateTime": "Data/hora final",
|
||||
"cronExpression": "Expressió Cron",
|
||||
"cronSchedule": "Planificació: ",
|
||||
"invalidCronExpression": "Expressió Cron no vàlida: {0}",
|
||||
"recurringInterval": "Interval",
|
||||
"Recurring": "Recurrència",
|
||||
"strategyManual": "Activa/Inactiva manualment",
|
||||
"warningTimezone": "Està utilitzant la zona horària del servidor",
|
||||
"weekdayShortMon": "Dill",
|
||||
"weekdayShortTue": "Dim",
|
||||
"weekdayShortWed": "Dim",
|
||||
"weekdayShortThu": "Dij",
|
||||
"weekdayShortFri": "Div",
|
||||
"weekdayShortSat": "Diss",
|
||||
"weekdayShortSun": "Dg",
|
||||
"dayOfWeek": "Dia de la setmana",
|
||||
"dayOfMonth": "Dia del mes",
|
||||
"lastDay": "Últim dia",
|
||||
"lastDay2": "2n últim dia del mes",
|
||||
"lastDay3": "3r Darrer Dia del Mes",
|
||||
"lastDay4": "4t Darrer Dia del Mes",
|
||||
"No Maintenance": "Sense manteniment",
|
||||
"maintenanceStatus-under-maintenance": "Sota manteniment",
|
||||
"maintenanceStatus-inactive": "Inactiu",
|
||||
"maintenanceStatus-scheduled": "Programat",
|
||||
"maintenanceStatus-unknown": "Desconegut",
|
||||
"Server Timezone": "Zona horària del servidor",
|
||||
"statusPageMaintenanceEndDate": "Final",
|
||||
"IconUrl": "URL de la icona",
|
||||
"Enable DNS Cache": "(Obsolet) Habilita la memòria cau DNS per als monitors HTTP(s)",
|
||||
"Enable": "Habilita",
|
||||
"Disable": "Desactiva",
|
||||
"chromeExecutable": "Executable Chrome/Chromium",
|
||||
"chromeExecutableAutoDetect": "Detecció automàtica",
|
||||
"dnsCacheDescription": "Pot ser que no funcioni treballant amb entorns IPv6, desactiva'l si detectes qualsevol problema.",
|
||||
"Single Maintenance Window": "Finestra de Manteniment únic",
|
||||
"Maintenance Time Window of a Day": "Finestra de Temps del manteniment d'un Dia",
|
||||
"Effective Date Range": "Rang de data eficaç (Opcional)",
|
||||
"Schedule Maintenance": "Programa de manteniment",
|
||||
"Edit Maintenance": "Edita el manteniment",
|
||||
"Date and Time": "Data i hora",
|
||||
"DateTime Range": "Rang de data i temps",
|
||||
"loadingError": "Impossible obtenir la data, si us plau prova-ho més endavant.",
|
||||
"plugin": "Connector | Connectors",
|
||||
"install": "Instal·la",
|
||||
"installing": "Instal·lant",
|
||||
"uninstall": "Desinstal·la",
|
||||
"confirmUninstallPlugin": "Estàs segur de desinstal·lar aquest connector?",
|
||||
"notificationRegional": "Regional",
|
||||
"Clone Monitor": "Clona el monitor",
|
||||
"Clone": "Clona",
|
||||
"cloneOf": "Clon de {0}",
|
||||
"secureOptionNone": "Cap / STARTTLS (25, 587)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
"Ignore TLS Error": "Ignora error TLS",
|
||||
"From Email": "Des de Correu",
|
||||
"emailCustomisableContent": "Contingut personalitzable",
|
||||
"emailCustomSubject": "Tema personalitzable",
|
||||
"leave blank for default subject": "deixar en blanc per tema per defecte",
|
||||
"emailCustomBody": "Cos personalitzat",
|
||||
"leave blank for default body": "deixa en blanc per un cos per defecte",
|
||||
"emailTemplateServiceName": "Nom de servei",
|
||||
"emailTemplateHostnameOrURL": "Nom de host o URL",
|
||||
"emailTemplateStatus": "Estat",
|
||||
"emailTemplateMonitorJSON": "objecte que descriu el monitor",
|
||||
"emailTemplateHeartbeatJSON": "objecte que descriu el batec del cor",
|
||||
"To Email": "Destí email",
|
||||
"smtpCC": "CC",
|
||||
"smtpBCC": "BCC",
|
||||
"Discord Webhook URL": "Discord Webhook URL",
|
||||
"wayToGetDiscordURL": "Pots rebre aquest per anar a Paràmetres de Servidor -> Integracions -> Vista *Webhooks -> Nou *Webhook",
|
||||
"Bot Display Name": "Nom de pantalla de bot",
|
||||
"Prefix Custom Message": "Prefix de missatge personalitzat",
|
||||
"Hello @everyone is...": "Hola {'@'} a tothom …",
|
||||
"Send to channel": "Envia al canal",
|
||||
"Create new forum post": "Crea una nova publicació",
|
||||
"postToExistingThread": "Publica a un fil existent",
|
||||
"forumPostName": "Nom de publicació de fòrum",
|
||||
"threadForumPostID": "Fil / identificador de fòrum",
|
||||
"e.g. {discordThreadID}": "exemple {discordThreadID}",
|
||||
"wayToGetTeamsURL": "Pot aprendre com crear una URL de webhook {0}.",
|
||||
"wayToGetZohoCliqURL": "Pot aprendre com crear una URL de webhook {0}.",
|
||||
"needSignalAPI": "Necessites tenir una senyal de client amb REST API.",
|
||||
"wayToCheckSignalURL": "Pot comprovar aquesta URL per veure com configurar:",
|
||||
"Number": "Número",
|
||||
"Recipients": "Receptors",
|
||||
"Access Token": "Fitxa d'accés",
|
||||
"Channel access token (Long-lived)": "Fitxa d'accés del canal (de llarga vida)",
|
||||
"Line Developers Console": "Consola de Desenvolupadors de la línia",
|
||||
"appriseNotInstalled": "L'apèndix no està instal·lat. {0}",
|
||||
"Method": "Mètode",
|
||||
"clearDataOlderThan": "Conserva les dades de l'historial del monitor durant {0} dies.",
|
||||
"steamApiKeyDescription": "Per a monitoritzar un servidor de jocs de vapor, necessiteu una clau Steam Web-API. Podeu registrar la vostra clau API aquí: ",
|
||||
"shrinkDatabaseDescriptionSqlite": "Activa la base de dades {vacuum} per a SQLite. {auto.vacuum} ja està activat, però això no desfragmenta la base de dades ni reempaqueta les pàgines individuals de la base de dades de la manera com ho fa l'ordre {vacuum}.",
|
||||
"liquidIntroduction": "S'aconsegueix la flexibilitat mitjançant el llenguatge de templatació líquid. Consulteu el {0} per a les instruccions d'ús. Aquestes són les variables disponibles:",
|
||||
"selectedMonitorCount": "Seleccionat: {0}",
|
||||
"deleteStatusPageMsg": "Esteu segur que voleu suprimir aquesta pàgina d'estat?",
|
||||
"proxyDescription": "Els intermediaris s'han d'assignar a un monitor perquè funcioni.",
|
||||
"enableProxyDescription": "Aquest servidor intermediari no afectarà les sol·licituds del monitor fins que estigui activat. Podeu controlar temporalment desactivar el servidor intermediari de tots els monitors per l'estat d'activació.",
|
||||
"statusPageSpecialSlugDesc": "Àlies especial {0}: aquesta pàgina es mostrarà quan no es proporcioni l'àlies",
|
||||
"Authentication": "Autenticació",
|
||||
"Page Not Found": "Pàgina no trobada",
|
||||
"Don't know how to get the token? Please read the guide:": "No saps com aconseguir el testimoni? Si us plau, llegiu la guia:",
|
||||
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "La connexió actual es pot perdre si esteu connectant a través del túnel Cloudflare. Segur que voleu aturar-ho? Escriviu la contrasenya actual per confirmar-la.",
|
||||
"Refresh Interval": "Interval de refresc",
|
||||
"signedInDispDisabled": "Autenticació desactivada.",
|
||||
"RadiusCalledStationIdDescription": "Identificador del dispositiu anomenat",
|
||||
"Also check beta release": "Comprova també la versió beta",
|
||||
"The resource is no longer available.": "El recurs ja no està disponible.",
|
||||
"certificationExpiryDescription": "Els monitors HTTPS activen la notificació quan el certificat TLS caduca a:",
|
||||
"deleteDockerHostMsg": "Esteu segur que voleu suprimir aquest amfitrió de l'acoblador per a tots els monitors?",
|
||||
"tailscalePingWarning": "Per utilitzar el monitor de Ping Tailscale, heu d'instal·lar el Kuma Uptime sense Docker i també instal·lar el client Tailscale al vostre servidor.",
|
||||
"telegramSendSilentlyDescription": "Envia el missatge en silenci. Els usuaris rebran una notificació sense so.",
|
||||
"telegramProtectContentDescription": "Si està activat, els missatges del bot del Telegram estaran protegits contra reenviaments i desaments.",
|
||||
"wayToGetTelegramChatID": "Podeu obtenir el vostre ID de xat enviant un missatge al bot i anant a aquest URL per veure el xat id:",
|
||||
"trustProxyDescription": "Confia en les capçaleres «X-Forwarded-*». Si voleu obtenir la IP del client correcta i el vostre Kuma Uptime està darrere d'un servidor intermediari com Nginx o Apache, hauríeu d'activar-ho.",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Es pot crear un testimoni d'accés de llarga durada fent clic al nom del vostre perfil (a baix a l'esquerra) i desplaçant-vos a la part inferior i després feu clic a Crea un testimoni. ",
|
||||
"default: notify all devices": "per defecte: notifica tots els dispositius",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Podeu trobar una llista dels Serveis de Notificació a l'assistent d'inici a «Eines de revelador . Serveis» cerca «notificació» per trobar el nom del vostre dispositiu/telèfon.",
|
||||
"backupOutdatedWarning": "Obsolet: Atès que s'han afegit moltes característiques i aquesta funció de còpia de seguretat és una mica inexistent, no pot generar o restaurar una còpia de seguretat completa.",
|
||||
"lastDay1": "L'últim dia del mes",
|
||||
"pauseMaintenanceMsg": "Segur que voleu fer una pausa?",
|
||||
"maintenanceStatus-ended": "Finalitzat",
|
||||
"Display Timezone": "Mostra la zona horària",
|
||||
"enableNSCD": "Habilita NSCD (Dimoni de memòria cau de Servei del Nom ) per accelerar les sol·licituds de DNS",
|
||||
"chromeExecutableDescription": "Per a usuaris de Docker, si el Chrome no està encara instal·lat, pot dur uns quants minuts per instal·lar i mostrar el resultat de prova. Duu 1*GB d'espai de disc.",
|
||||
"uninstalling": "Desinstal·lant",
|
||||
"smtp": "Correu electrònic (SMTP)",
|
||||
"smtpLiquidIntroduction": "Els següents dos camps són personalitzables via \"Liquid templating Language\". Per favor refereix al {0} per a instruccions d'ús. Aquests són les variables disponibles:",
|
||||
"emailTemplateMsg": "missatge de la notificació",
|
||||
"emailTemplateLimitedToUpDownNotification": "només disponible per estats UP/Down, altrament null",
|
||||
"Select message type": "Selecciona el tipus de missatge",
|
||||
"whatHappensAtForumPost": "Crea una nova publicació de fòrum. Això no publica un missatge a un fòrum existent. Per publicar un fil existent utilitza \"{option}\"",
|
||||
"Channel access token": "Fitxa d'accés del canal",
|
||||
"Body": "Cos",
|
||||
"or": "o",
|
||||
"PushUrl": "URL de captura",
|
||||
"HeadersInvalidFormat": "Les capçaleres de sol·licitud no són JSON vàlides: ",
|
||||
"BodyInvalidFormat": "El cos de petició no és JSON vàlid: ",
|
||||
"Security": "Seguretat",
|
||||
"Pick Accepted Status Codes...": "Trieu els codis d'estat acceptats…",
|
||||
"error": "error",
|
||||
"critical": "crítica",
|
||||
"Please input title and content": "Introduïu el títol i el contingut",
|
||||
"telegramServerUrl": "(Opcional) Url del servidor",
|
||||
"telegramServerUrlDescription": "Per saltar-se les limitacions del bot de Telegram o tenir accés a regions bloquejades (China, Iran, etc). Per a més informació fes click {0}. Per defecte {1}",
|
||||
"templateServiceName": "Nom del servei",
|
||||
"templateHostnameOrURL": "Adreça URL o nom del host",
|
||||
"templateStatus": "Estat",
|
||||
"telegramUseTemplate": "Fes servir una plantilla de missatge personalitzada",
|
||||
"telegramUseTemplateDescription": "Si s'activa, el missatge s'enviarà fent servir una plantilla personalitzada.",
|
||||
"telegramTemplateFormatDescription": "Telegram permet l'ús de diferents tipus de llenguatges de marcat, llegeix Telegram {0} per més detalls."
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user