From 3a188017228b2437820133827c36b03a7b9597f7 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Wed, 10 Aug 2022 21:46:43 -0400 Subject: [PATCH 01/58] 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 b99516124..4433e2a5f 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 ac6a3e2e5..7dcc7d64b 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -396,6 +396,22 @@ + +
+ + +
+
@@ -644,6 +660,7 @@ export default { mqttTopic: "", mqttSuccessMessage: "", authMethod: null, + bodyEncoding: null }; if (this.$root.proxyList && !this.monitor.proxyId) { From 2b9bf095a609bbf2f2c517209b13cb57dbd1e1b1 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Thu, 11 Aug 2022 20:57:03 -0400 Subject: [PATCH 02/58] 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 000000000..de02bede1 --- /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 b234d67d0..ecf6af728 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 2feef1356..af3d162a5 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 2a2c4bf61..616a10cda 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 7dcc7d64b..b4aceba7b 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -398,8 +398,8 @@
- - @@ -660,7 +660,7 @@ export default { mqttTopic: "", mqttSuccessMessage: "", authMethod: null, - bodyEncoding: null + httpBodyEncoding: "json" }; if (this.$root.proxyList && !this.monitor.proxyId) { @@ -698,7 +698,8 @@ export default { * @returns {boolean} Is the form input valid? */ isInputValid() { - if (this.monitor.body) { + //TODO: Handle validation for all 3 possible options + null value + if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) { try { JSON.parse(this.monitor.body); } catch (err) { @@ -729,8 +730,8 @@ export default { return; } - // Beautify the JSON format - if (this.monitor.body) { + // Beautify the JSON format (only if httpBodyEncoding is not set or === json) + if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) { this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4); } From 31cc328839c9462169070b4b67ba4044fb6716d8 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Thu, 11 Aug 2022 21:08:13 -0400 Subject: [PATCH 03/58] 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 5d9c4cd42..8f30a0c1d 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 e338d7785..31d926287 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -559,5 +559,5 @@ export default { disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.", trustProxyDescription: "Trust 'X-Forwarded-*' headers. If you want to get the correct client IP and your Uptime Kuma is behind such as Nginx or Apache, you should enable this.", wayToGetLineNotifyToken: "You can get an access token from {0}", - "Body Encoding": "Body Encoding" + "Body Encoding": "Body Encoding", }; From 5809088f27eb9604b043b782ac375f1feecc2e5a Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 15:52:43 -0400 Subject: [PATCH 04/58] 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 509b841c2..c541ecf36 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -249,20 +249,28 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Prepare Options for axios`); - // Set content-type header and body values based on the httpBodyEncoding type selected - // TODO: Check if this.headers already contains a content-type header set by the user; if so, don't inject one - let bodyValue = null; - let contentType = null; + + // Check if this.headers already contains a content-type header set by the user; if so, don't inject one + let contentTypeUserDefinedHeader = this.headers.find(function(header) { + return header[0].toLowerCase() == "content-type"; + }); + + let contentType = contentTypeUserDefinedHeader ? + contentTypeUserDefinedHeader[1] : + null; + + let bodyValue = null; + if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json") { bodyValue = JSON.parse(this.body); - contentType = "application/json"; + contentType = contentType ? contentType : "application/json"; } else if (this.body && (this.httpBodyEncoding === "xml")) { bodyValue = this.body; - contentType = "text/xml"; + contentType = contentType ? contentType : "text/xml"; } else if (this.body && (this.httpBodyEncoding === "form")) { bodyValue = this.body; - contentType = "application/x-www-form-urlencoded"; + contentType = contentType ? contentType : "application/x-www-form-urlencoded"; } const options = { From 6537f4fe746c157c8a87ff25bbcefeb7a62522f3 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 17:09:10 -0400 Subject: [PATCH 05/58] 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 c541ecf36..48b0b1d32 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -249,28 +249,18 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Prepare Options for axios`); - - - // Check if this.headers already contains a content-type header set by the user; if so, don't inject one - let contentTypeUserDefinedHeader = this.headers.find(function(header) { - return header[0].toLowerCase() == "content-type"; - }); - - let contentType = contentTypeUserDefinedHeader ? - contentTypeUserDefinedHeader[1] : - null; - + let contentType = null; let bodyValue = null; if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json") { bodyValue = JSON.parse(this.body); - contentType = contentType ? contentType : "application/json"; + contentType = "application/json"; } else if (this.body && (this.httpBodyEncoding === "xml")) { bodyValue = this.body; - contentType = contentType ? contentType : "text/xml"; + contentType = "text/xml"; } else if (this.body && (this.httpBodyEncoding === "form")) { bodyValue = this.body; - contentType = contentType ? contentType : "application/x-www-form-urlencoded"; + contentType = "application/x-www-form-urlencoded"; } const options = { From f6919aef1d3b59dd23229feef6f2815004f34a80 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 17:10:56 -0400 Subject: [PATCH 06/58] 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 ba6676703..ea246c600 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -741,7 +741,6 @@ export default { * @returns {boolean} Is the form input valid? */ isInputValid() { - //TODO: Handle validation for all 3 possible options + null value if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) { try { JSON.parse(this.monitor.body); From 4a7e96f9ea6235d797b3a0f68b8300d82aadfea6 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 6 Oct 2022 12:08:10 +0800 Subject: [PATCH 07/58] Init C# Project for building exe --- .dockerignore | 3 +- .gitignore | 3 + extra/exe-builder/App.config | 10 ++ extra/exe-builder/Program.cs | 59 +++++++++ extra/exe-builder/Properties/AssemblyInfo.cs | 36 ++++++ .../Properties/Resources.Designer.cs | 62 ++++++++++ extra/exe-builder/Properties/Resources.resx | 117 ++++++++++++++++++ .../Properties/Settings.Designer.cs | 23 ++++ .../exe-builder/Properties/Settings.settings | 7 ++ extra/exe-builder/UptimeKuma.csproj | 79 ++++++++++++ extra/exe-builder/UptimeKuma.sln | 16 +++ .../UptimeKuma.sln.DotSettings.user | 3 + 12 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 extra/exe-builder/App.config create mode 100644 extra/exe-builder/Program.cs create mode 100644 extra/exe-builder/Properties/AssemblyInfo.cs create mode 100644 extra/exe-builder/Properties/Resources.Designer.cs create mode 100644 extra/exe-builder/Properties/Resources.resx create mode 100644 extra/exe-builder/Properties/Settings.Designer.cs create mode 100644 extra/exe-builder/Properties/Settings.settings create mode 100644 extra/exe-builder/UptimeKuma.csproj create mode 100644 extra/exe-builder/UptimeKuma.sln create mode 100644 extra/exe-builder/UptimeKuma.sln.DotSettings.user diff --git a/.dockerignore b/.dockerignore index babc429a2..22b71b38c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -31,6 +31,7 @@ tsconfig.json /tmp /babel.config.js /ecosystem.config.js +extra/exe-builder ### .gitignore content (commented rules are duplicated) @@ -45,6 +46,6 @@ dist-ssr #!/data/.gitkeep #.vscode +### End of .gitignore content -### End of .gitignore content diff --git a/.gitignore b/.gitignore index 8eb05867b..8e0edeacb 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 000000000..09265d8b6 --- /dev/null +++ b/extra/exe-builder/App.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs new file mode 100644 index 000000000..5516a1ff0 --- /dev/null +++ b/extra/exe-builder/Program.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows.Forms; +using UptimeKuma.Properties; + +namespace UptimeKuma { + static class Program { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new UptimeKumaApplicationContext()); + } + } + + public class UptimeKumaApplicationContext : ApplicationContext + { + private NotifyIcon trayIcon; + + public UptimeKumaApplicationContext() + { + // Initialize Tray Icon + trayIcon = new NotifyIcon(); + + trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); + trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { + new MenuItem("Check for Update", CheckForUpdate), + new MenuItem("About", About), + new MenuItem("Exit", Exit), + }); + + trayIcon.Visible = true; + } + + void Exit(object sender, EventArgs e) + { + // Hide tray icon, otherwise it will remain shown until user mouses over it + trayIcon.Visible = false; + Application.Exit(); + } + + void About(object sender, EventArgs e) + { + MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info"); + } + + void CheckForUpdate(object sneder, EventArgs e) { + + } + } +} + diff --git a/extra/exe-builder/Properties/AssemblyInfo.cs b/extra/exe-builder/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..2552870b3 --- /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 000000000..8c8e559c5 --- /dev/null +++ b/extra/exe-builder/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UptimeKuma.Properties { + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", + "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState + .Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = + new global::System.Resources.ResourceManager("UptimeKuma.Properties.Resources", + typeof(Resources).Assembly); + resourceMan = temp; + } + + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState + .Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } + set { resourceCulture = value; } + } + } +} \ No newline at end of file diff --git a/extra/exe-builder/Properties/Resources.resx b/extra/exe-builder/Properties/Resources.resx new file mode 100644 index 000000000..ffecec851 --- /dev/null +++ b/extra/exe-builder/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/extra/exe-builder/Properties/Settings.Designer.cs b/extra/exe-builder/Properties/Settings.Designer.cs new file mode 100644 index 000000000..6c63b395b --- /dev/null +++ b/extra/exe-builder/Properties/Settings.Designer.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UptimeKuma.Properties { + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( + "Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = + ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { return defaultInstance; } + } + } +} \ No newline at end of file diff --git a/extra/exe-builder/Properties/Settings.settings b/extra/exe-builder/Properties/Settings.settings new file mode 100644 index 000000000..abf36c5d3 --- /dev/null +++ b/extra/exe-builder/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj new file mode 100644 index 000000000..d62166e41 --- /dev/null +++ b/extra/exe-builder/UptimeKuma.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04} + WinExe + UptimeKuma + uptime-kuma + v4.7.2 + 512 + true + true + ..\..\public\favicon.ico + 10 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + favicon.ico + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/extra/exe-builder/UptimeKuma.sln b/extra/exe-builder/UptimeKuma.sln new file mode 100644 index 000000000..201d7e234 --- /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 000000000..b4ca9dadf --- /dev/null +++ b/extra/exe-builder/UptimeKuma.sln.DotSettings.user @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file From da778f05ac30a681eeb71735dd21fc86eaef4a23 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 00:16:07 +0800 Subject: [PATCH 08/58] 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 5516a1ff0..840bc873c 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection; @@ -23,6 +24,7 @@ namespace UptimeKuma { public class UptimeKumaApplicationContext : ApplicationContext { private NotifyIcon trayIcon; + private Process process; public UptimeKumaApplicationContext() { @@ -31,19 +33,41 @@ namespace UptimeKuma { trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { + new MenuItem("Open", Open), new MenuItem("Check for Update", CheckForUpdate), new MenuItem("About", About), new MenuItem("Exit", Exit), }); trayIcon.Visible = true; + + var startInfo = new ProcessStartInfo(); + startInfo.FileName = "node/node.exe"; + startInfo.Arguments = "server/server.js"; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + startInfo.CreateNoWindow = true; + startInfo.WorkingDirectory = "core"; + + process = new Process(); + process.StartInfo = startInfo; + process.EnableRaisingEvents = true; + try { + process.Start(); + Open(null, null); + } catch (Exception e) { + MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error"); + throw; + } } - void Exit(object sender, EventArgs e) - { - // Hide tray icon, otherwise it will remain shown until user mouses over it - trayIcon.Visible = false; - Application.Exit(); + void Open(object sender, EventArgs e) { + Process.Start("http://localhost:3001"); + } + + void CheckForUpdate(object sender, EventArgs e) { + } void About(object sender, EventArgs e) @@ -51,8 +75,12 @@ namespace UptimeKuma { MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info"); } - void CheckForUpdate(object sneder, EventArgs e) { - + void Exit(object sender, EventArgs e) + { + // Hide tray icon, otherwise it will remain shown until user mouses over it + trayIcon.Visible = false; + process.Close(); + Application.Exit(); } } } From 4c456547807dc3fb4d6de0a4da741ef252cb5c7e Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 18:38:14 +0800 Subject: [PATCH 09/58] WIP --- extra/exe-builder/Program.cs | 67 +++++++++++++++++++++-------- extra/exe-builder/UptimeKuma.csproj | 5 ++- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 840bc873c..84ecda317 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; using UptimeKuma.Properties; @@ -14,7 +15,7 @@ namespace UptimeKuma { /// The main entry point for the application. /// [STAThread] - static void Main() { + static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new UptimeKumaApplicationContext()); @@ -28,37 +29,44 @@ namespace UptimeKuma { public UptimeKumaApplicationContext() { - // Initialize Tray Icon trayIcon = new NotifyIcon(); trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { - new MenuItem("Open", Open), - new MenuItem("Check for Update", CheckForUpdate), - new MenuItem("About", About), - new MenuItem("Exit", Exit), + new("Open", Open), + //new("Debug Console", DebugConsole), + new("Check for Update...", CheckForUpdate), + new("Visit GitHub...", VisitGitHub), + new("About", About), + new("Exit", Exit), }); + trayIcon.MouseDoubleClick += new MouseEventHandler(Open); + trayIcon.Visible = true; - var startInfo = new ProcessStartInfo(); - startInfo.FileName = "node/node.exe"; - startInfo.Arguments = "server/server.js"; - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.WorkingDirectory = "core"; + var startInfo = new ProcessStartInfo { + FileName = "node/node.exe", + Arguments = "server/server.js --data-dir=\"../data/\"", + RedirectStandardOutput = false, + RedirectStandardError = false, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = "core" + }; process = new Process(); process.StartInfo = startInfo; process.EnableRaisingEvents = true; + process.Exited += new EventHandler(ProcessExited); + + try { process.Start(); - Open(null, null); + //Open(null, null); + } catch (Exception e) { MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error"); - throw; } } @@ -66,8 +74,17 @@ namespace UptimeKuma { Process.Start("http://localhost:3001"); } + void DebugConsole(object sender, EventArgs e) { + + } + void CheckForUpdate(object sender, EventArgs e) { + Process.Start("https://github.com/louislam/uptime-kuma/releases"); + } + void VisitGitHub(object sender, EventArgs e) + { + Process.Start("https://github.com/louislam/uptime-kuma"); } void About(object sender, EventArgs e) @@ -79,9 +96,25 @@ namespace UptimeKuma { { // Hide tray icon, otherwise it will remain shown until user mouses over it trayIcon.Visible = false; - process.Close(); + process.Kill(); + } + + void ProcessExited(object sender, EventArgs e) { + + if (process.ExitCode != 0) { + var line = ""; + while (!process.StandardOutput.EndOfStream) + { + line += process.StandardOutput.ReadLine(); + } + + MessageBox.Show("Uptime Kuma exited unexpectedly. Exit code: " + process.ExitCode + " " + line); + } + + trayIcon.Visible = false; Application.Exit(); } + } } diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index d62166e41..c3c6aad27 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -13,7 +13,7 @@ true true ..\..\public\favicon.ico - 10 + 9 AnyCPU @@ -34,6 +34,9 @@ prompt 4 + + COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "C:\Users\LouisLam\Desktop\uptime-kuma-win64\" + From 3eaccb560ed1a0590a6bb3bf7f73765bbe06db00 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 8 Oct 2022 16:23:11 +0800 Subject: [PATCH 10/58] 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 000000000..26a474e9c --- /dev/null +++ b/extra/exe-builder/DownloadForm.Designer.cs @@ -0,0 +1,84 @@ +using System.ComponentModel; + +namespace UptimeKuma { + partial class DownloadForm { + /// + /// Required designer variable. + /// + private IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DownloadForm)); + this.progressBar = new System.Windows.Forms.ProgressBar(); + this.label = new System.Windows.Forms.Label(); + this.labelData = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // progressBar + // + this.progressBar.Location = new System.Drawing.Point(12, 12); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(472, 41); + this.progressBar.TabIndex = 0; + // + // label + // + this.label.Location = new System.Drawing.Point(12, 59); + this.label.Name = "label"; + this.label.Size = new System.Drawing.Size(472, 23); + this.label.TabIndex = 1; + this.label.Text = "Preparing..."; + // + // labelData + // + this.labelData.Location = new System.Drawing.Point(12, 82); + this.labelData.Name = "labelData"; + this.labelData.Size = new System.Drawing.Size(472, 23); + this.labelData.TabIndex = 2; + // + // DownloadForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(496, 117); + this.Controls.Add(this.labelData); + this.Controls.Add(this.label); + this.Controls.Add(this.progressBar); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.Name = "DownloadForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Uptime Kuma"; + this.Load += new System.EventHandler(this.DownloadForm_Load); + this.ResumeLayout(false); + } + + private System.Windows.Forms.Label labelData; + + private System.Windows.Forms.Label label; + + private System.Windows.Forms.ProgressBar progressBar; + + #endregion + } +} + diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs new file mode 100644 index 000000000..9c740e312 --- /dev/null +++ b/extra/exe-builder/DownloadForm.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Windows.Forms; + +namespace UptimeKuma { + public partial class DownloadForm : Form { + private readonly Queue downloadQueue = new(); + private readonly WebClient webClient = new(); + + public DownloadForm() { + InitializeComponent(); + } + + private void DownloadForm_Load(object sender, EventArgs e) { + webClient.DownloadProgressChanged += DownloadProgressChanged; + webClient.DownloadFileCompleted += DownloadFileCompleted; + + if (!File.Exists("node")) { + downloadQueue.Enqueue(new DownloadItem { + URL = "https://nodejs.org/dist/v16.17.1/node-v16.17.1-win-x64.zip", + Filename = "node.zip" + }); + } + + if (!File.Exists("node")) { + downloadQueue.Enqueue(new DownloadItem { + URL = "https://github.com/louislam/uptime-kuma/archive/refs/tags/1.18.3.zip", + Filename = "core.zip" + }); + } + + DownloadNextFile(); + } + + void DownloadNextFile() { + if (downloadQueue.Count > 0) { + var item = downloadQueue.Dequeue(); + label.Text = item.URL; + webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); + } else { + // TODO: Finished, extract? + } + } + + void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { + progressBar.Value = e.ProgressPercentage; + var total = e.TotalBytesToReceive / 1024; + var current = e.BytesReceived / 1024; + labelData.Text = $"{current}KB/{total}KB"; + } + + void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + DownloadNextFile(); + } + } + + public class DownloadItem { + public string URL { get; set; } + public string Filename { get; set; } + } +} + diff --git a/extra/exe-builder/DownloadForm.resx b/extra/exe-builder/DownloadForm.resx new file mode 100644 index 000000000..e87e0c0d4 --- /dev/null +++ b/extra/exe-builder/DownloadForm.resx @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA + AABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////BPT09Bfu7u4e8fHxJPPz8yv19fUy9fX1M/Pz8yvx8fEk9vb2HPPz8xXMzMwFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wHv7+8f7u7uPPPz81Tx8fFs8fHxgPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGB8fHxcfHx8V3x8fFI9PT0MOvr6w0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADy8vIU8fHxS/Dw8Hbx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fFr9PT0R/Dw8CIAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA8vLyFPHx8Vnx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fFs9fX1Mb+/vwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAICAgALy8vI88fHxfvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy8nby8vI8gICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAzMzMBfHx8Vrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyYf///wwAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8vLyYPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8W/z8/MWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+9R8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLw8PB26urqDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLy8ijx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu7w7Ifj79ud2u7PtNLrw83P677dzeu85c3r + u+rM67rwzOu68c7rverQ68Dj0uvD3NbuyM3b7c+64u7apujv5ZPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxXgAAAAEAAAAAAAAAAAAAAAAAAAAA4+PjCfDw + 8Hfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLd7tSmzeu92MbqsvvG6bH/xumy/8fq + s//H6rP/yOq0/8jqtf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//Q7MDx1u7Kz9/t + 163s8OuJ8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu/v7y8AAAAAAAAAAAAA + AAAAAAAA7u7uPfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC5PDdl8jqtuTE6a7/xOmv/8Xp + sP/G6bH/xumx/8bpsv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zr + u//N67v/zey8/87svf/P67742e3Mx+jv5ZLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw + 8HWAgIACAAAAAAAAAACqqqoD8vLyc/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLf7degxOiu+cPo + rf/D6a7/xOmu/8Xpr//F6bD/xumx/8bpsf/G6bL/x+qz/8fqs//I6rT/yOq1/8nqtv/J6rb/yuu3/8rr + uP/L67j/y+u5/8zruv/M67v/zeu7/83svP/O7L3/zuy9/87svfzc7tK28fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fEkAAAAAAAAAADz8/Mq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgunv + 5o3D6a/0wuis/8Lorf/D6K3/xOmu/8Tprv/F6a//xemw/8bpsf/G6bH/xumy/8fqs//H6rP/yOq0/8jq + tf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/87svf/O7L3/3e/TtPHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAADy8vJM8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgszqutDB6Kv/weir/8LorP/D6K3/w+it/8Tprv/E6a7/xemv/8XpsP/G6bH/xumx/8bp + sv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zru//N67v/zey8/87s + vf/O7L3/zuy++u3w6Yzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJ1AAAAAAAAAADx8fFr8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC6O/kjsDoqvzA6Kr/weir/8Loq//C6Kz/w+it/8Porf/E6a7/xOmu/8Xp + r//F6bD/xumx/8bpsf/G6bL/x+qz/8fqtP/I6rT/yOq1/8nqtv/J6rb/yuu3/8rruP/L67n/y+u5/8zr + uv/M67v/zeu7/83svP/O7L3/zuy9/93u07Xx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC////Bv// + /wfx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1ezJsr/nqf/A56n/weiq/8Hoq//C6Kv/wuis/8Po + rf/D6K3/xOmu/8Pprv+856T/uOed/7bmmv+05Zf/teWZ/7jnnf+86KP/wOio/8fqs//J6rb/yeq2/8rr + t//K67j/y+u5/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/9buyNLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8vLyE/Ly8hPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCy+q6zr/nqP/A56n/wOep/8Ho + qv/B6Kv/wuir/8LorP+u5Y//neF2/5bgav+V4Gr/luBr/5fhbP+Y4W7/meFv/5rhcf+b4nL/nOJ0/53i + dv+j5H//reaM/7nnnf/E6q//y+y4/8vruf/L67n/zOu6/8zru//N67v/zey8/9Lsxd/x8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC7+/vIPb29hzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/n + qP+/56j/wOep/8Dnqf/B6Kr/weir/7nmn/+R32T/kt9l/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nh + b/+a4XH/m+Jy/5zidP+d4nX/nuN3/5/jeP+f4nn/weqq/8rruP/L67n/y+u5/8zruv/M67v/zeu7/9Ls + w+Lx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwI/Hx8SXx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGCxeix5L/nqP+/56j/v+eo/8Dnqf/A56n/weiq/7Pllv+Q3mP/kd9k/5LfZf+T32f/lOBo/5Xg + av+W4Gv/l+Ft/5jhbv+Z4W//muFx/5vicv+c4nT/neJ1/57jd/+f43j/xOmu/8rrt//K67j/y+u5/8vr + uf/M67r/zOu7/9Tsxtfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC9PT0GO/v7yDx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGCx+m037/nqP+/56j/v+eo/7/nqP/A56n/wOip/7TmmP+P3mH/kN5j/5Hf + ZP+S32b/k99n/5TgaP+V4Gr/luBr/5fhbf+Y4W7/meFw/5rhcf+b4nL/nOJ0/53idf+h5Hz/yuu2/8nq + t//K67f/yuu4/8vruf/L67n/zOu6/9ftysrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7e3tDvT0 + 9Bfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCyOq117/nqP+/56j/v+eo/7/nqP+/56j/wOep/7vn + of+O3mD/j95h/5DeY/+R32T/kt9m/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nhcP+a4nH/m+Jy/5zi + dP+r5Yr/yOq1/8nqtv/J6rf/yuu3/8rruP/L67n/y+u5/9zu1LHx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLz8/OA////A+7u7g/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCz+q+xb/nqP+/56j/v+eo/7/n + qP+/56j/v+eo/8Dnqf+S4Gb/jt5g/4/eYf+Q3mP/kd9k/5LfZv+T32f/lOBo/5Xgav+W4Gv/l+Ft/5jh + bv+Z4XD/muJx/5vic/+4553/yOq0/8jqtf/J6rb/yeq3/8rrt//K67j/y+u5/+bw4Zfx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fFrAAAAAP///wHz8/N88fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1+zMrr/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+f4Xn/jd5f/47eYP+P3mH/kN5j/5HfZP+S32b/k99n/5Tg + af+V4Gr/luBr/5fhbf+Y4W7/meFw/5vic//F6rD/x+q0/8jqtP/I6rX/yeq2/8nqt//K67f/zOu88u/x + 74Px8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLv7+9QAAAAAAAAAADw8PBm8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC5e7gk7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//jN1d/43eX/+O3mD/j95h/5De + Y/+R32T/kt9m/5PfZ/+U4Gn/leBq/5bga/+X4W3/mOFu/6rliP/G6rL/x+qz/8fqtP/I6rT/yOq1/8nq + tv/J6rf/1OzGy/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YL19fUzAAAAAAAAAADy8vJO8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgsPoru2/56j/v+eo/7/nqP+/56j/v+eo/7/nqP++6Kf/j95i/4zd + Xf+N3l//jt5g/4/eYv+Q3mP/kd9k/5LfZv+T32f/lOBp/5Xgav+W4Gz/l+Ft/7voov/G6bL/xuqy/8fq + s//H6rT/yOq1/8jqtf/J6rb/4e/Zo/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PARAAAAAAAA + AADu7u4u8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgszpvMm/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/q+SL/4vdXP+M3V3/jd5f/47eYP+P3mL/kN9j/5HfZP+S32b/k99n/5Tgaf+V4Gr/qOOH/8Xp + sP/G6bH/xumy/8bqsv/H6rP/x+q0/8jqtf/K67jy8PHwhPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8WoAAAAAAAAAAAAAAADo6OgL8fHxgfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguDv2J2/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/6Xjgv+L3Vz/jN1d/43eX/+O3mD/j95i/5DfY/+R32T/kt9m/5Pf + Z/+k44D/xOmu/8XpsP/F6bD/xumx/8bpsv/G6rL/x+qz/8fqtP/W7cnB8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvPz80AAAAAAAAAAAAAAAAAAAAAA8PDwZ/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLD6K/rv+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//kt5n/4zdXf+N3l//jt5g/4/e + Yv+Q32P/luFs/67kj//D6K3/xOmu/8Tpr//F6bD/xemw/8bpsf/G6bL/xuqy/8fqtP7o7+WR8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xYAAAAAAAAAAAAAAAAAAAAA8vLyPPHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLV7ci0v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOio/7Xl + mv+u5I7/rOSM/67kj/+35pz/wumr/8Lorf/D6K3/w+it/8Tprv/E6a//xemw/8XpsP/G6bH/xumy/9Ds + wNPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyZQAAAAAAAAAAAAAAAAAAAAAAAAAA////DPHx + 8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/wOep/8Doqv/B6Kr/weir/8LorP/C6K3/w+it/8Porv/E6a7/xOmv/8Xp + sP/F6bD/yOq18uvw6Yvx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7+/vMQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAPHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6O/ij8LorPG/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weiq/8Hoq//C6Kz/wuit/8Po + rf/D6K7/xOmu/8Tpr//F6bH74u/anvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PB6////BQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPz8yrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguHu + 2pnB56v2v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/wOiq/8Ho + q//B6Kv/wuis/8Lorf/D6K3/w+mu/8Tprv3b7dKq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fFJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHy8vJf8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLi7tyXwumt8L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/wOep/8Doqv/B6Kv/weir/8LorP/C6K3/xOiv+d7u1aTx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvLy8nb///8KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+8Q8/Pze/Hx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6/Dpiszqu82/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weir/8Hoq//H6bTj5e7elfHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8yoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA9fX1MvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLe7tShx+mz3r/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/xumy5drtz6rv8e+D8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyTgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPHx8Unx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgubv45DU68e2y+q6z8XoseTD6a7uweir9MPpru7F6bHly+q50tLsxLrl796U8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJh////AwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wHx8fFZ8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8Wzf398IAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8D8/PzVfHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwZujo + 6AsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////AfHx8Ujx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fFa////BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/Mp8vLydvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8/PzfPHx8TcAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CvLy8lDz8/N/8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvPz84Hx8fFa8PDwEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADw8PAR8vLyTvHx8X3x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fF/8/PzVvT09BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wXz8/Mq8/PzU/Hx8XDx8fGB8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLy8vJz8fHxWO/v7y////8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8G7e3tHfLy + 8ifu7u4u8PDwNPT09C/y8vIo7+/vH+Pj4wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///////wAA////////AAD///////8AAP//gAf//wAA//gAAD//AAD/wAAAB/8AAP+A + AAAB/wAA/gAAAAB/AAD8AAAAAD8AAPgAAAAAHwAA8AAAAAAPAADwAAAAAAcAAOAAAAAABwAA4AAAAAAD + AADAAAAAAAMAAMAAAAAAAwAAwAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA + AAAAAQAAgAAAAAABAACAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAABwAAwAAAAAAH + AADgAAAAAAcAAOAAAAAADwAA4AAAAAAPAADwAAAAAB8AAPAAAAAAHwAA+AAAAAA/AAD8AAAAAD8AAPwA + AAAAfwAA/gAAAAD/AAD/AAAAAf8AAP+AAAAD/wAA/8AAAAf/AAD/8AAAH/8AAP/8AAA//wAA//8AAf// + AAD//+AP//8AAP///////wAA////////AAD///////8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAgICAAu/v7xD09PQX7u7uHvDw8CP29vYb8vLyFOrq6gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICA + gALy8vIm7+/vT/Pz82fz8/N98fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw8Hrw8PBm7+/vUPT0 + 9C3o6OgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOPj + 4wnz8/NC8vLydPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YHy8vJj8/PzKoCAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADx8fEl8vLydfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxcfHx8SUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA9PT0LfHx8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8/PzgPLy8j0AAAABAAAAAAAA + AAAAAAAAAAAAAO3t7Rzx8fGA8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLr8OmM5O7emeTv + 3Z7h79mj5fDem+nv45Tu8u6H8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy + 8joAAAAAAAAAAAAAAAD///8E8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC7vDshtns0K7N67zayeq288fq + s//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/P7L7w0+zF29vv0Lrn8OKX8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8/PzfvPz8xUAAAAAAAAAAPX19TLx8fGC8fHxgvHx8YLx8fGC8fHxgt3u1KXF6rHzxOmv/8Xp + sP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/M67v/zey8/87svf/S7MPj4u7Zp/Hx + 8YLx8fGC8fHxgvHx8YLx8fGC8/PzVQAAAAAAAAAA8fHxavHx8YLx8fGC8fHxgvHx8YLf7defwuis/cPo + rf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruv/M67v/zey8/87s + vf/N67z/3e7SufHx8YLx8fGC8fHxgvHx8YLz8/N8////Bf///w3x8fGC8fHxgvHx8YLx8fGC8fHxgsXp + sOnB6Kv/wuis/8Porf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vr + uv/M67v/zey8/87svf/O67z96/Hoj/Hx8YLx8fGC8fHxgvHx8YLy8vIm8/PzK/Hx8YLx8fGC8fHxgvHx + 8YLg79icwOep/8Hoqv/B6Kv/wuis/8Porf/E6a7/wuit/73opP+76KL/u+eh/77opv/D6a3/yeu1/8nq + tv/K67f/y+u5/8zruv/M67v/zey8/87svf/d7tSz8fHxgvHx8YLx8fGC8fHxgvHx8Tby8vI68fHxgvHx + 8YLx8fGC8fHxgtTrxre/56j/wOep/8Hoqv/B6Kv/uOad/53idv+V4Gn/leBq/5fhbP+Y4W//muFx/5vi + c/+e4Xb/puWD/7PmlP/D6a3/y+u5/8zruv/M67v/zey8/9rtzsHx8fGC8fHxgvHx8YLx8fGC8/PzQfPz + 80Lx8fGC8fHxgvHx8YLx8fGC0OvAwr/nqP+/56j/wOep/8Hoqv+o44b/kd9k/5LfZv+U4Gj/leBq/5fh + bf+Y4W//muFx/5vic/+d4nX/n+N3/7fnm//K67j/y+u5/8zruv/M67v/2u3QvPHx8YLx8fGC8fHxgvHx + 8YLy8vI98/PzP/Hx8YLx8fGC8fHxgvHx8YLQ6sK/v+eo/7/nqP+/56j/wOep/6jjhv+P3mL/kd9k/5Lf + Zv+U4Gj/leBr/5fhbf+Y4W//muFx/5zic/+d4nX/v+mm/8nqt//K67j/y+u5/8zruv/f79au8fHxgvHx + 8YLx8fGC8fHxgvX19TLx8fE38fHxgvHx8YLx8fGC8fHxgtTrybO/56j/v+eo/7/nqP+/56j/sOSS/47e + YP+P3mL/kd9k/5LfZv+U4Gj/leBr/5fhbf+Z4W//muJx/5/jd//H6bP/yeq2/8nqt//K67j/y+u5/+nv + 45Tx8fGC8fHxgvHx8YLx8fGC7+/vIPHx8SXx8fGC8fHxgvHx8YLx8fGC4e/Zm7/nqP+/56j/v+eo/7/n + qP+956X/jt5h/47eYP+P3mL/kd9k/5LfZv+U4Gn/luBr/5fhbf+Z4W//q+aK/8fqs//I6rT/yeq2/8nq + t//N7Lvw8fHxgvHx8YLx8fGC8fHxgvPz84D///8G6+vrDfHx8YLx8fGC8fHxgvHx8YLv8e+Dweis87/n + qP+/56j/v+eo/7/nqP+d4XX/jN1e/47eYP+P3mL/kd9k/5PfZ/+U4Gn/luBr/5fhbf+86KP/xuqy/8fq + s//I6rX/yeq2/9Tsx8nx8fGC8fHxgvHx8YLx8fGC8PDwaAAAAAAAAAAA8fHxbPHx8YLx8fGC8fHxgvHx + 8YLM6rrMv+eo/7/nqP+/56j/v+eo/7blmv+N3V//jN1e/47eYP+Q3mL/kd9k/5PfZ/+U4Gn/qeSH/8Xp + sP/G6bH/xuqy/8fqs//I6rX/5fDem/Hx8YLx8fGC8fHxgvHx8YLz8/M/AAAAAAAAAADz8/NB8fHxgvHx + 8YLx8fGC8fHxgt3s06O/56j/v+eo/7/nqP+/56j/v+eo/7Xmmf+U32n/jN1e/47eYP+Q3mL/k99o/6zk + i//D6a7/xemv/8XpsP/G6bH/xuqy/8vqu+jx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xUAAAAAAAAAAPT0 + 9Bfx8fGC8fHxgvHx8YLx8fGC8fHvg8Tpsee/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+35pz/suWV/7Xm + mf/A6Kj/wuit/8Porf/E6a7/xemv/8XpsP/G6bH/3e3UqvHx8YLx8fGC8fHxgvHx8YLw8PBmAAAAAAAA + AAAAAAAAAAAAAPHx8W7x8fGC8fHxgvHx8YLx8fGC4u7cmMHnqvm/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/wOep/8Hoqv/C6Kz/wuit/8Porf/E6a7/xemv/9Hrwszx8fGC8fHxgvHx8YLx8fGC8fHxgvX1 + 9TEAAAAAAAAAAAAAAAAAAAAA7u7uO/Hx8YLx8fGC8fHxgvHx8YLx8fGC3e7SpMHoqfq/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz/wuit/8Porf/O67zV8PHwhPHx8YLx8fGC8fHxgvHx + 8YLy8vJ2////BQAAAAAAAAAAAAAAAAAAAACqqqoD8PDwafHx8YLx8fGC8fHxgvHx8YLx8fGC4O/YnMTo + ruy/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz90uvEwe/x74Px8fGC8fHxgvHx + 8YLx8fGC8fHxgvPz8ykAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/MW8fHxfPHx8YLx8fGC8fHxgvHx + 8YLx8fGC8PLuhdXtyLXF6bHlv+eo/7/nqP+/56j/v+eo/7/nqP/B6Kv0zeq8zOXv4JTx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADy8vIm8fHxgPHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLs8OmJ4e/Zm93u06Pf7def5+/hkvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxXf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADy8vIo8/PzffHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8VnMzMwFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD29vYb8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz83/v7+9BgICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8/PzQPLy8nnx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz84Hx8fFc9PT0GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////B/X19TLx8fFc8PDwevHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgPHx8Wv09PRE9PT0FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA7+/vEPb29hvw8PAj7+/vH/T09Be/v78EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////8B///wAA//wAAD/wAAAP4AAAB+AA + AAfAAAADwAAAA4AAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAADwAAAA8AAAAPAAAAH4AAAB+AA + AA/wAAAP+AAAH/gAAD/+AAB//wAB///AA///+B////////////8oAAAAEAAAACAAAAABACAAAAAAAAAE + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CfDw8BH///8GAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAAu7u7i7x8fFe8PDwevHx8YLx8fGC8fHxgvDw + 8Hvx8fFs7+/vT/Dw8CMAAAABAAAAAAAAAAAAAAAA5ubmCvLy8l/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8/PzZu7u7g8AAAAAAAAAAPHx8V3x8fGC8fHxgunv5o7Z7c200+vFytTs + xc7W7cnH2+7QueLu2qbu8OyH8fHxgvHx8YLx8fFu////BfHx8STx8fGC8fHxgtrtzq3D6a/8xemw/8bp + sv/I6rT/yeq2/8vruP/M67v/z+u++Nzu0bjx8fGC8fHxgu/v7zDx8fFI8fHxguzw6ojC56z3wuis/8Tp + rv/E6q3/weiq/8fqsv/J6rb/y+u5/8zru//N67z/6/HpjfHx8YLy8vJN8fHxXPHx8YLg79icv+eo/8Ho + qv+k4n//lOBo/5fhbf+a4XH/n+J5/7Pmlv/L67n/zOu7/+Xw353x8fGC8fHxXvHx8Vrx8fGC4O3Zm7/n + qP+/56j/nuF3/5HfZP+U4Gj/l+Ft/5ricf+x5pL/yeq3/8vruf/r8emN8fHxgu/v70/x8fFK8fHxguzw + 6ojA6Kn8v+eo/6njiP+O3mD/kd9k/5Tgaf+X4W3/vuim/8jqtP/N67zr8fHxgvHx8YLy8vI68/PzK/Hx + 8YLx8fGCx+m03L/nqP++6Kb/meBw/47eYP+S32X/q+SL/8XpsP/G6rL/1+zLvvHx8YLz8/OB8PDwEdXV + 1Qbx8fF98fHxgt/t1Z/A56j9v+eo/7/nqP+656H/vuim/8Lorf/E6a7/yOq18Ovw6Yvx8fGC8vLyYwAA + AAAAAAAA8fHxR/Hx8YLx8fGC2O3NrMDnqfq/56j/v+eo/7/nqP/B6Kv/xumy7OTu3Zfx8fGC8/PzgfLy + 8icAAAAAAAAAAP///wPz8/Nm8fHxgvHx8YLo7+SO0+zFuczquszM6bzJ1+zMru7w7Ibx8fGC8fHxgvHx + 8UcAAAAAAAAAAAAAAAAAAAAA4+PjCfHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgfPz + 80D///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8/PzK/Ly8mDz8/N+8fHxgvHx8YLy8vJ68vLyUezs + 7BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAevr6w3j4+MJAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD8fwAA4AcAAMADAACAAQAAgAEAAIABAACAAQAAgAEAAIAB + AADAAwAAwAMAAOAHAADwDwAA/n8AAP//AAA= + + + \ No newline at end of file diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 84ecda317..84aa6e456 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 c3c6aad27..aa0a8bf85 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -51,8 +51,17 @@ + + Form + + + DownloadForm.cs + + + DownloadForm.cs + ResXFileCodeGenerator Resources.Designer.cs From 655ba015a07c87e7b1a24ce8cafbe171103acad1 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 8 Oct 2022 23:47:27 +0800 Subject: [PATCH 11/58] 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 9c740e312..5bb88b6d3 100644 --- a/extra/exe-builder/DownloadForm.cs +++ b/extra/exe-builder/DownloadForm.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Net; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; namespace UptimeKuma { public partial class DownloadForm : Form { private readonly Queue downloadQueue = new(); private readonly WebClient webClient = new(); + private DownloadItem currentDownloadItem; public DownloadForm() { InitializeComponent(); @@ -18,17 +23,19 @@ namespace UptimeKuma { webClient.DownloadProgressChanged += DownloadProgressChanged; webClient.DownloadFileCompleted += DownloadFileCompleted; - if (!File.Exists("node")) { + if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { URL = "https://nodejs.org/dist/v16.17.1/node-v16.17.1-win-x64.zip", - Filename = "node.zip" + Filename = "node.zip", + TargetFolder = "node" }); } - if (!File.Exists("node")) { + if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { URL = "https://github.com/louislam/uptime-kuma/archive/refs/tags/1.18.3.zip", - Filename = "core.zip" + Filename = "core.zip", + TargetFolder = "core" }); } @@ -38,28 +45,130 @@ namespace UptimeKuma { void DownloadNextFile() { if (downloadQueue.Count > 0) { var item = downloadQueue.Dequeue(); - label.Text = item.URL; - webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); + + currentDownloadItem = item; + + // Download if the zip file is not existing + if (!File.Exists(item.Filename)) { + label.Text = item.URL; + webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); + } else { + progressBar.Value = 100; + label.Text = "Use local " + item.Filename; + DownloadFileCompleted(null, null); + } } else { - // TODO: Finished, extract? + npmSetup(); + } + } + + void npmSetup() { + if (Directory.Exists("core/node_modules")) { + // Application.Restart(); } + + label.Text = "npm run setup"; + progressBar.Value = 50; + labelData.Text = ""; + + var startInfo = new ProcessStartInfo { + FileName = "cmd.exe", + Arguments = "run setup", + RedirectStandardOutput = false, + RedirectStandardError = false, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = false, + WorkingDirectory = "core" + }; + + var process = new Process(); + process.StartInfo = startInfo; + process.EnableRaisingEvents = true; + process.Exited += (object _, EventArgs e) => { + // Application.Restart(); + progressBar.Value = 100; + + if (process.ExitCode == 0) { + label.Text = "Done"; + } else { + label.Text = "Failed, exit code: " + process.ExitCode; + } + + }; + process.Start(); + process.StandardInput.WriteLine("\"../node/npm\" run setup"); } void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { progressBar.Value = e.ProgressPercentage; var total = e.TotalBytesToReceive / 1024; var current = e.BytesReceived / 1024; - labelData.Text = $"{current}KB/{total}KB"; + + if (total > 0) { + labelData.Text = $"{current}KB/{total}KB"; + } } - void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + async void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + Extract(currentDownloadItem); DownloadNextFile(); } + + void Extract(DownloadItem item) { + if (Directory.Exists(item.TargetFolder)) { + var dir = new DirectoryInfo(item.TargetFolder); + dir.Delete(true); + } + + if (Directory.Exists("temp")) { + var dir = new DirectoryInfo("temp"); + dir.Delete(true); + } + + labelData.Text = $"Extracting {item.Filename}..."; + + ZipFile.ExtractToDirectory(item.Filename, "temp"); + + string[] dirList; + + // Move to the correct level + dirList = Directory.GetDirectories("temp"); + + + + if (dirList.Length > 0) { + var dir = dirList[0]; + + // As sometime ExtractToDirectory is still locking the directory, loop until ok + while (true) { + try { + Directory.Move(dir, item.TargetFolder); + break; + } catch (Exception exception) { + Thread.Sleep(1000); + } + } + + } else { + MessageBox.Show("Unexcepted Error: Cannot move extracted files, folder not found."); + } + + labelData.Text = $"Extracted"; + + if (Directory.Exists("temp")) { + var dir = new DirectoryInfo("temp"); + dir.Delete(true); + } + + File.Delete(item.Filename); + } } public class DownloadItem { public string URL { get; set; } public string Filename { get; set; } + public string TargetFolder { get; set; } } } diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 84aa6e456..1b78f0385 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 aa0a8bf85..2ad857b2c 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -35,11 +35,12 @@ 4 - COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "C:\Users\LouisLam\Desktop\uptime-kuma-win64\" + COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\" + From a487347b3316cc3192b312a195ac44a46160d310 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 9 Oct 2022 03:47:06 +0800 Subject: [PATCH 12/58] [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 5bb88b6d3..f16af4222 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 1b78f0385..82c76b05d 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -45,7 +45,7 @@ namespace UptimeKuma { trayIcon.MouseDoubleClick += new MouseEventHandler(Open); trayIcon.Visible = true; - if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules")) { + if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules") && Directory.Exists("core/dist")) { // Go go go StartProcess(); } else { From da16796ec45a3ecb12f16a1855bca0813beb2571 Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Sat, 19 Nov 2022 16:10:30 -0800 Subject: [PATCH 13/58] 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 2b0576224..88923e665 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 9daf31ac6..4eb014ff7 100644 --- a/src/components/notifications/Telegram.vue +++ b/src/components/notifications/Telegram.vue @@ -28,6 +28,15 @@ {{ telegramGetUpdatesURL("masked") }}

+ +
+ + +
+ +
+ {{ $t("telegramSendSilentlyDescription") }} +
diff --git a/src/languages/en.js b/src/languages/en.js index 86abb7912..492689e56 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 Date: Thu, 12 Jan 2023 15:09:05 +0200 Subject: [PATCH 14/58] 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 2b0576224..9c8f57501 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 9daf31ac6..9b373b997 100644 --- a/src/components/notifications/Telegram.vue +++ b/src/components/notifications/Telegram.vue @@ -29,6 +29,40 @@

+ +
+ + +
+ +
+ +
+ {{ $t("Thread ID Description") }} +
+
+ +
+
+ + +
+ +
+ {{ $t("Silent Notification Description") }} +
+
+ +
+
+ + +
+ +
+ {{ $t("Protect Forwarding Description") }} +
+