From 3a188017228b2437820133827c36b03a7b9597f7 Mon Sep 17 00:00:00 2001
From: Justin Tisdale <jtisdale@Admins-MacBook-Pro.local>
Date: Wed, 10 Aug 2022 21:46:43 -0400
Subject: [PATCH 01/57] 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 @@
                                     </select>
                                 </div>
 
+                                <!-- Encoding -->
+                                <div class="my-3">
+                                    <label for="bodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
+                                    <select id="bodyEncoding" v-model="monitor.bodyEncoding" class="form-select">
+                                        <option value="json">
+                                            JSON
+                                        </option>
+                                        <option value="form">
+                                            x-www-form-urlencoded
+                                        </option>
+                                        <option value="xml">
+                                            XML
+                                        </option>
+                                    </select>
+                                </div>
+
                                 <!-- Body -->
                                 <div class="my-3">
                                     <label for="body" class="form-label">{{ $t("Body") }}</label>
@@ -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 <justin@justintisdale.com>
Date: Thu, 11 Aug 2022 20:57:03 -0400
Subject: [PATCH 02/57] 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 @@
 
                                 <!-- Encoding -->
                                 <div class="my-3">
-                                    <label for="bodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
-                                    <select id="bodyEncoding" v-model="monitor.bodyEncoding" class="form-select">
+                                    <label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
+                                    <select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
                                         <option value="json">
                                             JSON
                                         </option>
@@ -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 <justin@justintisdale.com>
Date: Thu, 11 Aug 2022 21:08:13 -0400
Subject: [PATCH 03/57] 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 <justin@justintisdale.com>
Date: Mon, 26 Sep 2022 15:52:43 -0400
Subject: [PATCH 04/57] 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 <justin@justintisdale.com>
Date: Mon, 26 Sep 2022 17:09:10 -0400
Subject: [PATCH 05/57] 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 <justin@justintisdale.com>
Date: Mon, 26 Sep 2022 17:10:56 -0400
Subject: [PATCH 06/57] 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 <louislam@users.noreply.github.com>
Date: Thu, 6 Oct 2022 12:08:10 +0800
Subject: [PATCH 07/57] Init C# Project for building exe

---
 .dockerignore                                 |   5 +-
 .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, 418 insertions(+), 2 deletions(-)
 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
+
+
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 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup>
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
+    </startup>
+
+    <System.Windows.Forms.ApplicationConfigurationSection>
+        <add key="DpiAwareness" value="PerMonitorV2" />
+    </System.Windows.Forms.ApplicationConfigurationSection>
+</configuration>
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 {
+        /// <summary>
+        /// The main entry point for the application.
+        /// </summary>
+        [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 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace UptimeKuma.Properties {
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder",
+        "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance",
+            "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
+            .Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if ((resourceMan == null)) {
+                    global::System.Resources.ResourceManager temp =
+                        new global::System.Resources.ResourceManager("UptimeKuma.Properties.Resources",
+                            typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
+            .Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get { return resourceCulture; }
+            set { resourceCulture = value; }
+        }
+    }
+}
\ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ 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 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace UptimeKuma.Properties {
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(
+        "Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        private static Settings defaultInstance =
+            ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default {
+            get { return defaultInstance; }
+        }
+    }
+}
\ 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 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+    <PropertyGroup>
+        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+        <ProjectGuid>{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}</ProjectGuid>
+        <OutputType>WinExe</OutputType>
+        <RootNamespace>UptimeKuma</RootNamespace>
+        <AssemblyName>uptime-kuma</AssemblyName>
+        <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+        <FileAlignment>512</FileAlignment>
+        <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+        <Deterministic>true</Deterministic>
+        <ApplicationIcon>..\..\public\favicon.ico</ApplicationIcon>
+        <LangVersion>10</LangVersion>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugSymbols>true</DebugSymbols>
+        <DebugType>full</DebugType>
+        <Optimize>false</Optimize>
+        <OutputPath>bin\Debug\</OutputPath>
+        <DefineConstants>DEBUG;TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugType>pdbonly</DebugType>
+        <Optimize>true</Optimize>
+        <OutputPath>bin\Release\</OutputPath>
+        <DefineConstants>TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <ItemGroup>
+        <Reference Include="System" />
+        <Reference Include="System.Core" />
+        <Reference Include="System.Xml.Linq" />
+        <Reference Include="System.Data.DataSetExtensions" />
+        <Reference Include="Microsoft.CSharp" />
+        <Reference Include="System.Data" />
+        <Reference Include="System.Deployment" />
+        <Reference Include="System.Drawing" />
+        <Reference Include="System.Net.Http" />
+        <Reference Include="System.Windows.Forms" />
+        <Reference Include="System.Xml" />
+    </ItemGroup>
+    <ItemGroup>
+        <Compile Include="Program.cs" />
+        <Compile Include="Properties\AssemblyInfo.cs" />
+        <EmbeddedResource Include="Properties\Resources.resx">
+            <Generator>ResXFileCodeGenerator</Generator>
+            <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+            <SubType>Designer</SubType>
+        </EmbeddedResource>
+        <Compile Include="Properties\Resources.Designer.cs">
+            <AutoGen>True</AutoGen>
+            <DependentUpon>Resources.resx</DependentUpon>
+        </Compile>
+        <None Include="..\..\public\favicon.ico">
+          <Link>favicon.ico</Link>
+        </None>
+        <None Include="Properties\Settings.settings">
+            <Generator>SettingsSingleFileGenerator</Generator>
+            <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+        </None>
+        <Compile Include="Properties\Settings.Designer.cs">
+            <AutoGen>True</AutoGen>
+            <DependentUpon>Settings.settings</DependentUpon>
+            <DesignTimeSharedInput>True</DesignTimeSharedInput>
+        </Compile>
+    </ItemGroup>
+    <ItemGroup>
+        <None Include="App.config" />
+    </ItemGroup>
+    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ 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 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=UptimeKuma_002FProperties_002FResources/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file

From da778f05ac30a681eeb71735dd21fc86eaef4a23 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Fri, 7 Oct 2022 00:16:07 +0800
Subject: [PATCH 08/57] 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 <louislam@users.noreply.github.com>
Date: Fri, 7 Oct 2022 18:38:14 +0800
Subject: [PATCH 09/57] WIP

---
 extra/exe-builder/Program.cs        | 69 +++++++++++++++++++++--------
 extra/exe-builder/UptimeKuma.csproj |  5 ++-
 2 files changed, 55 insertions(+), 19 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.
         /// </summary>
         [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,10 +74,19 @@ namespace UptimeKuma {
             Process.Start("http://localhost:3001");
         }
 
-        void CheckForUpdate(object sender, EventArgs e) {
+        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)
         {
             MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info");
@@ -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 @@
         <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
         <Deterministic>true</Deterministic>
         <ApplicationIcon>..\..\public\favicon.ico</ApplicationIcon>
-        <LangVersion>10</LangVersion>
+        <LangVersion>9</LangVersion>
     </PropertyGroup>
     <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
         <PlatformTarget>AnyCPU</PlatformTarget>
@@ -34,6 +34,9 @@
         <ErrorReport>prompt</ErrorReport>
         <WarningLevel>4</WarningLevel>
     </PropertyGroup>
+    <PropertyGroup>
+      <PostBuildEvent>COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "C:\Users\LouisLam\Desktop\uptime-kuma-win64\"</PostBuildEvent>
+    </PropertyGroup>
     <ItemGroup>
         <Reference Include="System" />
         <Reference Include="System.Core" />

From 3eaccb560ed1a0590a6bb3bf7f73765bbe06db00 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sat, 8 Oct 2022 16:23:11 +0800
Subject: [PATCH 10/57] 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 {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing) {
+            if (disposing && (components != null)) {
+                components.Dispose();
+            }
+
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent() {
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DownloadForm));
+            this.progressBar = new System.Windows.Forms.ProgressBar();
+            this.label = new System.Windows.Forms.Label();
+            this.labelData = new System.Windows.Forms.Label();
+            this.SuspendLayout();
+            // 
+            // progressBar
+            // 
+            this.progressBar.Location = new System.Drawing.Point(12, 12);
+            this.progressBar.Name = "progressBar";
+            this.progressBar.Size = new System.Drawing.Size(472, 41);
+            this.progressBar.TabIndex = 0;
+            // 
+            // label
+            // 
+            this.label.Location = new System.Drawing.Point(12, 59);
+            this.label.Name = "label";
+            this.label.Size = new System.Drawing.Size(472, 23);
+            this.label.TabIndex = 1;
+            this.label.Text = "Preparing...";
+            // 
+            // labelData
+            // 
+            this.labelData.Location = new System.Drawing.Point(12, 82);
+            this.labelData.Name = "labelData";
+            this.labelData.Size = new System.Drawing.Size(472, 23);
+            this.labelData.TabIndex = 2;
+            // 
+            // DownloadForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(496, 117);
+            this.Controls.Add(this.labelData);
+            this.Controls.Add(this.label);
+            this.Controls.Add(this.progressBar);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+            this.MaximizeBox = false;
+            this.Name = "DownloadForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Uptime Kuma";
+            this.Load += new System.EventHandler(this.DownloadForm_Load);
+            this.ResumeLayout(false);
+        }
+
+        private System.Windows.Forms.Label labelData;
+
+        private System.Windows.Forms.Label label;
+
+        private System.Windows.Forms.ProgressBar progressBar;
+
+        #endregion
+    }
+}
+
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<DownloadItem> 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
+        AABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAA////BPT09Bfu7u4e8fHxJPPz8yv19fUy9fX1M/Pz8yvx8fEk9vb2HPPz8xXMzMwFAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//
+        /wHv7+8f7u7uPPPz81Tx8fFs8fHxgPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGB8fHxcfHx8V3x8fFI9PT0MOvr6w0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AADy8vIU8fHxS/Dw8Hbx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fFr9PT0R/Dw8CIAAAABAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAA8vLyFPHx8Vnx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fFs9fX1Mb+/vwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAICAgALy8vI88fHxfvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy8nby8vI8gICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAzMzMBfHx8Vrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyYf///wwAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8vLyYPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8W/z8/MWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+9R8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLw8PB26urqDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLy8ijx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu7w7Ifj79ud2u7PtNLrw83P677dzeu85c3r
+        u+rM67rwzOu68c7rverQ68Dj0uvD3NbuyM3b7c+64u7apujv5ZPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxXgAAAAEAAAAAAAAAAAAAAAAAAAAA4+PjCfDw
+        8Hfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLd7tSmzeu92MbqsvvG6bH/xumy/8fq
+        s//H6rP/yOq0/8jqtf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//Q7MDx1u7Kz9/t
+        163s8OuJ8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu/v7y8AAAAAAAAAAAAA
+        AAAAAAAA7u7uPfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC5PDdl8jqtuTE6a7/xOmv/8Xp
+        sP/G6bH/xumx/8bpsv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zr
+        u//N67v/zey8/87svf/P67742e3Mx+jv5ZLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw
+        8HWAgIACAAAAAAAAAACqqqoD8vLyc/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLf7degxOiu+cPo
+        rf/D6a7/xOmu/8Xpr//F6bD/xumx/8bpsf/G6bL/x+qz/8fqs//I6rT/yOq1/8nqtv/J6rb/yuu3/8rr
+        uP/L67j/y+u5/8zruv/M67v/zeu7/83svP/O7L3/zuy9/87svfzc7tK28fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fEkAAAAAAAAAADz8/Mq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgunv
+        5o3D6a/0wuis/8Lorf/D6K3/xOmu/8Tprv/F6a//xemw/8bpsf/G6bH/xumy/8fqs//H6rP/yOq0/8jq
+        tf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/87svf/O7L3/3e/TtPHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAADy8vJM8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgszqutDB6Kv/weir/8LorP/D6K3/w+it/8Tprv/E6a7/xemv/8XpsP/G6bH/xumx/8bp
+        sv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zru//N67v/zey8/87s
+        vf/O7L3/zuy++u3w6Yzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJ1AAAAAAAAAADx8fFr8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC6O/kjsDoqvzA6Kr/weir/8Loq//C6Kz/w+it/8Porf/E6a7/xOmu/8Xp
+        r//F6bD/xumx/8bpsf/G6bL/x+qz/8fqtP/I6rT/yOq1/8nqtv/J6rb/yuu3/8rruP/L67n/y+u5/8zr
+        uv/M67v/zeu7/83svP/O7L3/zuy9/93u07Xx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC////Bv//
+        /wfx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1ezJsr/nqf/A56n/weiq/8Hoq//C6Kv/wuis/8Po
+        rf/D6K3/xOmu/8Pprv+856T/uOed/7bmmv+05Zf/teWZ/7jnnf+86KP/wOio/8fqs//J6rb/yeq2/8rr
+        t//K67j/y+u5/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/9buyNLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8vLyE/Ly8hPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCy+q6zr/nqP/A56n/wOep/8Ho
+        qv/B6Kv/wuir/8LorP+u5Y//neF2/5bgav+V4Gr/luBr/5fhbP+Y4W7/meFv/5rhcf+b4nL/nOJ0/53i
+        dv+j5H//reaM/7nnnf/E6q//y+y4/8vruf/L67n/zOu6/8zru//N67v/zey8/9Lsxd/x8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC7+/vIPb29hzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/n
+        qP+/56j/wOep/8Dnqf/B6Kr/weir/7nmn/+R32T/kt9l/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nh
+        b/+a4XH/m+Jy/5zidP+d4nX/nuN3/5/jeP+f4nn/weqq/8rruP/L67n/y+u5/8zruv/M67v/zeu7/9Ls
+        w+Lx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwI/Hx8SXx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGCxeix5L/nqP+/56j/v+eo/8Dnqf/A56n/weiq/7Pllv+Q3mP/kd9k/5LfZf+T32f/lOBo/5Xg
+        av+W4Gv/l+Ft/5jhbv+Z4W//muFx/5vicv+c4nT/neJ1/57jd/+f43j/xOmu/8rrt//K67j/y+u5/8vr
+        uf/M67r/zOu7/9Tsxtfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC9PT0GO/v7yDx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGCx+m037/nqP+/56j/v+eo/7/nqP/A56n/wOip/7TmmP+P3mH/kN5j/5Hf
+        ZP+S32b/k99n/5TgaP+V4Gr/luBr/5fhbf+Y4W7/meFw/5rhcf+b4nL/nOJ0/53idf+h5Hz/yuu2/8nq
+        t//K67f/yuu4/8vruf/L67n/zOu6/9ftysrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7e3tDvT0
+        9Bfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCyOq117/nqP+/56j/v+eo/7/nqP+/56j/wOep/7vn
+        of+O3mD/j95h/5DeY/+R32T/kt9m/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nhcP+a4nH/m+Jy/5zi
+        dP+r5Yr/yOq1/8nqtv/J6rf/yuu3/8rruP/L67n/y+u5/9zu1LHx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLz8/OA////A+7u7g/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCz+q+xb/nqP+/56j/v+eo/7/n
+        qP+/56j/v+eo/8Dnqf+S4Gb/jt5g/4/eYf+Q3mP/kd9k/5LfZv+T32f/lOBo/5Xgav+W4Gv/l+Ft/5jh
+        bv+Z4XD/muJx/5vic/+4553/yOq0/8jqtf/J6rb/yeq3/8rrt//K67j/y+u5/+bw4Zfx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fFrAAAAAP///wHz8/N88fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1+zMrr/n
+        qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+f4Xn/jd5f/47eYP+P3mH/kN5j/5HfZP+S32b/k99n/5Tg
+        af+V4Gr/luBr/5fhbf+Y4W7/meFw/5vic//F6rD/x+q0/8jqtP/I6rX/yeq2/8nqt//K67f/zOu88u/x
+        74Px8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLv7+9QAAAAAAAAAADw8PBm8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC5e7gk7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//jN1d/43eX/+O3mD/j95h/5De
+        Y/+R32T/kt9m/5PfZ/+U4Gn/leBq/5bga/+X4W3/mOFu/6rliP/G6rL/x+qz/8fqtP/I6rT/yOq1/8nq
+        tv/J6rf/1OzGy/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YL19fUzAAAAAAAAAADy8vJO8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgsPoru2/56j/v+eo/7/nqP+/56j/v+eo/7/nqP++6Kf/j95i/4zd
+        Xf+N3l//jt5g/4/eYv+Q3mP/kd9k/5LfZv+T32f/lOBp/5Xgav+W4Gz/l+Ft/7voov/G6bL/xuqy/8fq
+        s//H6rT/yOq1/8jqtf/J6rb/4e/Zo/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PARAAAAAAAA
+        AADu7u4u8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgszpvMm/56j/v+eo/7/nqP+/56j/v+eo/7/n
+        qP+/56j/q+SL/4vdXP+M3V3/jd5f/47eYP+P3mL/kN9j/5HfZP+S32b/k99n/5Tgaf+V4Gr/qOOH/8Xp
+        sP/G6bH/xumy/8bqsv/H6rP/x+q0/8jqtf/K67jy8PHwhPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8WoAAAAAAAAAAAAAAADo6OgL8fHxgfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguDv2J2/56j/v+eo/7/n
+        qP+/56j/v+eo/7/nqP+/56j/v+eo/6Xjgv+L3Vz/jN1d/43eX/+O3mD/j95i/5DfY/+R32T/kt9m/5Pf
+        Z/+k44D/xOmu/8XpsP/F6bD/xumx/8bpsv/G6rL/x+qz/8fqtP/W7cnB8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvPz80AAAAAAAAAAAAAAAAAAAAAA8PDwZ/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLD6K/rv+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//kt5n/4zdXf+N3l//jt5g/4/e
+        Yv+Q32P/luFs/67kj//D6K3/xOmu/8Tpr//F6bD/xemw/8bpsf/G6bL/xuqy/8fqtP7o7+WR8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xYAAAAAAAAAAAAAAAAAAAAA8vLyPPHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLV7ci0v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOio/7Xl
+        mv+u5I7/rOSM/67kj/+35pz/wumr/8Lorf/D6K3/w+it/8Tprv/E6a//xemw/8XpsP/G6bH/xumy/9Ds
+        wNPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyZQAAAAAAAAAAAAAAAAAAAAAAAAAA////DPHx
+        8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n
+        qP+/56j/v+eo/7/nqP+/56j/wOep/8Doqv/B6Kr/weir/8LorP/C6K3/w+it/8Porv/E6a7/xOmv/8Xp
+        sP/F6bD/yOq18uvw6Yvx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7+/vMQAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAPHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6O/ij8LorPG/56j/v+eo/7/n
+        qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weiq/8Hoq//C6Kz/wuit/8Po
+        rf/D6K7/xOmu/8Tpr//F6bH74u/anvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PB6////BQAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPz8yrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguHu
+        2pnB56v2v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/wOiq/8Ho
+        q//B6Kv/wuis/8Lorf/D6K3/w+mu/8Tprv3b7dKq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fFJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHy8vJf8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLi7tyXwumt8L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n
+        qP+/56j/wOep/8Doqv/B6Kv/weir/8LorP/C6K3/xOiv+d7u1aTx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvLy8nb///8KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+8Q8/Pze/Hx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6/Dpiszqu82/56j/v+eo/7/nqP+/56j/v+eo/7/n
+        qP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weir/8Hoq//H6bTj5e7elfHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8yoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAA9fX1MvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLe7tShx+mz3r/n
+        qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/xumy5drtz6rv8e+D8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyTgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPHx8Unx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgubv45DU68e2y+q6z8XoseTD6a7uweir9MPpru7F6bHly+q50tLsxLrl796U8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJh////AwAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wHx8fFZ8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8Wzf398IAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8D8/PzVfHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwZujo
+        6AsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAA////AfHx8Ujx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fFa////BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/Mp8vLydvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8/PzfPHx8TcAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CvLy8lDz8/N/8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvPz84Hx8fFa8PDwEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AADw8PAR8vLyTvHx8X3x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fF/8/PzVvT09BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wXz8/Mq8/PzU/Hx8XDx8fGB8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLy8vJz8fHxWO/v7y////8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8G7e3tHfLy
+        8ifu7u4u8PDwNPT09C/y8vIo7+/vH+Pj4wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAP///////wAA////////AAD///////8AAP//gAf//wAA//gAAD//AAD/wAAAB/8AAP+A
+        AAAB/wAA/gAAAAB/AAD8AAAAAD8AAPgAAAAAHwAA8AAAAAAPAADwAAAAAAcAAOAAAAAABwAA4AAAAAAD
+        AADAAAAAAAMAAMAAAAAAAwAAwAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA
+        AAAAAQAAgAAAAAABAACAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAABwAAwAAAAAAH
+        AADgAAAAAAcAAOAAAAAADwAA4AAAAAAPAADwAAAAAB8AAPAAAAAAHwAA+AAAAAA/AAD8AAAAAD8AAPwA
+        AAAAfwAA/gAAAAD/AAD/AAAAAf8AAP+AAAAD/wAA/8AAAAf/AAD/8AAAH/8AAP/8AAA//wAA//8AAf//
+        AAD//+AP//8AAP///////wAA////////AAD///////8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAgICAAu/v7xD09PQX7u7uHvDw8CP29vYb8vLyFOrq6gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICA
+        gALy8vIm7+/vT/Pz82fz8/N98fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw8Hrw8PBm7+/vUPT0
+        9C3o6OgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOPj
+        4wnz8/NC8vLydPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YHy8vJj8/PzKoCAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AADx8fEl8vLydfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxcfHx8SUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAA9PT0LfHx8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8/PzgPLy8j0AAAABAAAAAAAA
+        AAAAAAAAAAAAAO3t7Rzx8fGA8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLr8OmM5O7emeTv
+        3Z7h79mj5fDem+nv45Tu8u6H8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy
+        8joAAAAAAAAAAAAAAAD///8E8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC7vDshtns0K7N67zayeq288fq
+        s//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/P7L7w0+zF29vv0Lrn8OKX8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8/PzfvPz8xUAAAAAAAAAAPX19TLx8fGC8fHxgvHx8YLx8fGC8fHxgt3u1KXF6rHzxOmv/8Xp
+        sP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/M67v/zey8/87svf/S7MPj4u7Zp/Hx
+        8YLx8fGC8fHxgvHx8YLx8fGC8/PzVQAAAAAAAAAA8fHxavHx8YLx8fGC8fHxgvHx8YLf7defwuis/cPo
+        rf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruv/M67v/zey8/87s
+        vf/N67z/3e7SufHx8YLx8fGC8fHxgvHx8YLz8/N8////Bf///w3x8fGC8fHxgvHx8YLx8fGC8fHxgsXp
+        sOnB6Kv/wuis/8Porf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vr
+        uv/M67v/zey8/87svf/O67z96/Hoj/Hx8YLx8fGC8fHxgvHx8YLy8vIm8/PzK/Hx8YLx8fGC8fHxgvHx
+        8YLg79icwOep/8Hoqv/B6Kv/wuis/8Porf/E6a7/wuit/73opP+76KL/u+eh/77opv/D6a3/yeu1/8nq
+        tv/K67f/y+u5/8zruv/M67v/zey8/87svf/d7tSz8fHxgvHx8YLx8fGC8fHxgvHx8Tby8vI68fHxgvHx
+        8YLx8fGC8fHxgtTrxre/56j/wOep/8Hoqv/B6Kv/uOad/53idv+V4Gn/leBq/5fhbP+Y4W//muFx/5vi
+        c/+e4Xb/puWD/7PmlP/D6a3/y+u5/8zruv/M67v/zey8/9rtzsHx8fGC8fHxgvHx8YLx8fGC8/PzQfPz
+        80Lx8fGC8fHxgvHx8YLx8fGC0OvAwr/nqP+/56j/wOep/8Hoqv+o44b/kd9k/5LfZv+U4Gj/leBq/5fh
+        bf+Y4W//muFx/5vic/+d4nX/n+N3/7fnm//K67j/y+u5/8zruv/M67v/2u3QvPHx8YLx8fGC8fHxgvHx
+        8YLy8vI98/PzP/Hx8YLx8fGC8fHxgvHx8YLQ6sK/v+eo/7/nqP+/56j/wOep/6jjhv+P3mL/kd9k/5Lf
+        Zv+U4Gj/leBr/5fhbf+Y4W//muFx/5zic/+d4nX/v+mm/8nqt//K67j/y+u5/8zruv/f79au8fHxgvHx
+        8YLx8fGC8fHxgvX19TLx8fE38fHxgvHx8YLx8fGC8fHxgtTrybO/56j/v+eo/7/nqP+/56j/sOSS/47e
+        YP+P3mL/kd9k/5LfZv+U4Gj/leBr/5fhbf+Z4W//muJx/5/jd//H6bP/yeq2/8nqt//K67j/y+u5/+nv
+        45Tx8fGC8fHxgvHx8YLx8fGC7+/vIPHx8SXx8fGC8fHxgvHx8YLx8fGC4e/Zm7/nqP+/56j/v+eo/7/n
+        qP+956X/jt5h/47eYP+P3mL/kd9k/5LfZv+U4Gn/luBr/5fhbf+Z4W//q+aK/8fqs//I6rT/yeq2/8nq
+        t//N7Lvw8fHxgvHx8YLx8fGC8fHxgvPz84D///8G6+vrDfHx8YLx8fGC8fHxgvHx8YLv8e+Dweis87/n
+        qP+/56j/v+eo/7/nqP+d4XX/jN1e/47eYP+P3mL/kd9k/5PfZ/+U4Gn/luBr/5fhbf+86KP/xuqy/8fq
+        s//I6rX/yeq2/9Tsx8nx8fGC8fHxgvHx8YLx8fGC8PDwaAAAAAAAAAAA8fHxbPHx8YLx8fGC8fHxgvHx
+        8YLM6rrMv+eo/7/nqP+/56j/v+eo/7blmv+N3V//jN1e/47eYP+Q3mL/kd9k/5PfZ/+U4Gn/qeSH/8Xp
+        sP/G6bH/xuqy/8fqs//I6rX/5fDem/Hx8YLx8fGC8fHxgvHx8YLz8/M/AAAAAAAAAADz8/NB8fHxgvHx
+        8YLx8fGC8fHxgt3s06O/56j/v+eo/7/nqP+/56j/v+eo/7Xmmf+U32n/jN1e/47eYP+Q3mL/k99o/6zk
+        i//D6a7/xemv/8XpsP/G6bH/xuqy/8vqu+jx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xUAAAAAAAAAAPT0
+        9Bfx8fGC8fHxgvHx8YLx8fGC8fHvg8Tpsee/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+35pz/suWV/7Xm
+        mf/A6Kj/wuit/8Porf/E6a7/xemv/8XpsP/G6bH/3e3UqvHx8YLx8fGC8fHxgvHx8YLw8PBmAAAAAAAA
+        AAAAAAAAAAAAAPHx8W7x8fGC8fHxgvHx8YLx8fGC4u7cmMHnqvm/56j/v+eo/7/nqP+/56j/v+eo/7/n
+        qP+/56j/wOep/8Hoqv/C6Kz/wuit/8Porf/E6a7/xemv/9Hrwszx8fGC8fHxgvHx8YLx8fGC8fHxgvX1
+        9TEAAAAAAAAAAAAAAAAAAAAA7u7uO/Hx8YLx8fGC8fHxgvHx8YLx8fGC3e7SpMHoqfq/56j/v+eo/7/n
+        qP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz/wuit/8Porf/O67zV8PHwhPHx8YLx8fGC8fHxgvHx
+        8YLy8vJ2////BQAAAAAAAAAAAAAAAAAAAACqqqoD8PDwafHx8YLx8fGC8fHxgvHx8YLx8fGC4O/YnMTo
+        ruy/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz90uvEwe/x74Px8fGC8fHxgvHx
+        8YLx8fGC8fHxgvPz8ykAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/MW8fHxfPHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8PLuhdXtyLXF6bHlv+eo/7/nqP+/56j/v+eo/7/nqP/B6Kv0zeq8zOXv4JTx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADy8vIm8fHxgPHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLs8OmJ4e/Zm93u06Pf7def5+/hkvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxXf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AADy8vIo8/PzffHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8VnMzMwFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAD29vYb8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz83/v7+9BgICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8/PzQPLy8nnx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz84Hx8fFc9PT0GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////B/X19TLx8fFc8PDwevHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8fHxgPHx8Wv09PRE9PT0FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAA7+/vEPb29hvw8PAj7+/vH/T09Be/v78EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////8B///wAA//wAAD/wAAAP4AAAB+AA
+        AAfAAAADwAAAA4AAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAADwAAAA8AAAAPAAAAH4AAAB+AA
+        AA/wAAAP+AAAH/gAAD/+AAB//wAB///AA///+B////////////8oAAAAEAAAACAAAAABACAAAAAAAAAE
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CfDw8BH///8GAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAAu7u7i7x8fFe8PDwevHx8YLx8fGC8fHxgvDw
+        8Hvx8fFs7+/vT/Dw8CMAAAABAAAAAAAAAAAAAAAA5ubmCvLy8l/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx
+        8YLx8fGC8fHxgvHx8YLx8fGC8/PzZu7u7g8AAAAAAAAAAPHx8V3x8fGC8fHxgunv5o7Z7c200+vFytTs
+        xc7W7cnH2+7QueLu2qbu8OyH8fHxgvHx8YLx8fFu////BfHx8STx8fGC8fHxgtrtzq3D6a/8xemw/8bp
+        sv/I6rT/yeq2/8vruP/M67v/z+u++Nzu0bjx8fGC8fHxgu/v7zDx8fFI8fHxguzw6ojC56z3wuis/8Tp
+        rv/E6q3/weiq/8fqsv/J6rb/y+u5/8zru//N67z/6/HpjfHx8YLy8vJN8fHxXPHx8YLg79icv+eo/8Ho
+        qv+k4n//lOBo/5fhbf+a4XH/n+J5/7Pmlv/L67n/zOu7/+Xw353x8fGC8fHxXvHx8Vrx8fGC4O3Zm7/n
+        qP+/56j/nuF3/5HfZP+U4Gj/l+Ft/5ricf+x5pL/yeq3/8vruf/r8emN8fHxgu/v70/x8fFK8fHxguzw
+        6ojA6Kn8v+eo/6njiP+O3mD/kd9k/5Tgaf+X4W3/vuim/8jqtP/N67zr8fHxgvHx8YLy8vI68/PzK/Hx
+        8YLx8fGCx+m03L/nqP++6Kb/meBw/47eYP+S32X/q+SL/8XpsP/G6rL/1+zLvvHx8YLz8/OB8PDwEdXV
+        1Qbx8fF98fHxgt/t1Z/A56j9v+eo/7/nqP+656H/vuim/8Lorf/E6a7/yOq18Ovw6Yvx8fGC8vLyYwAA
+        AAAAAAAA8fHxR/Hx8YLx8fGC2O3NrMDnqfq/56j/v+eo/7/nqP/B6Kv/xumy7OTu3Zfx8fGC8/PzgfLy
+        8icAAAAAAAAAAP///wPz8/Nm8fHxgvHx8YLo7+SO0+zFuczquszM6bzJ1+zMru7w7Ibx8fGC8fHxgvHx
+        8UcAAAAAAAAAAAAAAAAAAAAA4+PjCfHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgfPz
+        80D///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8/PzK/Ly8mDz8/N+8fHxgvHx8YLy8vJ68vLyUezs
+        7BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAevr6w3j4+MJAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD8fwAA4AcAAMADAACAAQAAgAEAAIABAACAAQAAgAEAAIAB
+        AADAAwAAwAMAAOAHAADwDwAA/n8AAP//AAA=
+</value>
+  </data>
+</root>
\ 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 @@
         <Reference Include="System.Xml" />
     </ItemGroup>
     <ItemGroup>
+        <Compile Include="DownloadForm.cs">
+          <SubType>Form</SubType>
+        </Compile>
+        <Compile Include="DownloadForm.Designer.cs">
+          <DependentUpon>DownloadForm.cs</DependentUpon>
+        </Compile>
         <Compile Include="Program.cs" />
         <Compile Include="Properties\AssemblyInfo.cs" />
+        <EmbeddedResource Include="DownloadForm.resx">
+          <DependentUpon>DownloadForm.cs</DependentUpon>
+        </EmbeddedResource>
         <EmbeddedResource Include="Properties\Resources.resx">
             <Generator>ResXFileCodeGenerator</Generator>
             <LastGenOutput>Resources.Designer.cs</LastGenOutput>

From 655ba015a07c87e7b1a24ce8cafbe171103acad1 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sat, 8 Oct 2022 23:47:27 +0800
Subject: [PATCH 11/57] 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<DownloadItem> 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 @@
         <WarningLevel>4</WarningLevel>
     </PropertyGroup>
     <PropertyGroup>
-      <PostBuildEvent>COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "C:\Users\LouisLam\Desktop\uptime-kuma-win64\"</PostBuildEvent>
+      <PostBuildEvent>COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\"</PostBuildEvent>
     </PropertyGroup>
     <ItemGroup>
         <Reference Include="System" />
         <Reference Include="System.Core" />
+        <Reference Include="System.IO.Compression.FileSystem" />
         <Reference Include="System.Xml.Linq" />
         <Reference Include="System.Data.DataSetExtensions" />
         <Reference Include="Microsoft.CSharp" />

From a487347b3316cc3192b312a195ac44a46160d310 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sun, 9 Oct 2022 03:47:06 +0800
Subject: [PATCH 12/57] [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 <git@nlutsenko.me>
Date: Sat, 19 Nov 2022 16:10:30 -0800
Subject: [PATCH 13/57] 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 @@
                 <a :href="telegramGetUpdatesURL('withToken')" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL("masked") }}</a>
             </p>
         </div>
+
+        <div class="form-check form-switch">
+            <input v-model="$parentnotification.telegramSendSilently" class="form-check-input" type="checkbox">
+            <label class="form-check-label">{{ $t("Send Silently") }}</label>
+        </div>
+
+        <div class="form-text">
+            {{ $t("telegramSendSilentlyDescription") }}
+        </div>
     </div>
 </template>
 
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 b91fe9d96d1b8be9f6abbb241432d53fd9b61247 Mon Sep 17 00:00:00 2001
From: shyneko <tminei@ukr.net>
Date: Thu, 12 Jan 2023 15:09:05 +0200
Subject: [PATCH 14/57] Added a more telegram options such as thread id, silent
 notifications and forward protect

---
 server/notification-providers/telegram.js | 17 +++++++++---
 src/components/notifications/Telegram.vue | 34 +++++++++++++++++++++++
 src/languages/en.js                       |  6 ++++
 3 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js
index 2b057622..9c8f5750 100644
--- a/server/notification-providers/telegram.js
+++ b/server/notification-providers/telegram.js
@@ -9,11 +9,20 @@ class Telegram extends NotificationProvider {
         let okMsg = "Sent Successfully.";
 
         try {
+            const paramsObj =
+            {
+                chat_id: notification.telegramChatID,
+                text: msg,
+                disable_notification: notification.telegramSilentNotification ?? false,
+                protect_content: notification.telegramProtectContent ?? false,
+
+            };
+            // if telegramChatThread specified, then add it to paramsObj
+            if (notification.telegramChatThread && notification.telegramChatThread.length > 0) {
+                paramsObj.message_thread_id = notification.telegramChatThread;
+            }
             await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
-                params: {
-                    chat_id: notification.telegramChatID,
-                    text: msg,
-                },
+                params: paramsObj
             });
             return okMsg;
 
diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index 9daf31ac..9b373b99 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -29,6 +29,40 @@
             </p>
         </div>
     </div>
+
+    <div class="mb-3">
+        <label for="telegram-chat-thread" class="form-label">{{ $t("Thread ID") }}</label>
+
+        <div class="input-group mb-3">
+            <input id="telegram-chat-thread" v-model="$parent.notification.telegramChatThread" type="text" class="form-control" required>
+        </div>
+
+       <div class="form-text">
+            {{ $t("Thread ID Description") }}
+        </div>
+    </div>
+
+    <div class="mb-3">
+         <div class="form-check form-switch">
+            <input v-model="$parent.notification.telegramSilentNotification" class="form-check-input" type="checkbox">
+            <label class="form-check-label">{{ $t("Silent Notification") }}</label>
+        </div>
+
+        <div class="form-text">
+            {{ $t("Silent Notification Description") }}
+        </div>
+    </div>
+
+    <div class="mb-3">
+         <div class="form-check form-switch">
+            <input v-model="$parent.notification.telegramProtectContent" class="form-check-input" type="checkbox">
+            <label class="form-check-label">{{ $t("Protect Forwarding") }}</label>
+        </div>
+
+        <div class="form-text">
+            {{ $t("Protect Forwarding Description") }}
+        </div>
+    </div>
 </template>
 
 <script>
diff --git a/src/languages/en.js b/src/languages/en.js
index 7a48d0d0..f09212b8 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -216,6 +216,12 @@ export default {
     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:",
     "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",
+    "Silent Notification": "Silent Notification",
+    "Silent Notification Description": "If enabled, the notification in Telegram will be without sound.",
+    "Protect Forwarding": "Protect Forwarding",
+    "Protect Forwarding Description": "If enabled, the notification in Telegram will be protected from forwarding and saving.",
+    "Thread ID": "Thread ID (optional)",
+    "Thread ID Description": "Since November 5 2022, bots can send messages to a specific thread. You can get the thread ID by clicking on the thread name in the chat.",
     chatIDNotFound: "Chat ID is not found; please send a message to this bot first",
     webhook: "Webhook",
     "Post URL": "Post URL",

From 521356e38a2a9386c1f981c3b56c0e2e22312de1 Mon Sep 17 00:00:00 2001
From: shyneko <tminei@ukr.net>
Date: Thu, 12 Jan 2023 15:21:56 +0200
Subject: [PATCH 15/57] LINT fixes

---
 src/components/notifications/Telegram.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index 9b373b99..7a1e659e 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -37,13 +37,13 @@
             <input id="telegram-chat-thread" v-model="$parent.notification.telegramChatThread" type="text" class="form-control" required>
         </div>
 
-       <div class="form-text">
+        <div class="form-text">
             {{ $t("Thread ID Description") }}
         </div>
     </div>
 
     <div class="mb-3">
-         <div class="form-check form-switch">
+        <div class="form-check form-switch">
             <input v-model="$parent.notification.telegramSilentNotification" class="form-check-input" type="checkbox">
             <label class="form-check-label">{{ $t("Silent Notification") }}</label>
         </div>
@@ -54,7 +54,7 @@
     </div>
 
     <div class="mb-3">
-         <div class="form-check form-switch">
+        <div class="form-check form-switch">
             <input v-model="$parent.notification.telegramProtectContent" class="form-check-input" type="checkbox">
             <label class="form-check-label">{{ $t("Protect Forwarding") }}</label>
         </div>

From 27e0b1eea103060202e79aa0ae2028165d11b006 Mon Sep 17 00:00:00 2001
From: shyneko <tminei@ukr.net>
Date: Thu, 12 Jan 2023 15:52:50 +0200
Subject: [PATCH 16/57] Remove required attribute for optional field

---
 src/components/notifications/Telegram.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index 7a1e659e..66bc7e93 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -34,7 +34,7 @@
         <label for="telegram-chat-thread" class="form-label">{{ $t("Thread ID") }}</label>
 
         <div class="input-group mb-3">
-            <input id="telegram-chat-thread" v-model="$parent.notification.telegramChatThread" type="text" class="form-control" required>
+            <input id="telegram-chat-thread" v-model="$parent.notification.telegramChatThread" type="text" class="form-control">
         </div>
 
         <div class="form-text">

From e427c70fef567f6957ab681c550e71fd0f92282c Mon Sep 17 00:00:00 2001
From: shyneko <tminei@ukr.net>
Date: Thu, 12 Jan 2023 16:00:02 +0200
Subject: [PATCH 17/57] Translate fixes

---
 src/languages/en.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index f09212b8..a1ac41b7 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -219,7 +219,7 @@ export default {
     "Silent Notification": "Silent Notification",
     "Silent Notification Description": "If enabled, the notification in Telegram will be without sound.",
     "Protect Forwarding": "Protect Forwarding",
-    "Protect Forwarding Description": "If enabled, the notification in Telegram will be protected from forwarding and saving.",
+    "Protect Forwarding Description": "If enabled, the bot messages in Telegram will be protected from forwarding and saving.",
     "Thread ID": "Thread ID (optional)",
     "Thread ID Description": "Since November 5 2022, bots can send messages to a specific thread. You can get the thread ID by clicking on the thread name in the chat.",
     chatIDNotFound: "Chat ID is not found; please send a message to this bot first",

From 3adc9e65d6d5cd461abc461929f29513bc41e21a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Sat, 14 Jan 2023 16:33:21 +0300
Subject: [PATCH 18/57] Add only xml support to http monitors

---
 server/model/monitor.js   |  7 ++-----
 src/pages/EditMonitor.vue | 23 ++++++++---------------
 2 files changed, 10 insertions(+), 20 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 48b0b1d3..d93fd6dd 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -251,16 +251,13 @@ 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";
-                    } else if (this.body && (this.httpBodyEncoding === "form")) {
-                        bodyValue = this.body;
-                        contentType = "application/x-www-form-urlencoded";
+                        contentType = "text/xml; charset=utf-8";
                     }
 
                     const options = {
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index ea246c60..05b89ab0 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -438,21 +438,14 @@
                                     </select>
                                 </div>
 
-                                <!-- Encoding -->
-                                <div class="my-3">
-                                    <label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
-                                    <select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
-                                        <option value="json">
-                                            JSON
-                                        </option>
-                                        <option value="form">
-                                            x-www-form-urlencoded
-                                        </option>
-                                        <option value="xml">
-                                            XML
-                                        </option>
-                                    </select>
-                                </div>
+                              <!-- Encoding -->
+                              <div class="my-3">
+                                <label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
+                                <select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
+                                  <option value="json">JSON</option>
+                                  <option value="xml">XML</option>
+                                </select>
+                              </div>
 
                                 <!-- Body -->
                                 <div class="my-3">

From 15c64d458b69489b455ee90b13dd610446a091ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Sat, 14 Jan 2023 16:48:12 +0300
Subject: [PATCH 19/57] Fix lint

---
 src/pages/EditMonitor.vue | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index a68a1206..65680109 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -463,13 +463,13 @@
                                 </div>
 
                               <!-- Encoding -->
-                              <div class="my-3">
-                                <label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
-                                <select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
-                                  <option value="json">JSON</option>
-                                  <option value="xml">XML</option>
-                                </select>
-                              </div>
+                                <div class="my-3">
+                                    <label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
+                                    <select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
+                                      <option value="json">JSON</option>
+                                      <option value="xml">XML</option>
+                                    </select>
+                                </div>
 
                                 <!-- Body -->
                                 <div class="my-3">

From 9890a0754b409d458d7563c0d9a31dcbb20d76b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Sat, 14 Jan 2023 16:48:26 +0300
Subject: [PATCH 20/57] Fix lint

---
 src/pages/EditMonitor.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 65680109..8d5e3e9d 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -462,7 +462,7 @@
                                     </select>
                                 </div>
 
-                              <!-- Encoding -->
+                                <!-- Encoding -->
                                 <div class="my-3">
                                     <label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
                                     <select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">

From cf21aa37376bc3175f4302940b5945a0704f81be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Sat, 14 Jan 2023 16:51:07 +0300
Subject: [PATCH 21/57] Fix lint

---
 src/pages/EditMonitor.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 8d5e3e9d..8706c4aa 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -466,8 +466,8 @@
                                 <div class="my-3">
                                     <label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
                                     <select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
-                                      <option value="json">JSON</option>
-                                      <option value="xml">XML</option>
+                                        <option value="json">JSON</option>
+                                        <option value="xml">XML</option>
                                     </select>
                                 </div>
 

From aef85078eb5fbb0e48db31cd40f5d7eaf6c06792 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Fri, 20 Jan 2023 12:29:56 +0300
Subject: [PATCH 22/57] reorder fix

---
 server/model/monitor.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index c0721c48..ec56ba45 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -290,9 +290,9 @@ class Monitor extends BeanModel {
                         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) : {}),
+                            ...(contentType ? { "Content-Type": contentType } : {}),
                             ...(basicAuthHeader),
-                            ...(contentType ? { "Content-Type": contentType } : {})
+                            ...(this.headers ? JSON.parse(this.headers) : {})
                         },
                         maxRedirects: this.maxredirects,
                         validateStatus: (status) => {

From f155ec9ba884607e889402fefbb1935adbd74d48 Mon Sep 17 00:00:00 2001
From: Thomas Spalinger <spali@spali.ch>
Date: Tue, 24 Jan 2023 09:14:16 +0000
Subject: [PATCH 23/57] remember prometheus instance and expose it in
 preperation for #2491,#680 and #898

---
 server/model/monitor.js | 14 +++++++-------
 server/server.js        |  3 ---
 2 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 28fae9e6..53b7985e 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -200,7 +200,7 @@ class Monitor extends BeanModel {
         let previousBeat = null;
         let retries = 0;
 
-        let prometheus = new Prometheus(this);
+        this.prometheus = new Prometheus(this);
 
         const beat = async () => {
 
@@ -729,7 +729,7 @@ class Monitor extends BeanModel {
             await R.store(bean);
 
             log.debug("monitor", `[${this.name}] prometheus.update`);
-            prometheus.update(bean, tlsInfo);
+            this.prometheus.update(bean, tlsInfo);
 
             previousBeat = bean;
 
@@ -814,15 +814,15 @@ class Monitor extends BeanModel {
         clearTimeout(this.heartbeatInterval);
         this.isStop = true;
 
-        this.prometheus().remove();
+        this.prometheus.remove();
     }
 
     /**
-     * Get a new prometheus instance
-     * @returns {Prometheus}
+     * Get prometheus instance
+     * @returns {Prometheus|undefined}
      */
-    prometheus() {
-        return new Prometheus(this);
+    getPrometheus() {
+        return this.prometheus;
     }
 
     /**
diff --git a/server/server.js b/server/server.js
index f43008e2..4574292b 100644
--- a/server/server.js
+++ b/server/server.js
@@ -674,9 +674,6 @@ let needSetup = false;
                     throw new Error("Permission denied.");
                 }
 
-                // Reset Prometheus labels
-                server.monitorList[monitor.id]?.prometheus()?.remove();
-
                 bean.name = monitor.name;
                 bean.type = monitor.type;
                 bean.url = monitor.url;

From 2673b509a598c9dc311b00cf9205a0353c9b5609 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Wed, 25 Jan 2023 20:22:50 +0300
Subject: [PATCH 24/57] Add "Body Encoding" to en.json

---
 src/lang/en.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/lang/en.json b/src/lang/en.json
index f2f16693..d01ccd20 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -682,5 +682,6 @@
     "onebotUserOrGroupId": "Group/User ID",
     "onebotSafetyTips": "For safety, must set access token",
     "PushDeer Key": "PushDeer Key",
-    "wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} ."
+    "wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} .",
+    "Body Encoding": "Body Encoding"
 }

From b2ddb5c9eb47105d9855106960e44295e04e427c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Thu, 2 Feb 2023 19:50:14 +0300
Subject: [PATCH 25/57] Dummy commit for build

---
 src/lang/en.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lang/en.json b/src/lang/en.json
index dc5663f1..e1d7abec 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -691,5 +691,5 @@
     "PushDeer Key": "PushDeer Key",
     "wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} .",
     "Custom Monitor Type": "Custom Monitor Type",
-    "Body Encoding": "Body Encoding"
+    "Body Encoding": "Body Encoding",
 }

From 603b3a7fb6145c196114f9786637430e65e6b561 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Thu, 2 Feb 2023 19:50:29 +0300
Subject: [PATCH 26/57] Dummy commit for build

---
 src/lang/en.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lang/en.json b/src/lang/en.json
index e1d7abec..dc5663f1 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -691,5 +691,5 @@
     "PushDeer Key": "PushDeer Key",
     "wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} .",
     "Custom Monitor Type": "Custom Monitor Type",
-    "Body Encoding": "Body Encoding",
+    "Body Encoding": "Body Encoding"
 }

From 1bfb29071874651d1364edd5cd9d4138fe31d46e Mon Sep 17 00:00:00 2001
From: Haytham Salama <haythamasalama@gmail.com>
Date: Sat, 4 Feb 2023 22:53:38 +0200
Subject: [PATCH 27/57] feat: add message thread id for telegram nonfiction

---
 server/notification-providers/telegram.js | 1 +
 src/components/notifications/Telegram.vue | 5 +++++
 2 files changed, 6 insertions(+)

diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js
index 2b057622..fb53b971 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,
+                    message_thread_id: notification.telegramMessageThreadID,
                 },
             });
             return okMsg;
diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index 723bd1be..3f446dc8 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -17,6 +17,11 @@
             </button>
         </div>
 
+
+        <label for="message_thread_id" class="form-label">{{ $t("Message Thread ID") }}</label>
+        <input id="message_thread_id" v-model="$parent.notification.telegramMessageThreadID" type="text" class="form-control">
+        <p class="form-text">Message Thread ID: Optional Unique identifier for the target message thread (topic) of the forum; for forum supergroups only</p>
+
         <div class="form-text">
             {{ $t("supportTelegramChatID") }}
 

From 4323dee7815559ff22ff191ef56639c686015926 Mon Sep 17 00:00:00 2001
From: Haytham Salama <haythamasalama@gmail.com>
Date: Sat, 4 Feb 2023 22:54:19 +0200
Subject: [PATCH 28/57] feat: add message thread id to lang

---
 src/lang/ar-SY.json | 1 +
 src/lang/bg-BG.json | 1 +
 src/lang/cs-CZ.json | 1 +
 src/lang/da-DK.json | 1 +
 src/lang/de-CH.json | 1 +
 src/lang/de-DE.json | 1 +
 src/lang/el-GR.json | 1 +
 src/lang/en.json    | 1 +
 src/lang/nl-NL.json | 1 +
 src/lang/pl.json    | 1 +
 src/lang/ru-RU.json | 1 +
 src/lang/sl-SI.json | 1 +
 src/lang/th-TH.json | 1 +
 src/lang/tr-TR.json | 1 +
 src/lang/uk-UA.json | 1 +
 src/lang/vi-VN.json | 1 +
 src/lang/zh-CN.json | 1 +
 src/lang/zh-HK.json | 1 +
 src/lang/zh-TW.json | 1 +
 19 files changed, 19 insertions(+)

diff --git a/src/lang/ar-SY.json b/src/lang/ar-SY.json
index d852a690..98dacd5f 100644
--- a/src/lang/ar-SY.json
+++ b/src/lang/ar-SY.json
@@ -215,6 +215,7 @@
     "Bot Token": "رمز الروبوت",
     "wayToGetTelegramToken": "يمكنك الحصول على رمز من {0}.",
     "Chat ID": "معرف الدردشة",
+    "Message Thread ID": "معرف المواضيع",
     "supportTelegramChatID": "دعم الدردشة المباشرة / معرف الدردشة للقناة",
     "wayToGetTelegramChatID": "يمكنك الحصول على معرف الدردشة الخاص بك عن طريق إرسال رسالة إلى الروبوت والانتقال إلى عنوان URL هذا لعرض Chat_id",
     "YOUR BOT TOKEN HERE": "رمز الروبوت الخاص بك هنا",
diff --git a/src/lang/bg-BG.json b/src/lang/bg-BG.json
index 3a5f532d..db81e21f 100644
--- a/src/lang/bg-BG.json
+++ b/src/lang/bg-BG.json
@@ -210,6 +210,7 @@
     "Bot Token": "Бот токен",
     "wayToGetTelegramToken": "Можете да получите токен от {0}.",
     "Chat ID": "Чат ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Поддържа Direct Chat / Group / Channel's Chat ID",
     "wayToGetTelegramChatID": "Можете да получите вашето чат ID, като изпратите съобщение на бота, след което е нужно да посетите този URL адрес за да го видите:",
     "YOUR BOT TOKEN HERE": "ВАШИЯТ БОТ ТОКЕН ТУК",
diff --git a/src/lang/cs-CZ.json b/src/lang/cs-CZ.json
index c6b28312..6a0738da 100644
--- a/src/lang/cs-CZ.json
+++ b/src/lang/cs-CZ.json
@@ -215,6 +215,7 @@
     "Bot Token": "Token robota",
     "wayToGetTelegramToken": "Token můžete získat od {0}.",
     "Chat ID": "ID chatu",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Podpora přímého chatu / skupiny / ID chatu kanálu",
     "wayToGetTelegramChatID": "ID chatu můžete získat tak, že robotovi zašlete zprávu a přejdete na tuto adresu URL, kde zobrazíte chat_id:",
     "YOUR BOT TOKEN HERE": "SEM ZADEJTE TOKEN VAŠEHO CHATBOTA",
diff --git a/src/lang/da-DK.json b/src/lang/da-DK.json
index 679431c3..4cfc222c 100644
--- a/src/lang/da-DK.json
+++ b/src/lang/da-DK.json
@@ -208,6 +208,7 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Du kan få et token fra {0}.",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Support Direct Chat / Group / Channel's Chat ID",
     "wayToGetTelegramChatID": "Du kan få dit chat-ID ved at sende en besked til bot'en og gå til denne URL for at se chat_id'et:",
     "YOUR BOT TOKEN HERE": "DIT BOT TOKEN HER",
diff --git a/src/lang/de-CH.json b/src/lang/de-CH.json
index 85da35e0..42cb3c9e 100644
--- a/src/lang/de-CH.json
+++ b/src/lang/de-CH.json
@@ -214,6 +214,7 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Hier kannst du einen Token erhalten {0}.",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Unterstützt Direkt Chat / Gruppe / Kanal Chat-ID's",
     "wayToGetTelegramChatID": "Du kannst die Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id: zu sehen.",
     "YOUR BOT TOKEN HERE": "HIER DEIN BOT TOKEN",
diff --git a/src/lang/de-DE.json b/src/lang/de-DE.json
index 45b5ae56..d4a023ec 100644
--- a/src/lang/de-DE.json
+++ b/src/lang/de-DE.json
@@ -214,6 +214,7 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Hier kannst du einen Token erhalten {0}.",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Unterstützt Direkt Chat / Gruppe / Kanal Chat-ID's",
     "wayToGetTelegramChatID": "Du kannst die Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id: zu sehen.",
     "YOUR BOT TOKEN HERE": "HIER DEIN BOT TOKEN",
diff --git a/src/lang/el-GR.json b/src/lang/el-GR.json
index c77d6158..7031290b 100644
--- a/src/lang/el-GR.json
+++ b/src/lang/el-GR.json
@@ -198,6 +198,7 @@
     "Bot Token": "Διακριτικό Bot",
     "wayToGetTelegramToken": "Μπορείτε να πάρετε ένα διακριτικό από {0}.",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Support Direct Chat / Group / Channel's Chat ID",
     "wayToGetTelegramChatID": "Μπορείτε να λάβετε το αναγνωριστικό συνομιλίας σας στέλνοντας ένα μήνυμα στο bot και μεταβαίνοντας σε αυτήν τη διεύθυνση URL για να προβάλετε το chat_id:",
     "YOUR BOT TOKEN HERE": "ΤΟ BOT ΣΑΣ ΔΙΑΚΡΙΤΙΚΌ ΕΔΩ",
diff --git a/src/lang/en.json b/src/lang/en.json
index 8a195a2a..6e26612d 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -364,6 +364,7 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "You can get a token from {0}.",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread 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:",
     "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",
diff --git a/src/lang/nl-NL.json b/src/lang/nl-NL.json
index 32c79545..e4c1da00 100644
--- a/src/lang/nl-NL.json
+++ b/src/lang/nl-NL.json
@@ -217,6 +217,7 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Je kunt een token krijgen van {0}.",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Ondersteuning Directe Chat / Groep / Kanaal Chat ID",
     "wayToGetTelegramChatID": "Je kunt je CHAT ID krijgen door een bericht te sturen naar de bot en naar deze URL te gaan om het chat_id te bekijken:",
     "YOUR BOT TOKEN HERE": "DE BOT TOKEN HIER",
diff --git a/src/lang/pl.json b/src/lang/pl.json
index ebc58797..50bb7fbf 100644
--- a/src/lang/pl.json
+++ b/src/lang/pl.json
@@ -189,6 +189,7 @@
     "Bot Token": "Token bota",
     "wayToGetTelegramToken": "Token można uzyskać z {0}.",
     "Chat ID": "Identyfikator czatu",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Czat wsparcia technicznego / Bezpośrednia rozmowa / Czat grupowy",
     "wayToGetTelegramChatID": "Możesz uzyskać swój identyfikator czatu, wysyłając wiadomość do bota i przechodząc pod ten adres URL, aby wyświetlić identyfikator czatu:",
     "YOUR BOT TOKEN HERE": "TWÓJ TOKEN BOTA",
diff --git a/src/lang/ru-RU.json b/src/lang/ru-RU.json
index 8395eedb..c38b6808 100644
--- a/src/lang/ru-RU.json
+++ b/src/lang/ru-RU.json
@@ -216,6 +216,7 @@
     "Bot Token": "Токен бота",
     "wayToGetTelegramToken": "Вы можете взять токен здесь - {0}.",
     "Chat ID": "ID чата",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Поддерживаются ID чатов, групп и каналов",
     "wayToGetTelegramChatID": "Вы можете взять ID вашего чата, отправив сообщение боту и перейдя по этому URL для просмотра chat_id:",
     "YOUR BOT TOKEN HERE": "ВАШ ТОКЕН БОТА ЗДЕСЬ",
diff --git a/src/lang/sl-SI.json b/src/lang/sl-SI.json
index f4ca81bd..bf32cbed 100644
--- a/src/lang/sl-SI.json
+++ b/src/lang/sl-SI.json
@@ -193,6 +193,7 @@
     "Bot Token": "Robotkov žetonček",
     "wayToGetTelegramToken": "Lahko dobiš žeton od {0}.",
     "Chat ID": "ID pogovora",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Direkten pogovor pomoči / Skupina / ID kanala",
     "wayToGetTelegramChatID": "Id lahko dobiš, če pošlješ sporočilo robotku in odpreš ta URL, da bi videl chat_id:",
     "YOUR BOT TOKEN HERE": "ROBOTKOV ŽETON TUKAJ",
diff --git a/src/lang/th-TH.json b/src/lang/th-TH.json
index 7ad132f5..76c2afb3 100644
--- a/src/lang/th-TH.json
+++ b/src/lang/th-TH.json
@@ -194,6 +194,7 @@
     "Bot Token": "กุญแจของบอท",
     "wayToGetTelegramToken": "คุณสามารถรับกุญแจได้จาก {0}.",
     "Chat ID": "ไอดีแชท",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "รองรับ แชทส่วนตัว, แชทกลุ่ม, ไอดีแชท",
     "wayToGetTelegramChatID": "คุณสามารถรับ ID แชทของคุณได้โดยส่งข้อความไปยังบอทและไปที่ URL นี้เพื่อดู chat_id :",
     "YOUR BOT TOKEN HERE": "กุญแจของบอทของคุณที่นี่",
diff --git a/src/lang/tr-TR.json b/src/lang/tr-TR.json
index b9bc8adc..a9070df3 100644
--- a/src/lang/tr-TR.json
+++ b/src/lang/tr-TR.json
@@ -197,6 +197,7 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "{0} adresinden bir token alabilirsiniz.",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Doğrudan Sohbet / Grup / Kanalın Sohbet Kimliğini Destekleyin",
     "wayToGetTelegramChatID": "Bot'a bir mesaj göndererek ve chat_id'yi görüntülemek için bu URL'ye giderek sohbet kimliğinizi alabilirsiniz:",
     "YOUR BOT TOKEN HERE": "BOT TOKENİNİZ BURADA",
diff --git a/src/lang/uk-UA.json b/src/lang/uk-UA.json
index fcd678a3..9ad37b66 100644
--- a/src/lang/uk-UA.json
+++ b/src/lang/uk-UA.json
@@ -216,6 +216,7 @@
     "Bot Token": "Токен бота",
     "wayToGetTelegramToken": "Ви можете взяти токен тут - {0}.",
     "Chat ID": "ID чату",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Підтримуються ID чатів, груп та каналів",
     "wayToGetTelegramChatID": "Ви можете взяти ID вашого чату, відправивши повідомлення боту і перейшовши по цьому URL для перегляду chat_id:",
     "YOUR BOT TOKEN HERE": "ВАШ ТОКЕН БОТА ТУТ",
diff --git a/src/lang/vi-VN.json b/src/lang/vi-VN.json
index 165bf1bb..4446a020 100644
--- a/src/lang/vi-VN.json
+++ b/src/lang/vi-VN.json
@@ -193,6 +193,7 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Bạn có thể lấy mã token từ",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Hỗ trợ chat trực tiếp / Nhóm / Kênh Chat ID",
     "wayToGetTelegramChatID": "Bạn có thể lấy chat id của mình bằng cách gửi tin nhắn tới bot và truy cập url này để xem chat_id:",
     "YOUR BOT TOKEN HERE": "MÃ BOT TOKEN CỦA BẠN",
diff --git a/src/lang/zh-CN.json b/src/lang/zh-CN.json
index a3393bd1..f52fab92 100644
--- a/src/lang/zh-CN.json
+++ b/src/lang/zh-CN.json
@@ -213,6 +213,7 @@
     "Bot Token": "机器人令牌",
     "wayToGetTelegramToken": "您可以从 {0} 获取 Token。",
     "Chat ID": "Chat ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "支持对话/群组/频道的 Chat ID",
     "wayToGetTelegramChatID": "您可以发送一条消息给您的机器人,然后访问此链接来查看 chat_id:",
     "YOUR BOT TOKEN HERE": "这里替换成您的 BOT TOKEN",
diff --git a/src/lang/zh-HK.json b/src/lang/zh-HK.json
index 14f25b5e..3310c995 100644
--- a/src/lang/zh-HK.json
+++ b/src/lang/zh-HK.json
@@ -211,6 +211,7 @@
     "Bot Token": "機器人權杖",
     "wayToGetTelegramToken": "您可以從 {0} 取得 Token。",
     "Chat ID": "聊天 ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "支援 對話/群組/頻道的聊天 ID",
     "wayToGetTelegramChatID": "傳送訊息給機器人,並前往以下網址以取得您的 chat ID:",
     "YOUR BOT TOKEN HERE": "在此填入您的機器人權杖",
diff --git a/src/lang/zh-TW.json b/src/lang/zh-TW.json
index 5eb0a699..992a2a0d 100644
--- a/src/lang/zh-TW.json
+++ b/src/lang/zh-TW.json
@@ -212,6 +212,7 @@
     "Bot Token": "機器人權杖",
     "wayToGetTelegramToken": "您可以從 {0} 取得權杖。",
     "Chat ID": "聊天 ID",
+    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "支援 對話/群組/頻道的聊天 ID",
     "wayToGetTelegramChatID": "傳送訊息給機器人,並前往以下網址以取得您的 chat ID:",
     "YOUR BOT TOKEN HERE": "在此填入您的機器人權杖",

From c42e5503827b283ca2e76aee43f8029b1d371549 Mon Sep 17 00:00:00 2001
From: Haytham Salama <haythamasalama@gmail.com>
Date: Sat, 4 Feb 2023 23:46:19 +0200
Subject: [PATCH 29/57] style: formats

---
 src/components/notifications/Telegram.vue | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index 3f446dc8..a1b74a8a 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -17,7 +17,6 @@
             </button>
         </div>
 
-
         <label for="message_thread_id" class="form-label">{{ $t("Message Thread ID") }}</label>
         <input id="message_thread_id" v-model="$parent.notification.telegramMessageThreadID" type="text" class="form-control">
         <p class="form-text">Message Thread ID: Optional Unique identifier for the target message thread (topic) of the forum; for forum supergroups only</p>

From 727acb32bf8423a5c56cf64776ce33eded9e5aa7 Mon Sep 17 00:00:00 2001
From: Brayan Lozano <brayan@mytide.io>
Date: Tue, 7 Feb 2023 21:18:26 -0500
Subject: [PATCH 30/57] Adds name + status + message to slack notification

---
 server/notification-providers/slack.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js
index 5a5d40cb..64a58738 100644
--- a/server/notification-providers/slack.js
+++ b/server/notification-providers/slack.js
@@ -42,7 +42,7 @@ class Slack extends NotificationProvider {
             const time = heartbeatJSON["time"];
             const textMsg = "Uptime Kuma Alert";
             let data = {
-                "text": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg,
+                "text": monitorJSON ? `${textMsg}\n${msg}` : textMsg,
                 "channel": notification.slackchannel,
                 "username": notification.slackusername,
                 "icon_emoji": notification.slackiconemo,

From d45aee450d3e01a9ee7a4a784ff99268e05fff76 Mon Sep 17 00:00:00 2001
From: Brayan Lozano <brayan@mytide.io>
Date: Tue, 7 Feb 2023 22:34:10 -0500
Subject: [PATCH 31/57] Removes unecessary ternary operator

---
 server/notification-providers/slack.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js
index 64a58738..da89f0f7 100644
--- a/server/notification-providers/slack.js
+++ b/server/notification-providers/slack.js
@@ -42,7 +42,7 @@ class Slack extends NotificationProvider {
             const time = heartbeatJSON["time"];
             const textMsg = "Uptime Kuma Alert";
             let data = {
-                "text": monitorJSON ? `${textMsg}\n${msg}` : textMsg,
+                "text": `${textMsg}\n${msg}`,
                 "channel": notification.slackchannel,
                 "username": notification.slackusername,
                 "icon_emoji": notification.slackiconemo,

From 36d160ad022706a4d4f29d22af7eb6c00df7c983 Mon Sep 17 00:00:00 2001
From: OidaTiftla <chm.stephan@outlook.com>
Date: Thu, 9 Feb 2023 09:04:47 +0100
Subject: [PATCH 32/57] Rename "consequently" to "consecutively" as suggested
 by @skylarv

https://github.com/louislam/uptime-kuma/pull/1212#issuecomment-1423373045
---
 src/lang/ar-SY.json       | 2 +-
 src/lang/bg-BG.json       | 2 +-
 src/lang/cs-CZ.json       | 2 +-
 src/lang/de-CH.json       | 2 +-
 src/lang/de-DE.json       | 2 +-
 src/lang/el-GR.json       | 2 +-
 src/lang/en.json          | 2 +-
 src/lang/es-ES.json       | 2 +-
 src/lang/fr-FR.json       | 2 +-
 src/lang/he-IL.json       | 2 +-
 src/lang/hr-HR.json       | 2 +-
 src/lang/id-ID.json       | 2 +-
 src/lang/ko-KR.json       | 2 +-
 src/lang/pl.json          | 2 +-
 src/lang/pt-BR.json       | 2 +-
 src/lang/th-TH.json       | 2 +-
 src/lang/tr-TR.json       | 2 +-
 src/lang/zh-CN.json       | 2 +-
 src/lang/zh-HK.json       | 2 +-
 src/lang/zh-TW.json       | 2 +-
 src/pages/EditMonitor.vue | 2 +-
 21 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/src/lang/ar-SY.json b/src/lang/ar-SY.json
index d852a690..fe2910dd 100644
--- a/src/lang/ar-SY.json
+++ b/src/lang/ar-SY.json
@@ -90,7 +90,7 @@
     "Heartbeat Interval": "فاصل نبضات القلب",
     "Retries": "يحاول مجدداً",
     "Heartbeat Retry Interval": "الفاصل الزمني لإعادة محاكمة نبضات القلب",
-    "Resend Notification if Down X times consequently": "إعادة تقديم الإخطار إذا انخفض x مرات بالتالي",
+    "Resend Notification if Down X times consecutively": "إعادة تقديم الإخطار إذا انخفض x مرات بالتالي",
     "Advanced": "متقدم",
     "Upside Down Mode": "وضع أسفل أسفل",
     "Max. Redirects": "الأعلى. إعادة التوجيه",
diff --git a/src/lang/bg-BG.json b/src/lang/bg-BG.json
index 3a5f532d..5bbebf9c 100644
--- a/src/lang/bg-BG.json
+++ b/src/lang/bg-BG.json
@@ -539,7 +539,7 @@
     "wayToGetLineNotifyToken": "Може да получите токен код за достъп от {0}",
     "resendEveryXTimes": "Изпращай повторно на всеки {0} пъти",
     "resendDisabled": "Повторното изпращане е изключено",
-    "Resend Notification if Down X times consequently": "Повторно изпращане на известие, ако е недостъпен X пъти последователно",
+    "Resend Notification if Down X times consecutively": "Повторно изпращане на известие, ако е недостъпен X пъти последователно",
     "Bark Group": "Bark група",
     "Bark Sound": "Bark звук",
     "HTTP Headers": "HTTP хедъри",
diff --git a/src/lang/cs-CZ.json b/src/lang/cs-CZ.json
index c6b28312..28ca79ee 100644
--- a/src/lang/cs-CZ.json
+++ b/src/lang/cs-CZ.json
@@ -90,7 +90,7 @@
     "Heartbeat Interval": "Heartbeat interval",
     "Retries": "Počet pokusů",
     "Heartbeat Retry Interval": "Interval opakování heartbeatu",
-    "Resend Notification if Down X times consequently": "Znovu zaslat oznámení, pokud je služba nedostupná Xkrát za sebou",
+    "Resend Notification if Down X times consecutively": "Znovu zaslat oznámení, pokud je služba nedostupná Xkrát za sebou",
     "Advanced": "Rozšířené",
     "Upside Down Mode": "Inverzní režim",
     "Max. Redirects": "Max. přesměrování",
diff --git a/src/lang/de-CH.json b/src/lang/de-CH.json
index 85da35e0..d8a46562 100644
--- a/src/lang/de-CH.json
+++ b/src/lang/de-CH.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 consecutively": "Benachrichtigung erneut senden, wenn Inaktiv X mal hintereinander",
     "retryCheckEverySecond": "Alle {0} Sekunden neu versuchen",
     "resendEveryXTimes": "Erneut versenden alle {0} mal",
     "resendDisabled": "Erneut versenden deaktiviert",
diff --git a/src/lang/de-DE.json b/src/lang/de-DE.json
index 45b5ae56..f6671598 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 consecutively": "Benachrichtigung erneut senden, wenn Inaktiv X mal hintereinander",
     "retryCheckEverySecond": "Alle {0} Sekunden neu versuchen",
     "resendEveryXTimes": "Erneut versenden alle {0} mal",
     "resendDisabled": "Erneut versenden deaktiviert",
diff --git a/src/lang/el-GR.json b/src/lang/el-GR.json
index c77d6158..fbd8a369 100644
--- a/src/lang/el-GR.json
+++ b/src/lang/el-GR.json
@@ -74,7 +74,7 @@
     "Heartbeat Interval": "Διάστημα καρδιακών παλμών",
     "Retries": "Επαναλήψεις",
     "Heartbeat Retry Interval": "Διάστημα επανάληψης παλμών καρδιάς",
-    "Resend Notification if Down X times consequently": "Αποστολή νέας ειδοποίησης εάν κατω X φορές κατά συνέχεια",
+    "Resend Notification if Down X times consecutively": "Αποστολή νέας ειδοποίησης εάν κατω X φορές κατά συνέχεια",
     "Advanced": "Προχωρημένα",
     "Upside Down Mode": "Ανάποδη λειτουργία",
     "Max. Redirects": "Μέγιστη. Ανακατευθύνσεις",
diff --git a/src/lang/en.json b/src/lang/en.json
index d907f4e0..0fed82c0 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -55,7 +55,7 @@
     "Heartbeat Interval": "Heartbeat Interval",
     "Retries": "Retries",
     "Heartbeat Retry Interval": "Heartbeat Retry Interval",
-    "Resend Notification if Down X times consequently": "Resend Notification if Down X times consequently",
+    "Resend Notification if Down X times consecutively": "Resend Notification if Down X times consecutively",
     "Advanced": "Advanced",
     "checkEverySecond": "Check every {0} seconds",
     "retryCheckEverySecond": "Retry every {0} seconds",
diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json
index 8fa80158..a9c8e34f 100644
--- a/src/lang/es-ES.json
+++ b/src/lang/es-ES.json
@@ -304,7 +304,7 @@
     "General Monitor Type": "Monitor Tipo General",
     "Specific Monitor Type": "Monitor Tipo Específico",
     "Monitor": "Monitores",
-    "Resend Notification if Down X times consequently": "Reenviar Notificación si Caído X veces consecutivas",
+    "Resend Notification if Down X times consecutively": "Reenviar Notificación si Caído X veces consecutivas",
     "resendEveryXTimes": "Reenviar cada {0} veces",
     "resendDisabled": "Reenvío deshabilitado",
     "needPushEvery": "Debe llamar a esta URL cada {0} segundos.",
diff --git a/src/lang/fr-FR.json b/src/lang/fr-FR.json
index ae39af3c..3580cca1 100644
--- a/src/lang/fr-FR.json
+++ b/src/lang/fr-FR.json
@@ -89,7 +89,7 @@
     "Heartbeat Interval": "Intervalle de vérification",
     "Retries": "Essais",
     "Heartbeat Retry Interval": "Réessayer l'intervalle de vérification",
-    "Resend Notification if Down X times consequently": "Renvoyer une notification si hors ligne X fois",
+    "Resend Notification if Down X times consecutively": "Renvoyer une notification si hors ligne X fois",
     "Advanced": "Avancé",
     "Upside Down Mode": "Mode inversé",
     "Max. Redirects": "Nombre maximum de redirections",
diff --git a/src/lang/he-IL.json b/src/lang/he-IL.json
index c8219ff5..c61ad77c 100644
--- a/src/lang/he-IL.json
+++ b/src/lang/he-IL.json
@@ -89,7 +89,7 @@
     "Heartbeat Interval": "מרווח פעימות",
     "Retries": "נסיונות חוזרים",
     "Heartbeat Retry Interval": "מרווח נסיונות חוזר של פעימות",
-    "Resend Notification if Down X times consequently": "שלח שוב הודעה אם ירד X פעמים כתוצאה מכך",
+    "Resend Notification if Down X times consecutively": "שלח שוב הודעה אם ירד X פעמים כתוצאה מכך",
     "Advanced": "מתקדם",
     "Upside Down Mode": "מצב הפוך",
     "Max. Redirects": "מקסימום הפניות מחדש",
diff --git a/src/lang/hr-HR.json b/src/lang/hr-HR.json
index 417b689e..b909ed55 100644
--- a/src/lang/hr-HR.json
+++ b/src/lang/hr-HR.json
@@ -378,7 +378,7 @@
     "resendEveryXTimes": "Ponovno pošalji svakih {0} puta",
     "resendDisabled": "Ponovno slanje je onemogućeno",
     "dnsPortDescription": "Port DNS poslužitelja. Zadana vrijednost je 53. Moguće je promijeniti ga u svakom trenutku.",
-    "Resend Notification if Down X times consequently": "Ponovno pošalji obavijest ako je usluga nedostupna više puta zaredom",
+    "Resend Notification if Down X times consecutively": "Ponovno pošalji obavijest ako je usluga nedostupna više puta zaredom",
     "topic": "Tema",
     "topicExplanation": "MQTT tema koja će se monitorirati",
     "successMessage": "Poruka o uspjehu",
diff --git a/src/lang/id-ID.json b/src/lang/id-ID.json
index 59a06521..ecd50672 100644
--- a/src/lang/id-ID.json
+++ b/src/lang/id-ID.json
@@ -74,7 +74,7 @@
     "Heartbeat Interval": "Jarak Waktu Heartbeat ",
     "Retries": "Coba lagi",
     "Heartbeat Retry Interval": "Jarak Waktu Heartbeat Mencoba kembali ",
-    "Resend Notification if Down X times consequently": "Kirim Ulang Notifikasi jika Tidak Aktif X kali",
+    "Resend Notification if Down X times consecutively": "Kirim Ulang Notifikasi jika Tidak Aktif X kali",
     "Advanced": "Tingkat Lanjut",
     "Upside Down Mode": "Mode Terbalik",
     "Max. Redirects": "Maksimal Pengalihan",
diff --git a/src/lang/ko-KR.json b/src/lang/ko-KR.json
index 2c2297c6..079c9f58 100644
--- a/src/lang/ko-KR.json
+++ b/src/lang/ko-KR.json
@@ -680,7 +680,7 @@
     "Passive Monitor Type": "수동 모니터링",
     "Specific Monitor Type": "특정 모니터링",
     "Monitor": "모니터",
-    "Resend Notification if Down X times consequently": "X번 중단될 경우 알림 다시 보내기",
+    "Resend Notification if Down X times consecutively": "X번 중단될 경우 알림 다시 보내기",
     "Schedule maintenance": "점검 예약하기",
     "Affected Monitors": "영향을 받는 모니터링",
     "Pick Affected Monitors...": "영향을 받는 모니터링 선택하기…",
diff --git a/src/lang/pl.json b/src/lang/pl.json
index ebc58797..27b5d72b 100644
--- a/src/lang/pl.json
+++ b/src/lang/pl.json
@@ -494,7 +494,7 @@
     "atLeastOneMonitor": "Wybierz co najmniej jeden monitor, którego dotyczy problem",
     "deleteMaintenanceMsg": "Czy na pewno chcesz usunąć tę konserwację?",
     "dnsPortDescription": "Port serwera DNS. Domyślnie 53. Możesz zmienić port w dowolnym momencie.",
-    "Resend Notification if Down X times consequently": "Wyślij ponownie powiadomienie, jeśli nie działa X razy pod rząd",
+    "Resend Notification if Down X times consecutively": "Wyślij ponownie powiadomienie, jeśli nie działa X razy pod rząd",
     "error": "błąd",
     "critical": "krytyczny",
     "wayToGetPagerDutyKey": "Możesz to uzyskać, przechodząc do Service -> Service Directory -> (wybierz usługę) -> Integrations -> Add integration. Tutaj możesz wyszukać \"Events API V2\". Więcej informacji {0}",
diff --git a/src/lang/pt-BR.json b/src/lang/pt-BR.json
index b7ebdbd4..a0c6d1fd 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 consecutively": "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: ",
diff --git a/src/lang/th-TH.json b/src/lang/th-TH.json
index 7ad132f5..1895a153 100644
--- a/src/lang/th-TH.json
+++ b/src/lang/th-TH.json
@@ -522,7 +522,7 @@
     "resendEveryXTimes": "ส่งซ้ำทุก {0} ครั้ง",
     "resendDisabled": "การส่งซ้ำถูกปิดใช้งาน",
     "dnsPortDescription": "พอร์ตของเซิร์ฟเวอร์ DNS, ค่าเริ่มต้นคือ 53, คุณสามารถเปลี่ยนพอร์ตตอนไหนก็ได้",
-    "Resend Notification if Down X times consequently": "ส่งการแจ้งเตือนซ้ำถ้าออฟไลน์ครบ X ครั้ง",
+    "Resend Notification if Down X times consecutively": "ส่งการแจ้งเตือนซ้ำถ้าออฟไลน์ครบ X ครั้ง",
     "error": "เกิดข้อผิดพลาด",
     "critical": "วิกฤต",
     "wayToGetPagerDutyKey": "คุณสามารถรับคีย์ได้โดยการไปที่ Service -> Service Directory -> (Select a service) -> Integrations -> Add integration, และค้นหา \"Events API V2\", สำหรับข้อมูลเพิ่มเติม {0}",
diff --git a/src/lang/tr-TR.json b/src/lang/tr-TR.json
index b9bc8adc..13ddd373 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 consecutively": "Sonuç olarak X kez düşerse bildirimi yeniden gönder",
     "Advanced": "Gelişmiş",
     "Upside Down Mode": "Ters/Düz Modu",
     "Max. Redirects": "Maksimum Yönlendirme",
diff --git a/src/lang/zh-CN.json b/src/lang/zh-CN.json
index a3393bd1..45529c6d 100644
--- a/src/lang/zh-CN.json
+++ b/src/lang/zh-CN.json
@@ -89,7 +89,7 @@
     "Heartbeat Interval": "心跳间隔",
     "Retries": "重试次数",
     "Heartbeat Retry Interval": "心跳重试间隔",
-    "Resend Notification if Down X times consequently": "连续失败时重复发送通知的间隔次数",
+    "Resend Notification if Down X times consecutively": "连续失败时重复发送通知的间隔次数",
     "Advanced": "高级",
     "Upside Down Mode": "反转监控",
     "Max. Redirects": "最大重定向次数",
diff --git a/src/lang/zh-HK.json b/src/lang/zh-HK.json
index 14f25b5e..024781a3 100644
--- a/src/lang/zh-HK.json
+++ b/src/lang/zh-HK.json
@@ -397,7 +397,7 @@
     "affectedStatusPages": "在已選取的狀態頁中顯示此維護訊息",
     "Primary Base URL": "主要 Base URL",
     "Passive Monitor Type": "被動監測器類型",
-    "Resend Notification if Down X times consequently": "若 X 次心跳皆離線,重新傳送通知",
+    "Resend Notification if Down X times consecutively": "若 X 次心跳皆離線,重新傳送通知",
     "Game": "遊戲",
     "Specific Monitor Type": "特定監測器類型",
     "Monitor": "監測器 | 監測器",
diff --git a/src/lang/zh-TW.json b/src/lang/zh-TW.json
index 5eb0a699..3e208215 100644
--- a/src/lang/zh-TW.json
+++ b/src/lang/zh-TW.json
@@ -89,7 +89,7 @@
     "Heartbeat Interval": "心跳間隔",
     "Retries": "重試次數",
     "Heartbeat Retry Interval": "心跳重試間隔",
-    "Resend Notification if Down X times consequently": "若 X 次心跳皆離線,重新傳送通知",
+    "Resend Notification if Down X times consecutively": "若 X 次心跳皆離線,重新傳送通知",
     "Advanced": "進階",
     "Upside Down Mode": "顛倒模式",
     "Max. Redirects": "最大重新導向次數",
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index e9cbd824..297759c5 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -340,7 +340,7 @@
 
                             <div class="my-3">
                                 <label for="resend-interval" class="form-label">
-                                    {{ $t("Resend Notification if Down X times consequently") }}
+                                    {{ $t("Resend Notification if Down X times consecutively") }}
                                     <span v-if="monitor.resendInterval > 0">({{ $t("resendEveryXTimes", [ monitor.resendInterval ]) }})</span>
                                     <span v-else>({{ $t("resendDisabled") }})</span>
                                 </label>

From 3439074835d1f40dd56ca2b6531dcbbec53071b0 Mon Sep 17 00:00:00 2001
From: Nelson Chan <chakflying@hotmail.com>
Date: Thu, 9 Feb 2023 17:42:02 +0800
Subject: [PATCH 33/57] Feat: Use message to improve errror status code

---
 server/routers/api-router.js         | 14 +++++++-------
 server/routers/status-page-router.js | 18 ++++++------------
 server/util-server.js                | 24 ++++++++++++++++++------
 3 files changed, 31 insertions(+), 25 deletions(-)

diff --git a/server/routers/api-router.js b/server/routers/api-router.js
index 665163ae..2d5f9661 100644
--- a/server/routers/api-router.js
+++ b/server/routers/api-router.js
@@ -1,5 +1,5 @@
 let express = require("express");
-const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin, send403 } = require("../util-server");
+const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin, sendHttpError } = require("../util-server");
 const { R } = require("redbean-node");
 const apicache = require("../modules/apicache");
 const Monitor = require("../model/monitor");
@@ -175,7 +175,7 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
         response.type("image/svg+xml");
         response.send(svg);
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
@@ -242,7 +242,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
         response.type("image/svg+xml");
         response.send(svg);
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
@@ -303,7 +303,7 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request,
         response.type("image/svg+xml");
         response.send(svg);
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
@@ -373,7 +373,7 @@ router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async (
         response.type("image/svg+xml");
         response.send(svg);
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
@@ -464,7 +464,7 @@ router.get("/api/badge/:id/cert-exp", cache("5 minutes"), async (request, respon
         response.type("image/svg+xml");
         response.send(svg);
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
@@ -536,7 +536,7 @@ router.get("/api/badge/:id/response", cache("5 minutes"), async (request, respon
         response.type("image/svg+xml");
         response.send(svg);
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
diff --git a/server/routers/status-page-router.js b/server/routers/status-page-router.js
index de075db8..28cf5f4c 100644
--- a/server/routers/status-page-router.js
+++ b/server/routers/status-page-router.js
@@ -2,7 +2,7 @@ let express = require("express");
 const apicache = require("../modules/apicache");
 const { UptimeKumaServer } = require("../uptime-kuma-server");
 const StatusPage = require("../model/status_page");
-const { allowDevAllOrigin, send403 } = require("../util-server");
+const { allowDevAllOrigin, sendHttpError } = require("../util-server");
 const { R } = require("redbean-node");
 const Monitor = require("../model/monitor");
 
@@ -44,10 +44,7 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons
         let statusPageData = await StatusPage.getStatusPageData(statusPage);
 
         if (!statusPageData) {
-            response.statusCode = 404;
-            response.json({
-                msg: "Not Found"
-            });
+            sendHttpError(response, "Not Found");
             return;
         }
 
@@ -55,7 +52,7 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons
         response.json(statusPageData);
 
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
@@ -103,7 +100,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
         });
 
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
@@ -119,10 +116,7 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async
         ]);
 
         if (!statusPage) {
-            response.statusCode = 404;
-            response.json({
-                msg: "Not Found"
-            });
+            sendHttpError(response, "Not Found");
             return;
         }
 
@@ -141,7 +135,7 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async
         });
 
     } catch (error) {
-        send403(response, error.message);
+        sendHttpError(response, error.message);
     }
 });
 
diff --git a/server/util-server.js b/server/util-server.js
index edce2890..c83c8cd1 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -730,15 +730,27 @@ exports.filterAndJoin = (parts, connector = "") => {
 };
 
 /**
- * Send a 403 response
+ * Send an Error response
  * @param {Object} res Express response object
  * @param {string} [msg=""] Message to send
  */
-module.exports.send403 = (res, msg = "") => {
-    res.status(403).json({
-        "status": "fail",
-        "msg": msg,
-    });
+module.exports.sendHttpError = (res, msg = "") => {
+    if (msg.includes("SQLITE_BUSY") || msg.includes("SQLITE_LOCKED")) {
+        res.status(503).json({
+            "status": "fail",
+            "msg": msg,
+        });
+    } else if (msg.toLowerCase().includes("not found")) {
+        res.status(404).json({
+            "status": "fail",
+            "msg": msg,
+        });
+    } else {
+        res.status(403).json({
+            "status": "fail",
+            "msg": msg,
+        });
+    }
 };
 
 function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {

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 34/57] 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<LooseObject<any>[]>}
      */
     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 c9b4a7f53ef502a5ea58faf970102b6098d0b479 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Fri, 17 Feb 2023 17:59:43 +0300
Subject: [PATCH 35/57] 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?= <omer@farukgenc.com>
Date: Sat, 18 Feb 2023 17:02:56 +0300
Subject: [PATCH 36/57] 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:", [ `
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <Uptime>Kuma</Uptime>
+  </soap:Body>
+</soap:Envelope>` ]);
+            }
             return this.$t("Example:", [ `
 {
     "key": "value"

From 3ab0faee91d5aae3b0d0dc6a54b8ceeafa64e337 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?= <omer@farukgenc.com>
Date: Sat, 18 Feb 2023 22:18:48 +0300
Subject: [PATCH 37/57] 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 <louislam@users.noreply.github.com>
Date: Mon, 20 Feb 2023 20:26:53 +0800
Subject: [PATCH 38/57] 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 @@
-<?xml version="1.0" encoding="utf-8" ?>
+<?xml version="1.0" encoding="utf-8"?>
 <configuration>
     <startup>
         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
@@ -7,4 +7,28 @@
     <System.Windows.Forms.ApplicationConfigurationSection>
         <add key="DpiAwareness" value="PerMonitorV2" />
     </System.Windows.Forms.ApplicationConfigurationSection>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Diagnostics.Tracing" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
 </configuration>
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<Version>(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 @@
+<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
+  <Costura />
+</Weavers>
\ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
+  <xs:element name="Weavers">
+    <xs:complexType>
+      <xs:all>
+        <xs:element name="Costura" minOccurs="0" maxOccurs="1">
+          <xs:complexType>
+            <xs:all>
+              <xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+            </xs:all>
+            <xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="DisableCompression" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="DisableCleanup" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="LoadAtModuleInit" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="ExcludeAssemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IncludeAssemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="Unmanaged32Assemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="Unmanaged64Assemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="PreloadOrder" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+          </xs:complexType>
+        </xs:element>
+      </xs:all>
+      <xs:attribute name="VerifyAssembly" type="xs:boolean">
+        <xs:annotation>
+          <xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="VerifyIgnoreCodes" type="xs:string">
+        <xs:annotation>
+          <xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="GenerateXsd" type="xs:boolean">
+        <xs:annotation>
+          <xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
+        </xs:annotation>
+      </xs:attribute>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>
\ 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 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.props" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" />
     <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
     <PropertyGroup>
         <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -38,18 +39,104 @@
       <PostBuildEvent>COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\"</PostBuildEvent>
     </PropertyGroup>
     <ItemGroup>
+        <Reference Include="Costura, Version=5.7.0.0, Culture=neutral, processorArchitecture=MSIL">
+          <HintPath>packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll</HintPath>
+        </Reference>
+        <Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
+        </Reference>
+        <Reference Include="mscorlib" />
+        <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+          <HintPath>packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
+        </Reference>
         <Reference Include="System" />
+        <Reference Include="System.AppContext, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll</HintPath>
+        </Reference>
+        <Reference Include="System.ComponentModel.Composition" />
+        <Reference Include="System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Console.4.3.0\lib\net46\System.Console.dll</HintPath>
+        </Reference>
         <Reference Include="System.Core" />
+        <Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+          <HintPath>packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Diagnostics.Tracing, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
+        </Reference>
         <Reference Include="System.IO.Compression.FileSystem" />
+        <Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Linq, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Linq.4.3.0\lib\net463\System.Linq.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Linq.Expressions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Numerics" />
+        <Reference Include="System.Reflection, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime.InteropServices, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll</HintPath>
+        </Reference>
         <Reference Include="System.Xml.Linq" />
         <Reference Include="System.Data.DataSetExtensions" />
         <Reference Include="Microsoft.CSharp" />
         <Reference Include="System.Data" />
         <Reference Include="System.Deployment" />
         <Reference Include="System.Drawing" />
-        <Reference Include="System.Net.Http" />
         <Reference Include="System.Windows.Forms" />
         <Reference Include="System.Xml" />
+        <Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
+        </Reference>
     </ItemGroup>
     <ItemGroup>
         <Compile Include="DownloadForm.cs">
@@ -60,6 +147,7 @@
         </Compile>
         <Compile Include="Program.cs" />
         <Compile Include="Properties\AssemblyInfo.cs" />
+        <Compile Include="Version.cs" />
         <EmbeddedResource Include="DownloadForm.resx">
           <DependentUpon>DownloadForm.cs</DependentUpon>
         </EmbeddedResource>
@@ -75,6 +163,7 @@
         <None Include="..\..\public\favicon.ico">
           <Link>favicon.ico</Link>
         </None>
+        <None Include="packages.config" />
         <None Include="Properties\Settings.settings">
             <Generator>SettingsSingleFileGenerator</Generator>
             <LastGenOutput>Settings.Designer.cs</LastGenOutput>
@@ -88,5 +177,18 @@
     <ItemGroup>
         <None Include="App.config" />
     </ItemGroup>
+    <ItemGroup>
+      <Content Include=".gitignore" />
+    </ItemGroup>
     <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+    <Import Project="packages\Fody.6.5.5\build\Fody.targets" Condition="Exists('packages\Fody.6.5.5\build\Fody.targets')" />
+    <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+      <PropertyGroup>
+        <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
+      </PropertyGroup>
+      <Error Condition="!Exists('packages\Fody.6.5.5\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Fody.6.5.5\build\Fody.targets'))" />
+      <Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.props'))" />
+      <Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.targets'))" />
+    </Target>
+    <Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.targets" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" />
 </Project>
\ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Costura.Fody" version="5.7.0" targetFramework="net472" developmentDependency="true" />
+  <package id="Fody" version="6.5.5" targetFramework="net472" developmentDependency="true" />
+  <package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net472" />
+  <package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net472" />
+  <package id="NETStandard.Library" version="1.6.1" targetFramework="net472" />
+  <package id="Newtonsoft.Json" version="13.0.2" targetFramework="net472" />
+  <package id="System.AppContext" version="4.3.0" targetFramework="net472" />
+  <package id="System.Collections" version="4.3.0" targetFramework="net472" />
+  <package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net472" />
+  <package id="System.Console" version="4.3.0" targetFramework="net472" />
+  <package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net472" />
+  <package id="System.Diagnostics.DiagnosticSource" version="4.3.0" targetFramework="net472" />
+  <package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net472" />
+  <package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net472" />
+  <package id="System.Globalization" version="4.3.0" targetFramework="net472" />
+  <package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net472" />
+  <package id="System.IO" version="4.3.0" targetFramework="net472" />
+  <package id="System.IO.Compression" version="4.3.0" targetFramework="net472" />
+  <package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net472" />
+  <package id="System.IO.FileSystem" version="4.3.0" targetFramework="net472" />
+  <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net472" />
+  <package id="System.Linq" version="4.3.0" targetFramework="net472" />
+  <package id="System.Linq.Expressions" version="4.3.0" targetFramework="net472" />
+  <package id="System.Net.Http" version="4.3.0" targetFramework="net472" />
+  <package id="System.Net.Primitives" version="4.3.0" targetFramework="net472" />
+  <package id="System.Net.Sockets" version="4.3.0" targetFramework="net472" />
+  <package id="System.ObjectModel" version="4.3.0" targetFramework="net472" />
+  <package id="System.Reflection" version="4.3.0" targetFramework="net472" />
+  <package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net472" />
+  <package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net472" />
+  <package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net472" />
+  <package id="System.Runtime" version="4.3.0" targetFramework="net472" />
+  <package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net472" />
+  <package id="System.Runtime.Handles" version="4.3.0" targetFramework="net472" />
+  <package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net472" />
+  <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net472" />
+  <package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net472" />
+  <package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net472" />
+  <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net472" />
+  <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net472" />
+  <package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net472" />
+  <package id="System.Text.Encoding" version="4.3.0" targetFramework="net472" />
+  <package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net472" />
+  <package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net472" />
+  <package id="System.Threading" version="4.3.0" targetFramework="net472" />
+  <package id="System.Threading.Tasks" version="4.3.0" targetFramework="net472" />
+  <package id="System.Threading.Timer" version="4.3.0" targetFramework="net472" />
+  <package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net472" />
+  <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net472" />
+</packages>
\ No newline at end of file

From 372c6b90783ec3fdc7ac0ebbd7a58f6a0de2ecdd Mon Sep 17 00:00:00 2001
From: Andreas Brett <andreasbrett@users.noreply.github.com>
Date: Mon, 20 Feb 2023 16:09:17 +0100
Subject: [PATCH 39/57] 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 @@
                 <div class="my-3">
                     <label for="description" class="form-label">{{ $t("Description") }}</label>
                     <textarea id="description" v-model="config.description" class="form-control"></textarea>
+                    <div class="form-text">
+                        {{ $t("markdownSupported") }}
+                    </div>
                 </div>
 
                 <!-- Footer Text -->
@@ -258,7 +261,9 @@
 
             <!-- Description -->
             <strong v-if="editMode">{{ $t("Description") }}:</strong>
-            <Editable v-model="config.description" :contenteditable="editMode" tag="div" class="mb-4 description" />
+            <Editable v-if="enableEditMode" v-model="config.description" :contenteditable="editMode" tag="div" class="mb-4 description" />
+            <!-- eslint-disable-next-line vue/no-v-html-->
+            <div v-if="! enableEditMode" class="alert-heading p-2" v-html="descriptionHTML"></div>
 
             <div v-if="editMode" class="mb-4">
                 <div>
@@ -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 <louislam@users.noreply.github.com>
Date: Mon, 20 Feb 2023 23:15:49 +0800
Subject: [PATCH 40/57] 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 <louislam@users.noreply.github.com>
Date: Tue, 21 Feb 2023 02:50:25 +0800
Subject: [PATCH 41/57] 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<Version>(new WebClient().DownloadString("https://uptime.kuma.pet/version"));
-
+            var versionJson = new WebClient().DownloadString("https://uptime.kuma.pet/version");
+            var versionObj = JsonConvert.DeserializeObject<Version>(versionJson);
 
             var nodeVersion = versionObj.nodejs;
             var uptimeKumaVersion = versionObj.latest;
+            var hasUpdateFile = File.Exists("update");
 
             if (!Directory.Exists("node")) {
                 downloadQueue.Enqueue(new DownloadItem {
@@ -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<Version>(File.ReadAllText("version.json"));
+
+                var versionJson = new WebClient().DownloadString("https://uptime.kuma.pet/version");
+                var latestVersionObj = JsonConvert.DeserializeObject<Version>(versionJson);
+
+                // Compare version, if the latest version is newer, then update
+                if (new System.Version(latestVersionObj.latest).CompareTo(new System.Version(currentVersionObj.latest)) > 0) {
+                    var result = MessageBox.Show("A new version is available. Do you want to update?", "Update", MessageBoxButtons.YesNo);
+                    if (result == DialogResult.Yes) {
+                        // Create a empty file `update`, so the app will download the core files again at startup
+                        File.Create("update").Close();
+
+                        trayIcon.Visible = false;
+                        process?.Kill();
+
+                        // Restart the app, it will download the core files again at startup
+                        Application.Restart();
+                    }
+                } else {
+                    MessageBox.Show("You are using the latest version.");
+                }
+            }
+
+
         }
 
         void VisitGitHub(object sender, EventArgs e)

From 0e38391454f012c6f0e87ab0e768b6aa15ab0ee8 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Tue, 21 Feb 2023 03:11:29 +0800
Subject: [PATCH 42/57] 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 @@
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
     <startup>
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
     </startup>
 
-    <System.Windows.Forms.ApplicationConfigurationSection>
-        <add key="DpiAwareness" value="PerMonitorV2" />
-    </System.Windows.Forms.ApplicationConfigurationSection>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-    <Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.props" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" />
-    <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
-    <PropertyGroup>
-        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-        <ProjectGuid>{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}</ProjectGuid>
-        <OutputType>WinExe</OutputType>
-        <RootNamespace>UptimeKuma</RootNamespace>
-        <AssemblyName>uptime-kuma</AssemblyName>
-        <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
-        <FileAlignment>512</FileAlignment>
-        <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-        <Deterministic>true</Deterministic>
-        <ApplicationIcon>..\..\public\favicon.ico</ApplicationIcon>
-        <LangVersion>9</LangVersion>
-    </PropertyGroup>
-    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-        <PlatformTarget>AnyCPU</PlatformTarget>
-        <DebugSymbols>true</DebugSymbols>
-        <DebugType>full</DebugType>
-        <Optimize>false</Optimize>
-        <OutputPath>bin\Debug\</OutputPath>
-        <DefineConstants>DEBUG;TRACE</DefineConstants>
-        <ErrorReport>prompt</ErrorReport>
-        <WarningLevel>4</WarningLevel>
-    </PropertyGroup>
-    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-        <PlatformTarget>AnyCPU</PlatformTarget>
-        <DebugType>pdbonly</DebugType>
-        <Optimize>true</Optimize>
-        <OutputPath>bin\Release\</OutputPath>
-        <DefineConstants>TRACE</DefineConstants>
-        <ErrorReport>prompt</ErrorReport>
-        <WarningLevel>4</WarningLevel>
-    </PropertyGroup>
-    <PropertyGroup>
-      <PostBuildEvent>COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\"</PostBuildEvent>
-    </PropertyGroup>
-    <ItemGroup>
-        <Reference Include="Costura, Version=5.7.0.0, Culture=neutral, processorArchitecture=MSIL">
-          <HintPath>packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll</HintPath>
-        </Reference>
-        <Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
-        </Reference>
-        <Reference Include="mscorlib" />
-        <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
-          <HintPath>packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
-        </Reference>
-        <Reference Include="System" />
-        <Reference Include="System.AppContext, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll</HintPath>
-        </Reference>
-        <Reference Include="System.ComponentModel.Composition" />
-        <Reference Include="System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Console.4.3.0\lib\net46\System.Console.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Core" />
-        <Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
-          <HintPath>packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Diagnostics.Tracing, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll</HintPath>
-        </Reference>
-        <Reference Include="System.IO, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
-        </Reference>
-        <Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
-          <HintPath>packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
-        </Reference>
-        <Reference Include="System.IO.Compression.FileSystem" />
-        <Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
-          <HintPath>packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath>
-        </Reference>
-        <Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath>
-        </Reference>
-        <Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Linq, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Linq.4.3.0\lib\net463\System.Linq.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Linq.Expressions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Numerics" />
-        <Reference Include="System.Reflection, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Runtime.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Runtime.InteropServices, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll</HintPath>
-        </Reference>
-        <Reference Include="System.Xml.Linq" />
-        <Reference Include="System.Data.DataSetExtensions" />
-        <Reference Include="Microsoft.CSharp" />
-        <Reference Include="System.Data" />
-        <Reference Include="System.Deployment" />
-        <Reference Include="System.Drawing" />
-        <Reference Include="System.Windows.Forms" />
-        <Reference Include="System.Xml" />
-        <Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
-        </Reference>
-    </ItemGroup>
-    <ItemGroup>
-        <Compile Include="DownloadForm.cs">
-          <SubType>Form</SubType>
-        </Compile>
-        <Compile Include="DownloadForm.Designer.cs">
-          <DependentUpon>DownloadForm.cs</DependentUpon>
-        </Compile>
-        <Compile Include="Program.cs" />
-        <Compile Include="Properties\AssemblyInfo.cs" />
-        <Compile Include="Version.cs" />
-        <EmbeddedResource Include="DownloadForm.resx">
-          <DependentUpon>DownloadForm.cs</DependentUpon>
-        </EmbeddedResource>
-        <EmbeddedResource Include="Properties\Resources.resx">
-            <Generator>ResXFileCodeGenerator</Generator>
-            <LastGenOutput>Resources.Designer.cs</LastGenOutput>
-            <SubType>Designer</SubType>
-        </EmbeddedResource>
-        <Compile Include="Properties\Resources.Designer.cs">
-            <AutoGen>True</AutoGen>
-            <DependentUpon>Resources.resx</DependentUpon>
-        </Compile>
-        <None Include="..\..\public\favicon.ico">
-          <Link>favicon.ico</Link>
-        </None>
-        <None Include="packages.config" />
-        <None Include="Properties\Settings.settings">
-            <Generator>SettingsSingleFileGenerator</Generator>
-            <LastGenOutput>Settings.Designer.cs</LastGenOutput>
-        </None>
-        <Compile Include="Properties\Settings.Designer.cs">
-            <AutoGen>True</AutoGen>
-            <DependentUpon>Settings.settings</DependentUpon>
-            <DesignTimeSharedInput>True</DesignTimeSharedInput>
-        </Compile>
-    </ItemGroup>
-    <ItemGroup>
-        <None Include="App.config" />
-    </ItemGroup>
-    <ItemGroup>
-      <Content Include=".gitignore" />
-    </ItemGroup>
-    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-    <Import Project="packages\Fody.6.5.5\build\Fody.targets" Condition="Exists('packages\Fody.6.5.5\build\Fody.targets')" />
-    <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
-      <PropertyGroup>
-        <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
-      </PropertyGroup>
-      <Error Condition="!Exists('packages\Fody.6.5.5\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Fody.6.5.5\build\Fody.targets'))" />
-      <Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.props'))" />
-      <Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.targets'))" />
-    </Target>
-    <Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.targets" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" />
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.props" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" />
+    <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+    <PropertyGroup>
+        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+        <ProjectGuid>{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}</ProjectGuid>
+        <OutputType>WinExe</OutputType>
+        <RootNamespace>UptimeKuma</RootNamespace>
+        <AssemblyName>uptime-kuma</AssemblyName>
+        <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+        <FileAlignment>512</FileAlignment>
+        <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+        <Deterministic>true</Deterministic>
+        <ApplicationIcon>..\..\public\favicon.ico</ApplicationIcon>
+        <LangVersion>9</LangVersion>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugSymbols>true</DebugSymbols>
+        <DebugType>full</DebugType>
+        <Optimize>false</Optimize>
+        <OutputPath>bin\Debug\</OutputPath>
+        <DefineConstants>DEBUG;TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugType>pdbonly</DebugType>
+        <Optimize>true</Optimize>
+        <OutputPath>bin\Release\</OutputPath>
+        <DefineConstants>TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <PropertyGroup>
+        <ApplicationManifest>app.manifest</ApplicationManifest>
+    </PropertyGroup>
+    <PropertyGroup>
+      <PostBuildEvent>COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\"</PostBuildEvent>
+    </PropertyGroup>
+    <ItemGroup>
+        <Reference Include="Costura, Version=5.7.0.0, Culture=neutral, processorArchitecture=MSIL">
+          <HintPath>packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll</HintPath>
+        </Reference>
+        <Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
+        </Reference>
+        <Reference Include="mscorlib" />
+        <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+          <HintPath>packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
+        </Reference>
+        <Reference Include="System" />
+        <Reference Include="System.AppContext, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll</HintPath>
+        </Reference>
+        <Reference Include="System.ComponentModel.Composition" />
+        <Reference Include="System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Console.4.3.0\lib\net46\System.Console.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Core" />
+        <Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+          <HintPath>packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Diagnostics.Tracing, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO.Compression.FileSystem" />
+        <Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath>
+        </Reference>
+        <Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Linq, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Linq.4.3.0\lib\net463\System.Linq.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Linq.Expressions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Numerics" />
+        <Reference Include="System.Reflection, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime.InteropServices, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Xml.Linq" />
+        <Reference Include="System.Data.DataSetExtensions" />
+        <Reference Include="Microsoft.CSharp" />
+        <Reference Include="System.Data" />
+        <Reference Include="System.Deployment" />
+        <Reference Include="System.Drawing" />
+        <Reference Include="System.Windows.Forms" />
+        <Reference Include="System.Xml" />
+        <Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
+        </Reference>
+    </ItemGroup>
+    <ItemGroup>
+        <Compile Include="DownloadForm.cs">
+          <SubType>Form</SubType>
+        </Compile>
+        <Compile Include="DownloadForm.Designer.cs">
+          <DependentUpon>DownloadForm.cs</DependentUpon>
+        </Compile>
+        <Compile Include="Program.cs" />
+        <Compile Include="Properties\AssemblyInfo.cs" />
+        <Compile Include="Version.cs" />
+        <EmbeddedResource Include="DownloadForm.resx">
+          <DependentUpon>DownloadForm.cs</DependentUpon>
+        </EmbeddedResource>
+        <EmbeddedResource Include="Properties\Resources.resx">
+            <Generator>ResXFileCodeGenerator</Generator>
+            <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+            <SubType>Designer</SubType>
+        </EmbeddedResource>
+        <Compile Include="Properties\Resources.Designer.cs">
+            <AutoGen>True</AutoGen>
+            <DependentUpon>Resources.resx</DependentUpon>
+        </Compile>
+        <None Include="..\..\public\favicon.ico">
+          <Link>favicon.ico</Link>
+        </None>
+        <None Include="packages.config" />
+        <None Include="Properties\Settings.settings">
+            <Generator>SettingsSingleFileGenerator</Generator>
+            <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+        </None>
+        <Compile Include="Properties\Settings.Designer.cs">
+            <AutoGen>True</AutoGen>
+            <DependentUpon>Settings.settings</DependentUpon>
+            <DesignTimeSharedInput>True</DesignTimeSharedInput>
+        </Compile>
+    </ItemGroup>
+    <ItemGroup>
+        <None Include="App.config" />
+    </ItemGroup>
+    <ItemGroup>
+      <Content Include=".gitignore" />
+      <Content Include="app.manifest" />
+    </ItemGroup>
+    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+    <Import Project="packages\Fody.6.5.5\build\Fody.targets" Condition="Exists('packages\Fody.6.5.5\build\Fody.targets')" />
+    <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+      <PropertyGroup>
+        <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
+      </PropertyGroup>
+      <Error Condition="!Exists('packages\Fody.6.5.5\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Fody.6.5.5\build\Fody.targets'))" />
+      <Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.props'))" />
+      <Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.targets'))" />
+    </Target>
+    <Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.targets" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" />
 </Project>
\ 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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+    <asmv3:application>
+        <asmv3:windowsSettings>
+            <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+            <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+        </asmv3:windowsSettings>
+    </asmv3:application>
+    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+        <security>
+            <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+                <!-- UAC Manifest Options
+                     If you want to change the Windows User Account Control level replace the
+                     requestedExecutionLevel node with one of the following.
+
+                <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+                <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
+                <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
+
+                    Specifying requestedExecutionLevel element will disable file and registry virtualization.
+                    Remove this element if your application requires this virtualization for backwards
+                    compatibility.
+                -->
+                <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+            </requestedPrivileges>
+        </security>
+    </trustInfo>
+</assembly>

From 02ddb58686a56146aa3545307067350a45eb2de5 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Tue, 21 Feb 2023 15:16:54 +0800
Subject: [PATCH 43/57] 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 @@
         <Compile Include="DownloadForm.Designer.cs">
           <DependentUpon>DownloadForm.cs</DependentUpon>
         </Compile>
+        <Compile Include="FS.cs" />
         <Compile Include="Program.cs" />
         <Compile Include="Properties\AssemblyInfo.cs" />
         <Compile Include="Version.cs" />

From dad21065cf3e6045bd3a6fcf6a2b5d20f0b9d08b Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Tue, 21 Feb 2023 15:45:18 +0800
Subject: [PATCH 44/57] 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 <louislam@users.noreply.github.com>
Date: Tue, 21 Feb 2023 16:03:41 +0800
Subject: [PATCH 45/57] [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 @@
       </dependentAssembly>
       <dependentAssembly>
         <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.1" newVersion="4.1.1.1" />
       </dependentAssembly>
       <dependentAssembly>
         <assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
         <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
       </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
+      </dependentAssembly>
     </assemblyBinding>
   </runtime>
 </configuration>
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 @@
         <Reference Include="System.AppContext, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
           <HintPath>packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll</HintPath>
         </Reference>
+        <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+          <HintPath>packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
+        </Reference>
         <Reference Include="System.ComponentModel.Composition" />
-        <Reference Include="System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Console.4.3.0\lib\net46\System.Console.dll</HintPath>
+        <Reference Include="System.Console, Version=4.0.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Console.4.3.1\lib\net46\System.Console.dll</HintPath>
         </Reference>
         <Reference Include="System.Core" />
-        <Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
-          <HintPath>packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
+        <Reference Include="System.Diagnostics.DiagnosticSource, Version=7.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+          <HintPath>packages\System.Diagnostics.DiagnosticSource.7.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
         </Reference>
         <Reference Include="System.Diagnostics.Tracing, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
           <HintPath>packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll</HintPath>
@@ -92,21 +95,30 @@
         <Reference Include="System.Linq.Expressions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
           <HintPath>packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll</HintPath>
         </Reference>
-        <Reference Include="System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll</HintPath>
+        <Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+          <HintPath>packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Net.Http, Version=4.1.1.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll</HintPath>
         </Reference>
         <Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
           <HintPath>packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
         </Reference>
         <Reference Include="System.Numerics" />
+        <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
+        </Reference>
         <Reference Include="System.Reflection, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
           <HintPath>packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll</HintPath>
         </Reference>
-        <Reference Include="System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
+        <Reference Include="System.Runtime, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll</HintPath>
         </Reference>
-        <Reference Include="System.Runtime.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll</HintPath>
+        <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+        </Reference>
+        <Reference Include="System.Runtime.Extensions, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll</HintPath>
         </Reference>
         <Reference Include="System.Runtime.InteropServices, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
           <HintPath>packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll</HintPath>
@@ -115,7 +127,7 @@
           <HintPath>packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
         </Reference>
         <Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
+          <HintPath>packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
         </Reference>
         <Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
           <HintPath>packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
@@ -123,11 +135,11 @@
         <Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
           <HintPath>packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
         </Reference>
-        <Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
+        <Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <HintPath>packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
         </Reference>
         <Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll</HintPath>
+          <HintPath>packages\System.Text.RegularExpressions.4.3.1\lib\net463\System.Text.RegularExpressions.dll</HintPath>
         </Reference>
         <Reference Include="System.Xml.Linq" />
         <Reference Include="System.Data.DataSetExtensions" />
@@ -138,7 +150,7 @@
         <Reference Include="System.Windows.Forms" />
         <Reference Include="System.Xml" />
         <Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
-          <HintPath>packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
+          <HintPath>packages\System.Xml.ReaderWriter.4.3.1\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
         </Reference>
     </ItemGroup>
     <ItemGroup>
@@ -186,14 +198,16 @@
       <Content Include="app.manifest" />
     </ItemGroup>
     <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-    <Import Project="packages\Fody.6.5.5\build\Fody.targets" Condition="Exists('packages\Fody.6.5.5\build\Fody.targets')" />
     <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
       <PropertyGroup>
         <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
       </PropertyGroup>
-      <Error Condition="!Exists('packages\Fody.6.5.5\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Fody.6.5.5\build\Fody.targets'))" />
       <Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.props'))" />
       <Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.targets'))" />
+      <Error Condition="!Exists('packages\Fody.6.6.4\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Fody.6.6.4\build\Fody.targets'))" />
+      <Error Condition="!Exists('packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets'))" />
     </Target>
     <Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.targets" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" />
+    <Import Project="packages\Fody.6.6.4\build\Fody.targets" Condition="Exists('packages\Fody.6.6.4\build\Fody.targets')" />
+    <Import Project="packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" />
 </Project>
\ 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 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="Costura.Fody" version="5.7.0" targetFramework="net472" developmentDependency="true" />
-  <package id="Fody" version="6.5.5" targetFramework="net472" developmentDependency="true" />
-  <package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net472" />
+  <package id="Fody" version="6.6.4" targetFramework="net472" developmentDependency="true" />
+  <package id="Microsoft.NETCore.Platforms" version="7.0.0" targetFramework="net472" />
   <package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net472" />
-  <package id="NETStandard.Library" version="1.6.1" targetFramework="net472" />
+  <package id="NETStandard.Library" version="2.0.3" targetFramework="net472" />
   <package id="Newtonsoft.Json" version="13.0.2" targetFramework="net472" />
   <package id="System.AppContext" version="4.3.0" targetFramework="net472" />
+  <package id="System.Console" version="4.3.1" targetFramework="net472" />
+  <package id="System.Diagnostics.DiagnosticSource" version="7.0.1" targetFramework="net472" />
+  <package id="System.Net.Http" version="4.3.4" targetFramework="net472" />
+  <package id="System.Runtime.Extensions" version="4.3.1" targetFramework="net472" />
+  <package id="System.Security.Cryptography.Algorithms" version="4.3.1" targetFramework="net472" />
+  <package id="System.Security.Cryptography.X509Certificates" version="4.3.2" targetFramework="net472" />
+  <package id="System.Text.RegularExpressions" version="4.3.1" targetFramework="net472" />
+  <package id="System.Xml.ReaderWriter" version="4.3.1" targetFramework="net472" />
+  <package id="System.Memory" version="4.5.5" targetFramework="net472" />
+  <package id="System.Net.Primitives" version="4.3.1" targetFramework="net472" />
+  <package id="System.Runtime" version="4.3.1" targetFramework="net472" />
+  <package id="System.Buffers" version="4.5.1" targetFramework="net472" />
   <package id="System.Collections" version="4.3.0" targetFramework="net472" />
   <package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net472" />
-  <package id="System.Console" version="4.3.0" targetFramework="net472" />
   <package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net472" />
-  <package id="System.Diagnostics.DiagnosticSource" version="4.3.0" targetFramework="net472" />
   <package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net472" />
   <package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net472" />
   <package id="System.Globalization" version="4.3.0" targetFramework="net472" />
@@ -23,30 +33,24 @@
   <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net472" />
   <package id="System.Linq" version="4.3.0" targetFramework="net472" />
   <package id="System.Linq.Expressions" version="4.3.0" targetFramework="net472" />
-  <package id="System.Net.Http" version="4.3.0" targetFramework="net472" />
-  <package id="System.Net.Primitives" version="4.3.0" targetFramework="net472" />
   <package id="System.Net.Sockets" version="4.3.0" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
   <package id="System.ObjectModel" version="4.3.0" targetFramework="net472" />
   <package id="System.Reflection" version="4.3.0" targetFramework="net472" />
   <package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net472" />
   <package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net472" />
   <package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net472" />
-  <package id="System.Runtime" version="4.3.0" targetFramework="net472" />
-  <package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" />
   <package id="System.Runtime.Handles" version="4.3.0" targetFramework="net472" />
   <package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net472" />
   <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net472" />
   <package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net472" />
-  <package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net472" />
   <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net472" />
   <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net472" />
-  <package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net472" />
   <package id="System.Text.Encoding" version="4.3.0" targetFramework="net472" />
   <package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net472" />
-  <package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net472" />
   <package id="System.Threading" version="4.3.0" targetFramework="net472" />
   <package id="System.Threading.Tasks" version="4.3.0" targetFramework="net472" />
   <package id="System.Threading.Timer" version="4.3.0" targetFramework="net472" />
-  <package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net472" />
   <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net472" />
 </packages>
\ No newline at end of file

From de7df46aa801c725426489a951d5f777d9ef55c7 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Wed, 22 Feb 2023 04:27:12 +0800
Subject: [PATCH 46/57] 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 @@
                         <div class="mb-3">
                             <label for="notification-type" class="form-label">{{ $t("Notification Type") }}</label>
                             <select id="notification-type" v-model="notification.type" class="form-select">
-                                <option v-for="type in notificationTypes" :key="type" :value="type">{{ $t(type) }}</option>
+                                <option v-for="(name, type) in notificationNameList" :key="type" :value="type">{{ name }}</option>
                             </select>
                         </div>
 
@@ -67,7 +67,7 @@
     </Confirm>
 </template>
 
-<script lang="ts">
+<script>
 import { Modal } from "bootstrap";
 
 import Confirm from "./Confirm.vue";
@@ -103,7 +103,71 @@ export default {
                 return null;
             }
             return NotificationFormList[this.notification.type];
-        }
+        },
+
+        notificationNameList() {
+            let list = {
+                "alerta": "Alerta",
+                "AlertNow": "AlertNow",
+                "AliyunSMS": "AliyunSMS (阿里云短信服务)",
+                "apprise": this.$t("apprise"),
+                "Bark": "Bark",
+                "clicksendsms": "ClickSend SMS",
+                "DingDing": "DingDing (钉钉自定义机器人)",
+                "discord": "Discord",
+                "Feishu": "Feishu (飞书)",
+                "FreeMobile": "FreeMobile",
+                "GoogleChat": "Google Chat (Google Workspace)",
+                "gorush": "Gorush",
+                "gotify": "Gotify",
+                "HomeAssistant": "Home Assistant",
+                "Kook": "Kook",
+                "line": "LINE Messenger",
+                "LineNotify": "LINE Notify",
+                "lunasea": "LunaSea",
+                "matrix": "Matrix",
+                "mattermost": "Mattermost",
+                "ntfy": "Ntfy",
+                "octopush": "Octopush",
+                "OneBot": "OneBot",
+                "PagerDuty": "PagerDuty",
+                "promosms": "PromoSMS",
+                "pushbullet": "Pushbullet",
+                "PushByTechulus": "Push by Techulus",
+                "PushDeer": "PushDeer",
+                "pushover": "Pushover",
+                "pushy": "Pushy",
+                "rocket.chat": "Rocket.Chat",
+                "serwersms": "SerwerSMS.pl",
+                "signal": "Signal",
+                "SMSManager": "SmsManager (smsmanager.cz)",
+                "slack": "Slack",
+                "squadcast": "SquadCast",
+                "SMSEagle": "SMSEagle",
+                "smtp": this.$t("smtp"),
+                "stackfield": "Stackfield",
+                "teams": "Microsoft Teams",
+                "telegram": "Telegram",
+                "Splunk": "Splunk",
+                "webhook": "Webhook",
+                "WeCom": "WeCom (企业微信群机器人)",
+                "GoAlert": "GoAlert",
+                "ServerChan": "ServerChan (Server酱)",
+                "ZohoCliq": "ZohoCliq"
+            };
+
+            // Sort by notification name
+            // No idea how, but it works
+            // https://stackoverflow.com/questions/1069666/sorting-object-property-by-values
+            const sortable = Object.entries(list)
+                .sort(([ , a ], [ , b ]) => a - b)
+                .reduce((r, [ k, v ]) => ({
+                    ...r,
+                    [k]: v
+                }), {});
+
+            return sortable;
+        },
     },
 
     watch: {
@@ -203,6 +267,7 @@ export default {
          * @return {string}
          */
         getUniqueDefaultName(notificationKey) {
+            /*
             let index = 1;
             let name = "";
             do {
@@ -211,7 +276,8 @@ export default {
                     number: index++
                 });
             } while (this.$root.notificationList.find(it => it.name === name));
-            return name;
+            return name;*/
+            return "123";
         }
     },
 };
diff --git a/src/components/notifications/Gorush.vue b/src/components/notifications/Gorush.vue
index b53be2d2..315ee677 100644
--- a/src/components/notifications/Gorush.vue
+++ b/src/components/notifications/Gorush.vue
@@ -16,7 +16,7 @@
     <div class="mb-3">
         <label for="gorush-platform" class="form-label">{{ $t("Platform") }}</label><span style="color: red;"><sup>*</sup></span>
         <select id="gorush-platform" v-model="$parent.notification.gorushPlatform" class="form-select">
-            <option value="ios">{{ $t("iOS") }}</option>
+            <option value="ios">iOS</option>
             <option value="android">{{ $t("Android") }}</option>
             <option value="huawei">{{ $t("Huawei") }}</option>
         </select>
diff --git a/src/lang/en.json b/src/lang/en.json
index 1e3242ca..97941a5b 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -186,7 +186,6 @@
     "defaultNotificationName": "My {notification} Alert ({number})",
     "here": "here",
     "Required": "Required",
-    "webhook": "Webhook",
     "Post URL": "Post URL",
     "Content Type": "Content Type",
     "webhookJsonDesc": "{0} is good for any modern HTTP servers such as Express.js",
@@ -360,7 +359,6 @@
     "Workstation": "Workstation",
     "Packet Size": "Packet Size",
     "telegram": "Telegram",
-    "ZohoCliq": "ZohoCliq",
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "You can get a token from {0}.",
     "Chat ID": "Chat ID",
@@ -388,7 +386,6 @@
     "backupOutdatedWarning": "Deprecated: Since a lot of features were added and this backup feature is a bit unmaintained, it cannot generate or restore a complete backup.",
     "backupRecommend": "Please backup the volume or the data folder (./data/) directly instead.",
     "Optional": "Optional",
-    "squadcast": "Squadcast",
     "or": "or",
     "recurringInterval": "Interval",
     "Recurring": "Recurring",
@@ -530,28 +527,11 @@
     "pushoversounds none": "None (silent)",
     "pushyAPIKey": "Secret API Key",
     "pushyToken": "Device token",
-    "discord": "Discord",
-    "teams": "Microsoft Teams",
-    "signal": "Signal",
-    "gotify": "Gotify",
-    "slack": "Slack",
-    "rocket.chat": "Rocket.Chat",
-    "pushover": "Pushover",
-    "pushy": "Pushy",
-    "PushByTechulus": "Push by Techulus",
-    "octopush": "Octopush",
-    "promosms": "PromoSMS",
-    "clicksendsms": "ClickSend SMS",
-    "lunasea": "LunaSea",
     "apprise": "Apprise (Support 50+ Notification services)",
     "GoogleChat": "Google Chat (Google Workspace only)",
-    "pushbullet": "Pushbullet",
-    "Kook": "Kook",
     "wayToGetKookBotToken": "Create application and get your bot token at {0}",
     "wayToGetKookGuildID": "Switch on 'Developer Mode' in Kook setting, and right click the guild to get its ID",
     "Guild ID": "Guild ID",
-    "line": "Line Messenger",
-    "mattermost": "Mattermost",
     "User Key": "User Key",
     "Device": "Device",
     "Message Title": "Message Title",
@@ -586,12 +566,10 @@
     "SendKey": "SendKey",
     "SMSManager API Docs": "SMSManager API Docs ",
     "Gateway Type": "Gateway Type",
-    "SMSManager": "SMSManager",
     "You can divide numbers with": "You can divide numbers with",
     "Base URL": "Base URL",
     "goAlertInfo": "GoAlert is a An open source application for on-call scheduling, automated escalations and notifications (like SMS or voice calls). Automatically engage the right person, the right way, and at the right time! {0}",
     "goAlertIntegrationKeyInfo": "Get generic API integration key for the service in this format \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" usually the value of token parameter of copied URL.",
-    "goAlert": "GoAlert",
     "AccessKeyId": "AccessKey ID",
     "SecretAccessKey": "AccessKey Secret",
     "PhoneNumbers": "PhoneNumbers",
@@ -606,7 +584,6 @@
     "For safety, must use secret key": "For safety, must use secret key",
     "Device Token": "Device Token",
     "Platform": "Platform",
-    "iOS": "iOS",
     "Android": "Android",
     "Huawei": "Huawei",
     "High": "High",
@@ -617,7 +594,6 @@
     "Proxy Protocol": "Proxy Protocol",
     "Proxy Server": "Proxy Server",
     "Proxy server has authentication": "Proxy server has authentication",
-    "matrix": "Matrix",
     "promosmsTypeEco": "SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients.",
     "promosmsTypeFlash": "SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients.",
     "promosmsTypeFull": "SMS FULL - Premium tier of SMS, You can use your Sender Name (You need to register name first). Reliable for alerts.",
@@ -653,19 +629,15 @@
     "do nothing": "do nothing",
     "auto acknowledged": "auto acknowledged",
     "auto resolve": "auto resolve",
-    "gorush": "Gorush",
-    "alerta": "Alerta",
     "alertaApiEndpoint": "API Endpoint",
     "alertaEnvironment": "Environment",
     "alertaApiKey": "API Key",
     "alertaAlertState": "Alert State",
     "alertaRecoverState": "Recover State",
-    "serwersms": "SerwerSMS.pl",
     "serwersmsAPIUser": "API Username (incl. webapi_ prefix)",
     "serwersmsAPIPassword": "API Password",
     "serwersmsPhoneNumber": "Phone number",
     "serwersmsSenderName": "SMS Sender Name (registered via customer portal)",
-    "smseagle": "SMSEagle",
     "smseagleTo": "Phone number(s)",
     "smseagleGroup": "Phonebook group name(s)",
     "smseagleContact": "Phonebook contact name(s)",
@@ -675,7 +647,6 @@
     "smseagleUrl": "Your SMSEagle device URL",
     "smseagleEncoding": "Send as Unicode",
     "smseaglePriority": "Message priority (0-9, default = 0)",
-    "stackfield": "Stackfield",
     "Recipient Number": "Recipient Number",
     "From Name/Number": "From Name/Number",
     "Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.",
diff --git a/src/lang/zh-CN.json b/src/lang/zh-CN.json
index f3a222dc..a0539593 100644
--- a/src/lang/zh-CN.json
+++ b/src/lang/zh-CN.json
@@ -272,7 +272,6 @@
     "apprise": "Apprise (支持 50+ 种通知服务)",
     "GoogleChat": "Google Chat(仅 Google Workspace)",
     "pushbullet": "Pushbullet",
-    "AliyunSMS": "阿里云短信服务",
     "Kook": "Kook",
     "wayToGetKookBotToken": "在 {0} 创建应用并获取机器人 Token",
     "wayToGetKookGuildID": "在 Kook 设置中打开“开发者模式”,然后右键点击频道可获取其 ID",
@@ -448,7 +447,6 @@
     "Bark Endpoint": "Bark 接入点",
     "Bark Group": "Bark 群组",
     "Bark Sound": "Bark 铃声",
-    "DingDing": "钉钉自定义机器人",
     "WebHookUrl": "钉钉自定义机器人 Webhook 地址",
     "SecretKey": "钉钉自定义机器人加签密钥",
     "For safety, must use secret key": "出于安全考虑,必须使用加签密钥",

From 7da48b27a57015d83341070bff1aa7cd9376ed47 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Wed, 22 Feb 2023 04:29:43 +0800
Subject: [PATCH 47/57] Fix `getUniqueDefaultName`

---
 src/components/NotificationDialog.vue | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue
index 02ddce80..9631538e 100644
--- a/src/components/NotificationDialog.vue
+++ b/src/components/NotificationDialog.vue
@@ -267,17 +267,16 @@ export default {
          * @return {string}
          */
         getUniqueDefaultName(notificationKey) {
-            /*
+
             let index = 1;
             let name = "";
             do {
                 name = this.$t("defaultNotificationName", {
-                    notification: this.$t(notificationKey).replace(/\(.+\)/, "").trim(),
+                    notification: this.notificationNameList[notificationKey].replace(/\(.+\)/, "").trim(),
                     number: index++
                 });
             } while (this.$root.notificationList.find(it => it.name === name));
-            return name;*/
-            return "123";
+            return name;
         }
     },
 };

From df5da0054e8b12ba44e8a9ec0a5d8ad9d1ee790d Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Wed, 22 Feb 2023 04:40:03 +0800
Subject: [PATCH 48/57] Remove more keys

---
 src/lang/en.json | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/lang/en.json b/src/lang/en.json
index 97941a5b..cfbad0b6 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -358,7 +358,6 @@
     "Domain": "Domain",
     "Workstation": "Workstation",
     "Packet Size": "Packet Size",
-    "telegram": "Telegram",
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "You can get a token from {0}.",
     "Chat ID": "Chat ID",
@@ -653,7 +652,6 @@
     "Octopush API Version": "Octopush API Version",
     "Legacy Octopush-DM": "Legacy Octopush-DM",
     "ntfy Topic": "ntfy Topic",
-    "HomeAssistant": "Home Assistant",
     "onebotHttpAddress": "OneBot HTTP Address",
     "onebotMessageType": "OneBot Message Type",
     "onebotGroupMessage": "Group",

From 7c8cff7708cef68017875175eaae2d02e15a15af Mon Sep 17 00:00:00 2001
From: Nelson Chan <chakflying@hotmail.com>
Date: Thu, 23 Feb 2023 17:02:16 +0800
Subject: [PATCH 49/57] Fix: Add null check for injected HTML

---
 src/pages/StatusPage.vue | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue
index f4a6eb76..edf32561 100644
--- a/src/pages/StatusPage.vue
+++ b/src/pages/StatusPage.vue
@@ -502,15 +502,27 @@ export default {
         },
 
         incidentHTML() {
-            return DOMPurify.sanitize(marked(this.incident.content));
+            if (this.incident.content != null) {
+                return DOMPurify.sanitize(marked(this.incident.content));
+            } else {
+                return "";
+            }
         },
 
         descriptionHTML() {
-            return DOMPurify.sanitize(marked(this.config.description));
+            if (this.config.description != null) {
+                return DOMPurify.sanitize(marked(this.config.description));
+            } else {
+                return "";
+            }
         },
 
         footerHTML() {
-            return DOMPurify.sanitize(marked(this.config.footerText));
+            if (this.config.footerText != null) {
+                return DOMPurify.sanitize(marked(this.config.footerText));
+            } else {
+                return "";
+            }
         },
     },
     watch: {

From fa7f75a930606d4365039395eb2894cb739ec674 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Thu, 23 Feb 2023 18:01:42 +0800
Subject: [PATCH 50/57] Organize notification list

---
 src/components/NotificationDialog.vue | 63 ++++++++++++++++++---------
 src/lang/en.json                      |  1 +
 2 files changed, 44 insertions(+), 20 deletions(-)

diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue
index 9631538e..c3851b56 100644
--- a/src/components/NotificationDialog.vue
+++ b/src/components/NotificationDialog.vue
@@ -13,7 +13,10 @@
                         <div class="mb-3">
                             <label for="notification-type" class="form-label">{{ $t("Notification Type") }}</label>
                             <select id="notification-type" v-model="notification.type" class="form-select">
-                                <option v-for="(name, type) in notificationNameList" :key="type" :value="type">{{ name }}</option>
+                                <option v-for="(name, type) in notificationNameList.regularList" :key="type" :value="type">{{ name }}</option>
+                                <optgroup :label="$t('notificationRegional')">
+                                    <option v-for="(name, type) in notificationNameList.regionalList" :key="type" :value="type">{{ name }}</option>
+                                </optgroup>
                             </select>
                         </div>
 
@@ -106,17 +109,13 @@ export default {
         },
 
         notificationNameList() {
-            let list = {
+            let regularList = {
                 "alerta": "Alerta",
                 "AlertNow": "AlertNow",
-                "AliyunSMS": "AliyunSMS (阿里云短信服务)",
                 "apprise": this.$t("apprise"),
                 "Bark": "Bark",
                 "clicksendsms": "ClickSend SMS",
-                "DingDing": "DingDing (钉钉自定义机器人)",
                 "discord": "Discord",
-                "Feishu": "Feishu (飞书)",
-                "FreeMobile": "FreeMobile",
                 "GoogleChat": "Google Chat (Google Workspace)",
                 "gorush": "Gorush",
                 "gotify": "Gotify",
@@ -131,16 +130,12 @@ export default {
                 "octopush": "Octopush",
                 "OneBot": "OneBot",
                 "PagerDuty": "PagerDuty",
-                "promosms": "PromoSMS",
                 "pushbullet": "Pushbullet",
                 "PushByTechulus": "Push by Techulus",
-                "PushDeer": "PushDeer",
                 "pushover": "Pushover",
                 "pushy": "Pushy",
                 "rocket.chat": "Rocket.Chat",
-                "serwersms": "SerwerSMS.pl",
                 "signal": "Signal",
-                "SMSManager": "SmsManager (smsmanager.cz)",
                 "slack": "Slack",
                 "squadcast": "SquadCast",
                 "SMSEagle": "SMSEagle",
@@ -150,23 +145,51 @@ export default {
                 "telegram": "Telegram",
                 "Splunk": "Splunk",
                 "webhook": "Webhook",
-                "WeCom": "WeCom (企业微信群机器人)",
                 "GoAlert": "GoAlert",
-                "ServerChan": "ServerChan (Server酱)",
                 "ZohoCliq": "ZohoCliq"
             };
 
+            // Put notifications here if it's not supported in most regions or its documentation is not in English
+            let regionalList = {
+                "AliyunSMS": "AliyunSMS (阿里云短信服务)",
+                "DingDing": "DingDing (钉钉自定义机器人)",
+                "Feishu": "Feishu (飞书)",
+                "FreeMobile": "FreeMobile (mobile.free.fr)",
+                "PushDeer": "PushDeer",
+                "promosms": "PromoSMS",
+                "serwersms": "SerwerSMS.pl",
+                "SMSManager": "SmsManager (smsmanager.cz)",
+                "WeCom": "WeCom (企业微信群机器人)",
+                "ServerChan": "ServerChan (Server酱)",
+            };
+
             // Sort by notification name
             // No idea how, but it works
             // https://stackoverflow.com/questions/1069666/sorting-object-property-by-values
-            const sortable = Object.entries(list)
-                .sort(([ , a ], [ , b ]) => a - b)
-                .reduce((r, [ k, v ]) => ({
-                    ...r,
-                    [k]: v
-                }), {});
+            let sort = (list2) => {
+                return Object.entries(list2)
+                    .sort(([ , a ], [ , b ]) => a.localeCompare(b))
+                    .reduce((r, [ k, v ]) => ({
+                        ...r,
+                        [k]: v
+                    }), {});
+            };
 
-            return sortable;
+            return {
+                regularList: sort(regularList),
+                regionalList: sort(regionalList),
+            };
+        },
+
+        notificationFullNameList() {
+            let list = {};
+            for (let [ key, value ] of Object.entries(this.notificationNameList.regularList)) {
+                list[key] = value;
+            }
+            for (let [ key, value ] of Object.entries(this.notificationNameList.regionalList)) {
+                list[key] = value;
+            }
+            return list;
         },
     },
 
@@ -272,7 +295,7 @@ export default {
             let name = "";
             do {
                 name = this.$t("defaultNotificationName", {
-                    notification: this.notificationNameList[notificationKey].replace(/\(.+\)/, "").trim(),
+                    notification: this.notificationFullNameList[notificationKey].replace(/\(.+\)/, "").trim(),
                     number: index++
                 });
             } while (this.$root.notificationList.find(it => it.name === name));
diff --git a/src/lang/en.json b/src/lang/en.json
index cfbad0b6..de86111a 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -432,6 +432,7 @@
     "uninstall": "Uninstall",
     "uninstalling": "Uninstalling",
     "confirmUninstallPlugin": "Are you sure want to uninstall this plugin?",
+    "notificationRegional": "Regional",
     "smtp": "Email (SMTP)",
     "secureOptionNone": "None / STARTTLS (25, 587)",
     "secureOptionTLS": "TLS (465)",

From 7e3734af53458f086df0a3daa79425532ea393e5 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Thu, 23 Feb 2023 20:59:24 +0800
Subject: [PATCH 51/57] Better handling

---
 server/notification-providers/telegram.js | 14 +++++++++-----
 src/components/notifications/Telegram.vue |  8 ++++----
 src/lang/ar-SY.json                       |  2 +-
 src/lang/bg-BG.json                       |  1 -
 src/lang/cs-CZ.json                       |  1 -
 src/lang/da-DK.json                       |  1 -
 src/lang/de-CH.json                       |  1 -
 src/lang/de-DE.json                       |  1 -
 src/lang/el-GR.json                       |  1 -
 src/lang/en.json                          |  3 ++-
 src/lang/nl-NL.json                       |  1 -
 src/lang/pl.json                          |  1 -
 src/lang/ru-RU.json                       |  1 -
 src/lang/sl-SI.json                       |  1 -
 src/lang/th-TH.json                       |  1 -
 src/lang/tr-TR.json                       |  1 -
 src/lang/uk-UA.json                       |  1 -
 src/lang/vi-VN.json                       |  1 -
 src/lang/zh-CN.json                       |  1 -
 src/lang/zh-HK.json                       |  1 -
 src/lang/zh-TW.json                       |  1 -
 21 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js
index fb53b971..7f46b3fc 100644
--- a/server/notification-providers/telegram.js
+++ b/server/notification-providers/telegram.js
@@ -9,12 +9,16 @@ class Telegram extends NotificationProvider {
         let okMsg = "Sent Successfully.";
 
         try {
+            let params = {
+                chat_id: notification.telegramChatID,
+                text: msg,
+            };
+            if (notification.telegramMessageThreadID) {
+                params.message_thread_id = notification.telegramMessageThreadID;
+            }
+
             await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
-                params: {
-                    chat_id: notification.telegramChatID,
-                    text: msg,
-                    message_thread_id: notification.telegramMessageThreadID,
-                },
+                params: params,
             });
             return okMsg;
 
diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index a1b74a8a..042774ac 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -17,10 +17,6 @@
             </button>
         </div>
 
-        <label for="message_thread_id" class="form-label">{{ $t("Message Thread ID") }}</label>
-        <input id="message_thread_id" v-model="$parent.notification.telegramMessageThreadID" type="text" class="form-control">
-        <p class="form-text">Message Thread ID: Optional Unique identifier for the target message thread (topic) of the forum; for forum supergroups only</p>
-
         <div class="form-text">
             {{ $t("supportTelegramChatID") }}
 
@@ -32,6 +28,10 @@
                 <a :href="telegramGetUpdatesURL('withToken')" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL("masked") }}</a>
             </p>
         </div>
+
+        <label for="message_thread_id" class="form-label">{{ $t("telegramMessageThreadID") }}</label>
+        <input id="message_thread_id" v-model="$parent.notification.telegramMessageThreadID" type="text" class="form-control">
+        <p class="form-text">{{ $t("telegramMessageThreadIDDescription") }}</p>
     </div>
 </template>
 
diff --git a/src/lang/ar-SY.json b/src/lang/ar-SY.json
index b44ed206..3a4cf140 100644
--- a/src/lang/ar-SY.json
+++ b/src/lang/ar-SY.json
@@ -215,7 +215,7 @@
     "Bot Token": "رمز الروبوت",
     "wayToGetTelegramToken": "يمكنك الحصول على رمز من {0}.",
     "Chat ID": "معرف الدردشة",
-    "Message Thread ID": "معرف المواضيع",
+    "telegramMessageThreadID": "معرف المواضيع",
     "supportTelegramChatID": "دعم الدردشة المباشرة / معرف الدردشة للقناة",
     "wayToGetTelegramChatID": "يمكنك الحصول على معرف الدردشة الخاص بك عن طريق إرسال رسالة إلى الروبوت والانتقال إلى عنوان URL هذا لعرض Chat_id",
     "YOUR BOT TOKEN HERE": "رمز الروبوت الخاص بك هنا",
diff --git a/src/lang/bg-BG.json b/src/lang/bg-BG.json
index 23e0e549..ae2cdce6 100644
--- a/src/lang/bg-BG.json
+++ b/src/lang/bg-BG.json
@@ -210,7 +210,6 @@
     "Bot Token": "Бот токен",
     "wayToGetTelegramToken": "Можете да получите токен от {0}.",
     "Chat ID": "Чат ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Поддържа Direct Chat / Group / Channel's Chat ID",
     "wayToGetTelegramChatID": "Можете да получите вашето чат ID, като изпратите съобщение на бота, след което е нужно да посетите този URL адрес за да го видите:",
     "YOUR BOT TOKEN HERE": "ВАШИЯТ БОТ ТОКЕН ТУК",
diff --git a/src/lang/cs-CZ.json b/src/lang/cs-CZ.json
index 5981971d..dc8e2637 100644
--- a/src/lang/cs-CZ.json
+++ b/src/lang/cs-CZ.json
@@ -215,7 +215,6 @@
     "Bot Token": "Token bota",
     "wayToGetTelegramToken": "Token můžete získat od {0}.",
     "Chat ID": "ID chatu",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Podpora přímého chatu / skupiny / ID chatu kanálu",
     "wayToGetTelegramChatID": "ID chatu můžete získat tak, že robotovi zašlete zprávu a přejdete na tuto adresu URL, kde zobrazíte chat_id:",
     "YOUR BOT TOKEN HERE": "SEM ZADEJTE TOKEN VAŠEHO CHATBOTA",
diff --git a/src/lang/da-DK.json b/src/lang/da-DK.json
index 02a63220..1b0fe210 100644
--- a/src/lang/da-DK.json
+++ b/src/lang/da-DK.json
@@ -208,7 +208,6 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Du kan få et token fra {0}.",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Support Direct Chat / Group / Channel's Chat ID",
     "wayToGetTelegramChatID": "Du kan få dit chat-ID ved at sende en besked til bot'en og gå til denne URL for at se chat_id'et:",
     "YOUR BOT TOKEN HERE": "DIT BOT TOKEN HER",
diff --git a/src/lang/de-CH.json b/src/lang/de-CH.json
index 7e0f0ebd..d8a46562 100644
--- a/src/lang/de-CH.json
+++ b/src/lang/de-CH.json
@@ -214,7 +214,6 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Hier kannst du einen Token erhalten {0}.",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Unterstützt Direkt Chat / Gruppe / Kanal Chat-ID's",
     "wayToGetTelegramChatID": "Du kannst die Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id: zu sehen.",
     "YOUR BOT TOKEN HERE": "HIER DEIN BOT TOKEN",
diff --git a/src/lang/de-DE.json b/src/lang/de-DE.json
index 37d27a6e..2e7bbb5f 100644
--- a/src/lang/de-DE.json
+++ b/src/lang/de-DE.json
@@ -214,7 +214,6 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Hier kannst du einen Token erhalten {0}.",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Unterstützt Direkt Chat / Gruppe / Kanal Chat-ID's",
     "wayToGetTelegramChatID": "Du kannst deine Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id: zu sehen.",
     "YOUR BOT TOKEN HERE": "HIER DEIN BOT TOKEN",
diff --git a/src/lang/el-GR.json b/src/lang/el-GR.json
index 1f8459f1..19a9bd4c 100644
--- a/src/lang/el-GR.json
+++ b/src/lang/el-GR.json
@@ -198,7 +198,6 @@
     "Bot Token": "Διακριτικό Bot",
     "wayToGetTelegramToken": "Μπορείτε να πάρετε ένα διακριτικό από {0}.",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Support Direct Chat / Group / Channel's Chat ID",
     "wayToGetTelegramChatID": "Μπορείτε να λάβετε το αναγνωριστικό συνομιλίας σας στέλνοντας ένα μήνυμα στο bot και μεταβαίνοντας σε αυτήν τη διεύθυνση URL για να προβάλετε το chat_id:",
     "YOUR BOT TOKEN HERE": "ΤΟ BOT ΣΑΣ ΔΙΑΚΡΙΤΙΚΌ ΕΔΩ",
diff --git a/src/lang/en.json b/src/lang/en.json
index e70dbb38..c1249d31 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -361,7 +361,8 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "You can get a token from {0}.",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
+    "telegramMessageThreadID": "(Optional) Message Thread ID",
+    "telegramMessageThreadIDDescription": "Optional Unique identifier for the target message thread (topic) of the forum; for forum supergroups only",
     "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:",
     "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",
diff --git a/src/lang/nl-NL.json b/src/lang/nl-NL.json
index e4c1da00..32c79545 100644
--- a/src/lang/nl-NL.json
+++ b/src/lang/nl-NL.json
@@ -217,7 +217,6 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Je kunt een token krijgen van {0}.",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Ondersteuning Directe Chat / Groep / Kanaal Chat ID",
     "wayToGetTelegramChatID": "Je kunt je CHAT ID krijgen door een bericht te sturen naar de bot en naar deze URL te gaan om het chat_id te bekijken:",
     "YOUR BOT TOKEN HERE": "DE BOT TOKEN HIER",
diff --git a/src/lang/pl.json b/src/lang/pl.json
index 6996e761..472b595c 100644
--- a/src/lang/pl.json
+++ b/src/lang/pl.json
@@ -189,7 +189,6 @@
     "Bot Token": "Token bota",
     "wayToGetTelegramToken": "Token można uzyskać z {0}.",
     "Chat ID": "Identyfikator czatu",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Czat wsparcia technicznego / Bezpośrednia rozmowa / Czat grupowy",
     "wayToGetTelegramChatID": "Możesz uzyskać swój identyfikator czatu, wysyłając wiadomość do bota i przechodząc pod ten adres URL, aby wyświetlić identyfikator czatu:",
     "YOUR BOT TOKEN HERE": "TWÓJ TOKEN BOTA",
diff --git a/src/lang/ru-RU.json b/src/lang/ru-RU.json
index 96ad8661..7ea1f643 100644
--- a/src/lang/ru-RU.json
+++ b/src/lang/ru-RU.json
@@ -216,7 +216,6 @@
     "Bot Token": "Токен бота",
     "wayToGetTelegramToken": "Вы можете взять токен здесь - {0}.",
     "Chat ID": "ID чата",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Поддерживаются ID чатов, групп и каналов",
     "wayToGetTelegramChatID": "Вы можете взять ID вашего чата, отправив сообщение боту и перейдя по этому URL для просмотра chat_id:",
     "YOUR BOT TOKEN HERE": "ВАШ ТОКЕН БОТА ЗДЕСЬ",
diff --git a/src/lang/sl-SI.json b/src/lang/sl-SI.json
index bf32cbed..f4ca81bd 100644
--- a/src/lang/sl-SI.json
+++ b/src/lang/sl-SI.json
@@ -193,7 +193,6 @@
     "Bot Token": "Robotkov žetonček",
     "wayToGetTelegramToken": "Lahko dobiš žeton od {0}.",
     "Chat ID": "ID pogovora",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Direkten pogovor pomoči / Skupina / ID kanala",
     "wayToGetTelegramChatID": "Id lahko dobiš, če pošlješ sporočilo robotku in odpreš ta URL, da bi videl chat_id:",
     "YOUR BOT TOKEN HERE": "ROBOTKOV ŽETON TUKAJ",
diff --git a/src/lang/th-TH.json b/src/lang/th-TH.json
index 2146c734..9f5c78a1 100644
--- a/src/lang/th-TH.json
+++ b/src/lang/th-TH.json
@@ -194,7 +194,6 @@
     "Bot Token": "กุญแจของบอท",
     "wayToGetTelegramToken": "คุณสามารถรับกุญแจได้จาก {0}.",
     "Chat ID": "ไอดีแชท",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "รองรับ แชทส่วนตัว, แชทกลุ่ม, ไอดีแชท",
     "wayToGetTelegramChatID": "คุณสามารถรับ ID แชทของคุณได้โดยส่งข้อความไปยังบอทและไปที่ URL นี้เพื่อดู chat_id :",
     "YOUR BOT TOKEN HERE": "กุญแจของบอทของคุณที่นี่",
diff --git a/src/lang/tr-TR.json b/src/lang/tr-TR.json
index d55f2aab..80d273e1 100644
--- a/src/lang/tr-TR.json
+++ b/src/lang/tr-TR.json
@@ -197,7 +197,6 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "{0} adresinden bir token alabilirsiniz.",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Doğrudan Sohbet / Grup / Kanalın Sohbet Kimliğini Destekleyin",
     "wayToGetTelegramChatID": "Bot'a bir mesaj göndererek ve chat_id'yi görüntülemek için bu URL'ye giderek sohbet kimliğinizi alabilirsiniz:",
     "YOUR BOT TOKEN HERE": "BOT TOKENİNİZ BURADA",
diff --git a/src/lang/uk-UA.json b/src/lang/uk-UA.json
index 7de6829c..9a63cfe0 100644
--- a/src/lang/uk-UA.json
+++ b/src/lang/uk-UA.json
@@ -216,7 +216,6 @@
     "Bot Token": "Токен бота",
     "wayToGetTelegramToken": "Ви можете взяти токен тут - {0}.",
     "Chat ID": "ID чату",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Підтримуються ID чатів, груп та каналів",
     "wayToGetTelegramChatID": "Ви можете взяти ID вашого чату, відправивши повідомлення боту і перейшовши по цьому URL для перегляду chat_id:",
     "YOUR BOT TOKEN HERE": "ВАШ ТОКЕН БОТА ТУТ",
diff --git a/src/lang/vi-VN.json b/src/lang/vi-VN.json
index 4446a020..165bf1bb 100644
--- a/src/lang/vi-VN.json
+++ b/src/lang/vi-VN.json
@@ -193,7 +193,6 @@
     "Bot Token": "Bot Token",
     "wayToGetTelegramToken": "Bạn có thể lấy mã token từ",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "Hỗ trợ chat trực tiếp / Nhóm / Kênh Chat ID",
     "wayToGetTelegramChatID": "Bạn có thể lấy chat id của mình bằng cách gửi tin nhắn tới bot và truy cập url này để xem chat_id:",
     "YOUR BOT TOKEN HERE": "MÃ BOT TOKEN CỦA BẠN",
diff --git a/src/lang/zh-CN.json b/src/lang/zh-CN.json
index 8bad70ab..a0539593 100644
--- a/src/lang/zh-CN.json
+++ b/src/lang/zh-CN.json
@@ -213,7 +213,6 @@
     "Bot Token": "机器人令牌",
     "wayToGetTelegramToken": "您可以从 {0} 获取 Token。",
     "Chat ID": "Chat ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "支持对话/群组/频道的 Chat ID",
     "wayToGetTelegramChatID": "您可以发送一条消息给您的机器人,然后访问此链接来查看 chat_id:",
     "YOUR BOT TOKEN HERE": "这里替换成您的 BOT TOKEN",
diff --git a/src/lang/zh-HK.json b/src/lang/zh-HK.json
index 1d7e0e65..8111b73d 100644
--- a/src/lang/zh-HK.json
+++ b/src/lang/zh-HK.json
@@ -211,7 +211,6 @@
     "Bot Token": "機器人權杖",
     "wayToGetTelegramToken": "您可以從 {0} 取得 Token。",
     "Chat ID": "聊天 ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "支援 對話/群組/頻道的聊天 ID",
     "wayToGetTelegramChatID": "傳送訊息給機器人,並前往以下網址以取得您的 chat ID:",
     "YOUR BOT TOKEN HERE": "在此填入您的機器人權杖",
diff --git a/src/lang/zh-TW.json b/src/lang/zh-TW.json
index 5a63ac54..3e208215 100644
--- a/src/lang/zh-TW.json
+++ b/src/lang/zh-TW.json
@@ -212,7 +212,6 @@
     "Bot Token": "機器人權杖",
     "wayToGetTelegramToken": "您可以從 {0} 取得權杖。",
     "Chat ID": "聊天 ID",
-    "Message Thread ID": "Message Thread ID",
     "supportTelegramChatID": "支援 對話/群組/頻道的聊天 ID",
     "wayToGetTelegramChatID": "傳送訊息給機器人,並前往以下網址以取得您的 chat ID:",
     "YOUR BOT TOKEN HERE": "在此填入您的機器人權杖",

From 10228874fa41c7afaf4cca8ac72f3edc9ab9f5bd Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Fri, 24 Feb 2023 16:54:58 +0800
Subject: [PATCH 52/57] Merge manually

---
 server/notification-providers/telegram.js | 1 +
 src/components/notifications/Telegram.vue | 2 +-
 src/lang/en.json                          | 2 ++
 3 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js
index 7f46b3fc..614e487e 100644
--- a/server/notification-providers/telegram.js
+++ b/server/notification-providers/telegram.js
@@ -12,6 +12,7 @@ class Telegram extends NotificationProvider {
             let params = {
                 chat_id: notification.telegramChatID,
                 text: msg,
+                disable_notification: notification.telegramSendSilently ?? false,
             };
             if (notification.telegramMessageThreadID) {
                 params.message_thread_id = notification.telegramMessageThreadID;
diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index 7816de74..a90ceafc 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -35,7 +35,7 @@
 
         <div class="form-check form-switch">
             <input v-model="$parentnotification.telegramSendSilently" class="form-check-input" type="checkbox">
-            <label class="form-check-label">{{ $t("Send Silently") }}</label>
+            <label class="form-check-label">{{ $t("telegramSendSilently") }}</label>
         </div>
 
         <div class="form-text">
diff --git a/src/lang/en.json b/src/lang/en.json
index c1249d31..ef6cb688 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -363,6 +363,8 @@
     "Chat ID": "Chat ID",
     "telegramMessageThreadID": "(Optional) Message Thread ID",
     "telegramMessageThreadIDDescription": "Optional Unique identifier for the target message thread (topic) of the forum; for forum supergroups only",
+    "telegramSendSilently": "Send Silently",
+    "telegramSendSilentlyDescription": "Sends the message silently. Users will receive a notification with no sound.",
     "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:",
     "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",

From 06278dc51fb89b3f7e323973fcc982c6b15cbd03 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Fri, 24 Feb 2023 17:03:40 +0800
Subject: [PATCH 53/57] Fix telegram silent issue

---
 src/components/notifications/Telegram.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index a90ceafc..52a63b8c 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -34,7 +34,7 @@
         <p class="form-text">{{ $t("telegramMessageThreadIDDescription") }}</p>
 
         <div class="form-check form-switch">
-            <input v-model="$parentnotification.telegramSendSilently" class="form-check-input" type="checkbox">
+            <input v-model="$parent.notification.telegramSendSilently" class="form-check-input" type="checkbox">
             <label class="form-check-label">{{ $t("telegramSendSilently") }}</label>
         </div>
 

From 2fa233ae7fa76eaf82bf7658c42dd61dc21cd22c Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Fri, 24 Feb 2023 17:12:57 +0800
Subject: [PATCH 54/57] Fix prometheus null issues

---
 server/model/monitor.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 4bb859e9..d6e1896a 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -755,7 +755,7 @@ class Monitor extends BeanModel {
             await R.store(bean);
 
             log.debug("monitor", `[${this.name}] prometheus.update`);
-            this.prometheus.update(bean, tlsInfo);
+            this.prometheus?.update(bean, tlsInfo);
 
             previousBeat = bean;
 
@@ -840,7 +840,7 @@ class Monitor extends BeanModel {
         clearTimeout(this.heartbeatInterval);
         this.isStop = true;
 
-        this.prometheus.remove();
+        this.prometheus?.remove();
     }
 
     /**

From af82ea742cbe1559b0fbc8d4cf80f7d850d5ae30 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Fri, 24 Feb 2023 17:21:53 +0800
Subject: [PATCH 55/57] Merge manually

---
 src/components/notifications/Telegram.vue | 27 ++---------------------
 src/lang/en.json                          |  2 ++
 2 files changed, 4 insertions(+), 25 deletions(-)

diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue
index 46824538..a7e46fde 100644
--- a/src/components/notifications/Telegram.vue
+++ b/src/components/notifications/Telegram.vue
@@ -43,37 +43,14 @@
         </div>
     </div>
 
-    <div class="mb-3">
-        <label for="telegram-chat-thread" class="form-label">{{ $t("Thread ID") }}</label>
-
-        <div class="input-group mb-3">
-            <input id="telegram-chat-thread" v-model="$parent.notification.telegramChatThread" type="text" class="form-control">
-        </div>
-
-        <div class="form-text">
-            {{ $t("Thread ID Description") }}
-        </div>
-    </div>
-
-    <div class="mb-3">
-        <div class="form-check form-switch">
-            <input v-model="$parent.notification.telegramSilentNotification" class="form-check-input" type="checkbox">
-            <label class="form-check-label">{{ $t("Silent Notification") }}</label>
-        </div>
-
-        <div class="form-text">
-            {{ $t("Silent Notification Description") }}
-        </div>
-    </div>
-
     <div class="mb-3">
         <div class="form-check form-switch">
             <input v-model="$parent.notification.telegramProtectContent" class="form-check-input" type="checkbox">
-            <label class="form-check-label">{{ $t("Protect Forwarding") }}</label>
+            <label class="form-check-label">{{ $t("telegramProtectContent") }}</label>
         </div>
 
         <div class="form-text">
-            {{ $t("Protect Forwarding Description") }}
+            {{ $t("telegramProtectContentDescription") }}
         </div>
     </div>
 </template>
diff --git a/src/lang/en.json b/src/lang/en.json
index ef6cb688..478ddddc 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -365,6 +365,8 @@
     "telegramMessageThreadIDDescription": "Optional Unique identifier for the target message thread (topic) of the forum; for forum supergroups only",
     "telegramSendSilently": "Send Silently",
     "telegramSendSilentlyDescription": "Sends the message silently. Users will receive a notification with no sound.",
+    "telegramProtectContent": "Protect Forwarding/Saving",
+    "telegramProtectContentDescription": "If enabled, the bot messages in Telegram will be protected from forwarding and saving.",
     "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:",
     "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",

From 7b8ed01f272fc4c6b69ff6299185e936a5e63735 Mon Sep 17 00:00:00 2001
From: Nelson Chan <chakflying@hotmail.com>
Date: Fri, 24 Feb 2023 21:06:00 +0800
Subject: [PATCH 56/57] Fix: getGameList returns nothing on first run

---
 server/socket-handlers/general-socket-handler.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/server/socket-handlers/general-socket-handler.js b/server/socket-handlers/general-socket-handler.js
index 11b47a5b..48412183 100644
--- a/server/socket-handlers/general-socket-handler.js
+++ b/server/socket-handlers/general-socket-handler.js
@@ -12,7 +12,7 @@ let gameList = null;
  * @returns {any[]}
  */
 function getGameList() {
-    if (!gameList) {
+    if (gameList == null) {
         gameList = gameResolver._readGames().games.sort((a, b) => {
             if ( a.pretty < b.pretty ) {
                 return -1;
@@ -22,9 +22,8 @@ function getGameList() {
             }
             return 0;
         });
-    } else {
-        return gameList;
     }
+    return gameList;
 }
 
 module.exports.generalSocketHandler = (socket, server) => {

From c65a920050688afed790a8bc6e400f45ee32f54c Mon Sep 17 00:00:00 2001
From: Nelson Chan <chakflying@hotmail.com>
Date: Fri, 24 Feb 2023 21:09:55 +0800
Subject: [PATCH 57/57] Chore: Fix code comment

---
 server/socket-handlers/general-socket-handler.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/socket-handlers/general-socket-handler.js b/server/socket-handlers/general-socket-handler.js
index 48412183..bb4a3808 100644
--- a/server/socket-handlers/general-socket-handler.js
+++ b/server/socket-handlers/general-socket-handler.js
@@ -9,7 +9,7 @@ let gameList = null;
 
 /**
  * Get a game list via GameDig
- * @returns {any[]}
+ * @returns {Object[]} list of games supported by GameDig
  */
 function getGameList() {
     if (gameList == null) {