From 934d633d4d693b808db4c6afa524f7b7f51595ff Mon Sep 17 00:00:00 2001 From: Juan Cruz Vincenti Date: Thu, 11 Nov 2021 20:06:32 -0300 Subject: [PATCH 001/163] Add description to monitor * Add description to monitor model * Add description field to database * Add english and spanish translation for description * Closes: #482 --- db/patch-add-description-monitor.sql | 7 +++++++ server/database.js | 1 + server/model/monitor.js | 2 ++ server/server.js | 2 ++ src/components/MonitorList.vue | 3 +++ src/icon.js | 2 ++ src/languages/en.js | 1 + src/languages/es-ES.js | 1 + src/pages/Details.vue | 1 + src/pages/EditMonitor.vue | 6 ++++++ 10 files changed, 26 insertions(+) create mode 100644 db/patch-add-description-monitor.sql diff --git a/db/patch-add-description-monitor.sql b/db/patch-add-description-monitor.sql new file mode 100644 index 00000000..da1aa55b --- /dev/null +++ b/db/patch-add-description-monitor.sql @@ -0,0 +1,7 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD description TEXT default null; + +COMMIT; diff --git a/server/database.js b/server/database.js index afcace70..55f08a0c 100644 --- a/server/database.js +++ b/server/database.js @@ -53,6 +53,7 @@ class Database { "patch-2fa-invalidate-used-token.sql": true, "patch-notification_sent_history.sql": true, "patch-monitor-basic-auth.sql": true, + "patch-add-description-monitor.sql": true, } /** diff --git a/server/model/monitor.js b/server/model/monitor.js index 6aa614b7..50f11e87 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -31,6 +31,7 @@ class Monitor extends BeanModel { return { id: this.id, name: this.name, + description: this.description, }; } @@ -54,6 +55,7 @@ class Monitor extends BeanModel { return { id: this.id, name: this.name, + description: this.description, url: this.url, method: this.method, body: this.body, diff --git a/server/server.js b/server/server.js index 868bbd5e..794f67b4 100644 --- a/server/server.js +++ b/server/server.js @@ -568,6 +568,7 @@ exports.entryPage = "dashboard"; } bean.name = monitor.name; + bean.description = monitor.description; bean.type = monitor.type; bean.url = monitor.url; bean.method = monitor.method; @@ -1134,6 +1135,7 @@ exports.entryPage = "dashboard"; let monitor = { // Define the new variable from earlier here name: monitorListData[i].name, + description: monitorListData[i].description, type: monitorListData[i].type, url: monitorListData[i].url, method: monitorListData[i].method || "GET", diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index ef51e89c..29d14d1f 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -23,6 +23,9 @@
{{ item.name }} + + +
diff --git a/src/icon.js b/src/icon.js index e78992f2..55e66291 100644 --- a/src/icon.js +++ b/src/icon.js @@ -33,6 +33,7 @@ import { faFile, faAward, faLink, + faInfoCircle, } from "@fortawesome/free-solid-svg-icons"; library.add( @@ -65,6 +66,7 @@ library.add( faFile, faAward, faLink, + faInfoCircle, ); export { FontAwesomeIcon }; diff --git a/src/languages/en.js b/src/languages/en.js index fee80a76..d238bb66 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -46,6 +46,7 @@ export default { Unknown: "Unknown", Pause: "Pause", Name: "Name", + Description: "Description", Status: "Status", DateTime: "DateTime", Message: "Message", diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js index d772db06..fe04d86a 100644 --- a/src/languages/es-ES.js +++ b/src/languages/es-ES.js @@ -34,6 +34,7 @@ export default { Unknown: "Desconocido", Pause: "Pausar", Name: "Nombre", + Description: "Descripción", Status: "Estado", DateTime: "Fecha y Hora", Message: "Mensaje", diff --git a/src/pages/Details.vue b/src/pages/Details.vue index d40561fe..12e86df4 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -2,6 +2,7 @@

{{ monitor.name }}

+

{{ monitor.description }}

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 4a0d0408..a0e68676 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -41,6 +41,12 @@
+ +
+ + +
+
From fbf2df9e7af19bee7f02ea41d2ead5b90ccd2882 Mon Sep 17 00:00:00 2001 From: Juan Cruz Vincenti Date: Thu, 25 Nov 2021 18:11:13 -0300 Subject: [PATCH 002/163] Add conditional rendering to description * Modify Details component * Modify MonitorList component --- src/components/MonitorList.vue | 2 +- src/pages/Details.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 29d14d1f..19f2c837 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -23,7 +23,7 @@
{{ item.name }} - +
diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 12e86df4..d96e42eb 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -2,7 +2,7 @@

{{ monitor.name }}

-

{{ monitor.description }}

+

{{ monitor.description }}

From 3a188017228b2437820133827c36b03a7b9597f7 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Wed, 10 Aug 2022 21:46:43 -0400 Subject: [PATCH 003/163] Add Body Encoding field --- src/languages/en.js | 1 + src/pages/EditMonitor.vue | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index b9951612..4433e2a5 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -553,4 +553,5 @@ export default { disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.", trustProxyDescription: "Trust 'X-Forwarded-*' headers. If you want to get the correct client IP and your Uptime Kuma is behind such as Nginx or Apache, you should enable this.", wayToGetLineNotifyToken: "You can get an access token from {0}", + "Body Encoding": "Body Encoding" }; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index ac6a3e2e..7dcc7d64 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -396,6 +396,22 @@
+ +
+ + +
+
@@ -644,6 +660,7 @@ export default { mqttTopic: "", mqttSuccessMessage: "", authMethod: null, + bodyEncoding: null }; if (this.$root.proxyList && !this.monitor.proxyId) { From 2b9bf095a609bbf2f2c517209b13cb57dbd1e1b1 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Thu, 11 Aug 2022 20:57:03 -0400 Subject: [PATCH 004/163] Add non-json support for http body --- db/patch-http-body-encoding.sql | 6 ++++++ server/database.js | 1 + server/model/monitor.js | 20 +++++++++++++++++++- server/server.js | 1 + src/pages/EditMonitor.vue | 13 +++++++------ 5 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 db/patch-http-body-encoding.sql diff --git a/db/patch-http-body-encoding.sql b/db/patch-http-body-encoding.sql new file mode 100644 index 00000000..de02bede --- /dev/null +++ b/db/patch-http-body-encoding.sql @@ -0,0 +1,6 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE [monitor] ADD http_body_encoding TEXT; + +COMMIT; diff --git a/server/database.js b/server/database.js index b234d67d..ecf6af72 100644 --- a/server/database.js +++ b/server/database.js @@ -62,6 +62,7 @@ class Database { "patch-add-clickable-status-page-link.sql": true, "patch-add-sqlserver-monitor.sql": true, "patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] }, + "patch-http-body-encoding.sql": true }; /** diff --git a/server/model/monitor.js b/server/model/monitor.js index 2feef135..af3d162a 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -103,6 +103,7 @@ class Monitor extends BeanModel { authMethod: this.authMethod, authWorkstation: this.authWorkstation, authDomain: this.authDomain, + httpBodyEncoding: this.httpBodyEncoding }; if (includeSensitiveData) { @@ -241,16 +242,33 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Prepare Options for axios`); + // Set content-type header and body values based on the httpBodyEncoding type selected + // TODO: Check if this.headers already contains a content-type header set by the user; if so, don't inject one + let bodyValue = null; + let contentType = null; + + if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json"){ + bodyValue = JSON.parse(this.body); + contentType = "application/json"; + } else if (this.body && (this.httpBodyEncoding === "xml")) { + bodyValue = this.body; + contentType = "text/xml"; + } else if (this.body && (this.httpBodyEncoding === "form")) { + bodyValue = this.body; + contentType = "application/x-www-form-urlencoded"; + } + const options = { url: this.url, method: (this.method || "get").toLowerCase(), - ...(this.body ? { data: JSON.parse(this.body) } : {}), + ...(bodyValue ? { data: bodyValue } : {}), timeout: this.interval * 1000 * 0.8, headers: { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "User-Agent": "Uptime-Kuma/" + version, ...(this.headers ? JSON.parse(this.headers) : {}), ...(basicAuthHeader), + ...(contentType ? { "Content-Type": contentType } : {}) }, maxRedirects: this.maxredirects, validateStatus: (status) => { diff --git a/server/server.js b/server/server.js index 2a2c4bf6..616a10cd 100644 --- a/server/server.js +++ b/server/server.js @@ -693,6 +693,7 @@ let needSetup = false; bean.authMethod = monitor.authMethod; bean.authWorkstation = monitor.authWorkstation; bean.authDomain = monitor.authDomain; + bean.httpBodyEncoding = monitor.httpBodyEncoding; await R.store(bean); diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 7dcc7d64..b4aceba7 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -398,8 +398,8 @@
- - @@ -660,7 +660,7 @@ export default { mqttTopic: "", mqttSuccessMessage: "", authMethod: null, - bodyEncoding: null + httpBodyEncoding: "json" }; if (this.$root.proxyList && !this.monitor.proxyId) { @@ -698,7 +698,8 @@ export default { * @returns {boolean} Is the form input valid? */ isInputValid() { - if (this.monitor.body) { + //TODO: Handle validation for all 3 possible options + null value + if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) { try { JSON.parse(this.monitor.body); } catch (err) { @@ -729,8 +730,8 @@ export default { return; } - // Beautify the JSON format - if (this.monitor.body) { + // Beautify the JSON format (only if httpBodyEncoding is not set or === json) + if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) { this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4); } From 31cc328839c9462169070b4b67ba4044fb6716d8 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Thu, 11 Aug 2022 21:08:13 -0400 Subject: [PATCH 005/163] fix lint --- server/model/monitor.js | 2 +- src/languages/en.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 5d9c4cd4..8f30a0c1 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -252,7 +252,7 @@ class Monitor extends BeanModel { let bodyValue = null; let contentType = null; - if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json"){ + if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json") { bodyValue = JSON.parse(this.body); contentType = "application/json"; } else if (this.body && (this.httpBodyEncoding === "xml")) { diff --git a/src/languages/en.js b/src/languages/en.js index e338d778..31d92628 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -559,5 +559,5 @@ export default { disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.", trustProxyDescription: "Trust 'X-Forwarded-*' headers. If you want to get the correct client IP and your Uptime Kuma is behind such as Nginx or Apache, you should enable this.", wayToGetLineNotifyToken: "You can get an access token from {0}", - "Body Encoding": "Body Encoding" + "Body Encoding": "Body Encoding", }; From 5809088f27eb9604b043b782ac375f1feecc2e5a Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 15:52:43 -0400 Subject: [PATCH 006/163] Don't override a user-defined content-type header --- server/model/monitor.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 509b841c..c541ecf3 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -249,20 +249,28 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Prepare Options for axios`); - // Set content-type header and body values based on the httpBodyEncoding type selected - // TODO: Check if this.headers already contains a content-type header set by the user; if so, don't inject one - let bodyValue = null; - let contentType = null; + + // Check if this.headers already contains a content-type header set by the user; if so, don't inject one + let contentTypeUserDefinedHeader = this.headers.find(function(header) { + return header[0].toLowerCase() == "content-type"; + }); + + let contentType = contentTypeUserDefinedHeader ? + contentTypeUserDefinedHeader[1] : + null; + + let bodyValue = null; + if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json") { bodyValue = JSON.parse(this.body); - contentType = "application/json"; + contentType = contentType ? contentType : "application/json"; } else if (this.body && (this.httpBodyEncoding === "xml")) { bodyValue = this.body; - contentType = "text/xml"; + contentType = contentType ? contentType : "text/xml"; } else if (this.body && (this.httpBodyEncoding === "form")) { bodyValue = this.body; - contentType = "application/x-www-form-urlencoded"; + contentType = contentType ? contentType : "application/x-www-form-urlencoded"; } const options = { From 6537f4fe746c157c8a87ff25bbcefeb7a62522f3 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 17:09:10 -0400 Subject: [PATCH 007/163] content-type change --- server/model/monitor.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index c541ecf3..48b0b1d3 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -249,28 +249,18 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Prepare Options for axios`); - - - // Check if this.headers already contains a content-type header set by the user; if so, don't inject one - let contentTypeUserDefinedHeader = this.headers.find(function(header) { - return header[0].toLowerCase() == "content-type"; - }); - - let contentType = contentTypeUserDefinedHeader ? - contentTypeUserDefinedHeader[1] : - null; - + let contentType = null; let bodyValue = null; if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json") { bodyValue = JSON.parse(this.body); - contentType = contentType ? contentType : "application/json"; + contentType = "application/json"; } else if (this.body && (this.httpBodyEncoding === "xml")) { bodyValue = this.body; - contentType = contentType ? contentType : "text/xml"; + contentType = "text/xml"; } else if (this.body && (this.httpBodyEncoding === "form")) { bodyValue = this.body; - contentType = contentType ? contentType : "application/x-www-form-urlencoded"; + contentType = "application/x-www-form-urlencoded"; } const options = { From f6919aef1d3b59dd23229feef6f2815004f34a80 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 17:10:56 -0400 Subject: [PATCH 008/163] remove TODO --- src/pages/EditMonitor.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index ba667670..ea246c60 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -741,7 +741,6 @@ export default { * @returns {boolean} Is the form input valid? */ isInputValid() { - //TODO: Handle validation for all 3 possible options + null value if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) { try { JSON.parse(this.monitor.body); From 4a7e96f9ea6235d797b3a0f68b8300d82aadfea6 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 6 Oct 2022 12:08:10 +0800 Subject: [PATCH 009/163] Init C# Project for building exe --- .dockerignore | 3 +- .gitignore | 3 + extra/exe-builder/App.config | 10 ++ extra/exe-builder/Program.cs | 59 +++++++++ extra/exe-builder/Properties/AssemblyInfo.cs | 36 ++++++ .../Properties/Resources.Designer.cs | 62 ++++++++++ extra/exe-builder/Properties/Resources.resx | 117 ++++++++++++++++++ .../Properties/Settings.Designer.cs | 23 ++++ .../exe-builder/Properties/Settings.settings | 7 ++ extra/exe-builder/UptimeKuma.csproj | 79 ++++++++++++ extra/exe-builder/UptimeKuma.sln | 16 +++ .../UptimeKuma.sln.DotSettings.user | 3 + 12 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 extra/exe-builder/App.config create mode 100644 extra/exe-builder/Program.cs create mode 100644 extra/exe-builder/Properties/AssemblyInfo.cs create mode 100644 extra/exe-builder/Properties/Resources.Designer.cs create mode 100644 extra/exe-builder/Properties/Resources.resx create mode 100644 extra/exe-builder/Properties/Settings.Designer.cs create mode 100644 extra/exe-builder/Properties/Settings.settings create mode 100644 extra/exe-builder/UptimeKuma.csproj create mode 100644 extra/exe-builder/UptimeKuma.sln create mode 100644 extra/exe-builder/UptimeKuma.sln.DotSettings.user diff --git a/.dockerignore b/.dockerignore index babc429a..22b71b38 100644 --- a/.dockerignore +++ b/.dockerignore @@ -31,6 +31,7 @@ tsconfig.json /tmp /babel.config.js /ecosystem.config.js +extra/exe-builder ### .gitignore content (commented rules are duplicated) @@ -45,6 +46,6 @@ dist-ssr #!/data/.gitkeep #.vscode +### End of .gitignore content -### End of .gitignore content diff --git a/.gitignore b/.gitignore index 8eb05867..8e0edeac 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ dist-ssr cypress/videos cypress/screenshots + +extra/exe-builder/bin +extra/exe-builder/obj diff --git a/extra/exe-builder/App.config b/extra/exe-builder/App.config new file mode 100644 index 00000000..09265d8b --- /dev/null +++ b/extra/exe-builder/App.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs new file mode 100644 index 00000000..5516a1ff --- /dev/null +++ b/extra/exe-builder/Program.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows.Forms; +using UptimeKuma.Properties; + +namespace UptimeKuma { + static class Program { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new UptimeKumaApplicationContext()); + } + } + + public class UptimeKumaApplicationContext : ApplicationContext + { + private NotifyIcon trayIcon; + + public UptimeKumaApplicationContext() + { + // Initialize Tray Icon + trayIcon = new NotifyIcon(); + + trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); + trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { + new MenuItem("Check for Update", CheckForUpdate), + new MenuItem("About", About), + new MenuItem("Exit", Exit), + }); + + trayIcon.Visible = true; + } + + void Exit(object sender, EventArgs e) + { + // Hide tray icon, otherwise it will remain shown until user mouses over it + trayIcon.Visible = false; + Application.Exit(); + } + + void About(object sender, EventArgs e) + { + MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info"); + } + + void CheckForUpdate(object sneder, EventArgs e) { + + } + } +} + diff --git a/extra/exe-builder/Properties/AssemblyInfo.cs b/extra/exe-builder/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2552870b --- /dev/null +++ b/extra/exe-builder/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Uptime Kuma")] +[assembly: AssemblyCopyright("Copyright © 2022 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("2DB53988-1D93-4AC0-90C4-96ADEAAC5C04")] + +// 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.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/extra/exe-builder/Properties/Resources.Designer.cs b/extra/exe-builder/Properties/Resources.Designer.cs new file mode 100644 index 00000000..8c8e559c --- /dev/null +++ b/extra/exe-builder/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace UptimeKuma.Properties { + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [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; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState + .Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } + set { resourceCulture = value; } + } + } +} \ No newline at end of file diff --git a/extra/exe-builder/Properties/Resources.resx b/extra/exe-builder/Properties/Resources.resx new file mode 100644 index 00000000..ffecec85 --- /dev/null +++ b/extra/exe-builder/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/extra/exe-builder/Properties/Settings.Designer.cs b/extra/exe-builder/Properties/Settings.Designer.cs new file mode 100644 index 00000000..6c63b395 --- /dev/null +++ b/extra/exe-builder/Properties/Settings.Designer.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +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; } + } + } +} \ No newline at end of file diff --git a/extra/exe-builder/Properties/Settings.settings b/extra/exe-builder/Properties/Settings.settings new file mode 100644 index 00000000..abf36c5d --- /dev/null +++ b/extra/exe-builder/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj new file mode 100644 index 00000000..d62166e4 --- /dev/null +++ b/extra/exe-builder/UptimeKuma.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04} + WinExe + UptimeKuma + uptime-kuma + v4.7.2 + 512 + true + true + ..\..\public\favicon.ico + 10 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + favicon.ico + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/extra/exe-builder/UptimeKuma.sln b/extra/exe-builder/UptimeKuma.sln new file mode 100644 index 00000000..201d7e23 --- /dev/null +++ b/extra/exe-builder/UptimeKuma.sln @@ -0,0 +1,16 @@ + +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 diff --git a/extra/exe-builder/UptimeKuma.sln.DotSettings.user b/extra/exe-builder/UptimeKuma.sln.DotSettings.user new file mode 100644 index 00000000..b4ca9dad --- /dev/null +++ b/extra/exe-builder/UptimeKuma.sln.DotSettings.user @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file From da778f05ac30a681eeb71735dd21fc86eaef4a23 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 00:16:07 +0800 Subject: [PATCH 010/163] Update --- extra/exe-builder/Program.cs | 42 ++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 5516a1ff..840bc873 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection; @@ -23,6 +24,7 @@ namespace UptimeKuma { public class UptimeKumaApplicationContext : ApplicationContext { private NotifyIcon trayIcon; + private Process process; public UptimeKumaApplicationContext() { @@ -31,19 +33,41 @@ namespace UptimeKuma { trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { + new MenuItem("Open", Open), new MenuItem("Check for Update", CheckForUpdate), new MenuItem("About", About), new MenuItem("Exit", Exit), }); trayIcon.Visible = true; + + var startInfo = new ProcessStartInfo(); + startInfo.FileName = "node/node.exe"; + startInfo.Arguments = "server/server.js"; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + startInfo.CreateNoWindow = true; + startInfo.WorkingDirectory = "core"; + + process = new Process(); + process.StartInfo = startInfo; + process.EnableRaisingEvents = true; + try { + process.Start(); + Open(null, null); + } catch (Exception e) { + MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error"); + throw; + } } - void Exit(object sender, EventArgs e) - { - // Hide tray icon, otherwise it will remain shown until user mouses over it - trayIcon.Visible = false; - Application.Exit(); + void Open(object sender, EventArgs e) { + Process.Start("http://localhost:3001"); + } + + void CheckForUpdate(object sender, EventArgs e) { + } void About(object sender, EventArgs e) @@ -51,8 +75,12 @@ namespace UptimeKuma { MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info"); } - void CheckForUpdate(object sneder, EventArgs e) { - + void Exit(object sender, EventArgs e) + { + // Hide tray icon, otherwise it will remain shown until user mouses over it + trayIcon.Visible = false; + process.Close(); + Application.Exit(); } } } From 4c456547807dc3fb4d6de0a4da741ef252cb5c7e Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 18:38:14 +0800 Subject: [PATCH 011/163] WIP --- extra/exe-builder/Program.cs | 67 +++++++++++++++++++++-------- extra/exe-builder/UptimeKuma.csproj | 5 ++- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 840bc873..84ecda31 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; using UptimeKuma.Properties; @@ -14,7 +15,7 @@ namespace UptimeKuma { /// The main entry point for the application. /// [STAThread] - static void Main() { + static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new UptimeKumaApplicationContext()); @@ -28,37 +29,44 @@ namespace UptimeKuma { public UptimeKumaApplicationContext() { - // Initialize Tray Icon trayIcon = new NotifyIcon(); trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { - new MenuItem("Open", Open), - new MenuItem("Check for Update", CheckForUpdate), - new MenuItem("About", About), - new MenuItem("Exit", Exit), + new("Open", Open), + //new("Debug Console", DebugConsole), + new("Check for Update...", CheckForUpdate), + new("Visit GitHub...", VisitGitHub), + new("About", About), + new("Exit", Exit), }); + trayIcon.MouseDoubleClick += new MouseEventHandler(Open); + trayIcon.Visible = true; - var startInfo = new ProcessStartInfo(); - startInfo.FileName = "node/node.exe"; - startInfo.Arguments = "server/server.js"; - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.WorkingDirectory = "core"; + 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 += new EventHandler(ProcessExited); + + try { process.Start(); - Open(null, null); + //Open(null, null); + } catch (Exception e) { MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error"); - throw; } } @@ -66,8 +74,17 @@ namespace UptimeKuma { Process.Start("http://localhost:3001"); } + void DebugConsole(object sender, EventArgs e) { + + } + void CheckForUpdate(object sender, EventArgs e) { + Process.Start("https://github.com/louislam/uptime-kuma/releases"); + } + void VisitGitHub(object sender, EventArgs e) + { + Process.Start("https://github.com/louislam/uptime-kuma"); } void About(object sender, EventArgs e) @@ -79,9 +96,25 @@ namespace UptimeKuma { { // Hide tray icon, otherwise it will remain shown until user mouses over it trayIcon.Visible = false; - process.Close(); + process.Kill(); + } + + 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(); } + } } diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index d62166e4..c3c6aad2 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -13,7 +13,7 @@ true true ..\..\public\favicon.ico - 10 + 9 AnyCPU @@ -34,6 +34,9 @@ prompt 4 + + COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "C:\Users\LouisLam\Desktop\uptime-kuma-win64\" + From 3eaccb560ed1a0590a6bb3bf7f73765bbe06db00 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 8 Oct 2022 16:23:11 +0800 Subject: [PATCH 012/163] Implement Download Logic --- extra/exe-builder/DownloadForm.Designer.cs | 84 +++++ extra/exe-builder/DownloadForm.cs | 65 ++++ extra/exe-builder/DownloadForm.resx | 377 +++++++++++++++++++++ extra/exe-builder/Program.cs | 23 +- extra/exe-builder/UptimeKuma.csproj | 9 + 5 files changed, 554 insertions(+), 4 deletions(-) create mode 100644 extra/exe-builder/DownloadForm.Designer.cs create mode 100644 extra/exe-builder/DownloadForm.cs create mode 100644 extra/exe-builder/DownloadForm.resx diff --git a/extra/exe-builder/DownloadForm.Designer.cs b/extra/exe-builder/DownloadForm.Designer.cs new file mode 100644 index 00000000..26a474e9 --- /dev/null +++ b/extra/exe-builder/DownloadForm.Designer.cs @@ -0,0 +1,84 @@ +using System.ComponentModel; + +namespace UptimeKuma { + partial class DownloadForm { + /// + /// Required designer variable. + /// + private IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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 + } +} + diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs new file mode 100644 index 00000000..9c740e31 --- /dev/null +++ b/extra/exe-builder/DownloadForm.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Windows.Forms; + +namespace UptimeKuma { + public partial class DownloadForm : Form { + private readonly Queue downloadQueue = new(); + private readonly WebClient webClient = new(); + + public DownloadForm() { + InitializeComponent(); + } + + private void DownloadForm_Load(object sender, EventArgs e) { + webClient.DownloadProgressChanged += DownloadProgressChanged; + webClient.DownloadFileCompleted += DownloadFileCompleted; + + if (!File.Exists("node")) { + downloadQueue.Enqueue(new DownloadItem { + URL = "https://nodejs.org/dist/v16.17.1/node-v16.17.1-win-x64.zip", + Filename = "node.zip" + }); + } + + if (!File.Exists("node")) { + downloadQueue.Enqueue(new DownloadItem { + URL = "https://github.com/louislam/uptime-kuma/archive/refs/tags/1.18.3.zip", + Filename = "core.zip" + }); + } + + DownloadNextFile(); + } + + void DownloadNextFile() { + if (downloadQueue.Count > 0) { + var item = downloadQueue.Dequeue(); + label.Text = item.URL; + webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); + } else { + // TODO: Finished, extract? + } + } + + void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { + progressBar.Value = e.ProgressPercentage; + var total = e.TotalBytesToReceive / 1024; + var current = e.BytesReceived / 1024; + labelData.Text = $"{current}KB/{total}KB"; + } + + void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + DownloadNextFile(); + } + } + + public class DownloadItem { + public string URL { get; set; } + public string Filename { get; set; } + } +} + diff --git a/extra/exe-builder/DownloadForm.resx b/extra/exe-builder/DownloadForm.resx new file mode 100644 index 00000000..e87e0c0d --- /dev/null +++ b/extra/exe-builder/DownloadForm.resx @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + 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= + + + \ No newline at end of file diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 84ecda31..84aa6e45 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -42,9 +43,23 @@ namespace UptimeKuma { }); trayIcon.MouseDoubleClick += new MouseEventHandler(Open); - trayIcon.Visible = true; + if (File.Exists("core") && File.Exists("node")) { + // Go go go + StartProcess(); + } else { + DownloadFiles(); + } + } + + void DownloadFiles() { + var form = new DownloadForm(); + form.Closed += Exit; + form.Show(); + } + + void StartProcess() { var startInfo = new ProcessStartInfo { FileName = "node/node.exe", Arguments = "server/server.js --data-dir=\"../data/\"", @@ -58,8 +73,7 @@ namespace UptimeKuma { process = new Process(); process.StartInfo = startInfo; process.EnableRaisingEvents = true; - process.Exited += new EventHandler(ProcessExited); - + process.Exited += ProcessExited; try { process.Start(); @@ -96,7 +110,8 @@ namespace UptimeKuma { { // Hide tray icon, otherwise it will remain shown until user mouses over it trayIcon.Visible = false; - process.Kill(); + process?.Kill(); + Application.Exit(); } void ProcessExited(object sender, EventArgs e) { diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index c3c6aad2..aa0a8bf8 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -51,8 +51,17 @@ + + Form + + + DownloadForm.cs + + + DownloadForm.cs + ResXFileCodeGenerator Resources.Designer.cs From 655ba015a07c87e7b1a24ce8cafbe171103acad1 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 8 Oct 2022 23:47:27 +0800 Subject: [PATCH 013/163] WIP --- extra/exe-builder/DownloadForm.cs | 127 ++++++++++++++++++++++++++-- extra/exe-builder/Program.cs | 2 +- extra/exe-builder/UptimeKuma.csproj | 3 +- 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs index 9c740e31..5bb88b6d 100644 --- a/extra/exe-builder/DownloadForm.cs +++ b/extra/exe-builder/DownloadForm.cs @@ -1,14 +1,19 @@ 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; namespace UptimeKuma { public partial class DownloadForm : Form { private readonly Queue downloadQueue = new(); private readonly WebClient webClient = new(); + private DownloadItem currentDownloadItem; public DownloadForm() { InitializeComponent(); @@ -18,17 +23,19 @@ namespace UptimeKuma { webClient.DownloadProgressChanged += DownloadProgressChanged; webClient.DownloadFileCompleted += DownloadFileCompleted; - if (!File.Exists("node")) { + if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { URL = "https://nodejs.org/dist/v16.17.1/node-v16.17.1-win-x64.zip", - Filename = "node.zip" + Filename = "node.zip", + TargetFolder = "node" }); } - if (!File.Exists("node")) { + if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { URL = "https://github.com/louislam/uptime-kuma/archive/refs/tags/1.18.3.zip", - Filename = "core.zip" + Filename = "core.zip", + TargetFolder = "core" }); } @@ -38,28 +45,130 @@ namespace UptimeKuma { void DownloadNextFile() { if (downloadQueue.Count > 0) { var item = downloadQueue.Dequeue(); - label.Text = item.URL; - webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); + + 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 { - // TODO: Finished, extract? + npmSetup(); + } + } + + void npmSetup() { + if (Directory.Exists("core/node_modules")) { + // Application.Restart(); } + + label.Text = "npm run setup"; + progressBar.Value = 50; + labelData.Text = ""; + + var startInfo = new ProcessStartInfo { + FileName = "cmd.exe", + Arguments = "run setup", + RedirectStandardOutput = false, + RedirectStandardError = false, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = false, + WorkingDirectory = "core" + }; + + var process = new Process(); + process.StartInfo = startInfo; + process.EnableRaisingEvents = true; + process.Exited += (object _, EventArgs e) => { + // Application.Restart(); + progressBar.Value = 100; + + if (process.ExitCode == 0) { + label.Text = "Done"; + } else { + label.Text = "Failed, exit code: " + process.ExitCode; + } + + }; + process.Start(); + process.StandardInput.WriteLine("\"../node/npm\" run setup"); } void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { progressBar.Value = e.ProgressPercentage; var total = e.TotalBytesToReceive / 1024; var current = e.BytesReceived / 1024; - labelData.Text = $"{current}KB/{total}KB"; + + if (total > 0) { + labelData.Text = $"{current}KB/{total}KB"; + } } - void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + async 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; } } } diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 84aa6e45..1b78f038 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -45,7 +45,7 @@ namespace UptimeKuma { trayIcon.MouseDoubleClick += new MouseEventHandler(Open); trayIcon.Visible = true; - if (File.Exists("core") && File.Exists("node")) { + if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules")) { // Go go go StartProcess(); } else { diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index aa0a8bf8..2ad857b2 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -35,11 +35,12 @@ 4 - COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "C:\Users\LouisLam\Desktop\uptime-kuma-win64\" + COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\" + From a487347b3316cc3192b312a195ac44a46160d310 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 9 Oct 2022 03:47:06 +0800 Subject: [PATCH 014/163] [exe] install dependencies and download dist --- extra/exe-builder/DownloadForm.cs | 21 +++++++++++---------- extra/exe-builder/Program.cs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs index 5bb88b6d..f16af422 100644 --- a/extra/exe-builder/DownloadForm.cs +++ b/extra/exe-builder/DownloadForm.cs @@ -63,12 +63,6 @@ namespace UptimeKuma { } void npmSetup() { - if (Directory.Exists("core/node_modules")) { - // Application.Restart(); - } - - label.Text = "npm run setup"; - progressBar.Value = 50; labelData.Text = ""; var startInfo = new ProcessStartInfo { @@ -86,10 +80,12 @@ namespace UptimeKuma { process.StartInfo = startInfo; process.EnableRaisingEvents = true; process.Exited += (object _, EventArgs e) => { - // Application.Restart(); - progressBar.Value = 100; + progressBar.Value = 100; if (process.ExitCode == 0) { + Task.Delay(2000).ContinueWith((task) => { + Application.Restart(); + }); label.Text = "Done"; } else { label.Text = "Failed, exit code: " + process.ExitCode; @@ -97,7 +93,12 @@ namespace UptimeKuma { }; process.Start(); - process.StandardInput.WriteLine("\"../node/npm\" run setup"); + label.Text = "Installing dependencies and download dist files"; + progressBar.Value = 50; + + process.StandardInput.WriteLine("\"../node/npm\" ci --production"); + process.StandardInput.WriteLine("\"../node/npm\" run download-dist"); + process.StandardInput.WriteLine("exit"); } void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { @@ -110,7 +111,7 @@ namespace UptimeKuma { } } - async void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { Extract(currentDownloadItem); DownloadNextFile(); } diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 1b78f038..82c76b05 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -45,7 +45,7 @@ namespace UptimeKuma { trayIcon.MouseDoubleClick += new MouseEventHandler(Open); trayIcon.Visible = true; - if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules")) { + if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules") && Directory.Exists("core/dist")) { // Go go go StartProcess(); } else { From da16796ec45a3ecb12f16a1855bca0813beb2571 Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Sat, 19 Nov 2022 16:10:30 -0800 Subject: [PATCH 015/163] Add ability to send Telegram notifications silently. --- server/notification-providers/telegram.js | 1 + src/components/notifications/Telegram.vue | 9 +++++++++ src/languages/en.js | 2 ++ 3 files changed, 12 insertions(+) diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js index 2b057622..88923e66 100644 --- a/server/notification-providers/telegram.js +++ b/server/notification-providers/telegram.js @@ -13,6 +13,7 @@ class Telegram extends NotificationProvider { params: { chat_id: notification.telegramChatID, text: msg, + disable_notification: notification.telegramSendSilently, }, }); return okMsg; diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue index 9daf31ac..4eb014ff 100644 --- a/src/components/notifications/Telegram.vue +++ b/src/components/notifications/Telegram.vue @@ -28,6 +28,15 @@ {{ telegramGetUpdatesURL("masked") }}

+ +
+ + +
+ +
+ {{ $t("telegramSendSilentlyDescription") }} +
diff --git a/src/languages/en.js b/src/languages/en.js index 86abb791..492689e5 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -214,6 +214,8 @@ export default { "Chat ID": "Chat ID", supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID", wayToGetTelegramChatID: "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:", + "Send Silently": "Send Silently", + telegramSendSilentlyDescription: "Sends the message silently. Users will receive a notification with no sound.", "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE", chatIDNotFound: "Chat ID is not found; please send a message to this bot first", webhook: "Webhook", From 608e3f5582604e6a7079859bcf722d7c645a4245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Haugsb=C3=B8?= Date: Tue, 27 Dec 2022 23:26:05 +0100 Subject: [PATCH 016/163] Feature: Clone existing monitor Closes #565 Closes #2319 Adds the feature of cloning existing monitor, I have briefly tested it with ping and https and ensured that all properties was cloned including notifications. --- src/languages/en.js | 2 ++ src/pages/Details.vue | 3 +++ src/pages/EditMonitor.vue | 27 ++++++++++++++++++++++++--- src/router.js | 4 ++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index e760f92e..59ea0570 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -57,6 +57,7 @@ export default { List: "List", Add: "Add", "Add New Monitor": "Add New Monitor", + "Clone Monitor": "Clone Monitor", "Quick Stats": "Quick Stats", Up: "Up", Down: "Down", @@ -70,6 +71,7 @@ export default { "No important events": "No important events", Resume: "Resume", Edit: "Edit", + Clone: "Clone", Delete: "Delete", Current: "Current", Uptime: "Uptime", diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 6d6a8dd9..40ed0358 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -30,6 +30,9 @@ {{ $t("Edit") }} + + {{ $t("Clone") }} + diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index c9d5ad2f..f0c99b5b 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -620,13 +620,23 @@ export default { }, pageName() { - return this.$t((this.isAdd) ? "Add New Monitor" : "Edit"); + let name = "Add New Monitor"; + if (this.isClone) { + name = "Clone Monitor"; + } else if (this.isEdit) { + name = "Edit"; + } + return this.$t(name); }, isAdd() { return this.$route.path === "/add"; }, + isClone() { + return this.$route.path.startsWith("/clone"); + }, + isEdit() { return this.$route.path.startsWith("/edit"); }, @@ -804,11 +814,22 @@ message HealthCheckResponse { this.monitor.notificationIDList[this.$root.notificationList[i].id] = true; } } - } else if (this.isEdit) { + } else if (this.isEdit || this.isClone) { this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => { if (res.ok) { this.monitor = res.monitor; + if (this.isClone) { + /** + * Cloning a monitor will include properties that can not be posted to backend + * as they are not valid columns in the SQLite table. + */ + this.monitor.id = undefined; // Remove id when cloning as we want a new id + this.monitor.includeSensitiveData = undefined; + this.monitor.maintenance = undefined; + this.monitor.tags = undefined; // FIXME: Cloning tags does not work yet + } + // Handling for monitors that are created before 1.7.0 if (this.monitor.retryInterval === 0) { this.monitor.retryInterval = this.monitor.interval; @@ -866,7 +887,7 @@ message HealthCheckResponse { this.monitor.headers = JSON.stringify(JSON.parse(this.monitor.headers), null, 4); } - if (this.isAdd) { + if (this.isAdd || this.isClone) { this.$root.add(this.monitor, async (res) => { if (res.ok) { diff --git a/src/router.js b/src/router.js index 38048826..a5938c22 100644 --- a/src/router.js +++ b/src/router.js @@ -63,6 +63,10 @@ const routes = [ path: "/edit/:id", component: EditMonitor, }, + { + path: "/clone/:id", + component: EditMonitor, + }, ], }, { From 4d0bdae6bf4ead2617f9b9f71e97e5edf0466d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Haugsb=C3=B8?= Date: Tue, 27 Dec 2022 23:27:23 +0100 Subject: [PATCH 017/163] Add jsdoc for Tag type Does not work properly but is still useful --- src/components/Tag.vue | 10 ++++++++-- src/components/TagsManager.vue | 20 +++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/components/Tag.vue b/src/components/Tag.vue index 0325d990..705408b2 100644 --- a/src/components/Tag.vue +++ b/src/components/Tag.vue @@ -18,9 +18,15 @@ From aad087caac48d5615676531d64de8bd4225406ac Mon Sep 17 00:00:00 2001 From: Jonne Saloranta <72470168+JonneSaloranta@users.noreply.github.com> Date: Tue, 14 Feb 2023 20:34:00 +0200 Subject: [PATCH 096/163] Added Finnish language translation (#2770) * Added Finnish language translation * Changed fi-FI to fi --- src/i18n.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n.js b/src/i18n.js index f57408e4..c0c07797 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -15,6 +15,7 @@ const languageList = { "fa": "Farsi", "pt-PT": "Português (Portugal)", "pt-BR": "Português (Brasileiro)", + "fi": "Suomi", "fr-FR": "Français (France)", "hu": "Magyar", "hr-HR": "Hrvatski", From d1175ff471ff814e652f48cb97132dae51939037 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 15 Feb 2023 02:50:49 +0800 Subject: [PATCH 097/163] Fix #2777 --- server/util-server.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/server/util-server.js b/server/util-server.js index edce2890..e099f673 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -292,14 +292,23 @@ exports.postgresQuery = function (connectionString, query) { client.end(); } else { // Connected here - client.query(query, (err, res) => { - if (err) { - reject(err); - } else { - resolve(res); + try { + // No query provided by user, use SELECT 1 + if (!query || (typeof query === "string" && query.trim() === "")) { + query = "SELECT 1"; } - client.end(); - }); + + client.query(query, (err, res) => { + if (err) { + reject(err); + } else { + resolve(res); + } + client.end(); + }); + } catch (e) { + reject(e); + } } }); From 7d363ea1469bb651de4ffaf641d8053fa98629be Mon Sep 17 00:00:00 2001 From: darkslash#2558 Date: Tue, 14 Feb 2023 19:08:51 +0000 Subject: [PATCH 098/163] Translated using Weblate (Japanese) Currently translated at 28.1% (196 of 697 strings) Added translation using Weblate (Chinese (Literary)) Added translation using Weblate (Mongolian) Co-authored-by: darkslash#2558 Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ja/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/ja.json | 23 ++++++++++++++++++++++- src/lang/lzh.json | 1 + src/lang/mn.json | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/lang/lzh.json create mode 100644 src/lang/mn.json diff --git a/src/lang/ja.json b/src/lang/ja.json index 3de54074..06f0d3c3 100644 --- a/src/lang/ja.json +++ b/src/lang/ja.json @@ -176,5 +176,26 @@ "Gateway Type": "ゲートウェイの種類", "Game": "ゲーム", "Help": "ヘルプ", - "Maintenance": "メンテナンス" + "Maintenance": "メンテナンス", + "resendDisabled": "再送信不可", + "Schedule maintenance": "メンテナンスのスケジュール", + "Affected Monitors": "影響を受けるモニター", + "Pick Affected Monitors...": "影響を受けるモニターを選択…", + "Start of maintenance": "メンテナンス開始", + "General Monitor Type": "汎用モニタータイプ", + "resendEveryXTimes": "{0}回ごとに再送信", + "markdownSupported": "マークダウン構文がサポートされています", + "All Status Pages": "すべてのステータス ページ", + "Monitor": "モニター |モニター", + "Resend Notification if Down X times consequently": "ダウンX回連続で通知再送", + "Push URL": "プッシュ URL", + "needPushEvery": "{0} 秒ごとにこの URL を呼び出す必要があります。", + "pushOptionalParams": "オプションのパラメーター: {0}", + "disableauth.message1": "認証を無効にしてもよろしいですか?", + "disableauth.message2": "これは、Cloudflare Access、Authelia、またはその他の認証メカニズムなど、Uptime Kuma の前にサードパーティ認証を実装するシナリオ向けに設計されています。", + "Please use this option carefully!": "このオプションは慎重に使用してください。", + "Primary Base URL": "プライマリ ベース URL", + "statusMaintenance": "メンテナンス", + "Passive Monitor Type": "パッシブモニタータイプ", + "Specific Monitor Type": "特定のモニターの種類" } diff --git a/src/lang/lzh.json b/src/lang/lzh.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/lang/lzh.json @@ -0,0 +1 @@ +{} diff --git a/src/lang/mn.json b/src/lang/mn.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/lang/mn.json @@ -0,0 +1 @@ +{} From a518188e6f9dcb87c49382ed4fe3b4c68609c3c3 Mon Sep 17 00:00:00 2001 From: victorpahuus Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 099/163] Translated using Weblate (Danish) Currently translated at 62.9% (439 of 697 strings) Co-authored-by: victorpahuus Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/da/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/da-DK.json | 90 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/src/lang/da-DK.json b/src/lang/da-DK.json index 9d6f0574..1b0fe210 100644 --- a/src/lang/da-DK.json +++ b/src/lang/da-DK.json @@ -29,7 +29,7 @@ "Delete": "Slet", "Current": "Aktuelt", "Uptime": "Oppetid", - "Cert Exp.": "Certifikatets udløb", + "Cert Exp.": "Certifikatets udløb.", "day": "Dag | Dage", "-day": "-Dage", "hour": "Timer", @@ -358,5 +358,91 @@ "High": "Høj", "Recipient Number": "Modtager Nummer", "From Name/Number": "Fra Navn/Nummer", - "Help": "Hjælp" + "Help": "Hjælp", + "Please use this option carefully!": "Brug venligst denne funktion med forsigtighed!", + "disableauth.message1": "Er du sikker på, at du vil deaktivere authentication?", + "successMessage": "Succesmeddelelse", + "error": "fejl", + "critical": "kritisk", + "Customize": "Tilpas", + "Custom Footer": "Brugerdefineret Footer", + "Custom CSS": "Brugerdefineret CSS", + "deleteStatusPageMsg": "Er du sikker på, at du vil slette denne statusside?", + "Proxies": "Proxies", + "default": "Standard", + "enabled": "Aktiveret", + "setAsDefault": "Indstil som standard", + "Certificate Chain": "Certificate Chain", + "Days Remaining:": "Dage tilbage:", + "No status pages": "Ingen statussider", + "Proxy": "Proxy", + "default: notify all devices": "standard: underretter alle enheder", + "Automations can optionally be triggered in Home Assistant:": "Automatiseringer kan valgfrit udløses i Home Assistant:", + "Trigger type:": "Trigger type:", + "Event type:": "Event type:", + "Event data:": "Event data:", + "Frontend Version": "Frontend Version", + "or": "eller", + "Notification Service": "Notifikationstjeneste", + "Domain": "Domæne", + "Google Analytics ID": "Google Analytics ID", + "Edit Tag": "Ændre Tag", + "Learn More": "Lær mere", + "Schedule maintenance": "Planlæg vedligeholdelse", + "Invalid": "Ugyldig", + "User": "Bruger", + "Installed": "Installeret", + "Not installed": "Ikke installeret", + "Running": "Køre", + "Not running": "Køre ikke", + "Remove Token": "Fjern Token", + "Start": "Start", + "Stop": "Stop", + "Add New Status Page": "Tilføj ny statusside", + "Next": "Næste", + "No Proxy": "Ingen proxy", + "New Status Page": "Ny statusside", + "Page Not Found": "Side blev ikke fundet", + "Reverse Proxy": "Reverse Proxy", + "Backup": "Backup", + "About": "Om", + "cloudflareWebsite": "Cloudflare hjemmeside", + "Message:": "Besked:", + "HTTP Headers": "HTTP Headers", + "Trust Proxy": "Trust Proxy", + "For example: nginx, Apache and Traefik.": "For eksempel: nginx, Apache og Traefik.", + "Please read": "Læs venligst", + "Show Powered By": "Vis Powered By", + "Domain Names": "Domænenavne", + "signedInDisp": "Logget ind som {0}", + "Certificate Expiry Notification": "Meddelelse om udløbsdato for certifikatet", + "API Username": "API Brugernavn", + "API Key": "API Key", + "Steam Game Server": "Steam Game Server", + "What you can try:": "Hvad du kan prøve:", + "Go back to the previous page.": "Gå tilbage til forrige side.", + "Coming Soon": "Kommer snart", + "settingsCertificateExpiry": "Udløb af TLS-certifikat", + "Setup Docker Host": "Opsæt Docker Host", + "Connection Type": "Forbindelsestype", + "Docker Daemon": "Docker Daemon", + "socket": "Socket", + "tcp": "TCP / HTTP", + "Docker Container": "Docker Container", + "Container Name / ID": "Container Navn / ID", + "Packet Size": "Pakke størrelse", + "Home Assistant URL": "Home Assistant URL", + "Frontend Version do not match backend version!": "Frontend versionen stemmer ikke overens med backend versionen!", + "Optional": "Valgfri", + "HomeAssistant": "Home Assistant", + "disableauth.message2": "Den er beregnet til scenarier hvor du har tænkt dig at implementere tredjepartsgodkendelse foran Uptime Kuma, f.eks. Cloudflare Access, Authelia eller andre godkendelsesmekanismer.", + "deleteProxyMsg": "Er du sikker på, at du vil slette denne proxy for alle monitors?", + "Valid": "Gyldig", + "Don't know how to get the token? Please read the guide:": "Ved du ikke, hvordan du får fat i din Token? Læs venligst guiden:", + "Subject:": "Emne:", + "Footer Text": "Footer tekst", + "Using a Reverse Proxy?": "Bruger du en Reverse Proxy?", + "deleteDockerHostMsg": "Er du sikker på, at du vil slette denne docker host for alle monitors?", + "Docker Host": "Docker Host", + "Docker Hosts": "Docker Hosts" } From 379d54e5209913b31c3b66ac714e76e2854372d9 Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 100/163] Translated using Weblate (German) Currently translated at 100.0% (697 of 697 strings) Co-authored-by: Denys Konovalov Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/de-DE.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lang/de-DE.json b/src/lang/de-DE.json index 2d4c0e30..a4081c4b 100644 --- a/src/lang/de-DE.json +++ b/src/lang/de-DE.json @@ -165,7 +165,7 @@ "Pink": "Pink", "Search...": "Suchen…", "Heartbeat Retry Interval": "Überprüfungsintervall", - "Resend Notification if Down X times consequently": "Benachrichtigung erneut senden, wenn Inaktiv X mal hintereinander", + "Resend Notification if Down X times consequently": "Benachrichtigung erneut senden, wenn inaktiv X Mal hintereinander", "retryCheckEverySecond": "Alle {0} Sekunden neu versuchen", "resendEveryXTimes": "Erneut versenden alle {0} mal", "resendDisabled": "Erneut versenden deaktiviert", @@ -275,7 +275,7 @@ "Read more": "Weiterlesen", "appriseInstalled": "Apprise ist installiert.", "appriseNotInstalled": "Apprise ist nicht installiert. {0}", - "Access Token": "Access Token", + "Access Token": "Zugriffstoken", "Channel access token": "Channel access token", "Line Developers Console": "Zeile Entwickler Konsole", "lineDevConsoleTo": "Line Developers Console - {0}", @@ -553,9 +553,9 @@ "socket": "Socket", "tcp": "TCP / HTTP", "Docker Container": "Docker Container", - "Container Name / ID": "Container Name / ID", - "Docker Host": "Docker Host", - "Docker Hosts": "Docker Hosts", + "Container Name / ID": "Container-Bezeichnung / ID", + "Docker Host": "Docker-Host", + "Docker Hosts": "Docker-Hosts", "ntfy Topic": "ntfy Thema", "Domain": "Domain", "Workstation": "Workstation", @@ -574,7 +574,7 @@ "Event type:": "Ereignistyp:", "Event data:": "Ereignis daten:", "Then choose an action, for example switch the scene to where an RGB light is red.": "Dann eine Aktion wählen, zum Beispiel eine Scene wählen in der ein RGB Licht rot ist.", - "Frontend Version": "Frontend Version", + "Frontend Version": "Frontend-Version", "Frontend Version do not match backend version!": "Die Frontend Version stimmt nicht mit der backend version überein!", "Maintenance": "Wartung", "statusMaintenance": "Wartung", From 31fa074ffcf473fbd5e54159a685a4a36a74f594 Mon Sep 17 00:00:00 2001 From: Luiz Felipe Arcos Campos Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 101/163] Translated using Weblate (Portuguese (Brazil)) Currently translated at 38.8% (271 of 697 strings) Co-authored-by: Luiz Felipe Arcos Campos Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pt_BR/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/pt-BR.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lang/pt-BR.json b/src/lang/pt-BR.json index b7ebdbd4..56c8f4f4 100644 --- a/src/lang/pt-BR.json +++ b/src/lang/pt-BR.json @@ -249,7 +249,7 @@ "enabled": "Ativado", "setAsDefault": "Definir como padrão", "Primary Base URL": "URL base principal", - "Resend Notification if Down X times consequently": "Reenviar notificação se OFFLINE X vezes consecutivamente", + "Resend Notification if Down X times consequently": "Reenviar Notificação se OFFLINE X vezes consecutivamente", "pushOptionalParams": "Parâmetros opcionais: {0}", "webhookFormDataDesc": "{multipart} é bom para PHP. O JSON precisará ser analisado com {decodeFunction}", "HeadersInvalidFormat": "Os cabeçalhos da solicitação não são um JSON válidos: ", @@ -269,5 +269,16 @@ "Start of maintenance": "Iniciar manutenção", "All Status Pages": "Todas as Status Pages", "Method": "Método", - "General Monitor Type": "Tipo de monitoramento geral" + "General Monitor Type": "Tipo de monitoramento geral", + "markdownSupported": "Sintaxe Markdown suportada", + "emojiCheatSheet": "Folha de dicas de emojis: {0}", + "topic": "Tema", + "topicExplanation": "Tópico MQTT para monitorar", + "successMessageExplanation": "Mensagem MQTT que será considerada como sucesso", + "Content Type": "Tipo de Conteúdo", + "Shrink Database": "Encolher Banco de Dados", + "Content": "Conteúdo", + "Pick a RR-Type...": "Escolha um tipo RR…", + "Pick Accepted Status Codes...": "Escolha Códigos de Status Aceitos…", + "Pick Affected Monitors...": "Escolher Monitores Afetados…" } From 8afc55db4e659e5d9b402327f3061576dd5dde61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Gen=C3=A7?= Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 102/163] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (697 of 697 strings) Co-authored-by: Ömer Faruk Genç Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/tr-TR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/tr-TR.json b/src/lang/tr-TR.json index 7091de1a..7fbf2948 100644 --- a/src/lang/tr-TR.json +++ b/src/lang/tr-TR.json @@ -74,7 +74,7 @@ "Heartbeat Interval": "Servis Test Aralığı", "Retries": "Yeniden deneme", "Heartbeat Retry Interval": "Sağlık Durumları Tekrar Deneme Sıklığı", - "Resend Notification if Down X times consequently": "Sonuç olarak X kez düşerse bildirimi yeniden gönder", + "Resend Notification if Down X times consequently": "Art arda X kez düşerse bildirimi yeniden gönder", "Advanced": "Gelişmiş", "Upside Down Mode": "Ters/Düz Modu", "Max. Redirects": "Maksimum Yönlendirme", From e3573ced65d40a2798db14dfe79a7f5c4f39f670 Mon Sep 17 00:00:00 2001 From: eltionb Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 103/163] Translated using Weblate (Albanian) Currently translated at 3.2% (23 of 697 strings) Added translation using Weblate (Albanian) Co-authored-by: eltionb Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/sq/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/sq.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/lang/sq.json diff --git a/src/lang/sq.json b/src/lang/sq.json new file mode 100644 index 00000000..a513329a --- /dev/null +++ b/src/lang/sq.json @@ -0,0 +1,25 @@ +{ + "Settings": "Opsione", + "Dashboard": "FaqeKryesore", + "Help": "Ndihma", + "Language": "Gjuha", + "Appearance": "Paraqitja", + "Theme": "Theme", + "General": "Te pergjithshme", + "Game": "Loje", + "Primary Base URL": "ULR Baze Primare", + "List": "List", + "Add": "Shto", + "Add New Monitor": "Shto Monitor te Ri", + "Quick Stats": "Statistika Flash", + "Up": "Lart", + "Down": "Poshte", + "Pending": "Ne Pritje", + "statusMaintenance": "Mirembatje", + "Maintenance": "Mirembajtje", + "Unknown": "Panjohur", + "languageName": "Shqip", + "New Update": "Update i ri", + "Version": "Version", + "Check Update On GitHub": "Kontrollo Update ne GitHub" +} From fb2f7179e99069751fff747b0312e2efb9cfce81 Mon Sep 17 00:00:00 2001 From: Jonne Saloranta Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 104/163] Translated using Weblate (Finnish) Currently translated at 100.0% (697 of 697 strings) Co-authored-by: Jonne Saloranta Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fi/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/fi.json | 593 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 592 insertions(+), 1 deletion(-) diff --git a/src/lang/fi.json b/src/lang/fi.json index 193a95c8..32454526 100644 --- a/src/lang/fi.json +++ b/src/lang/fi.json @@ -104,5 +104,596 @@ "Discourage search engines from indexing site": "Estä hakukoneita indeksoimasta sivua", "disableauth.message1": "Oletko varma että haluat poistaa todennuksen käytöstä?", "Please use this option carefully!": "Käytä tätä vaihtoehtoa varoen!", - "Remember me": "Muista minut" + "Remember me": "Muista minut", + "languageName": "Englanti", + "Primary Base URL": "Ensisijainen perus-URL-osoite", + "pushOptionalParams": "Valinnaiset parametrit: {0}", + "Not available, please setup.": "Ei saatavilla, määritä ensin.", + "needPushEvery": "Sinun pitäisi kutsua tätä URL joka {0} sekuntti.", + "disableauth.message2": "Se on suunniteltu tilanteisiin jossa aiot käyttää kolmannen osapuolen todennnusta Uptime Kuma:n edessä, kuten Cloudflare Access, Authelia tai jotain muuta todennus mekanismia.", + "No Monitors, please": "Ei seuraimia, kiitos", + "Resolver Server": "Ratkaisija palvelin", + "Resource Record Type": "Resusrssi tallenne tyyppi", + "Last Result": "Viimeinen tulos", + "Create your admin account": "Luo sinun järjestelmänvalvoja käyttäjä", + "Repeat Password": "Toista salasana", + "Import Backup": "Tuo varmuuskopio", + "Export Backup": "Vie varmuuskopio", + "Export": "Vie", + "Import": "Tuo", + "respTime": "Vast. aika (ms)", + "notAvailableShort": "Ei käytössä", + "Default enabled": "Oletus käytössä", + "Apply on all existing monitors": "Aseta jokaiselle olemassa olevaan seuraimeen", + "Create": "Luo", + "Clear Data": "Tyhjennä data", + "Events": "Tapahtumat", + "Heartbeats": "Sydämensyke", + "Auto Get": "Automaattinen haku", + "Schedule maintenance": "Ajoita huolto", + "Affected Monitors": "Vaikutetut seuraimet", + "Pick Affected Monitors...": "Poimi vaikutetut seuraimet…", + "Start of maintenance": "Huollon aloitus", + "All Status Pages": "Kaikki tilanne sivut", + "Select status pages...": "Valitse tilanne sivu…", + "alertNoFile": "Valitse tuotava tiedosto.", + "alertWrongFileType": "Valitse JSON tiedosto.", + "Clear all statistics": "Tyhjennä kaikki tilastot", + "Skip existing": "Ohita olemassa oleva", + "Overwrite": "Päälle kirjoita", + "Options": "Vaihtoehdot", + "Keep both": "Pidä molemmat", + "Verify Token": "Vahvista tunnus", + "Setup 2FA": "Määritä 2FA", + "Enable 2FA": "Ota 2FA käyttöön", + "Disable 2FA": "Poista 2FA käytöstä", + "2FA Settings": "2FA asetukset", + "Two Factor Authentication": "kaksivaiheinen tunnistautuminen", + "Active": "Aktiivinen", + "Token": "Tokeni", + "Show URI": "Näytä URI", + "Tags": "Tunnisteet", + "Tag with this name already exist.": "Tunniste tällä nimellä on jo olemassa.", + "Tag with this value already exist.": "Tunniste tällä arvolla on jo olemassa.", + "color": "Väri", + "value (optional)": "Arvo (valinnainen)", + "Gray": "Harmaa", + "Red": "Punainen", + "Orange": "Oranssi", + "Green": "Vihreä", + "Indigo": "Indigo", + "Purple": "Purppura", + "Pink": "Vaaleanpunainen", + "Custom": "Mukautettu", + "Search...": "Etsi…", + "Avg. Ping": "kesk.arv. viive", + "Entry Page": "Sisääntulosivu", + "statusPageNothing": "Täällä ei ole mitään. Lisää ryhmä tai seurain.", + "No Services": "Ei palveluita", + "Partially Degraded Service": "Osittain heikentynyt palvelu", + "Degraded Service": "Heikentynyt palvelu", + "Add Group": "Lisää ryhmä", + "Add a monitor": "Lisää seurain", + "Edit Status Page": "Muokkaa tilanne sivua", + "Go to Dashboard": "Mene kojelaudalle", + "Status Page": "Tilanne sivu", + "Status Pages": "Tilanne sivut", + "here": "täällä", + "Required": "Vaadittu", + "webhook": "Webhookki", + "Post URL": "Lähetys URL", + "Content Type": "Sisältö tyyppi", + "webhookJsonDesc": "{0} on hyvä jokaisille modernilleille HTTP palvelimille kuten Express.js", + "webhookAdditionalHeadersTitle": "Lisä otsakkeet", + "webhookAdditionalHeadersDesc": "Asettaa lisäpäätteet, jotka on lähetetty webhookilla.", + "Webhook URL": "Webhookin URL", + "Application Token": "Sovellus tokeni", + "Server URL": "Palvelin URL", + "Priority": "Prioriteetti", + "emojiCheatSheet": "Emoji lunttilappu: {0}", + "Read more": "Lue lisää", + "appriseInstalled": "Apprise on asennettu.", + "appriseNotInstalled": "Apprisea ei ole asennettu. {0}", + "Method": "Menetelmä", + "Body": "Runko", + "Headers": "Otsikot", + "PushUrl": "Työntö URL", + "BodyInvalidFormat": "Pyynnön runko ei ole kelvollinen JSON: ", + "Monitor History": "Seuraa historiaa", + "PasswordsDoNotMatch": "Salasanat eivät täsmää.", + "records": "tallenteet", + "One record": "Yksi tallenne", + "Current User": "Nykyinen käyttäjä", + "topic": "Aihe", + "topicExplanation": "MQTT seurattava aihe", + "successMessage": "Onnistumis viesti", + "successMessageExplanation": "MQTT-viesti, jota pidetään onnistuneena", + "recent": "Viimeaikainen", + "Done": "Tehty", + "Info": "Tiedot", + "Security": "Turvallisuus", + "Steam API Key": "Steam API-avain", + "Shrink Database": "Pienennä tietokanta", + "Pick a RR-Type...": "Valitse RR-tyyppi…", + "Pick Accepted Status Codes...": "Valitse hyväksytyt tilakoodit…", + "Default": "Oletus", + "HTTP Options": "HTTP-asetukset", + "Create Incident": "Luo tapaus", + "Title": "Otsikko", + "Content": "Sisältö", + "Style": "Tyyli", + "info": "Tiedot", + "warning": "Varoitus", + "danger": "vaara", + "error": "virhe", + "critical": "kriittinen", + "primary": "ensisijainen", + "dark": "Tumma", + "Post": "Lähetä", + "Please input title and content": "Syötä otsikko ja sisältö", + "Created": "Luo", + "Last Updated": "Viimeksi päivitetty", + "Unpin": "Irroita", + "Switch to Dark Theme": "Vaihda tummaan teemaan", + "Show Tags": "Näytä tunnisteet", + "Hide Tags": "Piilota tunnisteet", + "Description": "Kuvaus", + "No monitors available.": "Ei seuraimia saatavilla.", + "Add one": "Lisää yksi", + "No Monitors": "Ei seuraimia", + "Untitled Group": "Nimetön ryhmä", + "Services": "Palvelut", + "Discard": "Hävitä", + "Cancel": "Peruuttaa", + "Customize": "Mukauta", + "Custom Footer": "Mukautettu alatunniste", + "Custom CSS": "Mukautettu CSS", + "deleteStatusPageMsg": "Haluatko varmasti poistaa tämän tilasivun?", + "Proxies": "Välityspalvelimet", + "default": "Oletus", + "enabled": "Käytössä", + "setAsDefault": "Oletusasetuksena", + "deleteProxyMsg": "Haluatko varmasti poistaa tämän välityspalvelimen kaikista seuraimista?", + "proxyDescription": "Välityspalvelimet on määritettävä seuraimelle toimiakseen.", + "enableProxyDescription": "Tämä välityspalvelin ei vaikuta valvontapyyntöihin ennen kuin se on aktivoitu. Voit hallita välityspalvelimen väliaikaista poistamista käytöstä kaikista seuraimista aktivointitilan perusteella.", + "setAsDefaultProxyDescription": "Tämä välityspalvelin on oletuksena käytössä uusissa seuraimissa. Voit silti poistaa välityspalvelimen käytöstä erikseen jokaisesta seuraimesta.", + "Certificate Chain": "Sertifikaattiketju", + "Valid": "Voimassa oleva", + "Invalid": "Pätemätön", + "User": "Käyttäjä", + "Installed": "Asennettu", + "Not installed": "Ei asennettu", + "Running": "Käynnissä", + "Not running": "Ei käynnissä", + "Remove Token": "Poista token", + "Start": "Käynnistä", + "Stop": "Pysäytä", + "Add New Status Page": "Lisää uusi tilasivu", + "Slug": "Slug", + "startOrEndWithOnly": "Aloita tai lopeta vain {0}", + "No consecutive dashes": "Ei peräkkäisiä viivoja", + "Next": "Seuraava", + "No Proxy": "Ei välityspalvelinta", + "Authentication": "Todennus", + "HTTP Basic Auth": "HTTP-perustodennus", + "Page Not Found": "Sivua ei löydetty", + "Reverse Proxy": "Käänteinen välityspalvelin", + "Backup": "Varmuuskopio", + "About": "Tietoja", + "cloudflareWebsite": "Cloudflare verkkosivusto", + "Message:": "Viesti:", + "Don't know how to get the token? Please read the guide:": "Etkö tiedä kuinka saada tunnus? Ole hyvä ja lue opas:", + "HTTP Headers": "HTTP-otsikot", + "Trust Proxy": "Luota välityspalvelimeen", + "Other Software": "Muut ohjelmistot", + "For example: nginx, Apache and Traefik.": "Esimerkiksi: nginx, Apache ja Traefik.", + "Please read": "Ole hyvä ja lue", + "Subject:": "Aihe:", + "Valid To:": "Voimassa:", + "Days Remaining:": "Päiviä jäljellä:", + "Issuer:": "Myöntäjä:", + "Fingerprint:": "Sormenjälki:", + "No status pages": "Ei tilasivuja", + "Domain Name Expiry Notification": "Verkkotunnuksen vanhenemisilmoitus", + "Proxy": "Välityspalvelin", + "Date Created": "Luomis päivämäärä", + "Footer Text": "Alatunnisteen teksti", + "Show Powered By": "Näytä \"voimanlähteenä\"", + "Domain Names": "Verkkotunnus nimet", + "signedInDisp": "Kirjautunut sisään käyttäjänä {0}", + "signedInDispDisabled": "Todennus poistettu käytöstä.", + "RadiusSecretDescription": "Asiakkaan ja palvelimen välinen yhteinen salaisuus", + "RadiusCalledStationIdDescription": "Kutsutun laitteen tunniste", + "RadiusCallingStationId": "Kutsuaseman tunnus", + "Certificate Expiry Notification": "Varmenteen vanhenemisilmoitus", + "API Username": "API-käyttäjänimi", + "API Key": "API-avain", + "Show update if available": "Näytä päivitys, jos saatavilla", + "Also check beta release": "Tarkista myös betaversio", + "Using a Reverse Proxy?": "Käytätkö käänteistä välityspalvelinta?", + "The slug is already taken. Please choose another slug.": "Slug on jo otettu. Ole hyvä ja valitse toinen slug.", + "RadiusSecret": "Radius Secret", + "RadiusCalledStationId": "Kutsuttu aseman tunnus", + "Steam Game Server": "Steam pelipalvelin", + "Most likely causes:": "todennäköisimmät syyt:", + "The resource is no longer available.": "Resurssi ei ole enää saatavilla.", + "There might be a typing error in the address.": "Osoitteessa saattaa olla kirjoitusvirhe.", + "What you can try:": "Mitä voit kokeilla:", + "Retype the address.": "Kirjoita osoite uudelleen.", + "Go back to the previous page.": "Palaa edelliselle sivulle.", + "Coming Soon": "Tulossa pian", + "Connection String": "Yhteysmerkkijono", + "Query": "Tiedustelu", + "settingsCertificateExpiry": "TLS-sertifikaatin vanheneminen", + "certificationExpiryDescription": "HTTPS-seuraimet käynnistävät ilmoituksen, kun TLS-varmenne vanhenee:", + "Setup Docker Host": "Asenna Docker-isäntä", + "Connection Type": "Yhteystyyppi", + "tcp": "TCP / HTTP", + "Docker Container": "Docker-kontti", + "Container Name / ID": "Säilön nimi/tunnus", + "Docker Host": "Docker-isäntä", + "Docker Hosts": "Docker-isännät", + "Domain": "Verkkotunnus", + "Workstation": "Työasema", + "socket": "kanta", + "Packet Size": "Paketin koko", + "telegram": "Telegram", + "ZohoCliq": "ZohoCliq", + "Bot Token": "Botti tokeni", + "wayToGetTelegramToken": "Voit saada tunnuksen osoitteesta {0}.", + "Chat ID": "Chat-tunnus", + "wayToGetTelegramChatID": "Saat chat-tunnuksesi lähettämällä viestin botille ja siirtymällä tähän URL-osoitteeseen nähdäksesi chat_id:", + "YOUR BOT TOKEN HERE": "BOT TOKENISI TÄHÄN", + "chatIDNotFound": "Chat ID:tä ei löydy; lähetä ensin viesti tälle botille", + "disableCloudflaredNoAuthMsg": "Olet No Auth -tilassa, salasanaa ei tarvita.", + "trustProxyDescription": "Luota \"X-Forwarded-*\"-otsikoihin. Jos haluat saada oikean asiakas-IP:n ja Uptime Kumasi on välityspalvelimen, kuten Nginx tai Apache, takana, sinun tulee ottaa tämä käyttöön.", + "wayToGetLineNotifyToken": "Voit saada käyttötunnuksen osoitteesta {0}", + "Examples": "Esimerkkejä", + "Home Assistant URL": "Home Assistantin URL-osoite", + "Long-Lived Access Token": "Pitkäikäinen pääsytunnus", + "Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Pitkäikäinen pääsytunnus voidaan luoda napsauttamalla profiilisi nimeä (vasemmalla alareunassa) ja vierittämällä alas ja napsauttamalla sitten Luo tunnus. ", + "Notification Service": "Ilmoituspalvelu", + "default: notify all devices": "oletus: Ilmoita kaikille laitteille", + "A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Luettelo ilmoituspalveluista löytyy Home Assistantin kohdasta \"Kehittäjätyökalut > Palvelut\". Hae hakusanalla \"ilmoitus\" löytääksesi laitteesi/puhelimesi nimen.", + "Automations can optionally be triggered in Home Assistant:": "Automaatiot voidaan vaihtoehtoisesti laukaista Home Assistantissa:", + "Trigger type:": "Triggerin tyyppi:", + "Event type:": "Tapahtumatyyppi:", + "Frontend Version": "Käyttöliittymän versio", + "Frontend Version do not match backend version!": "Käyttöliittymän versio ei vastaa taustaversiota!", + "backupRecommend": "Varmuuskopioi asema tai tietokansio (./data/) suoraan sen sijaan.", + "Optional": "Vapaaehtoinen", + "squadcast": "Squadcast", + "or": "tai", + "recurringInterval": "Aikaväli", + "Recurring": "Toistuva", + "strategyManual": "Aktiivinen/ei-aktiivinen manuaalisesti", + "warningTimezone": "Se käyttää palvelimen aikavyöhykettä", + "weekdayShortMon": "Ma", + "weekdayShortTue": "Ti", + "weekdayShortWed": "Ke", + "weekdayShortThu": "To", + "weekdayShortFri": "Pe", + "weekdayShortSat": "La", + "weekdayShortSun": "Su", + "dayOfWeek": "Viikonpäivä", + "dayOfMonth": "Kuukauden päivä", + "lastDay": "Viimeinen päivä", + "lastDay1": "Kuukauden viimeinen päivä", + "lastDay2": "Kuukauden toiseksi viimeinen päivä", + "lastDay3": "Kuukauden 3. viimeinen päivä", + "No Maintenance": "Ei huoltoa", + "pauseMaintenanceMsg": "Haluatko varmasti keskeyttää?", + "maintenanceStatus-under-maintenance": "Huollossa", + "maintenanceStatus-inactive": "Epäaktiivinen", + "maintenanceStatus-scheduled": "Aikataulutettu", + "maintenanceStatus-ended": "Päättyi", + "maintenanceStatus-unknown": "Tuntematon", + "Display Timezone": "Näytä aikavyöhyke", + "Server Timezone": "Palvelimen aikavyöhyke", + "statusPageMaintenanceEndDate": "Loppu", + "Enable": "Ota käyttöön", + "Disable": "Poista käytöstä", + "Single Maintenance Window": "Yksi huoltoikkuna", + "Maintenance Time Window of a Day": "Päivän huoltoaikaikkuna", + "Effective Date Range": "Voimassa oleva ajanjakso", + "Schedule Maintenance": "Ajoita huolto", + "Date and Time": "Päivämäärä ja aika", + "DateTime Range": "Päivämäärä-aika-alue", + "loadingError": "Tietoja ei voi noutaa, yritä myöhemmin uudelleen.", + "plugin": "Lisäosa | Lisäosat", + "install": "Asenna", + "installing": "Asennetaan", + "uninstall": "Poista asennus", + "uninstalling": "Poistetaan asennusta", + "smtp": "Sähköposti (SMTP)", + "secureOptionNone": "Ei mitään / STARTTLS (25 587)", + "secureOptionTLS": "TLS (465)", + "Ignore TLS Error": "Ohita TLS-virhe", + "From Email": "Sähköpostista", + "emailCustomSubject": "Mukautettu aihe", + "To Email": "Sähköpostiin", + "smtpCC": "CC", + "smtpBCC": "BCC", + "Discord Webhook URL": "Discord Webhookin URL-osoite", + "Bot Display Name": "Botin näyttönimi", + "Prefix Custom Message": "Mukautetun viestin etuliite", + "Hello @everyone is...": "Hei {'@'}kaikki ovat…", + "wayToGetTeamsURL": "Voit oppia luomaan webhookin URL-osoitteen {0}.", + "wayToGetZohoCliqURL": "Voit oppia luomaan webhookin URL-osoitteen {0}.", + "wayToCheckSignalURL": "Voit tarkistaa tämän URL-osoitteen nähdäksesi, kuinka se määritetään:", + "Number": "Numero", + "Recipients": "Vastaanottajat", + "Access Token": "Käyttöoikeustunnus", + "Channel access token": "Kanavan käyttöoikeustunnus", + "Basic Settings": "Perus asetukset", + "User ID": "käyttäjätunnus", + "Messaging API": "Viestintä API", + "Line Developers Console": "Line Developers Console", + "lineDevConsoleTo": "Line Developers Console - {0}", + "dataRetentionTimeError": "Säilytysajan on oltava 0 tai suurempi", + "infiniteRetention": "Aseta arvoon 0, jos haluat loputtoman säilytyksen.", + "confirmDeleteTagMsg": "Haluatko varmasti poistaa tämän tunnisteen? Tähän tunnisteeseen liittyviä näyttöjä ei poisteta.", + "enableGRPCTls": "Salli lähettää gRPC-pyyntö TLS-yhteydellä", + "grpcMethodDescription": "Menetelmän nimi muunnetaan cammelCase-muotoon, kuten sayHello, check jne.", + "acceptedStatusCodesDescription": "Valitse tilakoodit, jotka katsotaan onnistuneeksi vastaukseksi.", + "deleteMonitorMsg": "Haluatko varmasti poistaa tämän seuraimen?", + "deleteMaintenanceMsg": "Haluatko varmasti poistaa tämän huollon?", + "deleteNotificationMsg": "Haluatko varmasti poistaa tämän ilmoituksen kaikista seuraimista?", + "dnsPortDescription": "DNS-palvelimen portti. Oletusarvo on 53. Voit vaihtaa porttia milloin tahansa.", + "rrtypeDescription": "Valitse valvottava RR-tyyppi", + "pauseMonitorMsg": "Haluatko varmasti keskeyttää?", + "clearHeartbeatsMsg": "Haluatko varmasti poistaa kaikki tämän seuraimen sydämenlyönnit?", + "confirmImportMsg": "Haluatko varmasti tuoda varmuuskopion? Varmista, että olet valinnut oikean tuontivaihtoehdon.", + "twoFAVerifyLabel": "Anna tunnuksesi vahvistaaksesi 2FA:", + "tokenValidSettingsMsg": "Token on voimassa! Voit nyt tallentaa 2FA-asetukset.", + "confirmEnableTwoFAMsg": "Haluatko varmasti ottaa 2FA:n käyttöön?", + "confirmDisableTwoFAMsg": "Haluatko varmasti poistaa 2FA:n käytöstä?", + "recurringIntervalMessage": "Juokse kerran päivässä | Suorita kerran {0} päivässä", + "affectedMonitorsDescription": "Valitse seuraimet, joihin nykyinen huolto vaikuttaa", + "affectedStatusPages": "Näytä tämä huoltoviesti valituilla tilasivuilla", + "atLeastOneMonitor": "Valitse vähintään yksi seurain, johon vaikuttaa", + "notificationDescription": "Ilmoitukset on määritettävä seuraimelle toimiakseen.", + "keywordDescription": "Hae avainsanaa tavallisessa HTML- tai JSON-vastauksessa. Haussa kirjainkoolla on merkitystä.", + "backupDescription": "Voit varmuuskopioida kaikki näytöt ja ilmoitukset JSON-tiedostoon.", + "backupDescription2": "Huomaa: historia- ja tapahtumatiedot eivät sisälly.", + "octopushLogin": "\"Kirjaudu\" ohjauspaneelin HTTP API -tunnistetiedoista", + "promosmsLogin": "API-kirjautumisnimi", + "promosmsPassword": "API-salasana", + "pushoversounds pushover": "Työnnä yli (oletus)", + "pushoversounds bike": "Pyörä", + "pushoversounds bugle": "Merkkitorvi", + "pushoversounds cashregister": "Kassakone", + "pushoversounds classical": "Klassinen", + "pushoversounds cosmic": "Kosminen", + "pushoversounds falling": "Putoaminen", + "pushoversounds gamelan": "Gamelan", + "pushoversounds incoming": "Saapuva", + "pushoversounds intermission": "Väliaika", + "pushoversounds magic": "Taika", + "pushoversounds mechanical": "Mekaaninen", + "pushoversounds pianobar": "Piano Baari", + "pushoversounds spacealarm": "Avaruushälytys", + "pushoversounds tugboat": "Hinaaja", + "pushoversounds alien": "Avaruusolio hälytys (pitkä)", + "pushoversounds climb": "Kiipeily (pitkä)", + "pushoversounds persistent": "Pysyvä (pitkä)", + "pushoversounds updown": "Ylös Alas (pitkä)", + "pushoversounds vibrate": "Vain värinä", + "pushoversounds none": "Ei mitään (hiljainen)", + "pushyAPIKey": "Salainen API-avain", + "pushyToken": "Laitteen tunnus", + "discord": "Discord", + "teams": "Microsoft Teams", + "signal": "Signal", + "gotify": "Gotify", + "slack": "Slack", + "rocket.chat": "Rocket.Chat", + "pushy": "Päällekäyvä", + "PushByTechulus": "Techuluksen työntö", + "octopush": "Mustekala", + "promosms": "PromoSMS", + "clicksendsms": "ClickSend SMS", + "lunasea": "LunaSea", + "GoogleChat": "Google Chat (vain Google Workspace)", + "Kook": "Kook", + "wayToGetKookGuildID": "Ota 'Kehittäjätila' käyttöön Kook-asetuksissa ja napsauta kiltaa hiiren kakkospainikkeella saadaksesi sen tunnuksen", + "Guild ID": "Killan tunnus", + "line": "Line Messenger", + "mattermost": "Mattermost", + "User Key": "Käyttäjäavain", + "Device": "Laite", + "Message Title": "Viestin otsikko", + "More info on:": "Lisätietoja: {0}", + "pushoverDesc2": "Jos haluat lähettää ilmoituksia eri laitteille, täytä Laite-kenttä.", + "SMS Type": "SMS-tyyppi", + "octopushTypePremium": "Premium (nopea - suositellaan hälytykseen)", + "octopushTypeLowCost": "Alhaiset kustannukset (hidas - joskus operaattori estää)", + "checkPrice": "Tarkista kohteen {0} hinnat:", + "apiCredentials": "API-tunnistetiedot", + "Check octopush prices": "Tarkista octopush hinnat {0}.", + "octopushPhoneNumber": "Puhelinnumero (Intl-muoto, esim.: +33612345678) ", + "octopushSMSSender": "Tekstiviestin lähettäjän nimi: 3-11 aakkosnumeerista merkkiä ja välilyönti (a-zA-Z0-9)", + "LunaSea Device ID": "LunaSea laitetunnus", + "Apprise URL": "Apprise URL-osoite", + "Example:": "Esimerkki: {0}", + "Read more:": "Lue lisää: {0}", + "Status:": "Tila: {0}", + "Strategy": "strategia", + "Free Mobile User Identifier": "Ilmainen mobiilikäyttäjätunnus", + "Enable TLS": "Ota TLS käyttöön", + "Proto Service Name": "Proto-palvelun nimi", + "Proto Method": "Proto-menetelmä", + "Proto Content": "Proto-sisältö", + "Economy": "Talous", + "Lowcost": "Halpa", + "high": "korkea", + "SendKey": "LähetäAvain", + "SMSManager API Docs": "SMSManager API Dokumentointi ", + "Gateway Type": "Yhdyskäytävän tyyppi", + "SMSManager": "SMSManager", + "Base URL": "Perus-URL-osoite", + "goAlertIntegrationKeyInfo": "Hanki yleinen API-integrointiavain palvelulle tässä muodossa \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" yleensä kopioidun URL-osoitteen tunnusparametrin arvona.", + "goAlert": "GoAlert", + "AccessKeyId": "Pääsyn avaimen tunnus", + "SecretAccessKey": "Pääsyn avaimen salaisuus", + "PhoneNumbers": "PuhelinNumerot", + "TemplateCode": "Mallikoodi", + "SignName": "AllekirjoitusNimi", + "Sms template must contain parameters: ": "Tekstiviestimallin tulee sisältää parametrit: ", + "Bark Group": "Bark ryhmä", + "Bark Sound": "Bark ääni", + "WebHookUrl": "WebHookUrl-osoite", + "SecretKey": "Salainen avain", + "For safety, must use secret key": "Turvallisuuden vuoksi on käytettävä salaista avainta", + "Device Token": "Laitteen tunnus", + "Platform": "Alusta", + "iOS": "iOS", + "Bark Endpoint": "Bark päätepiste", + "Huawei": "Huawei", + "High": "Korkea", + "Topic": "Aihe", + "WeCom Bot Key": "WeCom-bottiavain", + "Setup Proxy": "Asenna välityspalvelin", + "Proxy Protocol": "Välityspalvelinprotokolla", + "Proxy Server": "Välityspalvelin", + "matrix": "Matriisi", + "promosmsTypeFlash": "SMS FLASH - Viesti näkyy automaattisesti vastaanottajan laitteessa. Rajoitettu vain puolalaisille vastaanottajille.", + "promosmsTypeSpeed": "SMS SPEED - Järjestelmän korkein prioriteetti. Erittäin nopea ja luotettava, mutta kallis (noin kaksi kertaa SMS TÄYSI hinta).", + "promosmsPhoneNumber": "Puhelinnumero (puolalaiselle vastaanottajalle voit ohittaa suuntanumerot)", + "promosmsSMSSender": "Tekstiviestin lähettäjän nimi: Esirekisteröity nimi tai jokin oletusasetuksista: InfoSMS, SMS Info, MaxSMS, INFO, SMS", + "promosmsAllowLongSMS": "Salli pitkät tekstiviestit", + "Feishu WebHookUrl": "Feishu WebHookURL-osoite", + "Internal Room Id": "Huoneen sisäinen tunnus", + "Android": "Android", + "Channel Name": "Kanavan nimi", + "Uptime Kuma URL": "Uptime Kuma URL-osoite", + "Icon Emoji": "Ikoni Emoji", + "signalImportant": "TÄRKEÄÄ: Et voi sekoittaa ryhmiä ja numeroita vastaanottajissa!", + "aboutWebhooks": "Lisätietoja Webhooksista osoitteessa: {0}", + "aboutChannelName": "Kirjoita kanavan nimi {0} Kanavan nimi -kenttään, jos haluat ohittaa Webhook-kanavan. Esimerkki: #muu-kanava", + "aboutKumaURL": "Jos jätät Uptime Kuma URL -kentän tyhjäksi, se on oletuksena Project GitHub -sivu.", + "smtpDkimSettings": "DKIM-asetukset", + "smtpDkimDesc": "Katso Nodemailer DKIM {0} -sovelluksen käytöstä.", + "documentation": "dokumentointi", + "smtpDkimDomain": "Verkkotunnus nimi", + "smtpDkimKeySelector": "Näppäinvalitsin", + "smtpDkimPrivateKey": "Yksityinen avain", + "smtpDkimHashAlgo": "Hash-algoritmi (valinnainen)", + "smtpDkimheaderFieldNames": "Allekirjoitettavat otsikkoavaimet (valinnainen)", + "smtpDkimskipFields": "Otsikkonäppäimet, joita ei allekirjoiteta (valinnainen)", + "Integration Key": "Integrointiavain", + "Integration URL": "Integroinnin URL-osoite", + "Auto resolve or acknowledged": "Automaattinen ratkaisu tai kuittaus", + "do nothing": "Älä tee mitään", + "auto acknowledged": "automaattisesti kuitattu", + "auto resolve": "automaattinen ratkaisu", + "gorush": "Gorush", + "alerta": "Alerta", + "alertaApiEndpoint": "API-päätepiste", + "alertaEnvironment": "Ympäristö", + "alertaApiKey": "API-avain", + "alertaRecoverState": "Palautustila", + "serwersms": "SerwerSMS.pl", + "serwersmsAPIUser": "API-käyttäjänimi (sis. webapi_-etuliite)", + "serwersmsAPIPassword": "API-salasana", + "serwersmsPhoneNumber": "Puhelinnumero", + "smseagle": "SMSEagle", + "smseagleTo": "Puhelinnumero(t)", + "smseagleGroup": "Puhelinmuistioryhmän nimi/nimet", + "smseagleContact": "Puhelinmuistion yhteyshenkilön nimet", + "smseagleRecipientType": "Vastaanottajan tyyppi", + "smseagleRecipient": "Vastaanottaja(t) (jos useita, ne on erotettava pilkulla)", + "smseagleToken": "API-käyttöoikeustunnus", + "smseagleUrl": "SMSEagle-laitteesi URL-osoite", + "smseagleEncoding": "Lähetä Unicodena", + "smseaglePriority": "Viestin prioriteetti (0-9, oletus = 0)", + "stackfield": "Stackfield", + "Recipient Number": "Vastaanottajan numero", + "From Name/Number": "Nimestä/numerosta", + "Leave blank to use a shared sender number.": "Jätä tyhjäksi, jos haluat käyttää jaettua lähettäjän numeroa.", + "Octopush API Version": "Octopush API -versio", + "Legacy Octopush-DM": "Legacy Octopush-DM", + "ntfy Topic": "ntfy aihe", + "HomeAssistant": "Home Assistant", + "onebotHttpAddress": "OneBot HTTP-osoite", + "onebotGroupMessage": "Ryhmä", + "onebotPrivateMessage": "Yksityinen", + "onebotUserOrGroupId": "Ryhmä/käyttäjätunnus", + "onebotSafetyTips": "Käyttöoikeustunnus on asetettava turvallisuuden vuoksi", + "PushDeer Key": "PushDeer avain", + "wayToGetClickSendSMSToken": "Voit saada API-käyttäjänimen ja API-avaimen osoitteesta {0}.", + "Custom Monitor Type": "Mukautettu seurain tyyppi", + "Google Analytics ID": "Google Analytics -tunnus", + "Edit Tag": "Muokkaa tunnistetta", + "Server Address": "Palvelimen osoite", + "Learn More": "Lisätietoja", + "Inactive": "Epäaktiivinen", + "Add New below or Select...": "Lisää uusi alapuolella tai valitse…", + "Blue": "Sininen", + "Avg. Response": "Kesk.arv. vastaus", + "All Systems Operational": "Kaikki järjestelmät toiminnassa", + "defaultNotificationName": "Minun {ilmoitus} Hälytys ({numero})", + "webhookFormDataDesc": "{multipart} on hyvä PHP:lle. JSON pitää parsia {decodeFunction} avulla", + "HeadersInvalidFormat": "Pyynnön otsikot eivät ole kelvollisia JSON-tiedostoja: ", + "clearDataOlderThan": "Säilytä seuraimen historiatiedot {0} päivää.", + "steamApiKeyDescription": "Steam peli palveliment valvontaa varten tarvitset Steam Web-API -avaimen. Voit rekisteröidä API-avaimesi täällä: ", + "light": "Vaalea", + "Switch to Light Theme": "Vaihda vaaleaan teemaan", + "Powered by": "Voimanlähteenä", + "shrinkDatabaseDescription": "Käynnistä tietokanta VACUUM SQLitelle. Jos tietokanta on luotu 1.10.0:n jälkeen, AUTO_VACUUM on jo käytössä eikä tätä toimintoa tarvita.", + "Accept characters:": "Hyväksy merkit:", + "New Status Page": "Uusi tilasivu", + "wayToGetCloudflaredURL": "(Lataa cloudflared osoitteesta {0})", + "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Nykyinen yhteys saattaa katketa, jos muodostat parhaillaan yhteyttä Cloudflare-tunnelin kautta. Haluatko varmasti lopettaa sen? Vahvista se kirjoittamalla nykyinen salasanasi.", + "RadiusCallingStationIdDescription": "Kutsu laitteen tunniste", + "Check how to config it for WebSocket": "Tarkista, kuinka se määritetään WebSocketille", + "Docker Daemon": "Docker taustatoiminta", + "deleteDockerHostMsg": "Haluatko varmasti poistaa tämän docker-isännän kaikista seuraimista?", + "supportTelegramChatID": "Tukee suoraa chattia / ryhmää / kanavan chat-tunnusta", + "Event data:": "Tapahtumatyyppi:", + "Then choose an action, for example switch the scene to where an RGB light is red.": "Valitse sitten toiminto, esimerkiksi vaihda kohtaus sellaiseen, jossa RGB-valo on punainen.", + "backupOutdatedWarning": "Vanhentunut: Koska monia ominaisuuksia lisättiin ja tätä varmuuskopiointitoimintoa ei ole ylläpidetty, se ei voi luoda tai palauttaa täydellistä varmuuskopiota.", + "lastDay4": "Kuukauden 4. viimeinen päivä", + "IconUrl": "Kuvakkeen URL-osoite", + "Enable DNS Cache": "Ota DNS-välimuisti käyttöön", + "dnsCacheDescription": "Se ei ehkä toimi joissakin IPv6-ympäristöissä, poista se käytöstä, jos kohtaat ongelmia.", + "confirmUninstallPlugin": "Haluatko varmasti poistaa tämän laajennuksen?", + "wayToGetDiscordURL": "Saat tämän siirtymällä kohtaan Palvelinasetukset -> Integraatiot -> Näytä Webhookit -> Uusi Webhook", + "needSignalAPI": "Sinulla on oltava signaaliasiakas, jossa on REST API.", + "wayToGetLineChannelToken": "Avaa ensin {0}, luo palveluntarjoaja ja kanava (Viestintä API), sitten saat kanavan käyttö tokenin ja käyttäjätunnuksen yllä mainituista valikon kohdista.", + "Icon URL": "Kuvakkeen URL-osoite", + "aboutIconURL": "Voit ohittaa oletusprofiilikuvan antamalla linkin kuvaan kohdassa \"kuvakeen URL\". Ei käytetä, jos kuvake emoji on asetettu.", + "aboutMattermostChannelName": "Voit ohittaa oletuskanavan, jolle Webhook lähettää viestejä, kirjoittamalla kanavan nimen Kanavan nimi -kenttään. Tämä on otettava käyttöön Mattermost Webhook -asetuksissa. Esimerkki: #muu-kanava", + "resolverserverDescription": "Cloudflare on oletuspalvelin. Voit vaihtaa ratkaisijapalvelinta milloin tahansa.", + "enableDefaultNotificationDescription": "Tämä ilmoitus on oletuksena käytössä uusissa seuraimissa. Voit silti poistaa ilmoituksen käytöstä erikseen jokaiselta seuraimelta.", + "clearEventsMsg": "Haluatko varmasti poistaa kaikki tämän seuraimen tapahtumat?", + "confirmClearStatisticsMsg": "Haluatko varmasti poistaa KAIKKI tilastot?", + "importHandleDescription": "Valitse \"Ohita olemassa oleva\", jos haluat ohittaa jokaisen samannimisen seuraimen tai ilmoituksen. \"Korvaa\" poistaa kaikki olemassa olevat seuraimet ja ilmoitukset.", + "passwordNotMatchMsg": "Toistettu salasana ei täsmää.", + "backupDescription3": "Arkaluonteiset tiedot, kuten ilmoitustunnukset, sisältyvät vientitiedostoon. säilytä vienti turvallisesti.", + "endpoint": "päätepiste", + "octopushAPIKey": "\"API-avain\" ohjauspaneelin HTTP API -tunnistetiedoista", + "pushoversounds siren": "Sireeni", + "pushoversounds echo": "Ylityöntö kaiku (pitkä)", + "pushover": "Ylityöntö", + "apprise": "Apprise (tukee yli 50 ilmoituspalvelua)", + "pushbullet": "Pushbullet", + "wayToGetKookBotToken": "Luo sovellus ja hanki bot-tunnus osoitteessa {0}", + "Notification Sound": "Ilmoitusääni", + "pushoverDesc1": "Hätäprioriteetilla (2) on oletusarvoisesti 30 sekunnin aikakatkaisu uudelleenyritysten välillä, ja se vanhenee 1 tunnin kuluttua.", + "octopushLegacyHint": "Käytätkö Octopushin (2011-2020) vanhaa versiota vai uutta versiota?", + "Free Mobile API Key": "Ilmainen mobiilisovellusliittymäavain", + "You can divide numbers with": "Voit jakaa numerot", + "goAlertInfo": "GoAlert on avoimen lähdekoodin sovellus päivystykseen, automatisoituihin eskalaatioihin ja ilmoituksiin (kuten tekstiviestit tai äänipuhelut). Ota automaattisesti mukaan oikea henkilö, oikealla tavalla ja oikeaan aikaan! {0}", + "Retry": "Yritä uudelleen", + "Proxy server has authentication": "Välityspalvelimella on todennus", + "promosmsTypeEco": "SMS ECO - halpa mutta hidas ja usein ylikuormitettu. Rajoitettu vain puolalaisille vastaanottajille.", + "promosmsTypeFull": "SMS FULL - Premium-tason tekstiviestit, voit käyttää lähettäjän nimeäsi (sinun on rekisteröitävä nimi ensin). Luotettava hälytyksiä varten.", + "matrixHomeserverURL": "Kotipalvelimen URL-osoite (http(s):// ja valinnaisesti portti)", + "matrixDesc1": "Löydät sisäisen huonetunnuksen katsomalla Matrix-asiakasohjelman huoneasetusten lisäosaa. Sen pitäisi näyttää tältä: !QMdRCpUIfLwsfjxye6:home.server.", + "matrixDesc2": "On erittäin suositeltavaa, että luot uuden käyttäjän etkä käytä omaa Matrix-käyttäjätunnustasi, koska se antaa täyden pääsyn tilillesi ja kaikkiin huoneisiin, joihin liityit. Luo sen sijaan uusi käyttäjä ja kutsu se vain siihen huoneeseen, josta haluat saada ilmoituksen. Saat käyttöoikeustunnuksen suorittamalla {0}", + "wayToGetPagerDutyKey": "Saat tämän siirtymällä kohtaan Palvelu -> Palveluhakemisto -> (Valitse palvelu) -> Integraatiot -> Lisää integraatio. Täältä voit etsiä \"Events API V2\". Lisätietoja {0}", + "alertaAlertState": "Varoitustila", + "serwersmsSenderName": "Tekstiviestin lähettäjän nimi (rekisteröity asiakasportaalin kautta)", + "onebotMessageType": "OneBot-viestityyppi" } From 11b45dd274faca74fcd134402746ddace58e29a8 Mon Sep 17 00:00:00 2001 From: AmadeusGraves Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 105/163] Translated using Weblate (Spanish) Currently translated at 97.7% (681 of 697 strings) Co-authored-by: AmadeusGraves Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/es-ES.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 5adca7b7..429582de 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -534,7 +534,7 @@ "smtpCC": "CC", "smtpBCC": "CCO", "Discord Webhook URL": "URL Webhook de Discord", - "wayToGetDiscordURL": "Puede obtener esto yendo a Configuración del servidor -> Integraciones -> Crear webhook", + "wayToGetDiscordURL": "Puede obtener esto yendo a Configuración del servidor -> Integraciones -> Ver Webhooks -> Crear Webhook", "Bot Display Name": "Nombre para mostrar del Bot", "Hello @everyone is...": "Hola {'@'}todos están…", "wayToGetTeamsURL": "Puedes aprender cómo crear una URL webhook {0}.", @@ -591,7 +591,7 @@ "Kook": "Kook", "wayToGetKookBotToken": "Crea aplicación y obtén tu token de bot en {0}", "wayToGetKookGuildID": "Activa 'Modo Desarrollador' en los ajustes de Kook, y haz click derecho en la unión para obtener su ID", - "Guild ID": "", + "Guild ID": "ID de Gremio", "User Key": "Key de Usuario", "octopushTypePremium": "Premium (Rápido - recomendado para alertas)", "octopushTypeLowCost": "Bajo Coste (Lento - algunas veces bloqueado por operador)", @@ -661,7 +661,7 @@ "SMSManager": "SMSManager", "goAlertInfo": "GoAlert es una aplicación de código abierto para la programación de guardias, escaladas automatizadas y notificaciones (como SMS o llamadas de voz). ¡Involucre automáticamente a la persona adecuada, de la manera correcta y en el momento adecuado! {0}", "Free Mobile API Key": "Clave API de Free Mobile", - "high": "arriba", + "high": "alto", "SMSManager API Docs": "Documentación API de SMSManager ", "smseagleContact": "Nombre(s) de contacto de Guía Telefónica", "smseagleToken": "Token de Acceso a la API", @@ -685,5 +685,8 @@ "Server Address": "Dirección del Servidor", "Learn More": "Aprende Más", "Pick a RR-Type...": "Seleccione un Tipo RR", - "onebotHttpAddress": "Dirección HTTP OneBot" + "onebotHttpAddress": "Dirección HTTP OneBot", + "SendKey": "SendKey", + "octopushAPIKey": "\"Clave API\" de las credenciales HTTP API en el panel de control", + "octopushLogin": "\"Inicio de Sesión\" a partir de las credenciales API HTTP en el panel de control" } From 32ddff4e643706200559850d1441354652fa5fce Mon Sep 17 00:00:00 2001 From: MrEddX Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 106/163] Translated using Weblate (Bulgarian) Currently translated at 100.0% (697 of 697 strings) Co-authored-by: MrEddX Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/bg/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/bg-BG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/bg-BG.json b/src/lang/bg-BG.json index 66ce48fe..eb6ec700 100644 --- a/src/lang/bg-BG.json +++ b/src/lang/bg-BG.json @@ -676,7 +676,7 @@ "wayToGetKookGuildID": "Превключете в 'Developer Mode' в 'Kook' настройките, след което десен клик върху 'guild' за да вземете неговото 'ID'", "Guild ID": "Guild ID", "Help": "Помощ", - "Game": "игрови", + "Game": "Игра", "Custom": "Потребителски", "infiniteRetention": "Задайте стойност 0 за безкрайно съхранение.", "Monitor": "Монитор | Монитори", From 609a61e6007c49a4d20bc46dcb7ea05652b75e44 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 107/163] Translated using Weblate (Czech) Currently translated at 100.0% (697 of 697 strings) Co-authored-by: Michal Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/cs-CZ.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/cs-CZ.json b/src/lang/cs-CZ.json index 18a3d333..afcb6f1b 100644 --- a/src/lang/cs-CZ.json +++ b/src/lang/cs-CZ.json @@ -674,7 +674,7 @@ "Proto Content": "Proto obsah", "Economy": "Úsporná", "Lowcost": "Nízkonákladová", - "high": "high", + "high": "vysoká", "General Monitor Type": "Obecný typ dohledu", "Passive Monitor Type": "Pasivní typ dohledu", "Specific Monitor Type": "Konkrétní typ dohledu", From 1877b90b3ac592fbd7a5b7fbfb4876451aad504b Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 14 Feb 2023 19:08:52 +0000 Subject: [PATCH 108/163] Translated using Weblate (Finnish) Currently translated at 100.0% (697 of 697 strings) Co-authored-by: Louis Lam Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fi/ Translation: Uptime Kuma/Uptime Kuma --- src/lang/fi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/fi.json b/src/lang/fi.json index 32454526..5a7ce409 100644 --- a/src/lang/fi.json +++ b/src/lang/fi.json @@ -105,7 +105,7 @@ "disableauth.message1": "Oletko varma että haluat poistaa todennuksen käytöstä?", "Please use this option carefully!": "Käytä tätä vaihtoehtoa varoen!", "Remember me": "Muista minut", - "languageName": "Englanti", + "languageName": "Suomi", "Primary Base URL": "Ensisijainen perus-URL-osoite", "pushOptionalParams": "Valinnaiset parametrit: {0}", "Not available, please setup.": "Ei saatavilla, määritä ensin.", From e04a8203dcf8c1cf905f56e0ad834343e152c7ed Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 15 Feb 2023 03:20:40 +0800 Subject: [PATCH 109/163] Fix npm issue for deploy script --- extra/beta/update-version.js | 3 ++- extra/update-version.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/extra/beta/update-version.js b/extra/beta/update-version.js index 7abac5ef..3dafbe8d 100644 --- a/extra/beta/update-version.js +++ b/extra/beta/update-version.js @@ -22,7 +22,8 @@ if (! exists) { fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); // Also update package-lock.json - childProcess.spawnSync("npm", [ "install" ]); + const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; + childProcess.spawnSync(npm, [ "install" ]); commit(version); tag(version); diff --git a/extra/update-version.js b/extra/update-version.js index 246e1c1c..8d78f17d 100644 --- a/extra/update-version.js +++ b/extra/update-version.js @@ -26,7 +26,8 @@ if (! exists) { fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); // Also update package-lock.json - childProcess.spawnSync("npm", [ "install" ]); + const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; + childProcess.spawnSync(npm, [ "install" ]); commit(newVersion); tag(newVersion); From fdc3b2d57a9037c4415d18fe01762e7960a7fd43 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 15 Feb 2023 03:23:59 +0800 Subject: [PATCH 110/163] Update to 1.20.1 --- package-lock.json | 6 +++--- package.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index baf9f4f2..5e57a932 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.20.0-beta.0", + "version": "1.20.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.20.0-beta.0", + "version": "1.20.1", "license": "MIT", "dependencies": { "@grpc/grpc-js": "~1.7.3", @@ -55,7 +55,7 @@ "prom-client": "~13.2.0", "prometheus-api-metrics": "~3.2.1", "protobufjs": "~7.1.1", - "qs": "~6.10.0", + "qs": "~6.10.4", "redbean-node": "~0.2.0", "redis": "~4.5.1", "socket.io": "~4.5.3", diff --git a/package.json b/package.json index 5d4652d6..a3f6066b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.20.0", + "version": "1.20.1", "license": "MIT", "repository": { "type": "git", @@ -39,7 +39,7 @@ "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --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.20.0 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.20.1 && 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", From ee2eb5109b7784a253e8955bfd31e0548dd71d3a Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Tue, 14 Feb 2023 19:49:04 +0000 Subject: [PATCH 111/163] Added basic web interface for API keys Web interfaces for manging API keys have been added however translation keys are still required. Signed-off-by: Matthew Nickson --- db/patch-api-key-table.sql | 2 +- server/client.js | 26 ++ server/model/api_key.js | 75 ++++++ server/server.js | 5 +- .../socket-handlers/api-key-socket-handler.js | 144 ++++++++++ src/icon.js | 2 + src/layouts/Layout.vue | 6 + src/mixins/socket.js | 36 ++- src/pages/AddAPIKey.vue | 198 ++++++++++++++ src/pages/ManageAPIKeys.vue | 255 ++++++++++++++++++ src/pages/Settings.vue | 3 + src/router.js | 10 + 12 files changed, 759 insertions(+), 3 deletions(-) create mode 100644 server/model/api_key.js create mode 100644 server/socket-handlers/api-key-socket-handler.js create mode 100644 src/pages/AddAPIKey.vue create mode 100644 src/pages/ManageAPIKeys.vue diff --git a/db/patch-api-key-table.sql b/db/patch-api-key-table.sql index 151b6918..fc3a405b 100644 --- a/db/patch-api-key-table.sql +++ b/db/patch-api-key-table.sql @@ -10,4 +10,4 @@ CREATE TABLE [api_key] ( [expires] DATETIME DEFAULT NULL, CONSTRAINT FK_user FOREIGN KEY ([user_id]) REFERENCES [user]([id]) ON DELETE CASCADE ON UPDATE CASCADE ); -COMMIT; \ No newline at end of file +COMMIT; diff --git a/server/client.js b/server/client.js index ef96c7f4..3efbe8fd 100644 --- a/server/client.js +++ b/server/client.js @@ -113,6 +113,31 @@ async function sendProxyList(socket) { return list; } +/** + * Emit API key list to client + * @param {Socket} socket Socket.io socket instance + * @returns {Promise} + */ +async function sendAPIKeyList(socket) { + const timeLogger = new TimeLogger(); + + let result = []; + const list = await R.find( + "api_key", + "user_id=?", + [ socket.userID ], + ); + + for (let bean of list) { + result.push(bean.toPublicJSON()); + } + + io.to(socket.userID).emit("apiKeyList", result); + timeLogger.print("Sent API Key List"); + + return list; +} + /** * Emits the version information to the client. * @param {Socket} socket Socket.io socket instance @@ -157,6 +182,7 @@ module.exports = { sendImportantHeartbeatList, sendHeartbeatList, sendProxyList, + sendAPIKeyList, sendInfo, sendDockerHostList }; diff --git a/server/model/api_key.js b/server/model/api_key.js new file mode 100644 index 00000000..777519b9 --- /dev/null +++ b/server/model/api_key.js @@ -0,0 +1,75 @@ +const { BeanModel } = require("redbean-node/dist/bean-model"); +const { R } = require("redbean-node"); + +class APIKey extends BeanModel { + /** + * Get the current status of this API key + */ + getStatus() { + let expired = false; + if (expired) { + return "expired"; + } else if (this.active) { + return "active"; + } else if (!this.active) { + return "inactive"; + } + } + + /** + * Returns an object that ready to parse to JSON + * @returns {Object} + */ + toJSON() { + return { + id: this.id, + key: this.key, + name: this.name, + userID: this.user_id, + createdDate: this.created_date, + active: this.active, + expires: this.expires, + status: this.getStatus(), + }; + } + + /** + * Returns an object that ready to parse to JSON with sensitive fields + * removed + * @returns {Object} + */ + toPublicJSON() { + return { + id: this.id, + name: this.name, + userID: this.user_id, + createdDate: this.created_date, + active: this.active, + expires: this.expires, + status: this.getStatus(), + }; + } + + /** + * Create a new API Key and store it in the database + * @param {Object} key Object sent by client + * @param {int} userID ID of socket user + * @returns {Promise} + */ + static async save(key, userID) { + let bean; + bean = R.dispense("api_key"); + + bean.key = key.key; + bean.name = key.name; + bean.user_id = userID; + bean.active = key.active; + bean.expires = key.expires; + + await R.store(bean); + + return bean; + } +} + +module.exports = APIKey; diff --git a/server/server.js b/server/server.js index 5473cecd..a13fe466 100644 --- a/server/server.js +++ b/server/server.js @@ -126,7 +126,7 @@ if (config.demoMode) { } // Must be after io instantiation -const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList, sendDockerHostList } = require("./client"); +const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList } = require("./client"); const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler"); const databaseSocketHandler = require("./socket-handlers/database-socket-handler"); const TwoFA = require("./2fa"); @@ -135,6 +135,7 @@ const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudfl const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler"); const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler"); const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler"); +const { apiKeySocketHandler } = require("./socket-handlers/api-key-socket-handler"); const { generalSocketHandler } = require("./socket-handlers/general-socket-handler"); const { Settings } = require("./settings"); const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent"); @@ -1490,6 +1491,7 @@ let needSetup = false; proxySocketHandler(socket); dockerSocketHandler(socket); maintenanceSocketHandler(socket); + apiKeySocketHandler(socket); generalSocketHandler(socket, server); log.debug("server", "added all socket handlers"); @@ -1597,6 +1599,7 @@ async function afterLogin(socket, user) { sendNotificationList(socket); sendProxyList(socket); sendDockerHostList(socket); + sendAPIKeyList(socket); await sleep(500); diff --git a/server/socket-handlers/api-key-socket-handler.js b/server/socket-handlers/api-key-socket-handler.js new file mode 100644 index 00000000..a80dca83 --- /dev/null +++ b/server/socket-handlers/api-key-socket-handler.js @@ -0,0 +1,144 @@ +const { checkLogin } = require("../util-server"); +const { log } = require("../../src/util"); +const { R } = require("redbean-node"); +const crypto = require("crypto"); +const passwordHash = require("../password-hash"); +const apicache = require("../modules/apicache"); +const APIKey = require("../model/api_key"); +const { sendAPIKeyList } = require("../client"); + +/** + * Handlers for Maintenance + * @param {Socket} socket Socket.io instance + */ +module.exports.apiKeySocketHandler = (socket) => { + // Add a new api key + socket.on("addAPIKey", async (key, callback) => { + try { + checkLogin(socket); + let clearKey = crypto.randomUUID(); + let hashedKey = passwordHash.generate(clearKey); + key["key"] = hashedKey; + let bean = await APIKey.save(key, socket.userID); + + log.debug("apikeys", "Added API Key"); + log.debug("apikeys", key); + + // Append key ID to start of key seperated by -, used to get + // correct hash when validating key. + let formattedKey = bean.id + "-" + clearKey; + await sendAPIKeyList(socket); + + callback({ + ok: true, + msg: "Added Successfully.", + key: formattedKey, + keyID: bean.id, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("getAPIKeyList", async (callback) => { + try { + checkLogin(socket); + await sendAPIKeyList(socket); + callback({ + ok: true, + }); + } catch (e) { + console.error(e); + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("deleteAPIKey", async (keyID, callback) => { + try { + checkLogin(socket); + + log.debug("apikeys", `Deleted API Key: ${keyID} User ID: ${socket.userID}`); + + await R.exec("DELETE FROM api_key WHERE id = ? AND user_id = ? ", [ + keyID, + socket.userID, + ]); + + apicache.clear(); + + callback({ + ok: true, + msg: "Deleted Successfully.", + }); + + await sendAPIKeyList(socket); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("disableAPIKey", async (keyID, callback) => { + try { + checkLogin(socket); + + log.debug("apikeys", `Disabled Key: ${keyID} User ID: ${socket.userID}`); + + await R.exec("UPDATE api_key SET active = 0 WHERE id = ? ", [ + keyID, + ]); + + apicache.clear(); + + callback({ + ok: true, + msg: "Disabled Successfully.", + }); + + await sendAPIKeyList(socket); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("enableAPIKey", async (keyID, callback) => { + try { + checkLogin(socket); + + log.debug("apikeys", `Enabled Key: ${keyID} User ID: ${socket.userID}`); + + await R.exec("UPDATE api_key SET active = 1 WHERE id = ? ", [ + keyID, + ]); + + apicache.clear(); + + callback({ + ok: true, + msg: "Enabled Successfully", + }); + + await sendAPIKeyList(socket); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); +}; diff --git a/src/icon.js b/src/icon.js index b38bef3c..fd2d1b7f 100644 --- a/src/icon.js +++ b/src/icon.js @@ -44,6 +44,7 @@ import { faWrench, faHeartbeat, faFilter, + faKey, } from "@fortawesome/free-solid-svg-icons"; library.add( @@ -88,6 +89,7 @@ library.add( faWrench, faHeartbeat, faFilter, + faKey, ); export { FontAwesomeIcon }; diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index d8e96aa8..dfc540fa 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -57,6 +57,12 @@ +
  • + + {{ $t("API Keys") }} + +
  • +
  • {{ $t("Settings") }} diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 378af06a..114fd647 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -34,7 +34,8 @@ export default { allowLoginDialog: false, // Allowed to show login dialog, but "loggedIn" have to be true too. This exists because prevent the login dialog show 0.1s in first before the socket server auth-ed. loggedIn: false, monitorList: { }, - maintenanceList: { }, + maintenanceList: {}, + apiKeyList: {}, heartbeatList: { }, importantHeartbeatList: { }, avgPingList: { }, @@ -134,6 +135,10 @@ export default { this.maintenanceList = data; }); + socket.on("apiKeyList", (data) => { + this.apiKeyList = data; + }); + socket.on("notificationList", (data) => { this.notificationList = data; }); @@ -461,6 +466,17 @@ export default { socket.emit("getMaintenanceList", callback); }, + /** + * Send list of API keys + * @param {socketCB} callback + */ + getAPIKeyList(callback) { + if (!callback) { + callback = () => { }; + } + socket.emit("getAPIKeyList", callback); + }, + /** * Add a monitor * @param {Object} monitor Object representing monitor to add @@ -503,6 +519,24 @@ export default { socket.emit("deleteMaintenance", maintenanceID, callback); }, + /** + * Add an API key + * @param {Object} key API key to add + * @param {socketCB} callback + */ + addAPIKey(key, callback) { + socket.emit("addAPIKey", key, callback); + }, + + /** + * Delete specified API key + * @param {int} keyID ID of key to delete + * @param {socketCB} callback + */ + deleteAPIKey(keyID, callback) { + socket.emit("deleteAPIKey", keyID, callback); + }, + /** Clear the hearbeat list */ clearData() { console.log("reset heartbeat list"); diff --git a/src/pages/AddAPIKey.vue b/src/pages/AddAPIKey.vue new file mode 100644 index 00000000..9633f007 --- /dev/null +++ b/src/pages/AddAPIKey.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/pages/ManageAPIKeys.vue b/src/pages/ManageAPIKeys.vue new file mode 100644 index 00000000..9203e276 --- /dev/null +++ b/src/pages/ManageAPIKeys.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index 87404968..9251085d 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -7,6 +7,9 @@ {{ $t("Maintenance") }} + + {{ $t("API Keys") }} +
  • diff --git a/src/router.js b/src/router.js index 38048826..50b394c9 100644 --- a/src/router.js +++ b/src/router.js @@ -18,6 +18,8 @@ import NotFound from "./pages/NotFound.vue"; import DockerHosts from "./components/settings/Docker.vue"; import MaintenanceDetails from "./pages/MaintenanceDetails.vue"; import ManageMaintenance from "./pages/ManageMaintenance.vue"; +import ManageAPIKeys from "./pages/ManageAPIKeys.vue"; +import AddAPIKey from "./pages/AddAPIKey.vue"; // Settings - Sub Pages import Appearance from "./components/settings/Appearance.vue"; @@ -145,6 +147,14 @@ const routes = [ path: "/maintenance/edit/:id", component: EditMaintenance, }, + { + path: "/apikeys", + component: ManageAPIKeys + }, + { + path: "/apikeys/add", + component: AddAPIKey + }, ], }, ], From 05443f9bb7972970a8c3aa1c23d0ce36f50bf11f Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Tue, 14 Feb 2023 22:16:41 +0000 Subject: [PATCH 112/163] Added language keys Signed-off-by: Matthew Nickson --- src/lang/en.json | 16 +++++++++++++++- src/pages/AddAPIKey.vue | 2 +- src/pages/ManageAPIKeys.vue | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 15edee93..15b77cb4 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -695,5 +695,19 @@ "Google Analytics ID": "Google Analytics ID", "Edit Tag": "Edit Tag", "Server Address": "Server Address", - "Learn More": "Learn More" + "Learn More": "Learn More", + "API Keys": "API Keys", + "Expiry": "Expiry", + "Expiry date": "Expiry date", + "Don't expire": "Don't expire", + "Continue": "Continue", + "Add Another": "Add Another", + "Key Added": "Key Added", + "apiKeyAddedMsg": "Your API key has been added. Please make a note of it as it will not be shown again.", + "Add API Key": "Add API Key", + "No API Keys": "No API Keys", + "apiKey-active": "Active", + "apiKey-expired": "Expired", + "apiKey-inactive": "Inactive", + "Expires": "Expires" } diff --git a/src/pages/AddAPIKey.vue b/src/pages/AddAPIKey.vue index 9633f007..a2e4434e 100644 --- a/src/pages/AddAPIKey.vue +++ b/src/pages/AddAPIKey.vue @@ -8,7 +8,7 @@
    - + From cd796898d037babde1ea062d72a6054d0eb518c3 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Tue, 14 Feb 2023 22:41:06 +0000 Subject: [PATCH 113/163] Added expiry check for frontend Signed-off-by: Matthew Nickson --- server/model/api_key.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/model/api_key.js b/server/model/api_key.js index 777519b9..4f786cd2 100644 --- a/server/model/api_key.js +++ b/server/model/api_key.js @@ -1,19 +1,19 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { R } = require("redbean-node"); +const dayjs = require("dayjs"); class APIKey extends BeanModel { /** * Get the current status of this API key */ getStatus() { - let expired = false; - if (expired) { + let current = dayjs(); + let expiry = dayjs(this.expires); + if (expiry.diff(current) < 0) { return "expired"; - } else if (this.active) { - return "active"; - } else if (!this.active) { - return "inactive"; } + + return this.active ? "active" : "inactive"; } /** From e7feca1cd661b2215e4c08aadd7985f988b145b8 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Wed, 15 Feb 2023 00:39:29 +0000 Subject: [PATCH 114/163] Added API key authentication handler API key authentication is now possible by making use of the X-API-Key header. API authentication will only be enabled when a user adds their first API key, up until this point, they can still use their username and password to authenticate with API endpoints. After the user adds their first API key, they may only use API keys in future to authenticate with the API. In this commit, the prometheus /metrics endpoint has been changed over to the new authentication system. Signed-off-by: Matthew Nickson --- server/auth.js | 61 +++++++++++++++++++ server/server.js | 4 +- .../socket-handlers/api-key-socket-handler.js | 5 ++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/server/auth.js b/server/auth.js index fd19b0e4..f2c59d66 100644 --- a/server/auth.js +++ b/server/auth.js @@ -3,6 +3,8 @@ const passwordHash = require("./password-hash"); const { R } = require("redbean-node"); const { setting } = require("./util-server"); const { loginRateLimiter } = require("./rate-limiter"); +const { Settings } = require("./settings"); +const dayjs = require("dayjs"); /** * Login to web app @@ -33,6 +35,32 @@ exports.login = async function (username, password) { return null; }; +/** + * Validate a provided API key + * @param {string} key API Key passed by client + * @returns {Promise} + */ +async function validateAPIKey(key) { + if (typeof key !== "string") { + return false; + } + + let index = key.substring(0, key.indexOf("-")); + let clear = key.substring(key.indexOf("-") + 1, key.length); + console.log(index); + console.log(clear); + + let hash = await R.findOne("api_key", " id=? ", [ index ]); + + let current = dayjs(); + let expiry = dayjs(hash.expires); + if (expiry.diff(current) < 0, !hash.active) { + return false; + } + + return hash && passwordHash.verify(clear, hash.key); +} + /** * Callback for myAuthorizer * @callback myAuthorizerCB @@ -84,3 +112,36 @@ exports.basicAuth = async function (req, res, next) { next(); } }; + +/** + * Use X-API-Key header if API keys enabled, else use basic auth + * @param {express.Request} req Express request object + * @param {express.Response} res Express response object + * @param {express.NextFunction} next + */ +exports.apiAuth = async function (req, res, next) { + if (!await Settings.get("disableAuth")) { + let usingAPIKeys = await Settings.get("apiKeysEnabled"); + + loginRateLimiter.pass(null, 0).then((pass) => { + if (usingAPIKeys) { + let pwd = req.get("X-API-Key"); + if (pwd !== null && pwd !== undefined) { + validateAPIKey(pwd).then((valid) => { + if (valid) { + next(); + } else { + res.status(401).send(); + } + }); + } else { + res.status(401).send(); + } + } else { + exports.basicAuth(req, res, next); + } + }); + } else { + next(); + } +}; diff --git a/server/server.js b/server/server.js index 183a5bb3..8696aa70 100644 --- a/server/server.js +++ b/server/server.js @@ -87,7 +87,7 @@ log.debug("server", "Importing Background Jobs"); const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs"); const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter"); -const { basicAuth } = require("./auth"); +const { apiAuth } = require("./auth"); const { login } = require("./auth"); const passwordHash = require("./password-hash"); @@ -230,7 +230,7 @@ let needSetup = false; // Prometheus API metrics /metrics // With Basic Auth using the first user's username/password - app.get("/metrics", basicAuth, prometheusAPIMetrics()); + app.get("/metrics", apiAuth, prometheusAPIMetrics()); app.use("/", expressStaticGzip("dist", { enableBrotli: true, diff --git a/server/socket-handlers/api-key-socket-handler.js b/server/socket-handlers/api-key-socket-handler.js index a80dca83..cf124cad 100644 --- a/server/socket-handlers/api-key-socket-handler.js +++ b/server/socket-handlers/api-key-socket-handler.js @@ -5,6 +5,7 @@ const crypto = require("crypto"); const passwordHash = require("../password-hash"); const apicache = require("../modules/apicache"); const APIKey = require("../model/api_key"); +const { Settings } = require("../settings"); const { sendAPIKeyList } = require("../client"); /** @@ -29,6 +30,10 @@ module.exports.apiKeySocketHandler = (socket) => { let formattedKey = bean.id + "-" + clearKey; await sendAPIKeyList(socket); + // Enable API auth if the user creates a key, otherwise only basic + // auth will be used for API. + await Settings.set("apiKeysEnabled", true); + callback({ ok: true, msg: "Added Successfully.", From d553c4c4f75801c53c93236cd253dd9415c03cac Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Wed, 15 Feb 2023 00:53:42 +0000 Subject: [PATCH 115/163] Added missing translation keys Signed-off-by: Matthew Nickson --- src/lang/en.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lang/en.json b/src/lang/en.json index 15b77cb4..2dc487cb 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -709,5 +709,7 @@ "apiKey-active": "Active", "apiKey-expired": "Expired", "apiKey-inactive": "Inactive", - "Expires": "Expires" + "Expires": "Expires", + "disableAPIKeyMsg": "Are you sure you want to disable this API key?", + "deleteAPIKeyMsg": "Are you sure you want to delete this API key?" } From 01c71a0242a87e4957ee1c793be5c01c60f1f23d Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Wed, 15 Feb 2023 11:15:15 +0000 Subject: [PATCH 116/163] Fixed logic errors, removed dev leftovers Fixed a logic error where a comma was used instead of an or, also removed leftover console.logs from testing. Date picker is now dissabled when don't expire is checked. Signed-off-by: Matthew Nickson --- server/auth.js | 8 +++++--- src/lang/en.json | 3 ++- src/pages/AddAPIKey.vue | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/server/auth.js b/server/auth.js index f2c59d66..99084d16 100644 --- a/server/auth.js +++ b/server/auth.js @@ -47,14 +47,16 @@ async function validateAPIKey(key) { let index = key.substring(0, key.indexOf("-")); let clear = key.substring(key.indexOf("-") + 1, key.length); - console.log(index); - console.log(clear); let hash = await R.findOne("api_key", " id=? ", [ index ]); + if (hash === null) { + return false; + } + let current = dayjs(); let expiry = dayjs(hash.expires); - if (expiry.diff(current) < 0, !hash.active) { + if (expiry.diff(current) < 0 || !hash.active) { return false; } diff --git a/src/lang/en.json b/src/lang/en.json index 2dc487cb..007d8072 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -711,5 +711,6 @@ "apiKey-inactive": "Inactive", "Expires": "Expires", "disableAPIKeyMsg": "Are you sure you want to disable this API key?", - "deleteAPIKeyMsg": "Are you sure you want to delete this API key?" + "deleteAPIKeyMsg": "Are you sure you want to delete this API key?", + "Generate": "Generate" } diff --git a/src/pages/AddAPIKey.vue b/src/pages/AddAPIKey.vue index a2e4434e..e6b60233 100644 --- a/src/pages/AddAPIKey.vue +++ b/src/pages/AddAPIKey.vue @@ -28,6 +28,7 @@ format="yyyy-MM-dd HH:mm" modelType="yyyy-MM-dd HH:mm:ss" :required="!noExpire" + :disabled="noExpire" />
    @@ -46,7 +47,7 @@ id="monitor-submit-btn" class="btn btn-primary" type="submit" :disabled="processing" > - {{ $t("Save") }} + {{ $t("Generate") }}
    From dd1d71530fc3eeb435e669b2c31cc6daf7710a38 Mon Sep 17 00:00:00 2001 From: Luke Hamburg <1992842+luckman212@users.noreply.github.com> Date: Wed, 15 Feb 2023 14:06:29 -0500 Subject: [PATCH 117/163] sorted tags on dashboard see https://github.com/louislam/uptime-kuma/issues/2785 --- server/model/monitor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 4cbb56e1..bbeb0da0 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -143,7 +143,7 @@ class Monitor extends BeanModel { * @returns {Promise[]>} */ async getTags() { - return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [ this.id ]); + return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ? ORDER BY tag.name", [ this.id ]); } /** From 1d4af39820540371094a9fe2c9a7a857a7632eb1 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Wed, 15 Feb 2023 19:31:22 +0000 Subject: [PATCH 118/163] Fixed JSDoc for one method Signed-off-by: Matthew Nickson --- server/model/api_key.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/model/api_key.js b/server/model/api_key.js index 4f786cd2..1b27a60f 100644 --- a/server/model/api_key.js +++ b/server/model/api_key.js @@ -5,6 +5,7 @@ const dayjs = require("dayjs"); class APIKey extends BeanModel { /** * Get the current status of this API key + * @returns {string} active, inactive or expired */ getStatus() { let current = dayjs(); From b8720b46c3d0e0332e85a00a177a1001423a6f40 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Wed, 15 Feb 2023 21:53:49 +0000 Subject: [PATCH 119/163] Switched to using Authorization header Prometheus doesn't support using custom headers for exporters, however it does support using the Authorisation header with basic auth. As such, we switched from using X-API-Key to Authorization with the basic scheme and an empty username field. Also added a rate limit for API endpoints of 60 requests in a minute Signed-off-by: Matthew Nickson --- server/auth.js | 73 ++++++++++++++++++++++++++---------------- server/rate-limiter.js | 8 +++++ 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/server/auth.js b/server/auth.js index 99084d16..eddae4c3 100644 --- a/server/auth.js +++ b/server/auth.js @@ -2,7 +2,7 @@ const basicAuth = require("express-basic-auth"); const passwordHash = require("./password-hash"); const { R } = require("redbean-node"); const { setting } = require("./util-server"); -const { loginRateLimiter } = require("./rate-limiter"); +const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter"); const { Settings } = require("./settings"); const dayjs = require("dayjs"); @@ -37,10 +37,9 @@ exports.login = async function (username, password) { /** * Validate a provided API key - * @param {string} key API Key passed by client - * @returns {Promise} + * @param {string} key API key to verify */ -async function validateAPIKey(key) { +async function verifyAPIKey(key) { if (typeof key !== "string") { return false; } @@ -64,8 +63,8 @@ async function validateAPIKey(key) { } /** - * Callback for myAuthorizer - * @callback myAuthorizerCB + * Callback for basic auth authorizers + * @callback authCallback * @param {any} err Any error encountered * @param {boolean} authorized Is the client authorized? */ @@ -74,9 +73,31 @@ async function validateAPIKey(key) { * Custom authorizer for express-basic-auth * @param {string} username * @param {string} password - * @param {myAuthorizerCB} callback + * @param {authCallback} callback */ -function myAuthorizer(username, password, callback) { +function apiAuthorizer(username, password, callback) { + // API Rate Limit + apiRateLimiter.pass(null, 0).then((pass) => { + if (pass) { + verifyAPIKey(password).then((valid) => { + callback(null, valid); + // Only allow a set number of api requests per minute + // (currently set to 60) + apiRateLimiter.removeTokens(1); + }); + } else { + callback(null, false); + } + }); +} + +/** + * Custom authorizer for express-basic-auth + * @param {string} username + * @param {string} password + * @param {authCallback} callback + */ +function userAuthorizer(username, password, callback) { // Login Rate Limit loginRateLimiter.pass(null, 0).then((pass) => { if (pass) { @@ -101,7 +122,7 @@ function myAuthorizer(username, password, callback) { */ exports.basicAuth = async function (req, res, next) { const middleware = basicAuth({ - authorizer: myAuthorizer, + authorizer: userAuthorizer, authorizeAsync: true, challenge: true, }); @@ -124,25 +145,21 @@ exports.basicAuth = async function (req, res, next) { exports.apiAuth = async function (req, res, next) { if (!await Settings.get("disableAuth")) { let usingAPIKeys = await Settings.get("apiKeysEnabled"); - - loginRateLimiter.pass(null, 0).then((pass) => { - if (usingAPIKeys) { - let pwd = req.get("X-API-Key"); - if (pwd !== null && pwd !== undefined) { - validateAPIKey(pwd).then((valid) => { - if (valid) { - next(); - } else { - res.status(401).send(); - } - }); - } else { - res.status(401).send(); - } - } else { - exports.basicAuth(req, res, next); - } - }); + let middleware; + if (usingAPIKeys) { + middleware = basicAuth({ + authorizer: apiAuthorizer, + authorizeAsync: true, + challenge: true, + }); + } else { + middleware = basicAuth({ + authorizer: userAuthorizer, + authorizeAsync: true, + challenge: true, + }); + } + middleware(req, res, next); } else { next(); } diff --git a/server/rate-limiter.js b/server/rate-limiter.js index 6f185beb..ec77f1a4 100644 --- a/server/rate-limiter.js +++ b/server/rate-limiter.js @@ -54,6 +54,13 @@ const loginRateLimiter = new KumaRateLimiter({ errorMessage: "Too frequently, try again later." }); +const apiRateLimiter = new KumaRateLimiter({ + tokensPerInterval: 60, + interval: "minute", + fireImmediately: true, + errorMessage: "Too frequently, try again later." +}); + const twoFaRateLimiter = new KumaRateLimiter({ tokensPerInterval: 30, interval: "minute", @@ -63,5 +70,6 @@ const twoFaRateLimiter = new KumaRateLimiter({ module.exports = { loginRateLimiter, + apiRateLimiter, twoFaRateLimiter, }; From c9b4a7f53ef502a5ea58faf970102b6098d0b479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Gen=C3=A7?= Date: Fri, 17 Feb 2023 17:59:43 +0300 Subject: [PATCH 120/163] Change column type --- db/patch-http-body-encoding.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/patch-http-body-encoding.sql b/db/patch-http-body-encoding.sql index de02bede..fa75ae90 100644 --- a/db/patch-http-body-encoding.sql +++ b/db/patch-http-body-encoding.sql @@ -1,6 +1,6 @@ -- You should not modify if this have pushed to Github, unless it does serious wrong with the db. BEGIN TRANSACTION; -ALTER TABLE [monitor] ADD http_body_encoding TEXT; +ALTER TABLE [monitor] ADD http_body_encoding VARCHAR(25); COMMIT; From 9e3a77f4194b43bc617fb22756521f99b05e1aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Gen=C3=A7?= Date: Sat, 18 Feb 2023 17:02:56 +0300 Subject: [PATCH 121/163] Customize body placeholder for body encoding --- src/pages/EditMonitor.vue | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index d36693bb..2d28195e 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -732,6 +732,15 @@ message HealthCheckResponse { ` ]); }, bodyPlaceholder() { + if (this.monitor && this.monitor.httpBodyEncoding && this.monitor.httpBodyEncoding === "xml") { + return this.$t("Example:", [ ` + + + + Kuma + +` ]); + } return this.$t("Example:", [ ` { "key": "value" From 3ab0faee91d5aae3b0d0dc6a54b8ceeafa64e337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Gen=C3=A7?= Date: Sat, 18 Feb 2023 22:18:48 +0300 Subject: [PATCH 122/163] Add update query for old monitors and save new data correctly --- db/patch-http-body-encoding.sql | 8 +++++++- src/pages/EditMonitor.vue | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/db/patch-http-body-encoding.sql b/db/patch-http-body-encoding.sql index fa75ae90..322c8b89 100644 --- a/db/patch-http-body-encoding.sql +++ b/db/patch-http-body-encoding.sql @@ -1,6 +1,12 @@ -- You should not modify if this have pushed to Github, unless it does serious wrong with the db. BEGIN TRANSACTION; -ALTER TABLE [monitor] ADD http_body_encoding VARCHAR(25); +ALTER TABLE monitor ADD http_body_encoding VARCHAR(25); + +COMMIT; + +BEGIN TRANSACTION; + +UPDATE monitor SET http_body_encoding = 'json' WHERE (type = 'http' or type = 'keyword') AND http_body_encoding IS NULL; COMMIT; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 2d28195e..dbf44d73 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -952,6 +952,7 @@ message HealthCheckResponse { * @returns {void} */ async submit() { + this.processing = true; if (!this.isInputValid()) { @@ -964,6 +965,10 @@ message HealthCheckResponse { this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4); } + if (this.monitor.type && this.monitor.type !== "http" && this.monitor.type !== "keyword") { + this.monitor.httpBodyEncoding = null; + } + if (this.monitor.headers) { this.monitor.headers = JSON.stringify(JSON.parse(this.monitor.headers), null, 4); } From cf32b8bc64b866c9d050aa1747a123bc78d81e9e Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 20 Feb 2023 20:26:53 +0800 Subject: [PATCH 123/163] Read latest version from website and add `Run when system starts` --- extra/exe-builder/.gitignore | 1 + extra/exe-builder/App.config | 26 ++++- extra/exe-builder/DownloadForm.cs | 14 ++- extra/exe-builder/FodyWeavers.xml | 3 + extra/exe-builder/FodyWeavers.xsd | 141 ++++++++++++++++++++++++++++ extra/exe-builder/Program.cs | 28 +++++- extra/exe-builder/UptimeKuma.csproj | 104 +++++++++++++++++++- extra/exe-builder/Version.cs | 9 ++ extra/exe-builder/packages.config | 52 ++++++++++ 9 files changed, 373 insertions(+), 5 deletions(-) create mode 100644 extra/exe-builder/.gitignore create mode 100644 extra/exe-builder/FodyWeavers.xml create mode 100644 extra/exe-builder/FodyWeavers.xsd create mode 100644 extra/exe-builder/Version.cs create mode 100644 extra/exe-builder/packages.config diff --git a/extra/exe-builder/.gitignore b/extra/exe-builder/.gitignore new file mode 100644 index 00000000..d52874b6 --- /dev/null +++ b/extra/exe-builder/.gitignore @@ -0,0 +1 @@ +packages/ diff --git a/extra/exe-builder/App.config b/extra/exe-builder/App.config index 09265d8b..e1ab3695 100644 --- a/extra/exe-builder/App.config +++ b/extra/exe-builder/App.config @@ -1,4 +1,4 @@ - + @@ -7,4 +7,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs index f16af422..aed8bd29 100644 --- a/extra/exe-builder/DownloadForm.cs +++ b/extra/exe-builder/DownloadForm.cs @@ -8,6 +8,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using Newtonsoft.Json; namespace UptimeKuma { public partial class DownloadForm : Form { @@ -23,9 +24,18 @@ namespace UptimeKuma { webClient.DownloadProgressChanged += DownloadProgressChanged; webClient.DownloadFileCompleted += DownloadFileCompleted; + label.Text = "Reading latest version..."; + + // Read json from https://uptime.kuma.pet/version + var versionObj = JsonConvert.DeserializeObject(new WebClient().DownloadString("https://uptime.kuma.pet/version")); + + + var nodeVersion = versionObj.nodejs; + var uptimeKumaVersion = versionObj.latest; + if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { - URL = "https://nodejs.org/dist/v16.17.1/node-v16.17.1-win-x64.zip", + URL = $"https://nodejs.org/dist/v{nodeVersion}/node-v{nodeVersion}-win-x64.zip", Filename = "node.zip", TargetFolder = "node" }); @@ -33,7 +43,7 @@ namespace UptimeKuma { if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { - URL = "https://github.com/louislam/uptime-kuma/archive/refs/tags/1.18.3.zip", + URL = $"https://github.com/louislam/uptime-kuma/archive/refs/tags/{uptimeKumaVersion}.zip", Filename = "core.zip", TargetFolder = "core" }); diff --git a/extra/exe-builder/FodyWeavers.xml b/extra/exe-builder/FodyWeavers.xml new file mode 100644 index 00000000..f1dea8fc --- /dev/null +++ b/extra/exe-builder/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/extra/exe-builder/FodyWeavers.xsd b/extra/exe-builder/FodyWeavers.xsd new file mode 100644 index 00000000..ff119f71 --- /dev/null +++ b/extra/exe-builder/FodyWeavers.xsd @@ -0,0 +1,141 @@ + + + + + + + + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with line breaks. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with line breaks. + + + + + The order of preloaded assemblies, delimited with line breaks. + + + + + + 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. + + + + + Controls if .pdbs for reference assemblies are also embedded. + + + + + Controls if runtime assemblies are also embedded. + + + + + Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. + + + + + Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. + + + + + As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. + + + + + 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. + + + + + 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. + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with |. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with |. + + + + + The order of preloaded assemblies, delimited with |. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 82c76b05..27e345b7 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; +using Microsoft.Win32; using UptimeKuma.Properties; namespace UptimeKuma { @@ -25,17 +26,27 @@ namespace UptimeKuma { public class UptimeKumaApplicationContext : ApplicationContext { + const string appName = "Uptime Kuma"; + private NotifyIcon trayIcon; private Process process; + private MenuItem runWhenStarts; + + private RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); + public UptimeKumaApplicationContext() { trayIcon = new NotifyIcon(); + runWhenStarts = new MenuItem("Run when system starts", RunWhenStarts); + runWhenStarts.Checked = registryKey.GetValue(appName) != null; + trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { new("Open", Open), //new("Debug Console", DebugConsole), + runWhenStarts, new("Check for Update...", CheckForUpdate), new("Visit GitHub...", VisitGitHub), new("About", About), @@ -59,6 +70,21 @@ namespace UptimeKuma { 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", @@ -103,7 +129,7 @@ namespace UptimeKuma { void About(object sender, EventArgs e) { - MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info"); + MessageBox.Show("Uptime Kuma Windows Runtime v1.0.0" + Environment.NewLine + "© 2023 Louis Lam", "Info"); } void Exit(object sender, EventArgs e) diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index 2ad857b2..3f55649e 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -1,5 +1,6 @@  + Debug @@ -38,18 +39,104 @@ COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\" + + packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll + + + packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + + + + packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll + + + packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll + + + + packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll + + + packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll + + + packages\System.IO.4.3.0\lib\net462\System.IO.dll + + + packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + + + packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + packages\System.Linq.4.3.0\lib\net463\System.Linq.dll + + + packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll + + + packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll + + + packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + + + + packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + + + packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + + + packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll + + + packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll + + + packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + + + packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + + + packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll + - + + packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + @@ -60,6 +147,7 @@ + DownloadForm.cs @@ -75,6 +163,7 @@ favicon.ico + SettingsSingleFileGenerator Settings.Designer.cs @@ -88,5 +177,18 @@ + + + + + + + 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}. + + + + + + \ No newline at end of file diff --git a/extra/exe-builder/Version.cs b/extra/exe-builder/Version.cs new file mode 100644 index 00000000..896c7a24 --- /dev/null +++ b/extra/exe-builder/Version.cs @@ -0,0 +1,9 @@ +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; } + } +} diff --git a/extra/exe-builder/packages.config b/extra/exe-builder/packages.config new file mode 100644 index 00000000..82dd7c3b --- /dev/null +++ b/extra/exe-builder/packages.config @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 372c6b90783ec3fdc7ac0ebbd7a58f6a0de2ecdd Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Mon, 20 Feb 2023 16:09:17 +0100 Subject: [PATCH 124/163] add markdown support for description --- src/pages/StatusPage.vue | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index dcee15e7..f4a6eb76 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -20,6 +20,9 @@
    +
    + {{ $t("markdownSupported") }} +
    @@ -258,7 +261,9 @@ {{ $t("Description") }}: - + + +
    @@ -500,6 +505,10 @@ export default { return DOMPurify.sanitize(marked(this.incident.content)); }, + descriptionHTML() { + return DOMPurify.sanitize(marked(this.config.description)); + }, + footerHTML() { return DOMPurify.sanitize(marked(this.config.footerText)); }, From 4642f9be0af92e6e8cde9a67b8c460541b665ffa Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 20 Feb 2023 23:15:49 +0800 Subject: [PATCH 125/163] Fix download-dist.js do not exit sometimes --- extra/download-dist.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extra/download-dist.js b/extra/download-dist.js index b04beec7..a854ca8b 100644 --- a/extra/download-dist.js +++ b/extra/download-dist.js @@ -47,6 +47,7 @@ function download(url) { }); } console.log("Done"); + process.exit(0); }); tarStream.on("error", () => { From 22902139d2885fea7ee3d985f6755835f5f095a8 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 21 Feb 2023 02:50:25 +0800 Subject: [PATCH 126/163] Implement update --- extra/exe-builder/DownloadForm.cs | 39 ++++++++++++++++++++++-------- extra/exe-builder/Program.cs | 40 +++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs index aed8bd29..28a57c52 100644 --- a/extra/exe-builder/DownloadForm.cs +++ b/extra/exe-builder/DownloadForm.cs @@ -27,11 +27,12 @@ namespace UptimeKuma { label.Text = "Reading latest version..."; // Read json from https://uptime.kuma.pet/version - var versionObj = JsonConvert.DeserializeObject(new WebClient().DownloadString("https://uptime.kuma.pet/version")); - + var versionJson = new WebClient().DownloadString("https://uptime.kuma.pet/version"); + var versionObj = JsonConvert.DeserializeObject(versionJson); var nodeVersion = versionObj.nodejs; var uptimeKumaVersion = versionObj.latest; + var hasUpdateFile = File.Exists("update"); if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { @@ -41,12 +42,30 @@ namespace UptimeKuma { }); } - if (!Directory.Exists("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(); @@ -75,9 +94,12 @@ namespace UptimeKuma { 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 = "run setup", + Arguments = $"/k \"{cmd}\"", RedirectStandardOutput = false, RedirectStandardError = false, RedirectStandardInput = true, @@ -89,11 +111,11 @@ namespace UptimeKuma { var process = new Process(); process.StartInfo = startInfo; process.EnableRaisingEvents = true; - process.Exited += (object _, EventArgs e) => { + process.Exited += (_, e) => { progressBar.Value = 100; if (process.ExitCode == 0) { - Task.Delay(2000).ContinueWith((task) => { + Task.Delay(2000).ContinueWith(_ => { Application.Restart(); }); label.Text = "Done"; @@ -105,10 +127,7 @@ namespace UptimeKuma { process.Start(); label.Text = "Installing dependencies and download dist files"; progressBar.Value = 50; - - process.StandardInput.WriteLine("\"../node/npm\" ci --production"); - process.StandardInput.WriteLine("\"../node/npm\" run download-dist"); - process.StandardInput.WriteLine("exit"); + process.WaitForExit(); } void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 27e345b7..1385e830 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -4,11 +4,13 @@ using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; +using System.Net; using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.Win32; +using Newtonsoft.Json; using UptimeKuma.Properties; namespace UptimeKuma { @@ -56,7 +58,9 @@ namespace UptimeKuma { trayIcon.MouseDoubleClick += new MouseEventHandler(Open); trayIcon.Visible = true; - if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules") && Directory.Exists("core/dist")) { + 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 { @@ -110,6 +114,10 @@ namespace UptimeKuma { } } + void StopProcess() { + process?.Kill(); + } + void Open(object sender, EventArgs e) { Process.Start("http://localhost:3001"); } @@ -119,7 +127,35 @@ namespace UptimeKuma { } void CheckForUpdate(object sender, EventArgs e) { - Process.Start("https://github.com/louislam/uptime-kuma/releases"); + var needUpdate = false; + + // Check version.json exists + if (File.Exists("version.json")) { + // Load version.json and compare with the latest version from GitHub + var currentVersionObj = JsonConvert.DeserializeObject(File.ReadAllText("version.json")); + + var versionJson = new WebClient().DownloadString("https://uptime.kuma.pet/version"); + var latestVersionObj = JsonConvert.DeserializeObject(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) From 0e38391454f012c6f0e87ab0e768b6aa15ab0ee8 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 21 Feb 2023 03:11:29 +0800 Subject: [PATCH 127/163] Fix dpi issue using app.manifest --- extra/exe-builder/App.config | 5 +- extra/exe-builder/UptimeKuma.csproj | 390 ++++++++++++++-------------- extra/exe-builder/app.manifest | 28 ++ 3 files changed, 226 insertions(+), 197 deletions(-) create mode 100644 extra/exe-builder/app.manifest diff --git a/extra/exe-builder/App.config b/extra/exe-builder/App.config index e1ab3695..2514085c 100644 --- a/extra/exe-builder/App.config +++ b/extra/exe-builder/App.config @@ -1,12 +1,9 @@  - + - - - diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index 3f55649e..6b7534af 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -1,194 +1,198 @@ - - - - - - Debug - AnyCPU - {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04} - WinExe - UptimeKuma - uptime-kuma - v4.7.2 - 512 - true - true - ..\..\public\favicon.ico - 9 - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\" - - - - packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll - - - packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll - - - - packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll - - - - packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll - - - - packages\System.Console.4.3.0\lib\net46\System.Console.dll - - - - packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - - - packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll - - - packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll - - - packages\System.IO.4.3.0\lib\net462\System.IO.dll - - - packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - - - - packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll - - - packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll - - - packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - - - packages\System.Linq.4.3.0\lib\net463\System.Linq.dll - - - packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll - - - packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll - - - packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll - - - - packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll - - - packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll - - - packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll - - - packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll - - - packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - - - packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll - - - packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - - - packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - - - packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - - - packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll - - - - - - - - - - - packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll - - - - - Form - - - DownloadForm.cs - - - - - - DownloadForm.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - favicon.ico - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - - - - - - 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}. - - - - - - + + + + + + Debug + AnyCPU + {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04} + WinExe + UptimeKuma + uptime-kuma + v4.7.2 + 512 + true + true + ..\..\public\favicon.ico + 9 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + app.manifest + + + COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\" + + + + packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll + + + packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + + + + packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll + + + + packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll + + + + packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + + packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll + + + packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll + + + packages\System.IO.4.3.0\lib\net462\System.IO.dll + + + packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + + packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + + + packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + packages\System.Linq.4.3.0\lib\net463\System.Linq.dll + + + packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll + + + packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll + + + packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + + + + packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + + + packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + + + packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll + + + packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll + + + packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + + + packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + + + packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll + + + + + + + + + + + packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + + + Form + + + DownloadForm.cs + + + + + + DownloadForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + favicon.ico + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + + + + 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}. + + + + + + \ No newline at end of file diff --git a/extra/exe-builder/app.manifest b/extra/exe-builder/app.manifest new file mode 100644 index 00000000..4a48528f --- /dev/null +++ b/extra/exe-builder/app.manifest @@ -0,0 +1,28 @@ + + + + + true + PerMonitorV2 + + + + + + + + + + + From 02ddb58686a56146aa3545307067350a45eb2de5 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 21 Feb 2023 15:16:54 +0800 Subject: [PATCH 128/163] Fix cwd issues --- extra/exe-builder/FS.cs | 65 +++++++++++++++++++++++++++++ extra/exe-builder/UptimeKuma.csproj | 1 + 2 files changed, 66 insertions(+) create mode 100644 extra/exe-builder/FS.cs diff --git a/extra/exe-builder/FS.cs b/extra/exe-builder/FS.cs new file mode 100644 index 00000000..99a63694 --- /dev/null +++ b/extra/exe-builder/FS.cs @@ -0,0 +1,65 @@ +using System.IO; +using System.Reflection; + +namespace UptimeKuma { + + /** + * Current Directory using App location + */ + public class Directory { + private static string baseDir; + + public static string FullPath(string path) { + return Path.Combine(GetBaseDir(), path); + } + + public static string GetBaseDir() { + if (baseDir == null) { + baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + } + return baseDir; + } + + public static bool Exists(string path) { + return System.IO.Directory.Exists(FullPath(path)); + } + + public static void Delete(string path, bool recursive) { + System.IO.Directory.Delete(FullPath(path), recursive); + } + + public static void Move(string src, string dest) { + System.IO.Directory.Move(FullPath(src), FullPath(dest)); + } + + public static string[] GetDirectories(string path) { + return System.IO.Directory.GetDirectories(FullPath(path)); + } + } + + public class File { + + private static string FullPath(string path) { + return Directory.FullPath(path); + } + public static bool Exists(string path) { + return System.IO.File.Exists(FullPath(path)); + } + + public static FileStream Create(string path) { + return System.IO.File.Create(FullPath(path)); + } + + public static string ReadAllText(string path) { + return System.IO.File.ReadAllText(FullPath(path)); + } + + public static void Delete(string path) { + System.IO.File.Delete(FullPath(path)); + } + + public static void WriteAllText(string path, string content) { + System.IO.File.WriteAllText(FullPath(path), content); + } + } +} diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index 6b7534af..1b484d7a 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -148,6 +148,7 @@ DownloadForm.cs + From dad21065cf3e6045bd3a6fcf6a2b5d20f0b9d08b Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 21 Feb 2023 15:45:18 +0800 Subject: [PATCH 129/163] Update release procedures --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09c94e71..4c6a5587 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -235,6 +235,7 @@ https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc 1. Draft a release note 2. Make sure the repo is cleared +3. If the healthcheck is updated, remember to re-compile it: `npm run build-docker-builder-go` 3. `npm run release-final with env vars: `VERSION` and `GITHUB_TOKEN` 4. Wait until the `Press any key to continue` 5. `git push` From 2c62d197a08462fa7fb044f23395eb1464e9a031 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 21 Feb 2023 16:03:41 +0800 Subject: [PATCH 130/163] [exe] Update dependencies --- extra/exe-builder/App.config | 6 +++- extra/exe-builder/UptimeKuma.csproj | 48 +++++++++++++++++++---------- extra/exe-builder/packages.config | 30 ++++++++++-------- 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/extra/exe-builder/App.config b/extra/exe-builder/App.config index 2514085c..97eb34af 100644 --- a/extra/exe-builder/App.config +++ b/extra/exe-builder/App.config @@ -20,12 +20,16 @@ - + + + + + diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index 1b484d7a..bd4e0dea 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -56,13 +56,16 @@ packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll + + packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + - - packages\System.Console.4.3.0\lib\net46\System.Console.dll + + packages\System.Console.4.3.1\lib\net46\System.Console.dll - - packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + + packages\System.Diagnostics.DiagnosticSource.7.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll @@ -92,21 +95,30 @@ packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll - - packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll + + packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + + packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll - - packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + + packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll + + + packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll + + packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll @@ -115,7 +127,7 @@ packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll @@ -123,11 +135,11 @@ packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - - packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll - packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll + packages\System.Text.RegularExpressions.4.3.1\lib\net463\System.Text.RegularExpressions.dll @@ -138,7 +150,7 @@ - packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + packages\System.Xml.ReaderWriter.4.3.1\lib\net46\System.Xml.ReaderWriter.dll @@ -186,14 +198,16 @@ - 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}. - + + + + \ No newline at end of file diff --git a/extra/exe-builder/packages.config b/extra/exe-builder/packages.config index 82dd7c3b..aca26d67 100644 --- a/extra/exe-builder/packages.config +++ b/extra/exe-builder/packages.config @@ -1,17 +1,27 @@  - - + + - + + + + + + + + + + + + + - - @@ -23,30 +33,24 @@ - - + - - + - - - - \ No newline at end of file From de7df46aa801c725426489a951d5f777d9ef55c7 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 22 Feb 2023 04:27:12 +0800 Subject: [PATCH 131/163] Sort the notification list by name and remove translation keys of brand names or product names --- src/components/NotificationDialog.vue | 74 +++++++++++++++++++++++-- src/components/notifications/Gorush.vue | 2 +- src/lang/en.json | 29 ---------- src/lang/zh-CN.json | 2 - 4 files changed, 71 insertions(+), 36 deletions(-) diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 0ca95c22..02ddce80 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -13,7 +13,7 @@
    @@ -67,7 +67,7 @@ - From f9a6d7ec44b0e4cf4b3f7c8f572928b297a7cbd6 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 25 Feb 2023 16:19:48 +0800 Subject: [PATCH 146/163] Add a missing icon --- src/icon.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/icon.js b/src/icon.js index 6cc997bb..a69b2a88 100644 --- a/src/icon.js +++ b/src/icon.js @@ -3,6 +3,9 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; // Add Free Font Awesome Icons // https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free +// In order to add an icon, you have to: +// 1) add the icon name in the import statement below; +// 2) add the icon name to the library.add() statement below. import { faArrowAltCircleUp, faCog, @@ -45,6 +48,7 @@ import { faHeartbeat, faFilter, faInfoCircle, + faClone, } from "@fortawesome/free-solid-svg-icons"; library.add( @@ -90,6 +94,7 @@ library.add( faHeartbeat, faFilter, faInfoCircle, + faClone, ); export { FontAwesomeIcon }; From 43c797a34e2d08300386a06da6c04c8b5b57efe3 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 25 Feb 2023 16:20:59 +0800 Subject: [PATCH 147/163] Do not active the old monitor in the clone page --- src/router.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/router.js b/src/router.js index f1cf8a08..b9ee2631 100644 --- a/src/router.js +++ b/src/router.js @@ -65,12 +65,12 @@ const routes = [ path: "/edit/:id", component: EditMonitor, }, - { - path: "/clone/:id", - component: EditMonitor, - }, ], }, + { + path: "/clone/:id", + component: EditMonitor, + }, { path: "/add", component: EditMonitor, From 4fed0c152e901477a42481e5a016c9cae6456c44 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 25 Feb 2023 17:05:03 +0800 Subject: [PATCH 148/163] Show `Copy of` in front of the cloned monitor name --- src/lang/en.json | 1 + src/pages/EditMonitor.vue | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 29bd0bb5..ad55fe6e 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -441,6 +441,7 @@ "notificationRegional": "Regional", "Clone Monitor": "Clone Monitor", "Clone": "Clone", + "cloneOf": "Clone of {0}", "smtp": "Email (SMTP)", "secureOptionNone": "None / STARTTLS (25, 587)", "secureOptionTLS": "TLS (465)", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index e7ecb58a..4a4ae982 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -922,13 +922,14 @@ message HealthCheckResponse { this.monitor = res.monitor; if (this.isClone) { - /** - * Cloning a monitor will include properties that can not be posted to backend - * as they are not valid columns in the SQLite table. - */ + /* + * Cloning a monitor will include properties that can not be posted to backend + * as they are not valid columns in the SQLite table. + */ this.monitor.id = undefined; // Remove id when cloning as we want a new id this.monitor.includeSensitiveData = undefined; this.monitor.maintenance = undefined; + this.monitor.name = this.$t("cloneOf", [ this.monitor.name ]); this.monitor.tags = undefined; // FIXME: Cloning tags does not work yet } From a7b49fcd98b1f86db1ded571d252dc93fba14bab Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 25 Feb 2023 17:28:32 +0800 Subject: [PATCH 149/163] Fix json body after xml body added --- server/model/monitor.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index b071a622..ef29ba3b 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -276,19 +276,24 @@ class Monitor extends BeanModel { let contentType = null; let bodyValue = null; - if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json") { - bodyValue = JSON.parse(this.body); - contentType = "application/json"; - } else if (this.body && (this.httpBodyEncoding === "xml")) { - bodyValue = this.body; - contentType = "text/xml; charset=utf-8"; + if (this.body && (typeof this.body === "string" && this.body.trim().length > 0)) { + if (!this.httpBodyEncoding || this.httpBodyEncoding === "json") { + try { + bodyValue = JSON.parse(this.body); + contentType = "application/json"; + } catch (e) { + throw new Error("Your JSON body is invalid. " + e.message); + } + } else if (this.httpBodyEncoding === "xml") { + bodyValue = this.body; + contentType = "text/xml; charset=utf-8"; + } } // Axios Options const options = { url: this.url, method: (this.method || "get").toLowerCase(), - ...(bodyValue ? { data: bodyValue } : {}), timeout: this.interval * 1000 * 0.8, headers: { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", @@ -303,6 +308,10 @@ class Monitor extends BeanModel { }, }; + if (bodyValue) { + options.data = bodyValue; + } + if (this.proxy_id) { const proxy = await R.load("proxy", this.proxy_id); From 48c6f0578cda06c6633d42684b8db3a1f70dc521 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 25 Feb 2023 19:16:33 +0800 Subject: [PATCH 150/163] Merge manually --- src/lang/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lang/en.json b/src/lang/en.json index ad55fe6e..c9889fad 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -442,6 +442,7 @@ "Clone Monitor": "Clone Monitor", "Clone": "Clone", "cloneOf": "Clone of {0}", + "Description": "Description", "smtp": "Email (SMTP)", "secureOptionNone": "None / STARTTLS (25, 587)", "secureOptionTLS": "TLS (465)", From ea3b3abe367371797fc00b1d9934873522d510cb Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 25 Feb 2023 20:13:46 +0800 Subject: [PATCH 151/163] Fine tune --- server/model/monitor.js | 1 - src/components/MonitorList.vue | 5 +---- src/pages/EditMonitor.vue | 12 ++++++------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 0d915c37..312ac732 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -39,7 +39,6 @@ class Monitor extends BeanModel { id: this.id, name: this.name, sendUrl: this.sendUrl, - description: this.description, }; if (this.sendUrl) { diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 5a6e5ef4..d64b43c1 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -19,15 +19,12 @@ {{ $t("No Monitors, please") }} {{ $t("add one") }}
    - +
    {{ item.name }} - - -
    diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index f13060c2..fa1935b0 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -89,12 +89,6 @@
    - -
    - - -
    -
    @@ -420,6 +414,12 @@
    + +
    + + +
    +
    From 3c5f9981912d8f8efd49e54852c414b039a6e72e Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 26 Feb 2023 03:23:02 +0800 Subject: [PATCH 152/163] Update mongodb to 4.14.0, possibly fix #2820 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e57a932..e642d03b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "jsonwebtoken": "~9.0.0", "jwt-decode": "~3.1.2", "limiter": "~2.1.0", - "mongodb": "~4.13.0", + "mongodb": "~4.14.0", "mqtt": "~4.3.7", "mssql": "~8.1.4", "mysql2": "~2.3.3", @@ -14008,9 +14008,9 @@ } }, "node_modules/mongodb": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.13.0.tgz", - "integrity": "sha512-+taZ/bV8d1pYuHL4U+gSwkhmDrwkWbH1l4aah4YpmpscMwgFBkufIKxgP/G7m87/NUuQzc2Z75ZTI7ZOyqZLbw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", + "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", "dependencies": { "bson": "^4.7.0", "mongodb-connection-string-url": "^2.5.4", @@ -29626,9 +29626,9 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mongodb": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.13.0.tgz", - "integrity": "sha512-+taZ/bV8d1pYuHL4U+gSwkhmDrwkWbH1l4aah4YpmpscMwgFBkufIKxgP/G7m87/NUuQzc2Z75ZTI7ZOyqZLbw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", + "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", "requires": { "@aws-sdk/credential-providers": "^3.186.0", "bson": "^4.7.0", diff --git a/package.json b/package.json index a3f6066b..fa0614a8 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "jsonwebtoken": "~9.0.0", "jwt-decode": "~3.1.2", "limiter": "~2.1.0", - "mongodb": "~4.13.0", + "mongodb": "~4.14.0", "mqtt": "~4.3.7", "mssql": "~8.1.4", "mysql2": "~2.3.3", From 5b0b743f81d1eb00f9dbca4acdbfc13a7da1683e Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 26 Feb 2023 17:00:54 +0800 Subject: [PATCH 153/163] Update to 1.20.2 --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index e642d03b..159d3366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.20.1", + "version": "1.20.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.20.1", + "version": "1.20.2", "license": "MIT", "dependencies": { "@grpc/grpc-js": "~1.7.3", diff --git a/package.json b/package.json index fa0614a8..686947ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.20.1", + "version": "1.20.2", "license": "MIT", "repository": { "type": "git", @@ -39,7 +39,7 @@ "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --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.20.1 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.20.2 && 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", From b91c526d2e7c742d75a8179382c3b057adaa4f22 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 26 Feb 2023 17:56:01 +0800 Subject: [PATCH 154/163] [exe] Show server status --- extra/exe-builder/Program.cs | 28 ++++++++++++++++++-- extra/exe-builder/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 1385e830..c120d6df 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -5,6 +5,7 @@ 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.Tasks; @@ -33,19 +34,26 @@ namespace UptimeKuma { private NotifyIcon trayIcon; private Process process; + private MenuItem statusMenuItem; private MenuItem runWhenStarts; private RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); - public UptimeKumaApplicationContext() - { + + public UptimeKumaApplicationContext() { + 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; + trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { + statusMenuItem, new("Open", Open), //new("Debug Console", DebugConsole), runWhenStarts, @@ -109,6 +117,22 @@ namespace UptimeKuma { 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; + trayIcon.Text = runningText; + break; + } catch (Exception) { + System.Threading.Thread.Sleep(2000); + } + } + }); + } catch (Exception e) { MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error"); } diff --git a/extra/exe-builder/Properties/AssemblyInfo.cs b/extra/exe-builder/Properties/AssemblyInfo.cs index 2552870b..59b36bb8 100644 --- a/extra/exe-builder/Properties/AssemblyInfo.cs +++ b/extra/exe-builder/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Uptime Kuma")] -[assembly: AssemblyCopyright("Copyright © 2022 Louis Lam")] +[assembly: AssemblyCopyright("Copyright © 2023 Louis Lam")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] From 42a69c16ca42de0b34688b95e1f2e0188ad2811b Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Sun, 26 Feb 2023 16:47:34 +0000 Subject: [PATCH 155/163] Switched to crypto.randomBytes fpr key generation Keys are now 32 bytes long encoded in a URL safe base64 string Signed-off-by: Matthew Nickson --- server/auth.js | 7 ++++--- server/socket-handlers/api-key-socket-handler.js | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/server/auth.js b/server/auth.js index eddae4c3..c42a74c4 100644 --- a/server/auth.js +++ b/server/auth.js @@ -44,8 +44,9 @@ async function verifyAPIKey(key) { return false; } - let index = key.substring(0, key.indexOf("-")); - let clear = key.substring(key.indexOf("-") + 1, key.length); + // uk prefix + key ID is before _ + let index = key.substring(2, key.indexOf("_")); + let clear = key.substring(key.indexOf("_") + 1, key.length); let hash = await R.findOne("api_key", " id=? ", [ index ]); @@ -137,7 +138,7 @@ exports.basicAuth = async function (req, res, next) { }; /** - * Use X-API-Key header if API keys enabled, else use basic auth + * Use use API Key if API keys enabled, else use basic auth * @param {express.Request} req Express request object * @param {express.Response} res Express response object * @param {express.NextFunction} next diff --git a/server/socket-handlers/api-key-socket-handler.js b/server/socket-handlers/api-key-socket-handler.js index cf124cad..546226f6 100644 --- a/server/socket-handlers/api-key-socket-handler.js +++ b/server/socket-handlers/api-key-socket-handler.js @@ -17,7 +17,7 @@ module.exports.apiKeySocketHandler = (socket) => { socket.on("addAPIKey", async (key, callback) => { try { checkLogin(socket); - let clearKey = crypto.randomUUID(); + let clearKey = crypto.randomBytes(32).toString("base64url"); let hashedKey = passwordHash.generate(clearKey); key["key"] = hashedKey; let bean = await APIKey.save(key, socket.userID); @@ -25,9 +25,9 @@ module.exports.apiKeySocketHandler = (socket) => { log.debug("apikeys", "Added API Key"); log.debug("apikeys", key); - // Append key ID to start of key seperated by -, used to get + // Append key ID and prefix to start of key seperated by _, used to get // correct hash when validating key. - let formattedKey = bean.id + "-" + clearKey; + let formattedKey = "uk" + bean.id + "_" + clearKey; await sendAPIKeyList(socket); // Enable API auth if the user creates a key, otherwise only basic From 11fa690e0915b9be166b861cf704c76db864e59d Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Sun, 26 Feb 2023 18:07:57 +0000 Subject: [PATCH 156/163] Updated API Keys UI The UI has now been moved to the settings page. Signed-off-by: Matthew Nickson --- src/components/APIKeyDialog.vue | 214 ++++++++++++++++++ .../settings/APIKeys.vue} | 127 +++++------ src/icon.js | 2 - src/layouts/Layout.vue | 6 - src/pages/AddAPIKey.vue | 199 ---------------- src/pages/Settings.vue | 6 +- src/router.js | 15 +- 7 files changed, 286 insertions(+), 283 deletions(-) create mode 100644 src/components/APIKeyDialog.vue rename src/{pages/ManageAPIKeys.vue => components/settings/APIKeys.vue} (56%) delete mode 100644 src/pages/AddAPIKey.vue diff --git a/src/components/APIKeyDialog.vue b/src/components/APIKeyDialog.vue new file mode 100644 index 00000000..106ad8c7 --- /dev/null +++ b/src/components/APIKeyDialog.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/src/pages/ManageAPIKeys.vue b/src/components/settings/APIKeys.vue similarity index 56% rename from src/pages/ManageAPIKeys.vue rename to src/components/settings/APIKeys.vue index ccfc0ce6..3ecd53b7 100644 --- a/src/pages/ManageAPIKeys.vue +++ b/src/components/settings/APIKeys.vue @@ -1,85 +1,83 @@ diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index a076a4d3..d3c153df 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -7,9 +7,6 @@ {{ $t("Maintenance") }} - - {{ $t("API Keys") }} -

    @@ -110,6 +107,9 @@ export default { security: { title: this.$t("Security"), }, + "api-keys": { + title: this.$t("API Keys") + }, proxies: { title: this.$t("Proxies"), }, diff --git a/src/router.js b/src/router.js index af86356e..b9493f09 100644 --- a/src/router.js +++ b/src/router.js @@ -18,8 +18,7 @@ import NotFound from "./pages/NotFound.vue"; import DockerHosts from "./components/settings/Docker.vue"; import MaintenanceDetails from "./pages/MaintenanceDetails.vue"; import ManageMaintenance from "./pages/ManageMaintenance.vue"; -import ManageAPIKeys from "./pages/ManageAPIKeys.vue"; -import AddAPIKey from "./pages/AddAPIKey.vue"; +import APIKeys from "./components/settings/APIKeys.vue"; import Plugins from "./components/settings/Plugins.vue"; // Settings - Sub Pages @@ -115,6 +114,10 @@ const routes = [ path: "security", component: Security, }, + { + path: "api-keys", + component: APIKeys, + }, { path: "proxies", component: Proxies, @@ -157,14 +160,6 @@ const routes = [ path: "/maintenance/edit/:id", component: EditMaintenance, }, - { - path: "/apikeys", - component: ManageAPIKeys - }, - { - path: "/apikeys/add", - component: AddAPIKey - }, ], }, ], From 669f8700b236cc7727b6485d5f6126ee6f88e233 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Sun, 26 Feb 2023 19:36:50 +0000 Subject: [PATCH 157/163] Switched to nanoid for key generation To try and prevent any security issues, use an external package to generate key instead of doing it ourselves. Note: we have to use nanoid version 3 as nanoid version 4 requires ESM. Currently, nanoid v3 is still supported. Signed-off-by: Matthew Nickson --- package-lock.json | 5 ++--- package.json | 1 + server/socket-handlers/api-key-socket-handler.js | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e57a932..328043b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "mqtt": "~4.3.7", "mssql": "~8.1.4", "mysql2": "~2.3.3", + "nanoid": "^3.3.4", "node-cloudflared-tunnel": "~1.0.9", "node-radius-client": "~1.0.0", "nodemailer": "~6.6.5", @@ -14247,7 +14248,6 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -29825,8 +29825,7 @@ "nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" }, "native-duplexpair": { "version": "1.0.0", diff --git a/package.json b/package.json index a3f6066b..7b5facab 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "mqtt": "~4.3.7", "mssql": "~8.1.4", "mysql2": "~2.3.3", + "nanoid": "^3.3.4", "node-cloudflared-tunnel": "~1.0.9", "node-radius-client": "~1.0.0", "nodemailer": "~6.6.5", diff --git a/server/socket-handlers/api-key-socket-handler.js b/server/socket-handlers/api-key-socket-handler.js index 546226f6..69b0b60d 100644 --- a/server/socket-handlers/api-key-socket-handler.js +++ b/server/socket-handlers/api-key-socket-handler.js @@ -1,7 +1,7 @@ const { checkLogin } = require("../util-server"); const { log } = require("../../src/util"); const { R } = require("redbean-node"); -const crypto = require("crypto"); +const { nanoid } = require("nanoid"); const passwordHash = require("../password-hash"); const apicache = require("../modules/apicache"); const APIKey = require("../model/api_key"); @@ -17,7 +17,8 @@ module.exports.apiKeySocketHandler = (socket) => { socket.on("addAPIKey", async (key, callback) => { try { checkLogin(socket); - let clearKey = crypto.randomBytes(32).toString("base64url"); + + let clearKey = nanoid(40); let hashedKey = passwordHash.generate(clearKey); key["key"] = hashedKey; let bean = await APIKey.save(key, socket.userID); From fee8fd932093aa466050fc49ea8fcdcf1912726b Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 27 Feb 2023 18:45:54 +0800 Subject: [PATCH 158/163] [exe] Use `Environment.CurrentDirectory` instead of overriding fs --- extra/exe-builder/FS.cs | 65 ----------------------------- extra/exe-builder/Program.cs | 6 +++ extra/exe-builder/UptimeKuma.csproj | 1 - 3 files changed, 6 insertions(+), 66 deletions(-) delete mode 100644 extra/exe-builder/FS.cs diff --git a/extra/exe-builder/FS.cs b/extra/exe-builder/FS.cs deleted file mode 100644 index 99a63694..00000000 --- a/extra/exe-builder/FS.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.IO; -using System.Reflection; - -namespace UptimeKuma { - - /** - * Current Directory using App location - */ - public class Directory { - private static string baseDir; - - public static string FullPath(string path) { - return Path.Combine(GetBaseDir(), path); - } - - public static string GetBaseDir() { - if (baseDir == null) { - baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - } - return baseDir; - } - - public static bool Exists(string path) { - return System.IO.Directory.Exists(FullPath(path)); - } - - public static void Delete(string path, bool recursive) { - System.IO.Directory.Delete(FullPath(path), recursive); - } - - public static void Move(string src, string dest) { - System.IO.Directory.Move(FullPath(src), FullPath(dest)); - } - - public static string[] GetDirectories(string path) { - return System.IO.Directory.GetDirectories(FullPath(path)); - } - } - - public class File { - - private static string FullPath(string path) { - return Directory.FullPath(path); - } - public static bool Exists(string path) { - return System.IO.File.Exists(FullPath(path)); - } - - public static FileStream Create(string path) { - return System.IO.File.Create(FullPath(path)); - } - - public static string ReadAllText(string path) { - return System.IO.File.ReadAllText(FullPath(path)); - } - - public static void Delete(string path) { - System.IO.File.Delete(FullPath(path)); - } - - public static void WriteAllText(string path, string content) { - System.IO.File.WriteAllText(FullPath(path), content); - } - } -} diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index c120d6df..69cb1a73 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -21,6 +21,12 @@ namespace UptimeKuma { /// [STAThread] static void Main(string[] args) { + var cwd = Path.GetDirectoryName(Application.ExecutablePath); + + if (cwd != null) { + Environment.CurrentDirectory = cwd; + } + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new UptimeKumaApplicationContext()); diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index bd4e0dea..ecd6a46b 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -160,7 +160,6 @@ DownloadForm.cs - From bba8c6fe4ec917484031a68edade6cd793e8d489 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 27 Feb 2023 18:48:11 +0800 Subject: [PATCH 159/163] [exe] Open menu item is clickable after the server is ready --- extra/exe-builder/Program.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 69cb1a73..31befa54 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -26,7 +26,7 @@ namespace UptimeKuma { if (cwd != null) { Environment.CurrentDirectory = cwd; } - + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new UptimeKumaApplicationContext()); @@ -42,6 +42,7 @@ namespace UptimeKuma { private MenuItem statusMenuItem; private MenuItem runWhenStarts; + private MenuItem openMenuItem; private RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); @@ -57,10 +58,13 @@ namespace UptimeKuma { 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, - new("Open", Open), + openMenuItem, //new("Debug Console", DebugConsole), runWhenStarts, new("Check for Update...", CheckForUpdate), @@ -131,6 +135,7 @@ namespace UptimeKuma { try { tcpClient.Connect("127.0.0.1", 3001); statusMenuItem.Text = runningText; + openMenuItem.Enabled = true; trayIcon.Text = runningText; break; } catch (Exception) { From fc8a324f41abe75cd60f0e482330ddfbec7d1c96 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 27 Feb 2023 18:52:19 +0800 Subject: [PATCH 160/163] [exe] single instance only --- extra/exe-builder/Program.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 31befa54..6004f6d4 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -8,6 +8,7 @@ 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; @@ -35,6 +36,8 @@ namespace UptimeKuma { public class UptimeKumaApplicationContext : ApplicationContext { + private static Mutex mutex = null; + const string appName = "Uptime Kuma"; private NotifyIcon trayIcon; @@ -48,6 +51,14 @@ namespace UptimeKuma { public UptimeKumaApplicationContext() { + + // Single instance only + bool createdNew; + mutex = new Mutex(true, appName, out createdNew); + if (!createdNew) { + return; + } + var startingText = "Starting server..."; trayIcon = new NotifyIcon(); trayIcon.Text = startingText; From 97e276bdb5ae959bb0657e8961c14f86a5a51427 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Mon, 27 Feb 2023 18:19:56 +0000 Subject: [PATCH 161/163] Fixed processing error with add API key Also added padding below add button Signed-off-by: Matthew Nickson --- src/components/APIKeyDialog.vue | 2 +- src/components/settings/APIKeys.vue | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/APIKeyDialog.vue b/src/components/APIKeyDialog.vue index 106ad8c7..7a4c2464 100644 --- a/src/components/APIKeyDialog.vue +++ b/src/components/APIKeyDialog.vue @@ -144,6 +144,7 @@ export default { this.$root.addAPIKey(this.key, async (res) => { this.keyaddmodal.hide(); + this.processing = false; if (res.ok) { this.clearKey = res.key; this.keymodal.show(); @@ -151,7 +152,6 @@ export default { } else { toast.error(res.msg); } - this.processing = false; }); }, } diff --git a/src/components/settings/APIKeys.vue b/src/components/settings/APIKeys.vue index 3ecd53b7..75778993 100644 --- a/src/components/settings/APIKeys.vue +++ b/src/components/settings/APIKeys.vue @@ -157,6 +157,7 @@ export default { .add-btn { padding-top: 20px; + padding-bottom: 20px; } .item { From 7e178d93dfcd15f781c0c46b4368e057d160abf9 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Mon, 27 Feb 2023 18:44:32 +0000 Subject: [PATCH 162/163] Moved location of disable expiry checkbox Co-authored-by: Nelson Chan Signed-off-by: Matthew Nickson --- src/components/APIKeyDialog.vue | 43 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/components/APIKeyDialog.vue b/src/components/APIKeyDialog.vue index 7a4c2464..745efd4a 100644 --- a/src/components/APIKeyDialog.vue +++ b/src/components/APIKeyDialog.vue @@ -22,25 +22,30 @@
    - - -
    - - +
    +
    + +
    +
    +
    + + +
    +
    From 958354e4db31eb59e7f054fb6a236c5ad1b9045f Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 28 Feb 2023 16:58:36 +0800 Subject: [PATCH 163/163] Minor --- server/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/database.js b/server/database.js index e4e5e4bb..5a83e1fb 100644 --- a/server/database.js +++ b/server/database.js @@ -68,11 +68,11 @@ class Database { "patch-monitor-add-resend-interval.sql": true, "patch-ping-packet-size.sql": true, "patch-maintenance-table2.sql": true, - "patch-api-key-table.sql": true, "patch-add-gamedig-monitor.sql": true, "patch-add-google-analytics-status-page-tag.sql": true, "patch-http-body-encoding.sql": true, "patch-add-description-monitor.sql": true, + "patch-api-key-table.sql": true, }; /**