mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 14:54:05 +00:00
Compare commits
39 commits
15db934c64
...
2eb834b30d
Author | SHA1 | Date | |
---|---|---|---|
|
2eb834b30d | ||
|
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 | ||
|
79a26180af | ||
|
7a82ae039c |
76 changed files with 3817 additions and 4352 deletions
|
@ -1,4 +1,4 @@
|
|||
name: json-yaml-validate
|
||||
name: validate
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
@ -25,3 +25,19 @@ jobs:
|
|||
with:
|
||||
comment: "true" # enable comment mode
|
||||
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
||||
|
||||
# General validations
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Validate language JSON files
|
||||
run: node ./extra/check-lang-json.js
|
||||
|
||||
- name: Validate knex migrations filename
|
||||
run: node ./extra/check-knex-filenames.mjs
|
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();
|
||||
});
|
||||
};
|
|
@ -1,3 +1,13 @@
|
|||
# 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:20-bookworm-slim AS base2-slim
|
||||
ARG TARGETPLATFORM
|
||||
|
@ -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.");
|
27
extra/check-lang-json.js
Normal file
27
extra/check-lang-json.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
// For #5231
|
||||
|
||||
const fs = require("fs");
|
||||
|
||||
let path = "./src/lang";
|
||||
|
||||
// list directories in the lang directory
|
||||
let jsonFileList = fs.readdirSync(path);
|
||||
|
||||
for (let jsonFile of jsonFileList) {
|
||||
if (!jsonFile.endsWith(".json")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let jsonPath = path + "/" + jsonFile;
|
||||
let originalContent = fs.readFileSync(jsonPath, "utf8");
|
||||
let langData = JSON.parse(originalContent);
|
||||
|
||||
let formattedContent = JSON.stringify(langData, null, 4) + "\n";
|
||||
|
||||
if (originalContent !== formattedContent) {
|
||||
console.error(`File ${jsonFile} is not formatted correctly.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("All lang json files are formatted correctly.");
|
57
extra/download-apprise.mjs
Normal file
57
extra/download-apprise.mjs
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Go to http://ftp.debian.org/debian/pool/main/a/apprise/ using fetch api, where it is a apache directory listing page
|
||||
// Use cheerio to parse the html and get the latest version of Apprise
|
||||
// call curl to download the latest version of Apprise
|
||||
// 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 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.");
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
// Get all the links in the page
|
||||
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(pattern) && !link.attribs.href.includes("~")) {
|
||||
links.push({
|
||||
filename: link.attribs.href,
|
||||
version: link.attribs.href.match(pattern)[1],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(links);
|
||||
|
||||
// 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");
|
233
extra/release/lib.mjs
Normal file
233
extra/release/lib.mjs
Normal file
|
@ -0,0 +1,233 @@
|
|||
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} repoName Docker Hub repository name
|
||||
* @param {string} version Version to check
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function checkTagExists(repoName, version) {
|
||||
console.log(`Checking if version ${version} exists on Docker Hub`);
|
||||
|
||||
// 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);
|
25
extra/remove-empty-lang-keys.js
Normal file
25
extra/remove-empty-lang-keys.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
// For #5231
|
||||
|
||||
const fs = require("fs");
|
||||
|
||||
let path = "../src/lang";
|
||||
|
||||
// list directories in the lang directory
|
||||
let jsonFileList = fs.readdirSync(path);
|
||||
|
||||
for (let jsonFile of jsonFileList) {
|
||||
if (!jsonFile.endsWith(".json")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let jsonPath = path + "/" + jsonFile;
|
||||
let langData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
|
||||
|
||||
for (let key in langData) {
|
||||
if (langData[key] === "") {
|
||||
delete langData[key];
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(jsonPath, JSON.stringify(langData, null, 4) + "\n");
|
||||
}
|
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,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");
|
||||
|
|
4220
package-lock.json
generated
4220
package-lock.json
generated
File diff suppressed because it is too large
Load diff
26
package.json
26
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "2.0.0-dev",
|
||||
"version": "2.0.0-beta.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -34,20 +34,14 @@
|
|||
"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.15 && npm ci --production && 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",
|
||||
|
@ -58,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",
|
||||
|
@ -68,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",
|
||||
|
@ -108,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",
|
||||
|
@ -116,7 +112,7 @@
|
|||
"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",
|
||||
|
|
|
@ -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,173 @@ 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 heartbeat
|
||||
WHERE monitor_id = ?
|
||||
ORDER BY time DESC
|
||||
LIMIT ?
|
||||
)
|
||||
`, [
|
||||
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 = {
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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,6 +50,7 @@ class Slack extends NotificationProvider {
|
|||
|
||||
const address = this.extractAddress(monitorJSON);
|
||||
if (address) {
|
||||
try {
|
||||
actions.push({
|
||||
"type": "button",
|
||||
"text": {
|
||||
|
@ -57,8 +58,12 @@ class Slack extends NotificationProvider {
|
|||
"text": "Visit site",
|
||||
},
|
||||
"value": "Site",
|
||||
"url": address,
|
||||
"url": new URL(address),
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
log.debug("slack", `Failed to parse address ${address} as URL`);
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
|
|
|
@ -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) {
|
||||
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,6 +220,9 @@ module.exports.statusPageSocketHandler = (socket) => {
|
|||
|
||||
// Delete groups that are not in the list
|
||||
log.debug("socket", "Delete groups that are not in the list");
|
||||
if (groupIDList.length === 0) {
|
||||
await R.exec("DELETE FROM `group` WHERE status_page_id = ?", [ statusPage.id ]);
|
||||
} else {
|
||||
const slots = groupIDList.map(() => "?").join(",");
|
||||
|
||||
const data = [
|
||||
|
@ -227,6 +230,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
|||
statusPage.id
|
||||
];
|
||||
await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots}) AND status_page_id = ?`, data);
|
||||
}
|
||||
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
|
||||
|
|
|
@ -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,6 +308,11 @@ class UptimeCalculator {
|
|||
}
|
||||
await R.store(dailyStatBean);
|
||||
|
||||
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;
|
||||
|
@ -311,7 +327,11 @@ class UptimeCalculator {
|
|||
}
|
||||
}
|
||||
await R.store(hourlyStatBean);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
@ -326,18 +346,23 @@ class UptimeCalculator {
|
|||
}
|
||||
}
|
||||
await R.store(minutelyStatBean);
|
||||
}
|
||||
|
||||
// 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(date.subtract(24, "hour")),
|
||||
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")),
|
||||
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,
|
||||
};
|
|
@ -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>
|
||||
|
|
|
@ -1050,5 +1050,52 @@
|
|||
"less than": "по-малко от",
|
||||
"greater than": "по-голямо от",
|
||||
"greater than or equal to": "по-голямо или равно на",
|
||||
"record": "запис"
|
||||
"record": "запис",
|
||||
"CurlDebugInfo": "За да отстраните грешки в монитора, можете или да поставите това в терминала на Вашата машина, или в терминала на машината, на която работи \"Uptime Kuma\" и да видите заявката.{newiline}Моля, вземете под внимание мрежовите разлики като {firewalls}, {dns_resolvers} или {docker_networks}.",
|
||||
"shrinkDatabaseDescriptionSqlite": "Инициира {vacuum} за база данни тип SQLite. Функцията {auto_vacuum} вече е активирана, но това не дефрагментира базата данни, нито препакетира отделните страници на базата данни по начина, по който го прави командата {vacuum}.",
|
||||
"ignoredTLSError": "TLS/SSL грешките са игнорирани",
|
||||
"Debug": "Отстраняване на грешки",
|
||||
"Copy": "Копирай",
|
||||
"CopyToClipboardError": "Неуспешно копиране в клипборда: {error}",
|
||||
"CopyToClipboardSuccess": "Копирано!",
|
||||
"firewalls": "защитни стени",
|
||||
"dns resolvers": "DNS преобразуватели",
|
||||
"docker networks": "docker мрежи",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Пълният oauth клиентски идентификационен поток не се поддържа в {curl}.{newline}Моля, вземете токен на носител и го предайте чрез опцията {oauth2_bearer}.",
|
||||
"CurlDebugInfoProxiesUnsupported": "Поддръжката на прокси в горната команда {curl} в момента не е внедрена.",
|
||||
"Message format": "Формат на съобщението",
|
||||
"Send rich messages": "Изпращай съобщения в \"rich\" формат",
|
||||
"Sound": "Звук",
|
||||
"Notification Channel": "Канал за известяване",
|
||||
"Alphanumerical string and hyphens only": "Само буквено-цифров низ и тирета",
|
||||
"Arcade": "Arcade",
|
||||
"Correct": "Правилно",
|
||||
"Fail": "Грешка",
|
||||
"Harp": "Арфа",
|
||||
"Reveal": "Разкрий",
|
||||
"Bubble": "Балон",
|
||||
"Doorbell": "Звънец на врата",
|
||||
"Flute": "Флейта",
|
||||
"Money": "Пари",
|
||||
"Scifi": "Nаучна фантастика",
|
||||
"Clear": "Премахни",
|
||||
"Elevator": "Асансьор",
|
||||
"Guitar": "Китара",
|
||||
"Pop": "Поп",
|
||||
"Custom sound to override default notification sound": "Персонализиран звук, заменящ звука за известяване по подразбиране",
|
||||
"Time Sensitive (iOS Only)": "Зависещи от часа (само за iOS)",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Известията от типа \"Зависещи от часа\" ще бъдат доставени незабавно, дори ако устройството е в режим „Не безпокойте“.",
|
||||
"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, в случай, че желаете да получавате отговори.",
|
||||
"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}."
|
||||
}
|
||||
|
|
|
@ -1051,5 +1051,9 @@
|
|||
"Client ID": "ID klienta",
|
||||
"Client Secret": "Tajemství klienta",
|
||||
"OAuth Scope": "OAuth rozsah",
|
||||
"Optional: Space separated list of scopes": "Volitelné: seznam rozsahů oddělte mezerami"
|
||||
"Optional: Space separated list of scopes": "Volitelné: seznam rozsahů oddělte mezerami",
|
||||
"Debug": "Ladění",
|
||||
"CopyToClipboardError": "Nelze zkopírovat do schránky: {error}",
|
||||
"ignoredTLSError": "Chyby TLS/SSL byly ignorovány",
|
||||
"Copy": "Kopírovat"
|
||||
}
|
||||
|
|
|
@ -1048,5 +1048,52 @@
|
|||
"less than or equal to": "kleiner als oder gleich",
|
||||
"greater than or equal to": "grösser als oder gleich",
|
||||
"record": "Eintrag",
|
||||
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut."
|
||||
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut.",
|
||||
"ignoredTLSError": "TLS/SSL-Fehler wurden ignoriert",
|
||||
"Debug": "Debug",
|
||||
"Copy": "Kopieren",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Der vollständige OAuth-Client-Credential-Flow wird in {curl} nicht unterstützt.{newline}Bitte besorge dir ein Bearer-Token und übergebe ihn über die {oauth2_bearer}-Option.",
|
||||
"Money": "Geld",
|
||||
"Custom sound to override default notification sound": "Benutzerdefinierter Ton, um den standardmässigen Benachrichtigungston zu ersetzen",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Entweder eine Text-Absender-ID oder eine Telefonnummer im E.164-Format, wenn du Antworten erhalten möchtest.",
|
||||
"CopyToClipboardError": "Konnte nicht in die Zwischenablage kopiert werden: {error}",
|
||||
"CopyToClipboardSuccess": "Kopiert!",
|
||||
"CurlDebugInfo": "Um den Monitor zu debuggen, kannst du dies entweder in das Terminal deines eigenen Rechners oder in das Terminal der Maschine, auf der Uptime Kuma läuft, einfügen und überprüfen, was du anforderst.{newiline}Bitte beachte Netzwerkunterschiede wie {firewalls}, {dns_resolvers} oder {docker_networks}.",
|
||||
"firewalls": "Firewalls",
|
||||
"dns resolvers": "DNS-Resolver",
|
||||
"docker networks": "Docker-Netzwerke",
|
||||
"CurlDebugInfoProxiesUnsupported": "Die Unterstützung von Proxys im oben genannten {curl}-Befehl ist derzeit nicht implementiert.",
|
||||
"Message format": "Nachrichtenformat",
|
||||
"Send rich messages": "Sende Rich-Text-Nachrichten",
|
||||
"Notification Channel": "Benachrichtigungskanal",
|
||||
"Sound": "Benachrichtigungston",
|
||||
"Alphanumerical string and hyphens only": "Nur alphanumerische Zeichen und Bindestriche",
|
||||
"Correct": "Korrekt",
|
||||
"Fail": "Fehlgeschlagen",
|
||||
"Harp": "Harfe",
|
||||
"Reveal": "Enthüllen",
|
||||
"Bubble": "Blase",
|
||||
"Doorbell": "Türklingel",
|
||||
"Flute": "Flöte",
|
||||
"Scifi": "Science Fiction",
|
||||
"Clear": "Klar",
|
||||
"Elevator": "Lift",
|
||||
"Guitar": "Gitarre",
|
||||
"Pop": "Pop",
|
||||
"Time Sensitive (iOS Only)": "Zeitkritisch (nur iOS)",
|
||||
"The phone number of the recipient in E.164 format.": "Die Telefonnummer des Empfängers im E.164-Format.",
|
||||
"Can be found on:": "Ist zu finden auf: {0}",
|
||||
"From": "Von",
|
||||
"Arcade": "Spielhalle",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Zeitkritische Benachrichtigungen werden sofort zugestellt, auch wenn sich das Gerät im Nicht stören-Modus befindet.",
|
||||
"RabbitMQ Nodes": "RabbitMQ-Verwaltungsknoten",
|
||||
"rabbitmqNodesDescription": "Gib die URL für die RabbitMQ-Verwaltungsknoten einschliesslich Protokoll und Port ein. Beispiel: {0}",
|
||||
"rabbitmqNodesRequired": "Setze die Knoten für diesen Monitor.",
|
||||
"RabbitMQ Username": "RabbitMQ Benutzername",
|
||||
"RabbitMQ Password": "RabbitMQ Passwort",
|
||||
"SendGrid API Key": "SendGrid-API-Schlüssel",
|
||||
"Separate multiple email addresses with commas": "Mehrere E-Mail-Adressen mit Kommas trennen",
|
||||
"rabbitmqNodesInvalid": "Benutze eine vollständig qualifizierte URL (beginnend mit 'http') für RabbitMQ-Knoten.",
|
||||
"rabbitmqHelpText": "Um den Monitor zu benutzen, musst du das Management Plugin in deinem RabbitMQ-Setup aktivieren. Weitere Informationen siehe {rabitmq_documentation}.",
|
||||
"aboutSlackUsername": "Ändert den Anzeigenamen des Absenders. Wenn du jemanden erwähnen möchtest, füge ihn stattdessen in den Namen ein."
|
||||
}
|
||||
|
|
|
@ -1051,5 +1051,52 @@
|
|||
"less than or equal to": "kleiner als oder gleich",
|
||||
"greater than or equal to": "größer als oder gleich",
|
||||
"record": "Eintrag",
|
||||
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut."
|
||||
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut.",
|
||||
"ignoredTLSError": "TLS/SSL-Fehler wurden ignoriert",
|
||||
"Message format": "Nachrichtenformat",
|
||||
"Notification Channel": "Benachrichtigungskanal",
|
||||
"Custom sound to override default notification sound": "Benutzerdefinierter Ton, um den standardmäßigen Benachrichtigungston zu ersetzen",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Zeitkritische Benachrichtigungen werden sofort zugestellt, auch wenn sich das Gerät im Nicht stören-Modus befindet.",
|
||||
"Debug": "Debug",
|
||||
"Copy": "Kopieren",
|
||||
"CopyToClipboardError": "Konnte nicht in die Zwischenablage kopiert werden: {error}",
|
||||
"CopyToClipboardSuccess": "Kopiert!",
|
||||
"CurlDebugInfo": "Um den Monitor zu debuggen, kannst du dies entweder in das Terminal deines eigenen Rechners oder in das Terminal der Maschine, auf der Uptime Kuma läuft, einfügen und überprüfen, was du anforderst.{newiline}Bitte beachte Netzwerkunterschiede wie {firewalls}, {dns_resolvers} oder {docker_networks}.",
|
||||
"firewalls": "Firewalls",
|
||||
"dns resolvers": "DNS-Resolver",
|
||||
"docker networks": "Docker-Netzwerke",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Der vollständige OAuth-Client-Credential-Flow wird in {curl} nicht unterstützt.{newline}Bitte besorge dir ein Bearer-Token und übergebe ihn über die {oauth2_bearer}-Option.",
|
||||
"CurlDebugInfoProxiesUnsupported": "Die Unterstützung von Proxys im oben genannten {curl}-Befehl ist derzeit nicht implementiert.",
|
||||
"Alphanumerical string and hyphens only": "Nur alphanumerische Zeichen und Bindestriche",
|
||||
"Correct": "Korrekt",
|
||||
"Harp": "Harfe",
|
||||
"Doorbell": "Türklingel",
|
||||
"Flute": "Flöte",
|
||||
"Money": "Geld",
|
||||
"Scifi": "Science Fiction",
|
||||
"Elevator": "Aufzug",
|
||||
"Guitar": "Gitarre",
|
||||
"Sound": "Benachrichtigungston",
|
||||
"Time Sensitive (iOS Only)": "Zeitkritisch (nur iOS)",
|
||||
"From": "Von",
|
||||
"Can be found on:": "Ist zu finden auf: {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Die Telefonnummer des Empfängers im E.164-Format.",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Entweder eine Text-Absender-ID oder eine Telefonnummer im E.164-Format, wenn du Antworten erhalten möchtest.",
|
||||
"Send rich messages": "Sende Rich-Text-Nachrichten",
|
||||
"Fail": "Fehlgeschlagen",
|
||||
"Reveal": "Enthüllen",
|
||||
"Bubble": "Blase",
|
||||
"Clear": "Klar",
|
||||
"Pop": "Pop",
|
||||
"Arcade": "Spielhalle",
|
||||
"rabbitmqNodesRequired": "Setze die Knoten für diesen Monitor.",
|
||||
"RabbitMQ Username": "RabbitMQ Benutzername",
|
||||
"RabbitMQ Password": "RabbitMQ Passwort",
|
||||
"SendGrid API Key": "SendGrid-API-Schlüssel",
|
||||
"Separate multiple email addresses with commas": "Mehrere E-Mail-Adressen mit Kommas trennen",
|
||||
"RabbitMQ Nodes": "RabbitMQ-Verwaltungsknoten",
|
||||
"rabbitmqNodesDescription": "Gib die URL für die RabbitMQ-Verwaltungsknoten einschließlich Protokoll und Port ein. Beispiel: {0}",
|
||||
"rabbitmqNodesInvalid": "Benutze eine vollständig qualifizierte URL (beginnend mit 'http') für RabbitMQ-Knoten.",
|
||||
"rabbitmqHelpText": "Um den Monitor zu benutzen, musst du das Management Plugin in deinem RabbitMQ-Setup aktivieren. Weitere Informationen siehe {rabitmq_documentation}.",
|
||||
"aboutSlackUsername": "Ändert den Anzeigenamen des Absenders. Wenn du jemanden erwähnen möchtest, füge ihn stattdessen in den Namen ein."
|
||||
}
|
||||
|
|
|
@ -97,8 +97,6 @@
|
|||
"pushOthers": "Others",
|
||||
"programmingLanguages": "Programming Languages",
|
||||
"Save": "Save",
|
||||
"Debug": "Debug",
|
||||
"Copy": "Copy",
|
||||
"Notifications": "Notifications",
|
||||
"Not available, please setup.": "Not available, please set up.",
|
||||
"Setup Notification": "Set Up Notification",
|
||||
|
@ -251,14 +249,6 @@
|
|||
"PushUrl": "Push URL",
|
||||
"HeadersInvalidFormat": "The request headers are not valid JSON: ",
|
||||
"BodyInvalidFormat": "The request body is not valid JSON: ",
|
||||
"CopyToClipboardError": "Couldn't copy to clipbard: {error}",
|
||||
"CopyToClipboardSuccess": "Copied!",
|
||||
"CurlDebugInfo": "To debug the monitor, you can either paste this into your own machines terminal or into the machines terminal which uptime kuma is running on and see what you are requesting.{newiline}Please be aware of networking differences like {firewalls}, {dns_resolvers} or {docker_networks}.",
|
||||
"firewalls": "firewalls",
|
||||
"dns resolvers": "dns resolvers",
|
||||
"docker networks": "docker networks",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Full oauth client credential flow is not supported in {curl}.{newline}Please get a bearer token and pass it via the {oauth2_bearer} option.",
|
||||
"CurlDebugInfoProxiesUnsupported": "Proxy support in the above {curl} command is currently not implemented.",
|
||||
"Monitor History": "Monitor History",
|
||||
"clearDataOlderThan": "Keep monitor history data for {0} days.",
|
||||
"PasswordsDoNotMatch": "Passwords do not match.",
|
||||
|
@ -731,6 +721,7 @@
|
|||
"Icon Emoji": "Icon Emoji",
|
||||
"signalImportant": "IMPORTANT: You cannot mix groups and numbers in recipients!",
|
||||
"aboutWebhooks": "More info about Webhooks on: {0}",
|
||||
"aboutSlackUsername": "Changes the display name of the message sender. If you want to mention someone, include it in the friendly name instead.",
|
||||
"aboutChannelName": "Enter the channel name on {0} Channel Name field if you want to bypass the Webhook channel. Ex: #other-channel",
|
||||
"aboutKumaURL": "If you leave the Uptime Kuma URL field blank, it will default to the Project GitHub page.",
|
||||
"smtpDkimSettings": "DKIM Settings",
|
||||
|
|
|
@ -242,7 +242,6 @@
|
|||
"Running": "Töötab",
|
||||
"resendEveryXTimes": "Saada uuesti {0} korda",
|
||||
"statusMaintenance": "Hooldus",
|
||||
"Webhook URL": "",
|
||||
"Server URL": "Serveri URL",
|
||||
"Priority": "Tähtsus",
|
||||
"emojiCheatSheet": "Emotikoni spikker: {0}",
|
||||
|
@ -254,7 +253,6 @@
|
|||
"PushUrl": "Lükka URL",
|
||||
"Monitor History": "Monitori ajalugu",
|
||||
"PasswordsDoNotMatch": "Paroolid ei ühti.",
|
||||
"records": "",
|
||||
"Current User": "Praegune kasutaja",
|
||||
"topic": "Teema",
|
||||
"successMessage": "Edukas sõnum",
|
||||
|
@ -308,7 +306,6 @@
|
|||
"General Monitor Type": "Üldine monitori tüüp",
|
||||
"webhookAdditionalHeadersDesc": "Lisab täiendavad päised saadetud webhookiga.",
|
||||
"Read more": "Loe rohkem",
|
||||
"HeadersInvalidFormat": "",
|
||||
"clearDataOlderThan": "Hoia monitori ajalugu alles {0} päeva.",
|
||||
"steamApiKeyDescription": "Steam Game Serveri monitoorimiseks on vaja sul Steam Web-API võtit. Sa saad registreerida enda API võtme siin: ",
|
||||
"Done": "Tehtud",
|
||||
|
|
|
@ -1048,5 +1048,52 @@
|
|||
"greater than": "enemmän kuin",
|
||||
"less than or equal to": "vähemmän tai yhtä paljon kuin",
|
||||
"greater than or equal to": "enemmän tai yhtä paljon kuin",
|
||||
"record": "tietue"
|
||||
"record": "tietue",
|
||||
"Notification Channel": "Ilmoituskanava",
|
||||
"Custom sound to override default notification sound": "Mukautettu ääni oletusäänen sijaan",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Aikaherkät ilmoitukset toimitetaan välittömästi vaikka laite olisi Älä Häiritse-tilassa.",
|
||||
"ignoredTLSError": "TLS/SSL-virheitä ei huomioida",
|
||||
"Debug": "Debug",
|
||||
"Copy": "Kopioi",
|
||||
"CopyToClipboardError": "Leikepöydälle kopiointi ei onnistunut: {error}",
|
||||
"CopyToClipboardSuccess": "Kopioitu!",
|
||||
"CurlDebugInfo": "Debugataksesi seurainta, voit joko kopioida tämän oman tietokoneesi komentoriville tai sen tietokoneen komentoriville jossa uptime kuma on käynissä nähdäksesi pyyntösi.{newiline}Otathan huomioon verkkoympäristöjen erot kuten {firewalls}, {dns_resolvers} tai {docker_networks}.",
|
||||
"firewalls": "palomuurit",
|
||||
"dns resolvers": "DNS-palvelimet",
|
||||
"docker networks": "Docker-verkot",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "{curl} ei tue täydellistä oauth-asiakkaan kirjautumistietojen välittämistä.{newline}Hanki bearer token -tunnus ja välitä se {oauth2_bearer}-vaihtoehdon kautta.",
|
||||
"CurlDebugInfoProxiesUnsupported": "Välityspalvelimen tukea yllä olevassa {curl}-komennossa ei ole tällä hetkellä toteutettu.",
|
||||
"shrinkDatabaseDescriptionSqlite": "Käynnistä tietokannan {vacuum} SQLitelle. {auto_vacuum} on jo käytössä, mutta se ei eheytä tietokantaa tai uudelleenpakkaa yksittäisiä tietokantasivuja samoin kuin {vacuum}.",
|
||||
"Message format": "Viestin muoto",
|
||||
"Send rich messages": "Lähetä monipuolisia viestejä (rich messages)",
|
||||
"Sound": "Ääni",
|
||||
"Alphanumerical string and hyphens only": "Vain aakkosnumeerinen merkkijono ja yhdysmerkit",
|
||||
"Arcade": "Pelihalli",
|
||||
"Harp": "Harppu",
|
||||
"Fail": "Virhe",
|
||||
"Correct": "Onnistuminen",
|
||||
"Reveal": "Ilmestys",
|
||||
"Bubble": "Kupla",
|
||||
"Doorbell": "Ovikello",
|
||||
"Flute": "Huilu",
|
||||
"Money": "Raha",
|
||||
"Clear": "Kirkas",
|
||||
"Elevator": "Hissi",
|
||||
"Guitar": "Kitara",
|
||||
"Time Sensitive (iOS Only)": "Aikaherkkä (vain iOS)",
|
||||
"From": "Lähettäjä",
|
||||
"Can be found on:": "Löydettävissä: {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Vastaanottajan puhelinnumero E.164-muodossa.",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Joko lähettäjätunnus (sender ID) tai puhelinnumero E-164-muodossa jos haluat pystyä vastaanottamaan vastausviestejä.",
|
||||
"Scifi": "Tieteisseikkailu",
|
||||
"Pop": "Poksahdus",
|
||||
"rabbitmqNodesRequired": "Aseta tämän seuraimen solmut.",
|
||||
"rabbitmqNodesInvalid": "Käytä RabbitMQ-solmuille täysin hyväksyttyä ('http' alkavaa) URL-osoitetta.",
|
||||
"RabbitMQ Username": "RabbitMQ Käyttäjänimi",
|
||||
"RabbitMQ Password": "RabbitMQ Salasana",
|
||||
"SendGrid API Key": "SendGrid API-avain",
|
||||
"Separate multiple email addresses with commas": "Erottele useammat sähköpostiosoitteet pilkuilla",
|
||||
"RabbitMQ Nodes": "RabbitMQ-hallintasolmut",
|
||||
"rabbitmqNodesDescription": "Anna URL RabbitMQ-hallintasolmuille sisältäen protokollan ja portin. Esimerkki: {0}",
|
||||
"rabbitmqHelpText": "Jotta voit käyttää seurainta, sinun on otettava hallintalaajennus käyttöön RabbitMQ-asetuksissa. Lisätietoja saat osoitteesta {rabitmq_documentation}."
|
||||
}
|
||||
|
|
|
@ -1051,5 +1051,51 @@
|
|||
"less than or equal to": "inférieur ou égal à",
|
||||
"greater than or equal to": "supérieur ou égal à",
|
||||
"record": "enregistrer",
|
||||
"shrinkDatabaseDescriptionSqlite": "Déclencher la commande {vacuum} pour la base de données SQLite. {auto_vacuum} est déjà activé, mais cela ne défragmente pas la base de données ni ne réorganise les pages individuelles de la base de données de la même manière que la commande {vacuum}."
|
||||
"shrinkDatabaseDescriptionSqlite": "Déclencher la commande {vacuum} pour la base de données SQLite. {auto_vacuum} est déjà activé, mais cela ne défragmente pas la base de données ni ne réorganise les pages individuelles de la base de données de la même manière que la commande {vacuum}.",
|
||||
"ignoredTLSError": "Les erreurs TLS/SSL ont été ignorées",
|
||||
"CopyToClipboardError": "Impossible de copier dans le presse-papiers : {error}",
|
||||
"CurlDebugInfo": "Pour déboguer la sonde, vous pouvez soit le coller dans le terminal de votre propre machine, soit dans le terminal de la machine sur lequel kuma de disponibilité s'exécute et voir ce qui vous est demandé. {newiline} Veuillez être conscient des différences de réseau telles que {firewalls}, {dns_resolvers}. ou {docker_networks}.",
|
||||
"Notification Channel": "Canal de notification",
|
||||
"Alphanumerical string and hyphens only": "Chaîne alphanumérique et tirets uniquement",
|
||||
"Custom sound to override default notification sound": "Son personnalisé pour remplacer le son de notification par défaut",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Les notifications urgentes seront envoyées immédiatement, même si l'appareil est en mode Ne pas déranger.",
|
||||
"Debug": "Déboguer",
|
||||
"Copy": "Copie",
|
||||
"CopyToClipboardSuccess": "Copié !",
|
||||
"firewalls": "pare-feu",
|
||||
"dns resolvers": "résolveurs DNS",
|
||||
"docker networks": "réseaux dockers",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Le flux complet des informations d'identification du client OAuth n'est pas pris en charge dans {curl}. {newline}Veuillez obtenir un jeton d'accès bearer token et le transmettre via l'option {oauth2_bearer}.",
|
||||
"CurlDebugInfoProxiesUnsupported": "La prise en charge du proxy dans la commande {curl} ci-dessus n'est actuellement pas implémentée.",
|
||||
"Message format": "Format des messages",
|
||||
"Send rich messages": "Envoyer des messages enrichis",
|
||||
"Sound": "Son",
|
||||
"Arcade": "Arcade",
|
||||
"Correct": "Correcte",
|
||||
"Fail": "Échouer",
|
||||
"Harp": "Harpe",
|
||||
"Reveal": "Révéler",
|
||||
"Bubble": "Bulle",
|
||||
"Doorbell": "Sonnette",
|
||||
"Flute": "Flûte",
|
||||
"Money": "Argent",
|
||||
"Scifi": "Science-fiction",
|
||||
"Clear": "Effacer",
|
||||
"Elevator": "Ascenseur",
|
||||
"Guitar": "Guitare",
|
||||
"Pop": "Pop",
|
||||
"Time Sensitive (iOS Only)": "Sensible au temps (iOS uniquement)",
|
||||
"From": "De",
|
||||
"Can be found on:": "Disponible sur : {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Le numéro de téléphone du destinataire au 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.": "Soit un identifiant d'expéditeur de texte, soit un numéro de téléphone au format E.164 si vous souhaitez pouvoir recevoir des réponses.",
|
||||
"RabbitMQ Nodes": "Nœuds de gestion RabbitMQ",
|
||||
"rabbitmqNodesDescription": "Entrez l'URL des nœuds de gestion RabbitMQ, y compris le protocole et le port. Exemple : {0}",
|
||||
"rabbitmqNodesRequired": "Veuillez définir les nœuds pour cette sonde.",
|
||||
"rabbitmqNodesInvalid": "Veuillez utiliser une URL complète (commençant par « http ») pour les nœuds RabbitMQ.",
|
||||
"RabbitMQ Username": "Nom d'utilisateur RabbitMQ",
|
||||
"RabbitMQ Password": "Mot de passe RabbitMQ",
|
||||
"rabbitmqHelpText": "Pour utiliser la sonde, vous devrez activer le plug-in de gestion dans votre configuration RabbitMQ. Pour plus d'informations, veuillez consulter la {rabitmq_documentation}.",
|
||||
"SendGrid API Key": "Clé API SendGrid",
|
||||
"Separate multiple email addresses with commas": "Séparez plusieurs adresses e-mail par des virgules"
|
||||
}
|
||||
|
|
|
@ -58,5 +58,53 @@
|
|||
"Cert Exp.": "प्रमाणपत्र अनुभव.",
|
||||
"dbName": "डेटाबेस का नाम",
|
||||
"now": "अभी",
|
||||
"-year": "-वर्ष"
|
||||
"-year": "-वर्ष",
|
||||
"Not available, please setup.": "उपलब्ध नहीं है, कृपया सेट अप करें।",
|
||||
"Auto": "स्वतः",
|
||||
"styleElapsedTime": "हार्टबीट बार के नीचे बीता हुआ समय",
|
||||
"Setup Notification": "सूचना सेट अप करें",
|
||||
"Light": "प्रकाश",
|
||||
"Dark": "अंधकार",
|
||||
"Theme - Heartbeat Bar": "थीम - हार्टबीट बार",
|
||||
"Host URL": "होस्ट यूआरएल",
|
||||
"Request Timeout": "अनुरोध समय सीमा",
|
||||
"Accepted Status Codes": "स्वीकृत स्थिति कोड",
|
||||
"Response": "प्रतिक्रिया",
|
||||
"locally configured mail transfer agent": "स्थानीय रूप से कॉन्फ़िगर किया गया मेल ट्रांसफर एजेंट",
|
||||
"Heartbeat Interval": "हार्टबीट अंतराल",
|
||||
"Retries": "पुनः प्रयासों की संख्या",
|
||||
"Heartbeat Retry Interval": "हार्टबीट पुनः प्रयास अंतराल",
|
||||
"Port": "पोर्ट",
|
||||
"checkEverySecond": "हर {0} सेकंड में जांच करें",
|
||||
"retryCheckEverySecond": "हर {0} सेकंड में पुनः प्रयास करें",
|
||||
"ignoreTLSErrorGeneral": "कनेक्शन के लिए TLS/SSL त्रुटि को अनदेखा करें",
|
||||
"needPushEvery": "आपको हर {0} सेकंड में इस URL को कॉल करना चाहिए।",
|
||||
"pushOptionalParams": "वैकल्पिक पैरामीटर: {0}",
|
||||
"Save": "सहेजें",
|
||||
"retriesDescription": "सेवा को डाउन के रूप में चिह्नित करने और सूचना भेजने से पहले अधिकतम पुनः प्रयासों की संख्या",
|
||||
"Notifications": "सूचनाएँ",
|
||||
"Check Update On GitHub": "गिटहब पर अपडेट जांचें",
|
||||
"timeoutAfter": "{0} सेकंड के बाद समय सीमा समाप्त",
|
||||
"upsideDownModeDescription": "स्थिति को उल्टा करें। यदि सेवा पहुंच योग्य है, तो यह DOWN है।",
|
||||
"Upside Down Mode": "उल्टा मोड",
|
||||
"Max. Redirects": "अधिकतम पुनर्निर्देश",
|
||||
"Push URL": "पुश यूआरएल",
|
||||
"Monitor": "मॉनिटर | मॉनिटर्स",
|
||||
"maxRedirectDescription": "अनुसरण करने के लिए अधिकतम पुनर्निर्देशनों की संख्या। पुनर्निर्देशनों को अक्षम करने के लिए 0 पर सेट करें।",
|
||||
"Advanced": "उन्नत",
|
||||
"Invert Keyword": "कीवर्ड उलटें",
|
||||
"Json Query Expression": "JSON क्वेरी अभिव्यक्ति",
|
||||
"URL": "यूआरएल",
|
||||
"Hostname": "होस्टनेम",
|
||||
"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": "या तो उस सर्वर का होस्टनेम दर्ज करें जिससे आप कनेक्ट करना चाहते हैं या {localhost} यदि आप {local_mta} का उपयोग करना चाहते हैं",
|
||||
"Resend Notification if Down X times consecutively": "यदि लगातार X बार डाउन हो, तो पुनः सूचना भेजें",
|
||||
"resendEveryXTimes": "हर {0} बार में पुनः भेजें",
|
||||
"resendDisabled": "पुनः भेजना निष्क्रिय किया गया है",
|
||||
"ignoredTLSError": "TLS/SSL त्रुटियों को अनदेखा किया गया है",
|
||||
"pushOthers": "अन्य",
|
||||
"programmingLanguages": "प्रोग्रामिंग भाषाएँ",
|
||||
"styleElapsedTimeShowNoLine": "दिखाएँ (कोई रेखा नहीं)",
|
||||
"Expected Value": "अपेक्षित मान",
|
||||
"ignoreTLSError": "HTTPS वेबसाइटों के लिए TLS/SSL त्रुटियों को अनदेखा करें",
|
||||
"pushViewCode": "पुश मॉनिटर का उपयोग कैसे करें? (कोड देखें)"
|
||||
}
|
||||
|
|
|
@ -983,5 +983,113 @@
|
|||
"threemaRecipientTypePhone": "Telefonski broj",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, bez vodećeg znaka +",
|
||||
"threemaBasicModeInfo": "Napomena: Ova integracija koristi Threema Gateway u osnovnom načinu rada (enkripcija temeljena na poslužitelju). Dodatne pojedinosti možete pronaći na {0}.",
|
||||
"smspartnerSenderNameInfo": "Mora biti između 3 i 11 znakova"
|
||||
"smspartnerSenderNameInfo": "Mora biti između 3 i 11 znakova",
|
||||
"shrinkDatabaseDescriptionSqlite": "Pokreni {vacuum} baze podataka za SQLite. Opcija {auto_vacuum} je već omogućena, ali to ne defragmentira bazu podataka niti ponovno pakira pojedinačne stranice baze podataka na način na koji to radi naredba {vacuum}.",
|
||||
"ignoredTLSError": "TLS/SSL greške se ignoriraju",
|
||||
"cacheBusterParam": "Dodaj parametar {0}",
|
||||
"cacheBusterParamDescription": "Nasumično generirani parametar, za preskakanje predmemorije.",
|
||||
"snmpCommunityStringHelptext": "Ovaj niz funkcionira kao lozinka za provjeru autentičnosti i kontrolu pristupa uređajima s omogućenim SNMP-om. Uskladite ga sa svojom konfiguracijom SNMP uređaja.",
|
||||
"privateOnesenderDesc": "Provjerite je li broj telefona valjan. Za slanje poruke na privatni telefonski broj, npr. 628123456789",
|
||||
"Go back to home page.": "Vratite se na početnu stranicu.",
|
||||
"signl4Docs": "Više informacija o tome kako konfigurirati SIGNL4 i kako dobiti SIGNL4 URL webhooka možete pronaći na {0}.",
|
||||
"not starts with": "ne počinje s",
|
||||
"less than or equal to": "manje od ili jednako",
|
||||
"Doorbell": "Zvono na vratima",
|
||||
"Custom sound to override default notification sound": "Prilagođeni zvuk za zamjenu zadanog zvuka obavijesti",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Vremenski osjetljive obavijesti bit će isporučene odmah, čak i ako je uređaj u načinu rada bez ometanja.",
|
||||
"now": "sada",
|
||||
"time ago": "prije {0}",
|
||||
"-year": "-godišnje",
|
||||
"Json Query Expression": "Upit u JSON obliku",
|
||||
"Community String": "Zajednički niz teksta",
|
||||
"conditionAddGroup": "Dodaj grupu",
|
||||
"conditionDeleteGroup": "Obriši grupu",
|
||||
"Debug": "Otklanjanje grešaka",
|
||||
"Copy": "Kopirati",
|
||||
"CopyToClipboardError": "Greška pri kopiranju u međuspremnik: {error}",
|
||||
"CopyToClipboardSuccess": "Kopirano!",
|
||||
"dns resolvers": "DNS razrješivači",
|
||||
"firewalls": "vatrozidi",
|
||||
"CurlDebugInfo": "Za otklanjanje grešaka u Monitoru, možete zalijepiti ovo u terminal na vlastitom računalu ili računalu na kojem se Uptime Kuma pokreće, kako biste isprobali Vaš mrežni zahtjev.{newiline}Budite svjesni mrežnih razlika koje mogu činiti {firewalls}, {dns_resolvers} ili {docker_networks}.",
|
||||
"docker networks": "Dockerove mreže",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Potpuni tok vjerodajnica klijenta OAuth nije podržan u {curl}.{newline}Nabavite token nositelja i proslijedite ga koristeći opciju {oauth2_bearer}.",
|
||||
"CurlDebugInfoProxiesUnsupported": "Proxy podrška u gornjoj naredbi {curl} trenutno nije implementirana.",
|
||||
"and": "i",
|
||||
"Message format": "Format poruke",
|
||||
"Send rich messages": "Slanje poruka s obogaćenim tekstom",
|
||||
"OID (Object Identifier)": "OID (Identifikator objekta)",
|
||||
"snmpOIDHelptext": "Unesite OID za senzor ili status kojeg želite monitorirati. Koristite alate za upravljanje mrežom poput MIB preglednika ili SNMP programa ako niste sigurni koja je vrijednost OID-a.",
|
||||
"Condition": "Uvjet",
|
||||
"SNMP Version": "Inačica SNMP-a",
|
||||
"Please enter a valid OID.": "Unesite važeći OID.",
|
||||
"Recipient Type": "Tip primatelja",
|
||||
"Private Number": "Privatni broj",
|
||||
"wayToGetOnesenderUrlandToken": "URL i token možete dobiti odlaskom na OneSender web stranicu. Više informacija na {0}",
|
||||
"Token Onesender": "OneSender Token",
|
||||
"Host Onesender": "Adresa OneSender domaćina",
|
||||
"Group ID": "Identifikator Grupe",
|
||||
"groupOnesenderDesc": "Provjerite je li Identifikator Grupe valjan. Za slanje poruke na Grupu, npr. 628123456789-342345",
|
||||
"Add Remote Browser": "Dodaj udaljeni preglednik",
|
||||
"New Group": "Nova grupa",
|
||||
"Group Name": "Naziv grupe",
|
||||
"OAuth2: Client Credentials": "OAuth2: vjerodajnice klijenta",
|
||||
"Authentication Method": "Metoda provjere autentičnosti",
|
||||
"Authorization Header": "Zaglavlje autorizacije",
|
||||
"Form Data Body": "Tijelo podataka obrasca",
|
||||
"OAuth Token URL": "URL OAuth tokena",
|
||||
"Client ID": "Klijentski identifikator",
|
||||
"Client Secret": "Klijentska tajna",
|
||||
"OAuth Scope": "OAuth opseg",
|
||||
"Optional: Space separated list of scopes": "Neobavezno: popis opsega odvojen razmakom",
|
||||
"No tags found.": "Nema pronađenih oznaka.",
|
||||
"Lost connection to the socket server.": "Izgubljena veza sa socket poslužiteljem.",
|
||||
"Cannot connect to the socket server.": "Nije moguće spojiti se na socket poslužitelj.",
|
||||
"SIGNL4": "SIGNL4",
|
||||
"SIGNL4 Webhook URL": "SIGNL4 URL webhooka",
|
||||
"Conditions": "Uvjeti",
|
||||
"conditionAdd": "Dodaj uvjet",
|
||||
"conditionDelete": "Obriši uvjet",
|
||||
"conditionValuePlaceholder": "Vrijednost",
|
||||
"equals": "je jednako",
|
||||
"not equals": "nije jednako",
|
||||
"contains": "sadržava",
|
||||
"not contains": "ne sadržava",
|
||||
"starts with": "počinje s",
|
||||
"ends with": "završava s",
|
||||
"not ends with": "ne završava s",
|
||||
"less than": "manje od",
|
||||
"greater than": "veće od",
|
||||
"greater than or equal to": "veće od ili jednako",
|
||||
"record": "zapis",
|
||||
"Notification Channel": "Kanal obavijesti",
|
||||
"Sound": "Zvuk",
|
||||
"Alphanumerical string and hyphens only": "Samo alfanumerički niz i crtice",
|
||||
"Arcade": "Arkadno",
|
||||
"Fail": "Neuspjeh",
|
||||
"Correct": "Ispravno",
|
||||
"Harp": "Harfa",
|
||||
"Reveal": "Otkrivanje",
|
||||
"Bubble": "Mjehurić",
|
||||
"Flute": "Flauta",
|
||||
"Money": "Novac",
|
||||
"Scifi": "Znanstvena fantastika",
|
||||
"Clear": "Čisto",
|
||||
"Elevator": "Dizalo",
|
||||
"Guitar": "Gitara",
|
||||
"Pop": "Pop",
|
||||
"Time Sensitive (iOS Only)": "Vremenski osjetljivo (samo iOS)",
|
||||
"From": "Od",
|
||||
"Can be found on:": "Može se pronaći na: {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Telefonski broj primatelja u formatu E.164.",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Ili identifikator pošiljatelja tekstualne poruke ili telefonski broj u formatu E.164 ako želite primati odgovore.",
|
||||
"jsonQueryDescription": "Izvršite JSON upit nad primljenim odgovorom i provjerite očekivanu povratnu vrijednost. Koristite \"$\" za zahtjeve u kojima ne očekujete JSON odgovor. Povratna vrijednost će se za usporedbu pretvoriti u niz znakova (string). Pogledajte stranicu {0} za dokumentaciju o jeziku upita. Testno okruženje možete pronaći na {1}.",
|
||||
"rabbitmqNodesRequired": "Postavite čvorove za ovaj Monitor.",
|
||||
"rabbitmqNodesInvalid": "Koristite potpuni URL (onaj koji počinje s 'http') za RabbitMQ čvorove.",
|
||||
"RabbitMQ Username": "RabbitMQ korisničko ime",
|
||||
"RabbitMQ Password": "RabbitMQ lozinka",
|
||||
"SendGrid API Key": "SendGrid API ključ",
|
||||
"Separate multiple email addresses with commas": "Više adresa e-pošte potrebno je odvojiti zarezima",
|
||||
"RabbitMQ Nodes": "RabbitMQ upravljački čvorovi",
|
||||
"rabbitmqNodesDescription": "Unesite URL za upravljačke čvorove RabbitMQ uključujući protokol i port. Primjer: {0}",
|
||||
"rabbitmqHelpText": "Za korištenje ovog Monitora morat ćete omogućiti dodatak \"Management Plugin\" u svom RabbitMQ-u. Za više informacija pogledajte {rabitmq_documentation}."
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
"Password": "Sandi",
|
||||
"Remember me": "Ingat saya",
|
||||
"Login": "Masuk",
|
||||
"No Monitors, please": "Tolong, jangan ada Monitor",
|
||||
"No Monitors, please": "Tidak ada monitor, silakan",
|
||||
"add one": "tambahkan satu",
|
||||
"Notification Type": "Tipe Notifikasi",
|
||||
"Email": "Surel",
|
||||
|
@ -1046,5 +1046,47 @@
|
|||
"Group Name": "Nama Grup",
|
||||
"OAuth2: Client Credentials": "OAuth2: Kredensial Klien",
|
||||
"Authentication Method": "Metode Autentikasi",
|
||||
"shrinkDatabaseDescriptionSqlite": "Memicu pangkalan data {vacuum} untuk SQLite. {auto_vacuum} sudah diaktifkan, tetapi tidak mendefragmentasi pangkalan data atau mengemas ulang halaman individual dari pangkalan data seperti yang dilakukan oleh perintah {vacuum}."
|
||||
"shrinkDatabaseDescriptionSqlite": "Memicu pangkalan data {vacuum} untuk SQLite. {auto_vacuum} sudah diaktifkan, tetapi tidak mendefragmentasi pangkalan data atau mengemas ulang halaman individual dari pangkalan data seperti yang dilakukan oleh perintah {vacuum}.",
|
||||
"ignoredTLSError": "Galat TLS/SSL sudah diabaikan",
|
||||
"Debug": "Awakutu",
|
||||
"Copy": "Salin",
|
||||
"CopyToClipboardError": "Tidak bisa menyalin ke papan klip: {galat}",
|
||||
"CopyToClipboardSuccess": "Tersalin!",
|
||||
"CurlDebugInfo": "Untuk pengawakutuan monitor, Anda bisa menempelkan ini ke terminal mesin Anda sendiri atau ke terminal mesin di mana uptime kuma sedang berjalan dan melihat apa yang Anda harapkan. Mohon perhatikan perbedaan jaringan seperti {firewalls}, {dns_resolvers}, atau {docker_networks}.",
|
||||
"aboutSlackUsername": "Mengubah nama tampilan pengirim pesan. Jika Anda ingin menyebut seseorang, sertakan dalam nama yang mudah diingat.",
|
||||
"Message format": "Format pesan",
|
||||
"Sound": "Suara",
|
||||
"Alphanumerical string and hyphens only": "Hanya string alfanumerik dan tanda hubung",
|
||||
"Elevator": "Pengundak",
|
||||
"Custom sound to override default notification sound": "Suara khusus untuk mengganti suara notifikasi bawaan",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Pemberitahuan yang sensitif terhadap waktu akan segera dikirimkan, meskipun perangkat dalam mode jangan ganggu.",
|
||||
"RabbitMQ Nodes": "RabbitMQ Management Nodes",
|
||||
"rabbitmqNodesDescription": "Masukkan URL untuk node manajemen RabbitMQ termasuk protokol dan port. Contoh: {0}",
|
||||
"RabbitMQ Password": "Kata kunci RabbitMQ",
|
||||
"rabbitmqHelpText": "Untuk menggunakan monitor, Anda perlu mengaktifkan Plugin Manajemen di pengaturan RabbitMQ Anda. Untuk informasi lebih lanjut, silakan baca {rabitmq_documentation}.",
|
||||
"rabbitmqNodesRequired": "Harap atur node untuk monitor ini.",
|
||||
"rabbitmqNodesInvalid": "Harap gunakan URL yang memenuhi syarat (dimulai dengan 'http') untuk node RabbitMQ.",
|
||||
"RabbitMQ Username": "Nama pengguna RabbitMQ",
|
||||
"Notification Channel": "Notifikasi Kanal",
|
||||
"Send rich messages": "Kirim rich messages",
|
||||
"Arcade": "Arkade",
|
||||
"Correct": "Benar",
|
||||
"Fail": "Gagal",
|
||||
"Harp": "Harpa",
|
||||
"Reveal": "Mengungkap",
|
||||
"Bubble": "Gelembung",
|
||||
"Doorbell": "Bel pintu",
|
||||
"Flute": "Seruling",
|
||||
"Money": "Uang",
|
||||
"Scifi": "Fiksi ilmiah",
|
||||
"Clear": "Jernih",
|
||||
"Guitar": "Gitar",
|
||||
"Pop": "Letupan",
|
||||
"Time Sensitive (iOS Only)": "Sensitif terhadap Waktu (Khusus iOS)",
|
||||
"From": "Dari",
|
||||
"Can be found on:": "Bisa ditemukan di: {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Nomor telepon penerima dalam 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.": "Antara ID pengirim teks atau nomor telepon dalam format E.164 jika Anda ingin menerima balasan.",
|
||||
"SendGrid API Key": "Kunci API SendGrid",
|
||||
"Separate multiple email addresses with commas": "Pisahkan beberapa alamat email dengan koma"
|
||||
}
|
||||
|
|
|
@ -672,5 +672,35 @@
|
|||
"emailCustomBody": "カスタム本文",
|
||||
"emailTemplateServiceName": "サービス名",
|
||||
"smtpLiquidIntroduction": "次の 2 つのテンプレート・フィールドは Liquid テンプレート言語で記述できます。これらの使い方は {0} にあります。以下は利用可能な変数です:",
|
||||
"emailTemplateStatus": "ステータス"
|
||||
"emailTemplateStatus": "ステータス",
|
||||
"now": "現在",
|
||||
"time ago": "{0}前",
|
||||
"-year": "年",
|
||||
"Json Query Expression": "Jsonクエリ表現",
|
||||
"ignoredTLSError": "TLS/SSLエラーは無視されました",
|
||||
"locally configured mail transfer agent": "独自設定されたメール転送エージェント",
|
||||
"ignoreTLSErrorGeneral": "接続時のTLS/SSLエラーを無視する",
|
||||
"successKeyword": "成功時のキーワード",
|
||||
"pushViewCode": "Push モニターの使い方(コードを見る)",
|
||||
"Reset Token": "トークンのリセット",
|
||||
"templateLimitedToUpDownCertNotifications": "監視対象の UP/DOWN と証明書の有効期限通知でのみ利用可能",
|
||||
"templateLimitedToUpDownNotifications": "監視対象の UP/DOWN の通知でのみ利用可能",
|
||||
"webhookBodyPresetOption": "プリセット - {0}",
|
||||
"Optional": "オプション",
|
||||
"and": "かつ",
|
||||
"From Email": "Emailより",
|
||||
"CurlDebugInfoProxiesUnsupported": "上記コマンド {curl} のProxyサポートは現在、実装されていません。",
|
||||
"Your User ID": "あなたのユーザーID",
|
||||
"programmingLanguages": "プログラミング言語",
|
||||
"Debug": "デバッグ",
|
||||
"Copy": "コピー",
|
||||
"CopyToClipboardError": "クリップボードにコピーできません: {error}",
|
||||
"CopyToClipboardSuccess": "コピーしました!",
|
||||
"firewalls": "ファイアウォール",
|
||||
"dns resolvers": "DNSリゾルバ",
|
||||
"docker networks": "Dockerネットワーク",
|
||||
"sameAsServerTimezone": "サーバータイムゾーンと同じ",
|
||||
"cronExpression": "Cron表記",
|
||||
"invalidCronExpression": "不正なCron表記です: {0}",
|
||||
"Single Maintenance Window": "シングルメンテナンスウィンドウ"
|
||||
}
|
||||
|
|
101
src/lang/pl.json
101
src/lang/pl.json
|
@ -294,7 +294,7 @@
|
|||
"emailCustomSubject": "Niestandardowy temat",
|
||||
"checkPrice": "Sprawdź ceny {0}:",
|
||||
"octopushLegacyHint": "Czy używasz starszej wersji Octopush (2011-2020) czy nowej wersji?",
|
||||
"Feishu WebHookUrl": "Feishu WebHookURL",
|
||||
"Feishu WebHookUrl": "Adres webhooka Feishu",
|
||||
"matrixHomeserverURL": "Adres URL serwera domowego (z http(s):// i opcjonalnie port)",
|
||||
"Internal Room Id": "Wewnętrzne ID pokoju",
|
||||
"matrixDesc1": "Możesz znaleźć wewnętrzne ID pokoju, patrząc w zaawansowanej sekcji ustawień pokoju w twoim kliencie Matrix. Powinien on wyglądać jak !QMdRCpUIfLwsfjxye6:home.server.",
|
||||
|
@ -410,7 +410,7 @@
|
|||
"SignName": "Podpis",
|
||||
"Sms template must contain parameters: ": "Szablon sms musi posiadać parametry: ",
|
||||
"Bark Endpoint": "Punkt końcowy Bark",
|
||||
"WebHookUrl": "WebHookUrl",
|
||||
"WebHookUrl": "Adres webhooka",
|
||||
"SecretKey": "Tajny klucz",
|
||||
"For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
|
||||
"Device Token": "Token urządzenia",
|
||||
|
@ -993,5 +993,100 @@
|
|||
"-year": "-rok",
|
||||
"and": "i",
|
||||
"now": "teraz",
|
||||
"cacheBusterParam": "Dodaj parametr {0}"
|
||||
"cacheBusterParam": "Dodaj parametr {0}",
|
||||
"CopyToClipboardError": "Błąd podczas kopiowania do schowka: {error}",
|
||||
"CurlDebugInfo": "Aby zdebugować monitor, możesz wkleić ten ciąg do terminala na swojej maszynie lub na maszynie gdzie uptime kuma jest uruchomina aby zobaczyć co żądasz.{newiline} Miej na uwadzę różnice sieciowe takich jak {firewalls}, {dns_resolvers} lub {docker_networks}.",
|
||||
"shrinkDatabaseDescriptionSqlite": "Zażądaj {vacuum} bazy dla SQLite. {auto_vacuum} jest już włączone jednak nie defragmentuje to bazy ani nie przepakowuje indywidualnych stron bazy w taki sam sposób jak robi to polecenie {vacuum}.",
|
||||
"cacheBusterParamDescription": "Losowo wygenerowany parametr w celu pominięcia pamięci podręcznej.",
|
||||
"Group Name": "Nazwa grupy",
|
||||
"OAuth2: Client Credentials": "OAuth2: Poświadczenia klienta",
|
||||
"signl4Docs": "Aby zdobyć więcej informacji jak skonfigurować SIGNL4 i jak zdobyć odnośnik webhooka SIGNL4 udaj się do {0}.",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Czasowo wrażliwe powiadomienia zostaną dostarczone natychmiastowo, nawet jeśli urzadzenie jest w trybie nie przeszkadzać.",
|
||||
"time ago": "{0} temu",
|
||||
"Json Query Expression": "Wyrażenie zapytania JSON",
|
||||
"Condition": "Warunek",
|
||||
"snmpCommunityStringHelptext": "Ten ciąg funkcjonuje jako hasło do uwierzytelnienia i kontroli dostęp do urządzeń SNMP. Dopasuj go do konfiguracji urządzenia SNMP.",
|
||||
"SNMP Version": "Wersja SNMP",
|
||||
"OID (Object Identifier)": "OID (Identyfikator obiektu)",
|
||||
"snmpOIDHelptext": "Wprowadź OID sensora lub statusu który chcesz monitorować. Użyj narzędzia do zarządzania siecią jak przeglądarki MIB lub oprogramowanie SNMP jeśli nie masz pewności co do OID.",
|
||||
"Please enter a valid OID.": "Wprowadź poprawny OID.",
|
||||
"Host Onesender": "Serwer Onesender",
|
||||
"Token Onesender": "Token Onesender",
|
||||
"Recipient Type": "Typ odbiorcy",
|
||||
"Go back to home page.": "Powróć do strony domowej.",
|
||||
"No tags found.": "Nie znaleziono etykiet.",
|
||||
"Authorization Header": "Nagłówek autoryzacji",
|
||||
"Form Data Body": "Zawartość formularza danych",
|
||||
"OAuth Token URL": "Odnośnik tokena OAuth",
|
||||
"Client ID": "Identyfikator klienta",
|
||||
"Client Secret": "Sekret klienta",
|
||||
"OAuth Scope": "Zakres OAuth",
|
||||
"Optional: Space separated list of scopes": "Opcjonalne: Oddzielona spacją lista zakresów",
|
||||
"SIGNL4 Webhook URL": "Odnośnik webhooka SIGNL4",
|
||||
"Lost connection to the socket server.": "Utracono połączenie do serwera.",
|
||||
"Cannot connect to the socket server.": "Nie można połączyć z serwerem.",
|
||||
"SIGNL4": "SIGNL4",
|
||||
"less than": "mniej niż",
|
||||
"Conditions": "Warunek",
|
||||
"conditionAdd": "Dodaj warunek",
|
||||
"conditionDelete": "Usuń warunek",
|
||||
"conditionAddGroup": "Dodaj grupę",
|
||||
"conditionDeleteGroup": "Usuń grupę",
|
||||
"conditionValuePlaceholder": "Wartość",
|
||||
"equals": "równa się",
|
||||
"not equals": "nie jest równe",
|
||||
"contains": "zawiera",
|
||||
"not contains": "nie zawiera",
|
||||
"starts with": "zaczyna się od",
|
||||
"not starts with": "nie zaczyna się od",
|
||||
"ends with": "kończy się",
|
||||
"ignoredTLSError": "Błędy TLS/SSL zostały zignorowane",
|
||||
"Debug": "Debuguj",
|
||||
"Copy": "Kopiuj",
|
||||
"CopyToClipboardSuccess": "Skopiowano!",
|
||||
"firewalls": "zapory sieciowe",
|
||||
"dns resolvers": "serwery rozwiązywania nazw domenowych",
|
||||
"docker networks": "sieci docker",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Pełen ciąg poświadczeń klienta oauth nie jest obsługiwany w {curl}.{newline}Zdobądź bearer token i przekaż go przez opcję {oauth2_bearer}.",
|
||||
"CurlDebugInfoProxiesUnsupported": "Obsługa proxy w powyższym poleceniu {curl} nie jest aktualnie zaimplementowana.",
|
||||
"Message format": "Format wiadomości",
|
||||
"Send rich messages": "Wysyłaj bogate wiadomości",
|
||||
"Community String": "Ciąg community",
|
||||
"Private Number": "Numer prywatny",
|
||||
"groupOnesenderDesc": "Upewnij się, że GroupID jest poprawne. Aby wysłać wiadomość do grupy, np.: 628123456789-342345",
|
||||
"privateOnesenderDesc": "Upewnij się, że numer telefonu jest poprawny. Aby wysłać wiadomość do prywatnego numeru, np.: 628123456789",
|
||||
"Group ID": "ID grupy",
|
||||
"wayToGetOnesenderUrlandToken": "Aby zdobyć odnośnik i token udaj się na stronę Onesender. Więcej informacji {0}",
|
||||
"Add Remote Browser": "Dodaj zdalną przeglądarkę",
|
||||
"New Group": "Nowa grupa",
|
||||
"Authentication Method": "Metoda uwierzytelnienia",
|
||||
"not ends with": "nie kończy się",
|
||||
"greater than": "więcej niż",
|
||||
"less than or equal to": "mniej lub równe",
|
||||
"greater than or equal to": "więcej lub równe",
|
||||
"record": "rekord",
|
||||
"Notification Channel": "Kanał powiadomień",
|
||||
"Sound": "Dźwięk",
|
||||
"Alphanumerical string and hyphens only": "Tylko ciąg alfanumeryczny i dywiz (kreska)",
|
||||
"Time Sensitive (iOS Only)": "Czasowo wrażliwe (tylko iOS)",
|
||||
"From": "Od",
|
||||
"Can be found on:": "Może być znalezione w: {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Numer telefonu odbiorcy w formacie E.164.",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Identyfikator wysyłającego wiadomość lub numer telefonu w formacie E.164 jeśli chcesz otrzymywać odpowiedzi.",
|
||||
"jsonQueryDescription": "Przetwórz i wyciągnij specyficzne dane z odpowiedzi JSON serwera używając zapytania JSON lub użyj \"$\" dla nieprzetworzonej odpowiedzi, jeśli nie spodziewasz się formatu JSON. Wynik jest następnie porównywany, jako ciągi, do spodziewanej wartości. Po dokumetnację zobacz {0} lub użyj {1} aby poeksperymentować z zapytaniami.",
|
||||
"Custom sound to override default notification sound": "Własny dzwięk nadpisujący domyślny dzwięk powiadomień",
|
||||
"Arcade": "Arcade",
|
||||
"Correct": "Correct",
|
||||
"Fail": "Fail",
|
||||
"Harp": "Harp",
|
||||
"Reveal": "Reveal",
|
||||
"Bubble": "Bubble",
|
||||
"Doorbell": "Doorbell",
|
||||
"Flute": "Flute",
|
||||
"Money": "Money",
|
||||
"Scifi": "Scifi",
|
||||
"Clear": "Clear",
|
||||
"Elevator": "Elevator",
|
||||
"Guitar": "Guitar",
|
||||
"Pop": "Pop"
|
||||
}
|
||||
|
|
|
@ -980,5 +980,12 @@
|
|||
"Group Name": "Nome do Grupo",
|
||||
"OAuth2: Client Credentials": "OAuth2: Client Credentials",
|
||||
"Authentication Method": "Método de Autenticação",
|
||||
"Authorization Header": "Header de Autorização"
|
||||
"Authorization Header": "Header de Autorização",
|
||||
"ignoredTLSError": "Erros TLS/SSL foram ignorados",
|
||||
"Debug": "Depurar",
|
||||
"Copy": "Copiar",
|
||||
"CopyToClipboardError": "Não foi possível copiar para a área de transferência: {error}",
|
||||
"CopyToClipboardSuccess": "Copiado!",
|
||||
"firewalls": "firewalls",
|
||||
"docker networks": "redes docker"
|
||||
}
|
||||
|
|
|
@ -971,5 +971,20 @@
|
|||
"Select message type": "Выберите тип сообщения",
|
||||
"Send to channel": "Отправить в канал",
|
||||
"Refresh Interval": "Интервал обновления",
|
||||
"ignoreTLSErrorGeneral": "Игнорировать ошибки TLS/SSL для подключения"
|
||||
"ignoreTLSErrorGeneral": "Игнорировать ошибки TLS/SSL для подключения",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Полная поддержка потока клиентских учетных данных OAuth отсутствует в {curl}.{newline}Пожалуйста, получите токен доступа и передайте его через параметр {oauth2_bearer}.",
|
||||
"now": "сейчас",
|
||||
"time ago": "{0} назад",
|
||||
"Refresh Interval Description": "Страница статуса будет полностью обновляться каждые {0} секунд",
|
||||
"and": "и",
|
||||
"e.g. {discordThreadID}": "например {discordThreadID}",
|
||||
"ignoredTLSError": "Ошибки TLS/SSL были проигнорированы",
|
||||
"Debug": "Отладка",
|
||||
"Copy": "Скопировать",
|
||||
"CopyToClipboardError": "Не удалось скопировать: {error}",
|
||||
"CopyToClipboardSuccess": "Скопировано!",
|
||||
"firewalls": "файрволы",
|
||||
"dns resolvers": "dns резолверы",
|
||||
"docker networks": "докер-сети",
|
||||
"CurlDebugInfoProxiesUnsupported": "Поддержка прокси в верхней {curl} команде в настоящее время не реализована."
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"Password": "Lösenord",
|
||||
"Remember me": "Kom ihåg mig",
|
||||
"Login": "Logga in",
|
||||
"No Monitors, please": "Inga övervakare, tack",
|
||||
"No Monitors, please": "Inga övervakare",
|
||||
"add one": "lägg till en",
|
||||
"Notification Type": "Notifieringstyp",
|
||||
"Email": "Epost",
|
||||
|
@ -957,5 +957,38 @@
|
|||
"threemaSenderIdentityFormat": "8 tecken, startar oftast med *",
|
||||
"threemaApiAuthenticationSecret": "Gateway-ID hemlighet",
|
||||
"threemaBasicModeInfo": "Notera: Denna integration använder Threema Gateway i standardläge (server-baserad kryptering). Mer detaljer kan hittas {0}.",
|
||||
"apiKeysDisabledMsg": "API-nycklar är inaktiverade för att autentisering är inaktiverad."
|
||||
"apiKeysDisabledMsg": "API-nycklar är inaktiverade för att autentisering är inaktiverad.",
|
||||
"CopyToClipboardError": "Kunde inte kopiera till urklipp: {fel}",
|
||||
"Message format": "Meddelandeformat",
|
||||
"Condition": "Villkor",
|
||||
"SNMP Version": "SNMP-version",
|
||||
"No tags found.": "Inga taggar hittades.",
|
||||
"now": "nu",
|
||||
"time ago": "{0} sedan",
|
||||
"-year": "-år",
|
||||
"Json Query Expression": "Json-frågeutryck",
|
||||
"and": "och",
|
||||
"Recipient Type": "Mottagartyp",
|
||||
"Private Number": "Privat nummer",
|
||||
"New Group": "Ny grupp",
|
||||
"Group Name": "Gruppnamn",
|
||||
"OAuth2: Client Credentials": "OAuth2: Klientuppgifter",
|
||||
"Authentication Method": "Autentiseringsmetod",
|
||||
"Authorization Header": "Autentiseringshuvud",
|
||||
"Client ID": "Klient ID",
|
||||
"Client Secret": "Klienthemlighet",
|
||||
"Go back to home page.": "Gå tillbaka till hemsidan.",
|
||||
"ignoredTLSError": "TLS/SSL-fel har ignorerats",
|
||||
"Debug": "Felsöka",
|
||||
"Copy": "Kopiera",
|
||||
"CopyToClipboardSuccess": "Kopierat!",
|
||||
"firewalls": "brandväggar",
|
||||
"dns resolvers": "dns-upplösare",
|
||||
"docker networks": "docker-nätverk",
|
||||
"cacheBusterParam": "Lägg till {0} parameter",
|
||||
"Community String": "Gruppsträng",
|
||||
"OID (Object Identifier)": "OID (Objektsidentifierare)",
|
||||
"Please enter a valid OID.": "Ange ett giltigt OID.",
|
||||
"Group ID": "GruppID",
|
||||
"Add Remote Browser": "Lägg till fjärrbläddrare"
|
||||
}
|
||||
|
|
|
@ -1051,5 +1051,51 @@
|
|||
"greater than or equal to": "büyük veya eşit",
|
||||
"record": "kayıt",
|
||||
"jsonQueryDescription": "JSON sorgusunu kullanarak sunucunun JSON yanıtından belirli verileri ayrıştırın ve çıkarın. JSON beklemiyorsanız ham yanıt için \"$\" sembolünü kullanın. Sonuç daha sonra metin olarak beklenen değerle karşılaştırılır. Belgeler için {0}'a bakın ve sorgularla denemeler yapmak için {1}'i kullanın.",
|
||||
"shrinkDatabaseDescriptionSqlite": "SQLite için {vacuum} veritabanını tetikle. {auto_vacuum} zaten etkin ancak bu, {vacuum} komutunun yaptığı gibi veritabanını birleştirmez veya tek tek veritabanı sayfalarını yeniden paketlemez."
|
||||
"shrinkDatabaseDescriptionSqlite": "SQLite için {vacuum} veritabanını tetikle. {auto_vacuum} zaten etkin ancak bu, {vacuum} komutunun yaptığı gibi veritabanını birleştirmez veya tek tek veritabanı sayfalarını yeniden paketlemez.",
|
||||
"Debug": "Hata ayıklama",
|
||||
"Copy": "Kopyala",
|
||||
"CopyToClipboardError": "Panoya kopyalanamadı: {hata}",
|
||||
"CopyToClipboardSuccess": "Kopyalandı!",
|
||||
"firewalls": "güvenlik duvarları",
|
||||
"dns resolvers": "dns çözücüler",
|
||||
"docker networks": "Docker ağları",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Tam Oauth istemci kimlik bilgisi akışı {curl}'de desteklenmiyor.{newline}Lütfen bir taşıyıcı belirteci alın ve bunu {oauth2_bearer} seçeneği aracılığıyla iletin.",
|
||||
"CurlDebugInfoProxiesUnsupported": "Yukarıdaki {curl} komutunda proxy desteği şu anda uygulanmamıştır.",
|
||||
"Message format": "Mesaj biçimi",
|
||||
"Send rich messages": "Zengin mesajlar gönder",
|
||||
"Notification Channel": "Bildirim Kanalı",
|
||||
"Sound": "Ses",
|
||||
"Alphanumerical string and hyphens only": "Yalnızca alfanümerik dize ve tireler",
|
||||
"Arcade": "Çarşı",
|
||||
"Correct": "Doğru",
|
||||
"Fail": "Hata",
|
||||
"Harp": "Harp",
|
||||
"Reveal": "Ortaya çıkarmak",
|
||||
"Bubble": "Kabarcık",
|
||||
"Doorbell": "Kapı zili",
|
||||
"Flute": "Flüt",
|
||||
"Money": "Para",
|
||||
"Scifi": "Bilimkurgu",
|
||||
"Clear": "Temizlemek",
|
||||
"Elevator": "Asansör",
|
||||
"Guitar": "Gitar",
|
||||
"Pop": "Pop",
|
||||
"Custom sound to override default notification sound": "Varsayılan bildirim sesini geçersiz kılmak için özel ses",
|
||||
"Time Sensitive (iOS Only)": "Zaman Duyarlı (Yalnızca iOS)",
|
||||
"From": "Kimden",
|
||||
"Can be found on:": "Şurada bulunabilir: {0}",
|
||||
"The phone number of the recipient in E.164 format.": "Alıcının E.164 formatındaki telefon numarası.",
|
||||
"ignoredTLSError": "TLS/SSL hataları göz ardı edildi",
|
||||
"CurlDebugInfo": "Monitörü hata ayıklamak için, bunu kendi makinenizin terminaline veya uptime kuma'nın çalıştığı makinenin terminaline yapıştırabilir ve ne istediğinizi görebilirsiniz.{newiline}Lütfen {firewalls}, {dns_resolvers} veya {docker_networks} gibi ağ farklılıklarına dikkat edin.",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Cihaz rahatsız etmeyin modunda olsa bile, zaman açısından hassas bildirimler anında iletilecek.",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Cevap alabilmek istiyorsanız E.164 formatında bir kısa mesaj gönderici kimliği veya bir telefon numarası.",
|
||||
"rabbitmqNodesRequired": "Lütfen bu monitör için sunucuları ayarlayın.",
|
||||
"rabbitmqNodesInvalid": "Lütfen RabbitMQ düğümleri için tam nitelikli ('http' ile başlayan) bir URL kullanın.",
|
||||
"RabbitMQ Username": "RabbitMQ Kullanıcı Adı",
|
||||
"RabbitMQ Password": "RabbitMQ Şifresi",
|
||||
"SendGrid API Key": "SendGrid API Anahtarı",
|
||||
"Separate multiple email addresses with commas": "Birden fazla e-posta adresini virgülle ayırın",
|
||||
"RabbitMQ Nodes": "RabbitMQ Yönetim Sunucuları",
|
||||
"rabbitmqNodesDescription": "Protokol ve port dahil olmak üzere RabbitMQ yönetim düğümleri için URL'yi girin. Örnek: {0}",
|
||||
"rabbitmqHelpText": "Monitörü kullanmak için, RabbitMQ kurulumunuzda Yönetim Eklentisini etkinleştirmeniz gerekecektir. Daha fazla bilgi için lütfen {rabitmq_documentation}'a bakın."
|
||||
}
|
||||
|
|
|
@ -1057,5 +1057,51 @@
|
|||
"less than or equal to": "менше або дорівнює",
|
||||
"greater than or equal to": "більше або дорівнює",
|
||||
"record": "запис",
|
||||
"shrinkDatabaseDescriptionSqlite": "Запускає команду {vacuum} для бази даних SQLite. Команда {auto_vacuum} вже увімкнена, але вона не дефрагментує базу даних і не перепаковує окремі сторінки бази даних так, як це робить команда {vacuum}."
|
||||
"shrinkDatabaseDescriptionSqlite": "Запускає команду {vacuum} для бази даних SQLite. Команда {auto_vacuum} вже увімкнена, але вона не дефрагментує базу даних і не перепаковує окремі сторінки бази даних так, як це робить команда {vacuum}.",
|
||||
"ignoredTLSError": "Помилки TLS/SSL проігноровано",
|
||||
"Debug": "Налагодження",
|
||||
"Copy": "Копіювати",
|
||||
"CopyToClipboardError": "Не вдалося скопіювати в буфер обміну: {error}",
|
||||
"CopyToClipboardSuccess": "Скопійовано!",
|
||||
"firewalls": "фаєрволи",
|
||||
"dns resolvers": "dns сервери",
|
||||
"docker networks": "docker-мережі",
|
||||
"CurlDebugInfoProxiesUnsupported": "Підтримка проксі у наведеній вище команді {curl} наразі не реалізована.",
|
||||
"Message format": "Формат повідомлення",
|
||||
"Send rich messages": "Надіслати розгорнуті повідомлення",
|
||||
"Notification Channel": "Канал сповіщення",
|
||||
"Sound": "Звук",
|
||||
"Alphanumerical string and hyphens only": "Тільки алфавітно-цифровий рядок і дефіси",
|
||||
"Arcade": "Аркада",
|
||||
"Correct": "Вірно",
|
||||
"Fail": "Невдача",
|
||||
"Harp": "Арфа",
|
||||
"Reveal": "Розкриття",
|
||||
"Bubble": "Бульбашка",
|
||||
"Doorbell": "Дзвінок",
|
||||
"Flute": "Флейта",
|
||||
"Money": "Гроші",
|
||||
"Scifi": "Наукова фантастика",
|
||||
"Clear": "Чисто",
|
||||
"Elevator": "Ліфт",
|
||||
"Guitar": "Гітара",
|
||||
"Pop": "Поп",
|
||||
"Custom sound to override default notification sound": "Користувацький звук для заміни звуку сповіщень за замовчуванням",
|
||||
"Time Sensitive (iOS Only)": "Чутливий до часу (лише для iOS)",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Сповіщення, що залежать від часу, будуть доставлені негайно, навіть якщо пристрій перебуває в режимі «Не турбувати».",
|
||||
"From": "Від",
|
||||
"The phone number of the recipient in E.164 format.": "Номер телефону одержувача у форматі E.164.",
|
||||
"CurlDebugInfo": "Для налагодження монітора ви можете вставити цей файл у термінал вашої власної машини або у термінал машини, на якій запущено uptime kuma, і подивитися, що ви запитуєте.{newline}Зверніть увагу на мережеві відмінності, такі як {firewalls}, {dns_resolvers} або {docker_networks}.",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "Повний потік облікових даних клієнта oauth не підтримується в {curl}.{newline}Потрібно отримати bearer-токен і передати його за допомогою опції {oauth2_bearer}.",
|
||||
"Can be found on:": "Можна знайти на: {0}",
|
||||
"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": "Будь ласка, встановіть вузли для цього монітора.",
|
||||
"RabbitMQ Username": "Ім'я користувача RabbitMQ",
|
||||
"RabbitMQ Password": "Пароль RabbitMQ",
|
||||
"SendGrid API Key": "API-ключ SendGrid",
|
||||
"Separate multiple email addresses with commas": "Розділяйте кілька адрес комами",
|
||||
"RabbitMQ Nodes": "Вузли керування RabbitMQ",
|
||||
"rabbitmqNodesDescription": "Введіть URL-адресу для вузлів керування RabbitMQ, включаючи протокол і порт. Приклад: {0}",
|
||||
"rabbitmqNodesInvalid": "Будь ласка, використовуйте повну URL-адресу (починаючи з 'http') для вузлів RabbitMQ.",
|
||||
"rabbitmqHelpText": "Щоб використовувати монітор, вам потрібно увімкнути плагін керування у налаштуваннях RabbitMQ. Для отримання додаткової інформації, будь ласка, зверніться до {rabitmq_documentation}."
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"languageName": "English",
|
||||
"languageName": "简体中文",
|
||||
"checkEverySecond": "检测频率 {0} 秒",
|
||||
"retryCheckEverySecond": "重试间隔 {0} 秒",
|
||||
"resendEveryXTimes": "每 {0} 次失败则重复发送一次",
|
||||
|
@ -1053,5 +1053,52 @@
|
|||
"greater than or equal to": "不少于",
|
||||
"record": "记录",
|
||||
"jsonQueryDescription": "使用 JSON 查询解析并提取服务器 JSON 响应中的特定数据,或者,如果不期望得到 JSON 响应,则可使用 \"$\" 获取原始响应。然后将结果转为字符串并与期望值进行字符串比较。有关更多文档,请参阅 {0},亦可使用 {1} 来尝试查询。",
|
||||
"shrinkDatabaseDescriptionSqlite": "触发 SQLite 数据库的 {vacuum} 命令。{auto_vacuum} 已经启用,但它不会像 {vacuum} 命令那样对数据库进行碎片整理,也不会重新打包各个数据库页面。"
|
||||
"shrinkDatabaseDescriptionSqlite": "触发 SQLite 数据库的 {vacuum} 命令。{auto_vacuum} 已经启用,但它不会像 {vacuum} 命令那样对数据库进行碎片整理,也不会重新打包各个数据库页面。",
|
||||
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "如需可被回复,请输入发送者 ID 或 E.164 格式的手机号码。",
|
||||
"Copy": "复制",
|
||||
"Debug": "Debug",
|
||||
"CopyToClipboardSuccess": "已复制!",
|
||||
"CopyToClipboardError": "无法复制到剪贴板:{error}",
|
||||
"CurlDebugInfoProxiesUnsupported": "上述 {curl} 命令中的代理支持目前尚未实现。",
|
||||
"docker networks": "Docker 网络",
|
||||
"dns resolvers": "DNS 解析器",
|
||||
"firewalls": "防火墙",
|
||||
"Message format": "消息格式",
|
||||
"Send rich messages": "发送富文本消息",
|
||||
"Sound": "声音",
|
||||
"Notification Channel": "通知频道",
|
||||
"Bubble": "Bubble(气泡)",
|
||||
"Reveal": "Reveal(揭示)",
|
||||
"Harp": "Harp(竖琴)",
|
||||
"Correct": "Correct(成功音)",
|
||||
"Fail": "Fail(失败音)",
|
||||
"Arcade": "Arcade(拱廊)",
|
||||
"Alphanumerical string and hyphens only": "仅限字母、数字和连字符(-)",
|
||||
"Custom sound to override default notification sound": "自定义声音,用以覆盖默认通知声音",
|
||||
"Pop": "Pop(流行音乐)",
|
||||
"Guitar": "Guitar(吉他)",
|
||||
"Elevator": "Elevator(电梯)",
|
||||
"Clear": "Clear(清除声)",
|
||||
"Scifi": "Scifi(科幻)",
|
||||
"Flute": "Flute(长笛)",
|
||||
"Doorbell": "Doorbell(门铃)",
|
||||
"The phone number of the recipient in E.164 format.": "收件人的 E.164 格式电话号码。",
|
||||
"Can be found on:": "可在此找到:{0}",
|
||||
"From": "发件人",
|
||||
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "即使设备处于专注模式,即时通知也会立即发送。",
|
||||
"Time Sensitive (iOS Only)": "即时通知(仅 iOS 可用)",
|
||||
"Money": "Money(钱)",
|
||||
"CurlDebugInfoOAuth2CCUnsupported": "{curl} 不支持完整的 oauth 客户端凭证流。{newline}请获取承载令牌(bearer token)并通过 {oauth2_bearer} 选项传递。",
|
||||
"CurlDebugInfo": "要调试监控项,您可以将该命令粘贴到您自己的或 uptime kuma 正在运行的机器的命令行终端中,并查看结果。{newiline}请注意网络差异,例如 {firewalls}、{dns_resolvers} 或 {docker_networks}。",
|
||||
"ignoredTLSError": "TLS/SSL 错误已被忽略",
|
||||
"SendGrid API Key": "SendGrid API 密钥",
|
||||
"RabbitMQ Password": "RabbitMQ 密码",
|
||||
"RabbitMQ Username": "RabbitMQ 用户名",
|
||||
"rabbitmqNodesInvalid": "请使用 RabbitMQ 节点的完整 URL(即完全限定 URL,以 http 开头)。",
|
||||
"rabbitmqNodesRequired": "请设置此监视项的节点。",
|
||||
"rabbitmqNodesDescription": "输入 RabbitMQ 管理节点的 URL,包括协议和端口。例如:{0}",
|
||||
"RabbitMQ Nodes": "RabbitMQ 管理节点",
|
||||
"Separate multiple email addresses with commas": "用逗号分隔多个电子邮件地址",
|
||||
"rabbitmqHelpText": "要使用此监控项,您需要在 RabbitMQ 设置中启用管理插件。有关更多信息,请参阅 {rabitmq_documentation}。",
|
||||
"aboutSlackUsername": "更改消息发件人的显示名称。如果您想提及某人,请另行将其包含在友好名称中。"
|
||||
}
|
||||
|
|
|
@ -1029,23 +1029,13 @@
|
|||
<div class="fixed-bottom-bar p-3">
|
||||
<button
|
||||
id="monitor-submit-btn"
|
||||
class="btn btn-primary me-2"
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
:disabled="processing"
|
||||
data-testid="save-button"
|
||||
>
|
||||
{{ $t("Save") }}
|
||||
</button>
|
||||
<button
|
||||
v-if="monitor.type === 'http'"
|
||||
id="monitor-debug-btn"
|
||||
class="btn btn-outline-primary"
|
||||
type="button"
|
||||
:disabled="processing"
|
||||
@click.stop="modal.show()"
|
||||
>
|
||||
{{ $t("Debug") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1057,58 +1047,9 @@
|
|||
<RemoteBrowserDialog ref="remoteBrowserDialog" />
|
||||
</div>
|
||||
</transition>
|
||||
<div ref="modal" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<textarea id="curl-debug" v-model="curlCommand" class="form-control mb-3" readonly wrap="off"></textarea>
|
||||
<button id="debug-copy-btn" class="btn btn-outline-primary position-absolute top-0 end-0 mt-3 me-3 border-0" type="button" @click.stop="copyToClipboard">
|
||||
<font-awesome-icon icon="copy" />
|
||||
</button>
|
||||
<i18n-t keypath="CurlDebugInfo" tag="p" class="form-text">
|
||||
<template #newiline>
|
||||
<br>
|
||||
</template>
|
||||
<template #firewalls>
|
||||
<a href="https://xkcd.com/2259/" target="_blank">{{ $t('firewalls') }}</a>
|
||||
</template>
|
||||
<template #dns_resolvers>
|
||||
<a href="https://www.reddit.com/r/sysadmin/comments/rxho93/thank_you_for_the_running_its_always_dns_joke_its/" target="_blank">{{ $t('dns resolvers') }}</a>
|
||||
</template>
|
||||
<template #docker_networks>
|
||||
<a href="https://youtu.be/bKFMS5C4CG0" target="_blank">{{ $t('docker networks') }}</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<div v-if="monitor.authMethod === 'oauth2-cc'" class="alert alert-warning d-flex align-items-center gap-2" role="alert">
|
||||
<div role="img" aria-label="Warning:">⚠️</div>
|
||||
<i18n-t keypath="CurlDebugInfoOAuth2CCUnsupported" tag="div">
|
||||
<template #curl>
|
||||
<code>curl</code>
|
||||
</template>
|
||||
<template #newline>
|
||||
<br>
|
||||
</template>
|
||||
<template #oauth2_bearer>
|
||||
<code>--oauth2-bearer TOKEN</code>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div v-if="monitor.proxyId" class="alert alert-warning d-flex align-items-center gap-2" role="alert">
|
||||
<div role="img" aria-label="Warning:">⚠️</div>
|
||||
<i18n-t keypath="CurlDebugInfoProxiesUnsupported" tag="div">
|
||||
<template #curl>
|
||||
<code>curl</code>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Modal } from "bootstrap";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { useToast } from "vue-toastification";
|
||||
import ActionSelect from "../components/ActionSelect.vue";
|
||||
|
@ -1123,8 +1064,6 @@ import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, sleep } fro
|
|||
import { hostNameRegexPattern } from "../util-frontend";
|
||||
import HiddenInput from "../components/HiddenInput.vue";
|
||||
import EditMonitorConditions from "../components/EditMonitorConditions.vue";
|
||||
import { version } from "../../package.json";
|
||||
const userAgent = `'Uptime-Kuma/${version}'`;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
|
@ -1192,7 +1131,6 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
modal: null,
|
||||
minInterval: MIN_INTERVAL_SECOND,
|
||||
maxInterval: MAX_INTERVAL_SECOND,
|
||||
processing: false,
|
||||
|
@ -1219,54 +1157,6 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
|
||||
curlCommand() {
|
||||
const command = [ "curl", "--verbose", "--head", "--request", this.monitor.method, "\\\n", "--user-agent", userAgent, "\\\n" ];
|
||||
if (this.monitor.ignoreTls) {
|
||||
command.push("--insecure", "\\\n");
|
||||
}
|
||||
if (this.monitor.headers) {
|
||||
try {
|
||||
// trying to parse the supplied data as json to trim whitespace
|
||||
for (const [ key, value ] of Object.entries(JSON.parse(this.monitor.headers))) {
|
||||
command.push("--header", `'${key}: ${value}'`, "\\\n");
|
||||
}
|
||||
} catch (e) {
|
||||
command.push("--header", `'${this.monitor.headers}'`, "\\\n");
|
||||
}
|
||||
}
|
||||
if (this.monitor.authMethod === "basic") {
|
||||
command.push("--user", `${this.monitor.basic_auth_user}:${this.monitor.basic_auth_pass}`, "--basic", "\\\n");
|
||||
} else if (this.monitor.authmethod === "mtls") {
|
||||
command.push("--cacert", `'${this.monitor.tlsCa}'`, "\\\n", "--key", `'${this.monitor.tlsKey}'`, "\\\n", "--cert", `'${this.monitor.tlsCert}'`, "\\\n");
|
||||
} else if (this.monitor.authMethod === "ntlm") {
|
||||
command.push("--user", `'${this.monitor.authDomain ? `${this.monitor.authDomain}/` : ""}${this.monitor.basic_auth_user}:${this.monitor.basic_auth_pass}'`, "--ntlm", "\\\n");
|
||||
}
|
||||
if (this.monitor.body && this.monitor.httpBodyEncoding === "json") {
|
||||
let json = "";
|
||||
try {
|
||||
// trying to parse the supplied data as json to trim whitespace
|
||||
json = JSON.stringify(JSON.parse(this.monitor.body));
|
||||
} catch (e) {
|
||||
json = this.monitor.body;
|
||||
}
|
||||
command.push("--header", "'Content-Type: application/json'", "\\\n", "--data", `'${json}'`, "\\\n");
|
||||
} else if (this.monitor.body && this.monitor.httpBodyEncoding === "xml") {
|
||||
command.push("--headers", "'Content-Type: application/xml'", "\\\n", "--data", `'${this.monitor.body}'`, "\\\n");
|
||||
}
|
||||
if (this.monitor.maxredirects) {
|
||||
command.push("--location", "--max-redirs", this.monitor.maxredirects, "\\\n");
|
||||
}
|
||||
if (this.monitor.timeout) {
|
||||
command.push("--max-time", this.monitor.timeout, "\\\n");
|
||||
}
|
||||
if (this.monitor.maxretries) {
|
||||
command.push("--retry", this.monitor.maxretries, "\\\n");
|
||||
}
|
||||
command.push("--url", this.monitor.url);
|
||||
return command.join(" ");
|
||||
},
|
||||
|
||||
ipRegex() {
|
||||
|
||||
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
||||
|
@ -1623,7 +1513,6 @@ message HealthCheckResponse {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal);
|
||||
this.init();
|
||||
|
||||
let acceptedStatusCodeOptions = [
|
||||
|
@ -1664,14 +1553,6 @@ message HealthCheckResponse {
|
|||
this.kafkaSaslMechanismOptions = kafkaSaslMechanismOptions;
|
||||
},
|
||||
methods: {
|
||||
async copyToClipboard() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.curlCommand);
|
||||
toast.success(this.$t("CopyToClipboardSuccess"));
|
||||
} catch (err) {
|
||||
toast.error(this.$t("CopyToClipboardError", { error: err.message }));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Initialize the edit monitor form
|
||||
* @returns {void}
|
||||
|
@ -1974,9 +1855,4 @@ message HealthCheckResponse {
|
|||
textarea {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
#curl-debug {
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue