aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--back-end/src/Endpoints/SiteLookupEndpoint.cs156
-rw-r--r--back-end/src/PlatformFeatures/Curl/CurlResult.cs19
-rw-r--r--back-end/src/PlatformFeatures/Curl/ICurlApp.cs34
-rw-r--r--back-end/src/PlatformFeatures/Curl/SystemCurlApp.cs338
-rw-r--r--back-end/src/PlatformFeatures/Curl/WebsiteLookupResult.cs23
-rw-r--r--back-end/src/PlatformFeatures/ISystemApp.cs31
-rw-r--r--back-end/src/SimpleBookmark.csproj10
-rw-r--r--back-end/src/SimpleBookmark.json13
-rw-r--r--back-end/src/SimpleBookmarkEntry.cs3
-rw-r--r--ci/config/SimpleBookmark.json13
-rw-r--r--ci/config/config.json34
-rw-r--r--ci/container/Dockerfile2
-rw-r--r--ci/container/Taskfile.yaml5
-rw-r--r--ci/container/config-templates/SimpleBookmark-template.json15
-rw-r--r--ci/container/docker-compose.yaml4
-rw-r--r--ci/container/run.sh2
-rw-r--r--ci/plugins.taskfile.yaml39
-rw-r--r--ci/release.taskfile.yaml111
-rw-r--r--ci/setup.sh50
-rw-r--r--ci/taskfile.yaml89
-rw-r--r--front-end/package-lock.json370
-rw-r--r--front-end/package.json6
-rw-r--r--front-end/src/buttons.scss10
-rw-r--r--front-end/src/components/Bookmarks.vue1
-rw-r--r--front-end/src/components/Boomarks/AddOrUpdateForm.vue118
-rw-r--r--front-end/src/components/Settings.vue8
-rw-r--r--front-end/src/components/Settings/Bookmarks.vue84
-rw-r--r--front-end/src/components/Settings/PkiSettings.vue5
-rw-r--r--front-end/src/components/Settings/Registation.vue2
-rw-r--r--front-end/src/main.ts4
-rw-r--r--front-end/src/store/bookmarks.ts4
-rw-r--r--front-end/src/store/socialMfaPlugin.ts74
-rw-r--r--front-end/src/store/websiteLookup.ts76
33 files changed, 1322 insertions, 431 deletions
diff --git a/back-end/src/Endpoints/SiteLookupEndpoint.cs b/back-end/src/Endpoints/SiteLookupEndpoint.cs
new file mode 100644
index 0000000..effe6aa
--- /dev/null
+++ b/back-end/src/Endpoints/SiteLookupEndpoint.cs
@@ -0,0 +1,156 @@
+// Copyright (C) 2024 Vaughn Nugent
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+using System;
+using System.Net;
+using System.Text;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Plugins;
+using VNLib.Plugins.Essentials;
+using VNLib.Plugins.Essentials.Endpoints;
+using VNLib.Plugins.Essentials.Extensions;
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Validation;
+
+using SimpleBookmark.PlatformFeatures.Curl;
+
+namespace SimpleBookmark.Endpoints
+{
+ [ConfigurationName("curl")]
+ internal sealed class SiteLookupEndpoint : ProtectedWebEndpoint
+ {
+ const string DefaultCurlExecName = "curl";
+ const int MaxTimeoutValue = 30000;
+
+ private readonly SystemCurlApp _curl;
+ private readonly IAsyncLazy<bool> _isSupported;
+
+ public SiteLookupEndpoint(PluginBase plugin, IConfigScope config)
+ {
+ string path = config.GetRequiredProperty("path", p => p.GetString()!);
+ InitPathAndLog(path, plugin.Log);
+
+ string exePath = config.GetValueOrDefault("exe_path", p => p.GetString(), DefaultCurlExecName);
+ bool httspOnly = config.GetValueOrDefault("https_only", p => p.GetBoolean(), false);
+
+ //Optional extra arguments
+ string[] extrArgs = config.GetValueOrDefault(
+ "extra_args",
+ p => p.EnumerateArray().Select(s => s.GetString()!).ToArray(),
+ Array.Empty<string>()
+ );
+
+ _curl = new SystemCurlApp(exePath, httspOnly, extrArgs);
+
+ //Immediately check if curl is supported
+ _isSupported = _curl.TestIsAvailable(plugin.UnloadToken).AsLazy();
+ }
+
+ protected override async ValueTask<VfReturnType> GetAsync(HttpEntity entity)
+ {
+ WebMessage webm = new();
+
+ bool isEnabled = await _isSupported;
+
+ //Allow site to cache if curl is supported on the platform
+ if (entity.QueryArgs.ContainsKey("support"))
+ {
+ webm.Success = isEnabled;
+ return VirtualOk(entity, webm);
+ }
+
+ //Assert supported value as curl is required for a normal url lookup
+ if(webm.Assert(isEnabled, "Curl is not supported on the current platform"))
+ {
+ return VirtualClose(entity, webm, HttpStatusCode.NotImplemented);
+ }
+
+ string? url = entity.QueryArgs.GetValueOrDefault("url");
+
+ if(webm.Assert(!string.IsNullOrWhiteSpace(url), "No url provided"))
+ {
+ return VirtualClose(entity, webm, HttpStatusCode.BadRequest);
+ }
+
+ if(webm.Assert(UrlFromBase64Url(url!, out Uri? uri), "Invalid url provided"))
+ {
+ return VirtualClose(entity, webm, HttpStatusCode.UnprocessableEntity);
+ }
+
+ int? timeoutMs = null;
+
+ //Allow clients to specify a timeout for the request
+ string? timeoutMsS = entity.QueryArgs.GetValueOrDefault("timeout");
+ if (timeoutMsS is not null && int.TryParse(timeoutMsS, out int _timeoutMs))
+ {
+ //Miniumum timeout must be greater than 1 second because curl is timed in seconds
+ timeoutMs = Math.Clamp(_timeoutMs, 1000, MaxTimeoutValue);
+ }
+
+ try
+ {
+ //Exec curl on the url
+ CurlResult result = await _curl.ExecLookupAsync(uri!, timeoutMs, entity.EventCancellation);
+
+ if(webm.Assert(result.IsError == false, result.ErrorMessage!))
+ {
+ return VirtualClose(entity, webm, HttpStatusCode.InternalServerError);
+ }
+
+ webm.Success = true;
+ webm.Result = result.Result; //Set curl lookup result as the response
+
+ return VirtualOk(entity, webm);
+ }
+ catch (TimeoutException)
+ {
+ webm.Result = "Request timed out";
+ return VirtualClose(entity, webm, HttpStatusCode.InternalServerError);
+ }
+ catch (OperationCanceledException)
+ {
+ webm.Result = "Request timed out";
+ return VirtualClose(entity, webm, HttpStatusCode.InternalServerError);
+ }
+ }
+
+ /*
+ * Reads in a base64url encoded string which is the user's search url and
+ * attempts to parse it into a uri. If the url is invalid, the function
+ */
+ private static bool UrlFromBase64Url(string base64Url, out Uri? uri)
+ {
+ uri = null;
+
+ //Alloc output buffer for decoded data
+ using UnsafeMemoryHandle<byte> output = MemoryUtil.UnsafeAllocNearestPage(base64Url.Length, true);
+
+ ERRNO decoded = VnEncoding.Base64UrlDecode(base64Url, output.Span, Encoding.UTF8);
+ if(decoded < 1)
+ {
+ return false;
+ }
+
+ //Recover the url string from its binary representation and try to parse it into a uri
+ string urlstring = Encoding.UTF8.GetString(output.Span[..(int)decoded]);
+ return Uri.TryCreate(urlstring, UriKind.Absolute, out uri);
+ }
+ }
+}
diff --git a/back-end/src/PlatformFeatures/Curl/CurlResult.cs b/back-end/src/PlatformFeatures/Curl/CurlResult.cs
new file mode 100644
index 0000000..7d70e0e
--- /dev/null
+++ b/back-end/src/PlatformFeatures/Curl/CurlResult.cs
@@ -0,0 +1,19 @@
+// Copyright (C) 2024 Vaughn Nugent
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+namespace SimpleBookmark.PlatformFeatures.Curl
+{
+ internal sealed record class CurlResult(WebsiteLookupResult? Result, bool IsError, string? ErrorMessage);
+}
diff --git a/back-end/src/PlatformFeatures/Curl/ICurlApp.cs b/back-end/src/PlatformFeatures/Curl/ICurlApp.cs
new file mode 100644
index 0000000..ec952e0
--- /dev/null
+++ b/back-end/src/PlatformFeatures/Curl/ICurlApp.cs
@@ -0,0 +1,34 @@
+// Copyright (C) 2024 Vaughn Nugent
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SimpleBookmark.PlatformFeatures.Curl
+{
+ internal interface ICurlApp
+ {
+ /// <summary>
+ /// Executes a lookup on the given website and returns the title and description
+ /// </summary>
+ /// <param name="website">The website url to search against</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>The result of the website lookup</returns>
+ Task<CurlResult> ExecLookupAsync(Uri website, int? timeoutMs, CancellationToken cancellation);
+ }
+}
diff --git a/back-end/src/PlatformFeatures/Curl/SystemCurlApp.cs b/back-end/src/PlatformFeatures/Curl/SystemCurlApp.cs
new file mode 100644
index 0000000..0949136
--- /dev/null
+++ b/back-end/src/PlatformFeatures/Curl/SystemCurlApp.cs
@@ -0,0 +1,338 @@
+// Copyright (C) 2024 Vaughn Nugent
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Diagnostics;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace SimpleBookmark.PlatformFeatures.Curl
+{
+ sealed class SystemCurlApp(string exePath, bool httpsOnly, string[] additionalArgs) : ISystemApp, ICurlApp
+ {
+ const int DefaultTimeoutMs = 5000;
+
+ ///<inheritdoc/>
+ public async Task<bool> TestIsAvailable(CancellationToken cancellation)
+ {
+ try
+ {
+ //Test if the curl application is available on the local system, may be at path
+ using Process? process = Exec(["--version"]);
+
+ if (process is null)
+ {
+ return false;
+ }
+
+ //Wait for the process to exit
+ await process.WaitForExitAsync(cancellation);
+
+ //If an ok status code, then we know the curl application is available
+ return process.ExitCode == 0;
+ }
+ //App not found
+ catch (Win32Exception)
+ {
+ return false;
+ }
+ }
+
+ private Process? Exec(string[] arguments)
+ {
+ ProcessStartInfo startInfo = new()
+ {
+ FileName = exePath,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ StandardOutputEncoding = Encoding.UTF8,
+ StandardErrorEncoding = Encoding.UTF8
+ };
+
+ //Add arguments
+ arguments.ForEach(startInfo.ArgumentList.Add);
+
+ return Process.Start(startInfo);
+ }
+
+ private void ValidateUrl(Uri? website)
+ {
+ ArgumentNullException.ThrowIfNull(website);
+
+ if (!website.IsAbsoluteUri)
+ {
+ throw new ArgumentException("The website url must be an absolute uri", nameof(website));
+ }
+
+ if (httpsOnly && website.Scheme != Uri.UriSchemeHttps)
+ {
+ throw new ArgumentException("The website url must be an https url only!", nameof(website));
+ }
+ else if (website.Scheme != Uri.UriSchemeHttp && website.Scheme != Uri.UriSchemeHttps)
+ {
+ //Http or https only
+ throw new ArgumentException("The website url must be an http or https url", nameof(website));
+ }
+ }
+
+ ///<inheritdoc/>
+ public async Task<CurlResult> ExecLookupAsync(Uri website, int? timeoutMs, CancellationToken cancellation)
+ {
+ //Validate the url
+ ValidateUrl(website);
+
+ string timeoutArg = timeoutMs.HasValue ? $"{timeoutMs.Value / 1000}" : $"{DefaultTimeoutMs / 1000}";
+
+ string[] args = [
+ "--max-time", timeoutArg, //Set the max time for the request
+ "-S", //Silent mode is required
+ "-H", "Accept: text/html,application/html", //Html is required
+ ..additionalArgs, //Additional global arguments
+ website.AbsoluteUri
+ ];
+
+ //Execute the curl command
+
+ using Process? process = Exec(args);
+
+ if (process is null)
+ {
+ return new CurlResult(null, true, "Curl is not enabled on this platform, lookup failed");
+ }
+
+ //Parse the html data
+ Task<string?> documentHeadTask = HtmlTokenReader.ReadHeadTokenAsync(process.StandardOutput, cancellation);
+
+ //Respect the user's timeout command and termimate the process if it exceeds the timeout
+ if (timeoutMs.HasValue)
+ {
+ await documentHeadTask.WaitAsync(TimeSpan.FromMilliseconds(timeoutMs.Value));
+
+ await Task.WhenAll(
+ DiscardStreamAsync(process.StandardOutput, cancellation),
+ DiscardStreamAsync(process.StandardError, cancellation)
+ ).WaitAsync(TimeSpan.FromMilliseconds(timeoutMs.Value));
+ }
+ else
+ {
+ await documentHeadTask;
+
+ await Task.WhenAll(
+ DiscardStreamAsync(process.StandardOutput, cancellation),
+ DiscardStreamAsync(process.StandardError, cancellation)
+ );
+ }
+
+ await process.WaitForExitAsync(cancellation);
+
+ if (process.ExitCode != 0)
+ {
+ return new CurlResult(null, true, "Curl exited with a non-zero status code");
+ }
+
+ string? documentHead = await documentHeadTask;
+
+ if (documentHead is null)
+ {
+ return new CurlResult(null, true, "Failed to parse html data");
+ }
+
+ //Get the lookup result from the document head segmetn
+ WebsiteLookupResult result = HtmlTokenReader.ParseHtmlData(documentHead);
+
+ return new CurlResult(result, false, null);
+ }
+
+ /// <summary>
+ /// Safely discards the entire stream of data from the reader without
+ /// allocating a large string buffer
+ /// </summary>
+ /// <param name="reader">The reader to discard</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that represents the discard opeartion</returns>
+ private static async Task DiscardStreamAsync(TextReader reader, CancellationToken cancellation)
+ {
+ using ArrayPoolBuffer<char> discarBuffer = new(8192);
+
+ while (await reader.ReadBlockAsync(discarBuffer.AsMemory(), cancellation) > 0)
+ { }
+ }
+
+ private static class HtmlTokenReader
+ {
+ /// <summary>
+ /// Gets the document title from the head of the html document
+ /// </summary>
+ /// <param name="head">The head string containing the title to parse</param>
+ /// <returns>The title string if found</returns>
+ public static string? GetDocTitleFromHead(string head)
+ {
+ ReadOnlySpan<char> headChars = head.AsSpan();
+
+ ReadOnlySpan<char> title = headChars.SliceAfterParam("<title>");
+ title = title.SliceBeforeParam("</title>");
+
+ return title.ToString();
+ }
+
+ /// <summary>
+ /// Attempts to get the document summary from the head of the html document
+ /// in the meta description tag
+ /// </summary>
+ /// <param name="head">The head string to parse</param>
+ /// <returns>The document description if found</returns>
+ public static string? GetDocumentSummary(string head)
+ {
+ ReadOnlySpan<char> headChars = head.AsSpan();
+
+ ReadOnlySpan<char> desc = headChars.SliceAfterParam("<meta name=\"description\" content=\"");
+ desc = desc.SliceBeforeParam("\"/>");
+ desc = desc.SliceBeforeParam("\">");
+
+ return desc.ToString();
+ }
+
+ /// <summary>
+ /// Attempts to get the document keywords from the head of the html document
+ /// by parsing the meta keywords tag
+ /// </summary>
+ /// <param name="head">The document head</param>
+ /// <returns>An array of document keywords found from the head section</returns>
+ public static string[]? GetDocumentKeywords(string head)
+ {
+ ReadOnlySpan<char> headChars = head.AsSpan();
+
+ ReadOnlySpan<char> kwStart = headChars.SliceAfterParam("<meta name=\"keywords\" content=\"");
+ ReadOnlySpan<char> kwSpan = kwStart.SliceBeforeParam("\">");
+
+ List<string> keywords = [];
+
+ //Split the keywords at comma, and remove any empty entries/whitespace
+ kwSpan.Split(',', keywords, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+
+ return keywords.ToArray();
+ }
+
+ public static WebsiteLookupResult ParseHtmlData(string documentHead)
+ {
+ //Parse head segments for title, description, and keywords
+ return new WebsiteLookupResult(
+ title: GetDocTitleFromHead(documentHead),
+ description: GetDocumentSummary(documentHead),
+ keywords: GetDocumentKeywords(documentHead)
+ );
+ }
+
+
+
+ public static async Task<string?> ReadHeadTokenAsync(TextReader reader, CancellationToken cancellation)
+ {
+ //String buffer to store parsed head data
+ StringBuilder stringBuilder = new(1024);
+
+ //Temp copy buffer
+ using ArrayPoolBuffer<char> buffer = new(4096);
+
+ bool isStart = true, isEnd = false;
+
+ //scan for docuemnt head
+ do
+ {
+ int read = await reader.ReadBlockAsync(buffer.AsMemory(), cancellation);
+
+ if (read == 0)
+ {
+ //Read should never return 0, if it does, then there is no head to read
+ return null;
+ }
+
+ if (isStart)
+ {
+ Memory<char> headSpan = HeadStart(buffer.AsMemory());
+
+ //No head was found, continue buffering
+ if (headSpan.IsEmpty)
+ {
+ continue;
+ }
+
+ /*
+ * Try to find the end of the head, if it is found, then we can break
+ */
+ isEnd = HeadEnd(ref headSpan);
+
+ //Valid head data to buffer
+ stringBuilder.Append(headSpan);
+
+ isStart = false;
+ }
+ else
+ {
+ //Head start was already found, just need to buffer until it ends
+ Memory<char> end = buffer.AsMemory();
+
+ isEnd = HeadEnd(ref end);
+
+ stringBuilder.Append(end);
+
+ if (isEnd)
+ {
+ break;
+ }
+ }
+
+ } while (!isEnd);
+
+ return stringBuilder.ToString();
+ }
+
+ static Memory<char> HeadStart(Memory<char> start)
+ {
+ //find start of head
+ int headStartIndex = start.Span.IndexOf("<head>");
+
+ if (headStartIndex == -1)
+ {
+ return default;
+ }
+
+ return start[headStartIndex..];
+ }
+
+ static bool HeadEnd(ref Memory<char> end)
+ {
+ //find end of head
+ int headEndIndex = end.Span.IndexOf("</head>");
+
+ if (headEndIndex == -1)
+ {
+ return false;
+ }
+
+ end = end[..headEndIndex];
+ return true;
+ }
+ }
+ }
+}
diff --git a/back-end/src/PlatformFeatures/Curl/WebsiteLookupResult.cs b/back-end/src/PlatformFeatures/Curl/WebsiteLookupResult.cs
new file mode 100644
index 0000000..e9d9bc0
--- /dev/null
+++ b/back-end/src/PlatformFeatures/Curl/WebsiteLookupResult.cs
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 Vaughn Nugent
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+namespace SimpleBookmark.PlatformFeatures.Curl
+{
+#pragma warning disable IDE1006 // Naming Styles (JSON serialization)
+
+ internal sealed record class WebsiteLookupResult(string? title, string? description, string[]? keywords);
+
+#pragma warning restore IDE1006 // Naming Styles
+}
diff --git a/back-end/src/PlatformFeatures/ISystemApp.cs b/back-end/src/PlatformFeatures/ISystemApp.cs
new file mode 100644
index 0000000..11d15f1
--- /dev/null
+++ b/back-end/src/PlatformFeatures/ISystemApp.cs
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 Vaughn Nugent
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace SimpleBookmark.PlatformFeatures
+{
+ internal interface ISystemApp
+ {
+ /// <summary>
+ /// Gets a value indicating if the curl application is available
+ /// on the local system.
+ /// </summary>
+ /// <returns>True if the curl exe is available on the local system, false otherwise</returns>
+ Task<bool> TestIsAvailable(CancellationToken cancellation);
+ }
+}
diff --git a/back-end/src/SimpleBookmark.csproj b/back-end/src/SimpleBookmark.csproj
index 609144b..1eeaaba 100644
--- a/back-end/src/SimpleBookmark.csproj
+++ b/back-end/src/SimpleBookmark.csproj
@@ -34,11 +34,11 @@
<ItemGroup>
<PackageReference Include="MemoryPack" Version="1.10.0" />
- <PackageReference Include="VNLib.Plugins.Extensions.Data" Version="0.1.0-ci0050" />
- <PackageReference Include="VNLib.Plugins.Extensions.Loading" Version="0.1.0-ci0050" />
- <PackageReference Include="VNLib.Plugins.Extensions.Loading.Sql" Version="0.1.0-ci0050" />
- <PackageReference Include="VNLib.Plugins.Extensions.Validation" Version="0.1.0-ci0050" />
- <PackageReference Include="VNLib.Plugins.Extensions.VNCache" Version="0.1.0-ci0052" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Data" Version="0.1.0-ci0052" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Loading" Version="0.1.0-ci0052" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Loading.Sql" Version="0.1.0-ci0052" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Validation" Version="0.1.0-ci0052" />
+ <PackageReference Include="VNLib.Plugins.Extensions.VNCache" Version="0.1.0-ci0053" />
</ItemGroup>
<ItemGroup>
diff --git a/back-end/src/SimpleBookmark.json b/back-end/src/SimpleBookmark.json
index 27ebff8..116587d 100644
--- a/back-end/src/SimpleBookmark.json
+++ b/back-end/src/SimpleBookmark.json
@@ -14,6 +14,19 @@
}
},
+ //System website lookup endpoint (aka curl)
+ "curl": {
+ "path": "/lookup",
+ "exe_path": "curl", //Path to the curl executable
+ "extra_args": [
+ "--globoff", //Disables unsafe url globbing
+ "--no-keepalive", //Disables keepalive, uneeded for a single lookup request
+ "--max-filesize", "100K", //Max file size 100K
+ "--max-redirs", "5", //Max redirects 5
+ "--location", //Follow redirects
+ ]
+ },
+
"registration": {
"path": "/register", //Path for the registration endpoint
"token_lifetime_mins": 360, //Token lifetime in minutes
diff --git a/back-end/src/SimpleBookmarkEntry.cs b/back-end/src/SimpleBookmarkEntry.cs
index a1c9590..13b94a5 100644
--- a/back-end/src/SimpleBookmarkEntry.cs
+++ b/back-end/src/SimpleBookmarkEntry.cs
@@ -50,9 +50,10 @@ namespace SimpleBookmark
//route the bm endpoint
this.Route<BookmarkEndpoint>();
this.Route<BmAccountEndpoint>();
+ this.Route<SiteLookupEndpoint>();
//Ensure database is created after a delay
- this.ObserveWork(() => this.EnsureDbCreatedAsync<SimpleBookmarkContext>(this), 1000);
+ this.ObserveWork(() => this.EnsureDbCreatedAsync<SimpleBookmarkContext>(this), 1500);
Log.Information("Plugin Loaded");
PrintHelloMessage();
diff --git a/ci/config/SimpleBookmark.json b/ci/config/SimpleBookmark.json
index 6cb1b93..64be3c1 100644
--- a/ci/config/SimpleBookmark.json
+++ b/ci/config/SimpleBookmark.json
@@ -14,6 +14,19 @@
}
},
+ //System website lookup endpoint (aka curl)
+ "curl": {
+ "path": "/api/lookup",
+ "exe_path": "curl", //Path to the curl executable
+ "extra_args": [
+ "--globoff", //Disables unsafe url globbing
+ "--no-keepalive", //Disables keepalive, uneeded for a single lookup request
+ "--max-filesize", "100K", //Max file size 100K
+ "--max-redirs", "5", //Max redirects 5
+ "--location", //Follow redirects
+ ]
+ },
+
"registration": {
"path": "/api/register", //Path for the registration endpoint
"token_lifetime_mins": 360, //Token lifetime in minutes
diff --git a/ci/config/config.json b/ci/config/config.json
index e4b33e8..61293b6 100644
--- a/ci/config/config.json
+++ b/ci/config/config.json
@@ -41,7 +41,7 @@
//Setup the native lib
"vnlib.net.compression": {
- "lib_path": "lib/vnlib_compress/build/<os-dependent-lib-path>",
+ "lib_path": "lib/vnlib_compress.dll",
"level": 1
},
@@ -95,16 +95,16 @@
//"cors_allowed_authority": [ "localhost:8080" ],
//Define a TLS certificate (enables TLS on the interface)
- "disabled ssl": {
+ "ssl": {
//Cert may be pem or pfx (include private key in pfx, or include private key in a pem file)
- "cert": "/path/to/cert.pfx|pem",
+ "cert": "ssl/cert.pem",
//A pem encoded private key, REQUIRED if using a PEM certificate, may be encrypted with a password
- "privkey": "/path/to/private_key.pem",
+ "privkey": "ssl/key.pem",
//An optional password for the ssl private key
- "password": "plain-text-password",
+ //"password": "plain-text-password",
//requires that any client connecting to this host present a valid certificate
"client_cert_required": false
@@ -129,29 +129,29 @@
"assets": "plugins/assets/"
},
- "disabled sys_log": {
- //"path": "path/to/syslog/file",
+ "sys_log": {
+ "path": "data/logs/syslog.txt",
//"template": "serilog template for writing to file",
- //"flush_sec": 5,
- //"retained_files": 31,
- //"file_size_limit": 10485760,
- //"interval": "infinite"
+ "flush_sec": 5,
+ "retained_files": 10,
+ "file_size_limit": 10485760,
+ "interval": "infinite"
},
"disabled app_log": {
- //"path": "path/to/applog/file",
+ "path": "data/logs/applog.txt",
//"template": "serilog template for writing to file",
- //"flush_sec": 5,
- //"retained_files": 31,
- //"file_size_limit": 10485760,
- //"interval": "infinite"
+ "flush_sec": 5,
+ "retained_files": 10,
+ "file_size_limit": 10485760,
+ "interval": "infinite"
},
//Sql for the users database
"sql": {
"debug": false,
"provider": "VNLib.Plugins.Extensions.Sql.SQLite.dll",
- "source": "simple-bookmark.db" //For sqlite only
+ "source": "data/simple-bookmark.db" //For sqlite only
},
//caching should be setup globally after VNCache #78a47dd
diff --git a/ci/container/Dockerfile b/ci/container/Dockerfile
index 6804a6e..f5ac798 100644
--- a/ci/container/Dockerfile
+++ b/ci/container/Dockerfile
@@ -32,7 +32,7 @@ COPY app/ /app
#pull compiled libs from build container
COPY --from=native-cont /build/out /app/lib
-RUN apk update && apk add --no-cache gettext icu-libs dumb-init
+RUN apk update && apk add --no-cache gettext icu-libs dumb-init curl
#workdir
WORKDIR /app
diff --git a/ci/container/Taskfile.yaml b/ci/container/Taskfile.yaml
index 97548dc..557e48d 100644
--- a/ci/container/Taskfile.yaml
+++ b/ci/container/Taskfile.yaml
@@ -41,13 +41,12 @@ tasks:
cmds:
# clean up the run.sh script to remove windows line endings in my wsl default instance
- cmd: wsl dos2unix ./run.sh
- platform: [ win-x64 ]
+ platforms: [ windows/amd64 ]
#init build image
- task: setup-container-image
#remove the default config file as it's not needed in the container
- - powershell -Command "rm -Force build/app/config.json"
- powershell -Command "rm -Force -Recurse build/app/config/"
- task: prune-sql-runtimes
@@ -84,7 +83,7 @@ tasks:
#make build directory
- powershell -Command "mkdir build, build/app, build/app/config-templates/, build/app/static/ -Force"
#copy the existing linux-x64 build to the build folder, this will be the container base
- - powershell -Command "cp -Recurse -Force ../build/linux-x64/* build/app/"
+ - powershell -Command "cp -Recurse -Force ../build/linux-x86_64/* build/app/"
#copy local scripts and config data into the build folder
- powershell -Command "cp -Force run.sh, Taskfile.yaml build/app/"
- powershell -Command "cp -Force Dockerfile, docker-compose.yaml build/"
diff --git a/ci/container/config-templates/SimpleBookmark-template.json b/ci/container/config-templates/SimpleBookmark-template.json
index a64a10a..c2bf780 100644
--- a/ci/container/config-templates/SimpleBookmark-template.json
+++ b/ci/container/config-templates/SimpleBookmark-template.json
@@ -14,6 +14,21 @@
}
},
+ //System website lookup endpoint (aka curl)
+ "curl": {
+ "path": "/api/lookup",
+ "exe_path": "curl", //Path to the curl executable
+ "extra_args": [
+ "--globoff", //Disables unsafe url globbing
+ "--no-keepalive", //Disables keepalive, uneeded for a single lookup request
+ "--max-filesize",
+ "100K", //Max file size 100K
+ "--max-redirs",
+ "5", //Max redirects 5
+ "--location", //Follow redirects
+ ]
+ },
+
"registration": {
"path": "/api/register", //Path for the registration endpoint
"token_lifetime_mins": ${REG_TOKEN_DURATION_MIN}, //Token lifetime in minutes
diff --git a/ci/container/docker-compose.yaml b/ci/container/docker-compose.yaml
index eb28055..03eb815 100644
--- a/ci/container/docker-compose.yaml
+++ b/ci/container/docker-compose.yaml
@@ -27,6 +27,7 @@ services:
CACHE_ASM_PATH: "VNLib.Data.Caching.Providers.VNCache.dll"
MEMCACHE_ONLY: "true"
REDIS_CONNECTION_STRING: ""
+ #at least one node required if MEMCACHE_ONLY is false
VNCACHE_INITIAL_NODES: "[]"
#ACCOUNTS
MAX_LOGIN_ATTEMPS: "10"
@@ -35,6 +36,7 @@ services:
PASSWORD_PEPPER: ""
DATABASE_PASSWORD: ""
REDIS_PASSWORD: ""
+ #if MEMCACHE_ONLY is false, then the following keys are required to connect to a VNCACHE cluster
VNCACHE_CLIENT_PRIVATE_KEY: ""
VNCACHE_CACHE_PUBLIC_KEY: ""
@@ -42,5 +44,5 @@ services:
HTTP_DOWNSTREAM_SERVERS: '[]'
#SSL_JSON: '{"cert": "ssl/cert.pem", "privkey":"ssl/priv.pem"}'
- SERVER_ARGS: "--input-off"
+ SERVER_ARGS: ""
diff --git a/ci/container/run.sh b/ci/container/run.sh
index 2c2636c..c780929 100644
--- a/ci/container/run.sh
+++ b/ci/container/run.sh
@@ -12,4 +12,4 @@ done
cp usr/assets/* plugins/assets/ -rf
#start the server
-dotnet webserver/VNLib.WebServer.dll --config config/config.json $SERVER_ARGS \ No newline at end of file
+dotnet webserver/VNLib.WebServer.dll --config config/config.json --input-off $SERVER_ARGS \ No newline at end of file
diff --git a/ci/plugins.taskfile.yaml b/ci/plugins.taskfile.yaml
index f39121d..cab3d53 100644
--- a/ci/plugins.taskfile.yaml
+++ b/ci/plugins.taskfile.yaml
@@ -15,18 +15,18 @@ tasks:
all:
deps:
- - install-accounts
- - install-router
- - install-sessions
- - install-vncache
- - install-vncache-sessions
- - install-users
- - install-sqlite
- - install-argon2-lib
- - install-compression
- - install-compressor-lib
-
+ - install-rpmalloc
+ - install-compressor-lib
+ - install-argon2-lib
+ - install-compression
+ - install-sqlite
cmds:
+ - task: install-accounts
+ - task: install-router
+ - task: install-sessions
+ - task: install-vncache
+ - task: install-vncache-sessions
+ - task: install-users
- echo "Installing and configuring plugins and UI"
- task: build-bookmarks
@@ -156,3 +156,20 @@ tasks:
cmd: powershell -Command "rm ./lib/argon2/{{.ITEM}} -Recurse"
ignore_error: true
+ install-rpmalloc:
+ cmds:
+ #install the rpmalloc source code package for Linux and Mac
+ - task: install:install
+ vars:
+ PROJECT_NAME: 'vnlib_rpmalloc'
+ MODULE_NAME: "VNLib.Core"
+ FILE_NAME: "src.tgz"
+ DIR: './lib/vnlib_rpmalloc'
+
+ #install the rpmalloc binary for Windows
+ - task: install:install
+ vars:
+ PROJECT_NAME: 'vnlib_rpmalloc'
+ MODULE_NAME: "VNLib.Core"
+ FILE_NAME: "win-x64-release-vnlib_rpmalloc.tgz"
+ DIR: './lib/vnlib_rpmalloc'
diff --git a/ci/release.taskfile.yaml b/ci/release.taskfile.yaml
new file mode 100644
index 0000000..f6fdf62
--- /dev/null
+++ b/ci/release.taskfile.yaml
@@ -0,0 +1,111 @@
+# https://taskfile.dev
+
+#Inlcuded taskfile for object cache server that is used to produce
+#ci builds for standalone caching servers
+
+version: "3"
+
+vars:
+ SSL_DIR: "ssl"
+ DATA_DIR: "data"
+ DEFAULT_EC_CURVE: "secp384r1"
+
+tasks:
+ default:
+ desc: "Runs the Simple-Bookmark server"
+ cmds:
+ - task: run
+
+ run:
+ desc: "Runs the Simple-Bookmark server"
+ silent: true
+ env:
+ #libraries intentionally do not have extensions, for cross-platform compatibility, the server will load them regardless
+ VNLIB_SHARED_HEAP_FILE_PATH: lib/vnlib_rpmalloc.dll
+ VNLIB_ARGON2_DLL_PATH: lib/argon2.dll
+ cmds:
+ - cmd: dotnet webserver/VNLib.WebServer.dll --config config/config.json {{.CLI_ARGS}}
+
+ setup-apt:
+ desc: "Performs initial setup on Debian apt amd64 based machines"
+ silent: true
+ cmds:
+ - apt update
+ - apt install -y dotnet-runtime-8.0 gcc cmake curl
+ - task: setup
+ - echo "Setup complete"
+
+ setup-dnf:
+ desc: "Performs initial setup on Fedora/Redhat amd (dnf) based machines"
+ silent: true
+ cmds:
+ - dnf update
+ - dnf install -y dotnet-runtime-8.0 gcc cmake curl
+ - task: setup
+ - echo "Setup complete"
+
+ setup-apk:
+ desc: "Performs initial setup using the APK package manager for amd64 based machines"
+ silent: true
+ cmds:
+ - apk update
+ - apk add --no-cache dotnet8-runtime build-base cmake curl
+ - task: setup
+ - echo "Setup complete"
+
+ setup:
+ desc: "Performs platform agnostic setup tasks without installing tools (no sudo needed)"
+ cmds:
+ #build rpmalloc lib
+ - task: build-rpmalloc
+ - task: build-argon2
+ - task: build-compress
+
+ #setup ssl dir
+ - cmd: mkdir ssl/
+ platforms: [ linux, darwin ]
+ ignore_error: true
+ - cmd: powershell -Command "mkdir ssl/"
+ platforms: [ windows/amd64 ]
+ ignore_error: true
+
+ create-cert:
+ desc: "Genereates a new self-signed TLS certificate"
+ cmds:
+ - openssl req -new -x509 -days 365 -keyout {{.SSL_DIR}}/key.pem -out {{.SSL_DIR}}/cert.pem -newkey ec -pkeyopt ec_paramgen_curve:{{.DEFAULT_EC_CURVE}} --nodes
+
+ build-rpmalloc:
+ internal: true
+ dir: 'lib/'
+ cmds:
+ #build rpmalloc library for linux/mac
+ - cmd: cd vnlib_rpmalloc/ && task && cp build/libvn_rpmalloc{{if eq OS "darwin"}}.dylib{{else}}.so{{end}} ../vnlib_rpmalloc.dll
+ platforms: [ linux, darwin ]
+
+ #for windows just copy the existing dll
+ - cmd: powershell -Command "cp vnlib_rpmalloc/vnlib_rpmalloc.dll vnlib_rpmalloc.dll"
+ platforms: [ windows/amd64 ]
+
+ build-argon2:
+ internal: true
+ dir: 'lib/'
+ cmds:
+ #build argon2 library for linux/mac
+ - cmd: cd argon2/ && task && cp build/libargon2{{if eq OS "darwin"}}.dylib{{else}}.so{{end}} ../argon2.dll
+ platforms: [ linux, darwin ]
+
+ #for windows just copy the existing dll
+ - cmd: powershell -Command "cp argon2/argon2.dll argon2.dll"
+ platforms: [ windows/amd64 ]
+
+ build-compress:
+ internal: true
+ dir: 'lib/'
+ cmds:
+ - cd vnlib_compress/ && task
+ #build the native compressor library for linux/mac
+ - cmd: cd vnlib_compress/ && cp build/libvn_compress{{if eq OS "darwin"}}.dylib{{else}}.so{{end}} ../vnlib_compress.dll
+ platforms: [ linux, darwin ]
+
+ - cmd: powershell -Command "cp vnlib_compress/build/Release/vnlib_compress.dll vnlib_compress.dll"
+ platforms: [ windows/amd64 ] \ No newline at end of file
diff --git a/ci/setup.sh b/ci/setup.sh
deleted file mode 100644
index 0cc153b..0000000
--- a/ci/setup.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-#! /bin/bash
-
-echo "Testing for go-task"
-#test for platform tools
-if ! command -v task &> /dev/null
-then
- echo "You must install go-task: from https://taskfile.dev/installation/"
- exit 1
-fi
-
-echo "Testing for cmake"
-#test for cmake
-if ! command -v cmake &> /dev/null
-then
- echo "You must have cmake installed globally"
- exit 1
-fi
-
-echo "Testing for GNUMake"
-#test for make
-if ! command -v make &> /dev/null
-then
- echo "You must have GNUMake installed globally"
- exit 1
-fi
-
-echo "Testing for git"
-#test for git
-if ! command -v git &> /dev/null
-then
- echo "You must have git installed globally"
- exit 1
-fi
-
-#build the argon2 native library
-pushd argon2 > /dev/null
-echo "Building Argon2 native library"
-make
-argon2_path=$(find "$(pwd)" -iname "libargon2.so.*")
-
-echo "Add the following environment variable"
-echo VNLIB_ARGON2_DLL_PATH=$argon2_path
-popd > /dev/null
-
-#build the vnlib_compress native library
-pushd vnlib_compress > /dev/null
-echo "Building vnlib_compress native library"
-task
-echo "Finished building vnlib_compress"
-popd > /dev/null \ No newline at end of file
diff --git a/ci/taskfile.yaml b/ci/taskfile.yaml
index a27b1ac..43e11a8 100644
--- a/ci/taskfile.yaml
+++ b/ci/taskfile.yaml
@@ -7,6 +7,7 @@ version: "3"
vars:
BUILDS_URL: https://www.vaughnnugent.com/public/resources/software/builds
+ SQLITE_OUT_DIR: "plugins/assets/VNLib.Plugins.Extensions.Loading.Sql.SQLite"
includes:
install:
@@ -30,22 +31,33 @@ tasks:
- cmd: powershell -Command "rm -Recurse -Force ./dist"
ignore_error: true
- #copy setup script for linux
- cmd: powershell -Command "mkdir lib -Force"
ignore_error: true
- - cmd: wsl dos2unix ./setup.sh #convert the setup script to unix line endings for linux
- platform: [ win-x64 ]
- - powershell -Command "cp setup.sh lib/ -Force"
- - task: install-plugins
+ - task: plugins:all
- task: install-webserver
+ - task: prune-runtimes
#run container build last
- task: container:build
install-webserver:
cmds:
- - for: [ win-x64, linux-x64, osx-x64 ]
+ - cmd : powershell -Command "mkdir webserver -Force"
+ ignore_error: true
+
+ #clone the webserver (it's cross platform when using dotnet command)
+ - task: install:install
+ vars:
+ PROJECT_NAME: 'VNLib.Webserver'
+ MODULE_NAME: "VNLib.Webserver"
+ FILE_NAME: "linux-x64-release.tgz"
+ DIR: 'webserver/'
+
+ #remove the executable since its not needed
+ - cmd: cd webserver/ && powershell -Command "rm VNlib.WebServer"
+
+ - for: [ windows-x86_64, linux-x86_64, osx-x86_64, windows-arm, linux-arm, osx-arm ]
task: create-env
vars:
TARGET_OS: '{{.ITEM}}'
@@ -55,7 +67,7 @@ tasks:
#make bin dir
- cmd: powershell -Command "mkdir bin -Force"
ignore_error: true
- - for: [ win-x64, linux-x64, osx-x64 ]
+ - for: [ windows-x86_64, linux-x86_64, osx-x86_64, windows-arm, linux-arm, osx-arm ]
task: pack
vars:
TARGET_OS: '{{.ITEM}}'
@@ -66,11 +78,6 @@ tasks:
ignore_error: true
- task: container:postbuild_success
-
- install-plugins:
- cmds:
- #add plugins
- - task: plugins:all
build-container:
cmds:
@@ -84,39 +91,57 @@ tasks:
- cmd: powershell -Command "mkdir {{.BUILD_DIR}} -Force"
ignore_error: true
- #copy build files
- - for: [ plugins, dist, lib, config ]
+ #copy build files for target os
+ - for: [ plugins, dist, lib, config, webserver ]
cmd: powershell -Command "cp -Recurse -Force {{.ITEM}} {{.BUILD_DIR}}"
- - task: get-webserver
- vars:
- TARGET_OS: '{{.TARGET_OS}}'
- BUILD_DIR: '{{.BUILD_DIR}}'
-
- get-webserver:
- internal: true
- cmds:
- - task: install:install
- vars:
- PROJECT_NAME: 'VNLib.Webserver'
- MODULE_NAME: "VNLib.Webserver"
- FILE_NAME: "{{.TARGET_OS}}-release.tgz"
- DIR: '{{.BUILD_DIR}}/webserver'
-
- - cmd: powershell -Command "cp -Force ./config/config.json {{.BUILD_DIR}}/config.json"
+ #copy release taskfile and rename it
+ - cmd: powershell -Command "cp -Force release.taskfile.yaml {{.BUILD_DIR}}/Taskfile.yaml"
pack:
internal: true
cmds:
- cmd: powershell -Command "mkdir build/{{.TARGET_OS}}/ -Force"
ignore_error: true
- - cd build/{{.TARGET_OS}} && tar -czf ../../bin/{{.TARGET_OS}}-release.tgz .
+ - cd build/{{.TARGET_OS}} && tar -czf ../../bin/{{.TARGET_OS}}-release.tgz .
+
+ prune-runtimes:
+ cmds:
+ #prune sqlite runtime native libraries that Im not targeting
+ #windows
+ - for: ['browser-wasm', 'linux-arm', 'linux-arm64', 'linux-armel', 'linux-mips64', 'linux-musl-arm', 'linux-musl-arm64', 'linux-musl-x64', 'linux-ppc64le', 'linux-s390x', 'linux-x64', 'linux-x86', 'maccatalyst-arm64', 'maccatalyst-x64', 'osx-arm64', 'osx-x64', 'win-arm', 'win-arm64' ]
+ cmd: cd build/windows-x86_64/{{.SQLITE_OUT_DIR}}/runtimes && powershell -Command "rm {{.ITEM}} -Recurse -Force"
+ ignore_error: true
+
+ #windows arm
+ - for: ['browser-wasm', 'linux-arm', 'linux-arm64', 'linux-armel', 'linux-mips64', 'linux-musl-arm', 'linux-musl-arm64', 'linux-musl-x64', 'linux-ppc64le', 'linux-s390x', 'linux-x64', 'linux-x86', 'maccatalyst-arm64', 'maccatalyst-x64', 'osx-arm64', 'osx-x64', 'win-x86', 'win-x64' ]
+ cmd: cd build/windows-arm/{{.SQLITE_OUT_DIR}}/runtimes && powershell -Command "rm {{.ITEM}} -Recurse -Force"
+ ignore_error: true
+
+ #linux x64
+ - for: ['browser-wasm', 'linux-arm', 'linux-arm64', 'linux-armel', 'linux-musl-arm', 'linux-musl-arm64', 'maccatalyst-arm64', 'maccatalyst-x64', 'osx-arm64', 'osx-x64', 'win-arm', 'win-arm64', 'win-x86', 'win-x64' ]
+ cmd: cd build/linux-x86_64/{{.SQLITE_OUT_DIR}}/runtimes && powershell -Command "rm {{.ITEM}} -Recurse -Force"
+ ignore_error: true
+ #linux arm
+ - for: ['browser-wasm', 'linux-mips64', 'linux-musl-x64', 'linux-ppc64le', 'linux-s390x', 'linux-x64', 'linux-x86', 'maccatalyst-arm64', 'maccatalyst-x64', 'osx-arm64', 'osx-x64', 'win-arm', 'win-arm64', 'win-x86', 'win-x64' ]
+ cmd: cd build/linux-arm/{{.SQLITE_OUT_DIR}}/runtimes && powershell -Command "rm {{.ITEM}} -Recurse -Force"
+ ignore_error: true
+
+ #osx x64
+ - for: ['browser-wasm', 'linux-arm', 'linux-arm64', 'linux-armel', 'linux-mips64', 'linux-musl-arm', 'linux-musl-arm64', 'linux-musl-x64', 'linux-ppc64le', 'linux-s390x', 'linux-x64', 'linux-x86', 'maccatalyst-arm64', 'win-arm', 'win-arm64', 'win-x86', 'win-x64' ]
+ cmd: cd build/osx-x86_64/{{.SQLITE_OUT_DIR}}/runtimes && powershell -Command "rm {{.ITEM}} -Recurse -Force"
+ ignore_error: true
+
+ #osx arm
+ - for: ['browser-wasm', 'linux-arm', 'linux-arm64', 'linux-armel', 'linux-mips64', 'linux-musl-arm', 'linux-musl-arm64', 'linux-musl-x64', 'linux-ppc64le', 'linux-s390x', 'linux-x64', 'linux-x86', 'maccatalyst-x64', 'osx-x64', 'win-arm', 'win-arm64', 'win-x86', 'win-x64' ]
+ cmd: cd build/osx-arm/{{.SQLITE_OUT_DIR}}/runtimes && powershell -Command "rm {{.ITEM}} -Recurse -Force"
+ ignore_error: true
clean:
ignore_error: true
cmds:
- - for: [ ./build, ./bin, ./dist, ./plugins, ./lib ]
+ - for: [ build/, bin/, dist/, plugins/, lib/, webserver/ ]
cmd: powershell -Command "rm -Recurse -Force '{{.ITEM}}'"
- task: container:clean \ No newline at end of file
diff --git a/front-end/package-lock.json b/front-end/package-lock.json
index 9231ae5..856b959 100644
--- a/front-end/package-lock.json
+++ b/front-end/package-lock.json
@@ -10,7 +10,7 @@
"license": "agpl3",
"dependencies": {
"@headlessui/vue": "^1.7.17",
- "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/eb9752ab262522271ccaf1ff127658b7202289a4/@vnuge-vnlib.browser/release.tgz",
+ "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/f2ac807486a00db4ba8486133d567e392f0fe98a/@vnuge-vnlib.browser/release.tgz",
"@vuelidate/core": "^2.0.2",
"@vuelidate/validators": "^2.0.2",
"@vueuse/core": "^10.3.x",
@@ -62,9 +62,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.23.9",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
- "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
+ "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -588,14 +588,14 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz",
- "integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==",
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"dependencies": {
- "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.9"
+ "@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
@@ -611,9 +611,9 @@
}
},
"node_modules/@jridgewell/set-array": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
- "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"engines": {
"node": ">=6.0.0"
@@ -625,9 +625,9 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.23",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz",
- "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==",
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -686,9 +686,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
- "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.1.tgz",
+ "integrity": "sha512-iU2Sya8hNn1LhsYyf0N+L4Gf9Qc+9eBTJJJsaOGUp+7x4n2M9dxTt8UvhJl3oeftSjblSlpCfvjA/IfP3g5VjQ==",
"cpu": [
"arm"
],
@@ -699,9 +699,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
- "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.1.tgz",
+ "integrity": "sha512-wlzcWiH2Ir7rdMELxFE5vuM7D6TsOcJ2Yw0c3vaBR3VOsJFVTx9xvwnAvhgU5Ii8Gd6+I11qNHwndDscIm0HXg==",
"cpu": [
"arm64"
],
@@ -712,9 +712,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
- "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.1.tgz",
+ "integrity": "sha512-YRXa1+aZIFN5BaImK+84B3uNK8C6+ynKLPgvn29X9s0LTVCByp54TB7tdSMHDR7GTV39bz1lOmlLDuedgTwwHg==",
"cpu": [
"arm64"
],
@@ -725,9 +725,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
- "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.1.tgz",
+ "integrity": "sha512-opjWJ4MevxeA8FhlngQWPBOvVWYNPFkq6/25rGgG+KOy0r8clYwL1CFd+PGwRqqMFVQ4/Qd3sQu5t7ucP7C/Uw==",
"cpu": [
"x64"
],
@@ -738,9 +738,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
- "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.1.tgz",
+ "integrity": "sha512-uBkwaI+gBUlIe+EfbNnY5xNyXuhZbDSx2nzzW8tRMjUmpScd6lCQYKY2V9BATHtv5Ef2OBq6SChEP8h+/cxifQ==",
"cpu": [
"arm"
],
@@ -751,9 +751,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
- "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.1.tgz",
+ "integrity": "sha512-0bK9aG1kIg0Su7OcFTlexkVeNZ5IzEsnz1ept87a0TUgZ6HplSgkJAnFpEVRW7GRcikT4GlPV0pbtVedOaXHQQ==",
"cpu": [
"arm64"
],
@@ -764,9 +764,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
- "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.1.tgz",
+ "integrity": "sha512-qB6AFRXuP8bdkBI4D7UPUbE7OQf7u5OL+R94JE42Z2Qjmyj74FtDdLGeriRyBDhm4rQSvqAGCGC01b8Fu2LthQ==",
"cpu": [
"arm64"
],
@@ -777,9 +777,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
- "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.1.tgz",
+ "integrity": "sha512-sHig3LaGlpNgDj5o8uPEoGs98RII8HpNIqFtAI8/pYABO8i0nb1QzT0JDoXF/pxzqO+FkxvwkHZo9k0NJYDedg==",
"cpu": [
"riscv64"
],
@@ -790,9 +790,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
- "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.1.tgz",
+ "integrity": "sha512-nD3YcUv6jBJbBNFvSbp0IV66+ba/1teuBcu+fBBPZ33sidxitc6ErhON3JNavaH8HlswhWMC3s5rgZpM4MtPqQ==",
"cpu": [
"x64"
],
@@ -803,9 +803,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
- "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.1.tgz",
+ "integrity": "sha512-7/XVZqgBby2qp/cO0TQ8uJK+9xnSdJ9ct6gSDdEr4MfABrjTyrW6Bau7HQ73a2a5tPB7hno49A0y1jhWGDN9OQ==",
"cpu": [
"x64"
],
@@ -816,9 +816,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
- "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.1.tgz",
+ "integrity": "sha512-CYc64bnICG42UPL7TrhIwsJW4QcKkIt9gGlj21gq3VV0LL6XNb1yAdHVp1pIi9gkts9gGcT3OfUYHjGP7ETAiw==",
"cpu": [
"arm64"
],
@@ -829,9 +829,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
- "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.1.tgz",
+ "integrity": "sha512-LN+vnlZ9g0qlHGlS920GR4zFCqAwbv2lULrR29yGaWP9u7wF5L7GqWu9Ah6/kFZPXPUkpdZwd//TNR+9XC9hvA==",
"cpu": [
"ia32"
],
@@ -842,9 +842,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
- "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.1.tgz",
+ "integrity": "sha512-n+vkrSyphvmU0qkQ6QBNXCGr2mKjhP08mPRM/Xp5Ck2FV4NrHU+y6axzDeixUrCBHVUS51TZhjqrKBBsHLKb2Q==",
"cpu": [
"x64"
],
@@ -855,20 +855,20 @@
]
},
"node_modules/@tanstack/virtual-core": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.2.tgz",
- "integrity": "sha512-DATZJs8iejkIUqXZe6ruDAnjFo78BKnIIgqQZrc7CmEFqfLEN/TPD91n4hRfo6hpRB6xC00bwKxv7vdjFNEmOg==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.3.tgz",
+ "integrity": "sha512-Y5B4EYyv1j9V8LzeAoOVeTg0LI7Fo5InYKgAjkY1Pu9GjtUwX/EKxNcU7ng3sKr99WEf+bPTcktAeybyMOYo+g==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/vue-virtual": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.1.2.tgz",
- "integrity": "sha512-RmUnhsFtRw9p4Ti/+rG2Hr3y4yFhs8Xdsn7x9tkPoKINbVya/5RSCoNUCCAg2iXNjOI5a55iBNzNV0SVwxMwKA==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.1.3.tgz",
+ "integrity": "sha512-OoRCSgp8Bc85Te3pg4OHFUukbWZeB25/O5rNd7MgMtrYIfJjNOaicZeJcvwqK6lDVTMpzohWUMVK/loqR1H8ig==",
"dependencies": {
- "@tanstack/virtual-core": "3.1.2"
+ "@tanstack/virtual-core": "3.1.3"
},
"funding": {
"type": "github",
@@ -930,8 +930,8 @@
},
"node_modules/@vnuge/vnlib.browser": {
"version": "0.1.13",
- "resolved": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/eb9752ab262522271ccaf1ff127658b7202289a4/@vnuge-vnlib.browser/release.tgz",
- "integrity": "sha512-yqJXL0H8g27KoCijlPXSG75ZxfWab4cFVdT2t2b+iodpHeytZHemlNteTubzMurA8WRg95WW3Z3mf5R184UnZA==",
+ "resolved": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/f2ac807486a00db4ba8486133d567e392f0fe98a/@vnuge-vnlib.browser/release.tgz",
+ "integrity": "sha512-j3BwCdXWJ46Q7GohS+rZg7M5k1/AS+uuycP7wY8RWI2YBKS80uTE6jbWZ0OuCybclBrCufvW7SlTTpfsbf33mw==",
"license": "MIT",
"peerDependencies": {
"@vueuse/core": "^10.x",
@@ -940,8 +940,7 @@
"jose": "^5.x",
"lodash-es": "^4.x",
"universal-cookie": "^7.0.x",
- "vue": "^3.x",
- "vue-router": "^4.x"
+ "vue": "^3.x"
}
},
"node_modules/@volar/language-core": {
@@ -973,49 +972,49 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz",
- "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
+ "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
"dependencies": {
"@babel/parser": "^7.23.9",
- "@vue/shared": "3.4.19",
+ "@vue/shared": "3.4.21",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz",
- "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
+ "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
"dependencies": {
- "@vue/compiler-core": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/compiler-core": "3.4.21",
+ "@vue/shared": "3.4.21"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz",
- "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
+ "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
"dependencies": {
"@babel/parser": "^7.23.9",
- "@vue/compiler-core": "3.4.19",
- "@vue/compiler-dom": "3.4.19",
- "@vue/compiler-ssr": "3.4.19",
- "@vue/shared": "3.4.19",
+ "@vue/compiler-core": "3.4.21",
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/compiler-ssr": "3.4.21",
+ "@vue/shared": "3.4.21",
"estree-walker": "^2.0.2",
- "magic-string": "^0.30.6",
- "postcss": "^8.4.33",
+ "magic-string": "^0.30.7",
+ "postcss": "^8.4.35",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz",
- "integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
+ "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
"dependencies": {
- "@vue/compiler-dom": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/shared": "3.4.21"
}
},
"node_modules/@vue/devtools-api": {
@@ -1073,48 +1072,48 @@
}
},
"node_modules/@vue/reactivity": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz",
- "integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz",
+ "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
"dependencies": {
- "@vue/shared": "3.4.19"
+ "@vue/shared": "3.4.21"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz",
- "integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
+ "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
"dependencies": {
- "@vue/reactivity": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/reactivity": "3.4.21",
+ "@vue/shared": "3.4.21"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz",
- "integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
+ "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
"dependencies": {
- "@vue/runtime-core": "3.4.19",
- "@vue/shared": "3.4.19",
+ "@vue/runtime-core": "3.4.21",
+ "@vue/shared": "3.4.21",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz",
- "integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
+ "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
"dependencies": {
- "@vue/compiler-ssr": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/compiler-ssr": "3.4.21",
+ "@vue/shared": "3.4.21"
},
"peerDependencies": {
- "vue": "3.4.19"
+ "vue": "3.4.21"
}
},
"node_modules/@vue/shared": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
- "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw=="
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
+ "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
},
"node_modules/@vuelidate/core": {
"version": "2.0.3",
@@ -1201,13 +1200,13 @@
}
},
"node_modules/@vueuse/core": {
- "version": "10.8.0",
- "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.8.0.tgz",
- "integrity": "sha512-G9Ok9fjx10TkNIPn8V1dJmK1NcdJCtYmDRyYiTMUyJ1p0Tywc1zmOoCQ2xhHYyz8ULBU4KjIJQ9n+Lrty74iVw==",
+ "version": "10.9.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz",
+ "integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==",
"dependencies": {
"@types/web-bluetooth": "^0.0.20",
- "@vueuse/metadata": "10.8.0",
- "@vueuse/shared": "10.8.0",
+ "@vueuse/metadata": "10.9.0",
+ "@vueuse/shared": "10.9.0",
"vue-demi": ">=0.14.7"
},
"funding": {
@@ -1240,17 +1239,17 @@
}
},
"node_modules/@vueuse/metadata": {
- "version": "10.8.0",
- "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.8.0.tgz",
- "integrity": "sha512-Nim/Vle5OgXcXhAvGOgkJQXB1Yb+Kq/fMbLuv3YYDYbiQrwr39ljuD4k9fPeq4yUyokYRo2RaNQmbbIMWB/9+w==",
+ "version": "10.9.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz",
+ "integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
- "version": "10.8.0",
- "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.8.0.tgz",
- "integrity": "sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ==",
+ "version": "10.9.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz",
+ "integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==",
"dependencies": {
"vue-demi": ">=0.14.7"
},
@@ -1377,9 +1376,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": {
- "version": "10.4.17",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz",
- "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==",
+ "version": "10.4.18",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
+ "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==",
"dev": true,
"funding": [
{
@@ -1396,8 +1395,8 @@
}
],
"dependencies": {
- "browserslist": "^4.22.2",
- "caniuse-lite": "^1.0.30001578",
+ "browserslist": "^4.23.0",
+ "caniuse-lite": "^1.0.30001591",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
@@ -1521,9 +1520,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001589",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz",
- "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==",
+ "version": "1.0.30001597",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz",
+ "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==",
"dev": true,
"funding": [
{
@@ -1757,9 +1756,9 @@
"dev": true
},
"node_modules/electron-to-chromium": {
- "version": "1.4.681",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.681.tgz",
- "integrity": "sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==",
+ "version": "1.4.700",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.700.tgz",
+ "integrity": "sha512-40dqKQ3F7C8fbBEmjSeJ+qEHCKzPyrP9SkeIBZ3wSCUH9nhWStrDz030XlDzlhNhlul1Z0fz7TpDFnsIzo4Jtg==",
"dev": true
},
"node_modules/emoji-regex": {
@@ -2254,9 +2253,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
- "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
@@ -2433,9 +2432,9 @@
}
},
"node_modules/jose": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.2.tgz",
- "integrity": "sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==",
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz",
+ "integrity": "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
@@ -2560,9 +2559,9 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.7",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
- "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
+ "version": "0.30.8",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
+ "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
@@ -3224,9 +3223,9 @@
}
},
"node_modules/rollup": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
- "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.1.tgz",
+ "integrity": "sha512-ggqQKvx/PsB0FaWXhIvVkSWh7a/PCLQAsMjBc+nA2M8Rv2/HG0X6zvixAB7KyZBRtifBUhy5k8voQX/mRnABPg==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -3239,19 +3238,19 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.12.0",
- "@rollup/rollup-android-arm64": "4.12.0",
- "@rollup/rollup-darwin-arm64": "4.12.0",
- "@rollup/rollup-darwin-x64": "4.12.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
- "@rollup/rollup-linux-arm64-gnu": "4.12.0",
- "@rollup/rollup-linux-arm64-musl": "4.12.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.12.0",
- "@rollup/rollup-linux-x64-gnu": "4.12.0",
- "@rollup/rollup-linux-x64-musl": "4.12.0",
- "@rollup/rollup-win32-arm64-msvc": "4.12.0",
- "@rollup/rollup-win32-ia32-msvc": "4.12.0",
- "@rollup/rollup-win32-x64-msvc": "4.12.0",
+ "@rollup/rollup-android-arm-eabi": "4.12.1",
+ "@rollup/rollup-android-arm64": "4.12.1",
+ "@rollup/rollup-darwin-arm64": "4.12.1",
+ "@rollup/rollup-darwin-x64": "4.12.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.12.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.12.1",
+ "@rollup/rollup-linux-arm64-musl": "4.12.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.12.1",
+ "@rollup/rollup-linux-x64-gnu": "4.12.1",
+ "@rollup/rollup-linux-x64-musl": "4.12.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.12.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.12.1",
+ "@rollup/rollup-win32-x64-msvc": "4.12.1",
"fsevents": "~2.3.2"
}
},
@@ -3656,9 +3655,9 @@
}
},
"node_modules/typescript": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
- "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+ "version": "5.4.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
+ "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
@@ -3723,9 +3722,9 @@
"dev": true
},
"node_modules/vite": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz",
- "integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==",
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz",
+ "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==",
"dev": true,
"dependencies": {
"esbuild": "^0.19.3",
@@ -3778,15 +3777,15 @@
}
},
"node_modules/vue": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz",
- "integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
+ "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
"dependencies": {
- "@vue/compiler-dom": "3.4.19",
- "@vue/compiler-sfc": "3.4.19",
- "@vue/runtime-dom": "3.4.19",
- "@vue/server-renderer": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/compiler-sfc": "3.4.21",
+ "@vue/runtime-dom": "3.4.21",
+ "@vue/server-renderer": "3.4.21",
+ "@vue/shared": "3.4.21"
},
"peerDependencies": {
"typescript": "*"
@@ -3821,21 +3820,6 @@
"eslint": ">=6.0.0"
}
},
- "node_modules/vue-router": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
- "integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==",
- "peer": true,
- "dependencies": {
- "@vue/devtools-api": "^6.5.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/posva"
- },
- "peerDependencies": {
- "vue": "^3.2.0"
- }
- },
"node_modules/vue-template-compiler": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
@@ -3864,9 +3848,9 @@
}
},
"node_modules/vue3-otp-input": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/vue3-otp-input/-/vue3-otp-input-0.4.1.tgz",
- "integrity": "sha512-wVl9i3DcWlO0C7fBI9V+RIP3crm/1tY72fuhvb3YM2JfbLoYofB96aPl5AgFhA0Cse5bQEMYtIvOeiqW3rfbAw==",
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/vue3-otp-input/-/vue3-otp-input-0.4.4.tgz",
+ "integrity": "sha512-LI1MeBiiEy59cnjqXzlcz4G4cMxZcHF/xOKilb6sfw4uFHfQ22Luu2ls0Bb51zL0pb3gGp7RuIL5eurEJXkoBg==",
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
@@ -3996,9 +3980,9 @@
"dev": true
},
"node_modules/yaml": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz",
- "integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
+ "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
"dev": true,
"bin": {
"yaml": "bin.mjs"
diff --git a/front-end/package.json b/front-end/package.json
index 30accd8..0631eae 100644
--- a/front-end/package.json
+++ b/front-end/package.json
@@ -14,11 +14,13 @@
"dev": "vite",
"watch": "vite build --watch --mode development --minify false",
"build": "vite build",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "update": "npm update",
+ "oudated": "npm outdated"
},
"dependencies": {
"@headlessui/vue": "^1.7.17",
- "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/eb9752ab262522271ccaf1ff127658b7202289a4/@vnuge-vnlib.browser/release.tgz",
+ "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/f2ac807486a00db4ba8486133d567e392f0fe98a/@vnuge-vnlib.browser/release.tgz",
"@vuelidate/core": "^2.0.2",
"@vuelidate/validators": "^2.0.2",
"@vueuse/core": "^10.3.x",
diff --git a/front-end/src/buttons.scss b/front-end/src/buttons.scss
index 7088deb..44df2c2 100644
--- a/front-end/src/buttons.scss
+++ b/front-end/src/buttons.scss
@@ -1,5 +1,13 @@
.btn{
- @apply focus:ring-2 focus:outline-none font-medium rounded text-sm px-4 py-2 text-center text-white;
+ @apply focus:ring-2 focus:outline-none font-medium rounded text-sm px-2.5 py-2 text-center text-white;
+
+ &.sm{
+ @apply text-xs px-2 py-1;
+ }
+
+ &.lg{
+ @apply text-lg px-4 py-3;
+ }
&.round{
@apply rounded-full;
diff --git a/front-end/src/components/Bookmarks.vue b/front-end/src/components/Bookmarks.vue
index cc3cd6a..274b0b4 100644
--- a/front-end/src/components/Bookmarks.vue
+++ b/front-end/src/components/Bookmarks.vue
@@ -387,7 +387,6 @@ const upload = (() => {
<span class="sr-only">Search</span>
</button>
</form>
-
</div>
<div class="relative ml-3 md:ml-10">
diff --git a/front-end/src/components/Boomarks/AddOrUpdateForm.vue b/front-end/src/components/Boomarks/AddOrUpdateForm.vue
index a4a3f1d..0370e0c 100644
--- a/front-end/src/components/Boomarks/AddOrUpdateForm.vue
+++ b/front-end/src/components/Boomarks/AddOrUpdateForm.vue
@@ -1,6 +1,8 @@
<script setup lang="ts">
import { computed, toRefs } from 'vue';
-import { join, split } from 'lodash-es';
+import { isEmpty, join, split } from 'lodash-es';
+import { useStore } from '../../store';
+import { useWait } from '@vnuge/vnlib.browser';
const emit = defineEmits(['submit'])
const props = defineProps<{
@@ -15,54 +17,110 @@ const tags = computed({
set: (value:string) => v$.value.Tags.$model = split(value, ',')
})
+const { websiteLookup:lookup } = useStore()
+const { setWaiting, waiting } = useWait()
+
+const execLookup = async () => {
+ //url must be valid before searching
+ if(v$.value.Url.$invalid) return
+
+ setWaiting(true)
+
+ try{
+ const { title, description, keywords } = await lookup.execLookup(v$.value.Url.$model);
+
+ //Set the title and description
+ if(title){
+ v$.value.Name.$model = title;
+ v$.value.Name.$dirty = true;
+ }
+
+ if(description){
+ v$.value.Description.$model = description;
+ v$.value.Description.$dirty = true;
+ }
+
+ if(!isEmpty(keywords)){
+ v$.value.Tags.$model = keywords;
+ v$.value.Tags.$dirty = true;
+ }
+ }
+ catch(e){
+ //Mostly ignore errors
+ console.error(e)
+ }
+ finally{
+ setWaiting(false)
+ }
+}
+
+const showSearchButton = computed(() => lookup.isSupported && !isEmpty(v$.value.Url.$model))
+
</script>
<template>
- <form class="grid grid-cols-1 gap-4 p-4" @submit.prevent="emit('submit')">
+ <form id="bm-add-or-update-form" class="grid grid-cols-1 gap-4 p-4" @submit.prevent="emit('submit')">
<fieldset>
- <label for="url" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">URL</label>
- <input type="text" id="url" class="input" placeholder="https://www.example.com"
- v-model="v$.Url.$model"
- :class="{'dirty': v$.Url.$dirty, 'error': v$.Url.$invalid}"
- required
- >
+ <label for="url" class="flex justify-between mb-2 text-sm font-medium text-gray-900 dark:text-white">
+ URL
+ </label>
+ <div class="flex gap-2">
+ <input type="text" id="url" class="input" placeholder="https://www.example.com" v-model="v$.Url.$model"
+ :class="{'dirty': v$.Url.$dirty, 'error': v$.Url.$invalid}" required>
+
+ <div class="">
+ <button
+ type="button"
+ :disabled="!showSearchButton || waiting"
+ @click.self.prevent="execLookup"
+ id="search-btn"
+ class="btn blue search-btn"
+ >
+ <svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
+ viewBox="0 0 20 20">
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
+ d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
+ </svg>
+ </button>
+ </div>
+ </div>
</fieldset>
<fieldset>
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Title</label>
- <input type="text" id="Name" class="input" placeholder="Hello World"
- v-model="v$.Name.$model"
- :class="{'dirty': v$.Name.$dirty, 'error': v$.Name.$invalid}"
- required
- >
+ <input type="text" id="Name" class="input" placeholder="Hello World" v-model="v$.Name.$model"
+ :class="{'dirty': v$.Name.$dirty, 'error': v$.Name.$invalid}" required>
</fieldset>
- <fieldset>
+ <fieldset>
<label for="tags" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Tags</label>
- <input type="text" id="tags" class="input" placeholder="tag1,tag2,tag3"
- v-model="tags"
- :class="{'dirty': v$.Tags.$dirty, 'error': v$.Tags.$invalid}"
- >
+ <input type="text" id="tags" class="input" placeholder="tag1,tag2,tag3" v-model="tags"
+ :class="{'dirty': v$.Tags.$dirty, 'error': v$.Tags.$invalid}">
</fieldset>
<fieldset>
- <label for="description" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Description</label>
+ <label for="description"
+ class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Description</label>
<textarea type="text" id="description" rows="5" class="input" placeholder="This is a bookmark"
v-model="v$.Description.$model"
- :class="{'dirty': v$.Description.$dirty, 'error': v$.Description.$invalid}"
- />
+ :class="{'dirty': v$.Description.$dirty, 'error': v$.Description.$invalid}" />
</fieldset>
-
+
<div class="flex justify-end">
- <button type="submit" class="btn blue">
- Submit
+ <button id="save-button" type="submit" form="bm-add-or-update-form" class="btn blue">
+ Save
</button>
</div>
</form>
</template>
-<style scoped lang="scss">input.search {
- @apply ps-10 p-2.5 border block w-full text-sm rounded;
- @apply bg-gray-50 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 border-gray-300 text-gray-900 focus:ring-blue-500 focus:border-blue-500;
-}
+<style scoped lang="scss">
+
+#bm-add-or-update-form {
+ .search-btn{
-button.search {
- @apply p-2.5 ms-2 text-sm font-medium text-white bg-blue-700 rounded border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
+ @apply my-auto px-3 py-2.5;
+
+ &:disabled{
+ @apply bg-gray-600;
+ }
+ }
}
+
</style> \ No newline at end of file
diff --git a/front-end/src/components/Settings.vue b/front-end/src/components/Settings.vue
index 83d3f79..504f38a 100644
--- a/front-end/src/components/Settings.vue
+++ b/front-end/src/components/Settings.vue
@@ -19,7 +19,7 @@ const darkMode = useDark();
<h2 class="text-2xl font-bold">Settings</h2>
<div class="flex flex-col w-full max-w-3xl gap-10 mt-3">
- <div class="">
+ <div class="mb-6">
<h3 class="text-xl font-bold">
General
</h3>
@@ -41,7 +41,7 @@ const darkMode = useDark();
</div>
</div>
- <div class="">
+ <div class="mb-6">
<h3 class="text-xl font-bold">Boomarks</h3>
<div class="relative mt-4">
@@ -51,7 +51,7 @@ const darkMode = useDark();
<PasswordReset />
- <div class="">
+ <div class="mb-8">
<h3 class="text-xl font-bold">Multi Factor Auth</h3>
<div class="relative mt-4 py-2.5">
@@ -66,7 +66,7 @@ const darkMode = useDark();
<Oauth2Apps />
</div>
- <div v-if="store.registation.status?.can_invite" class="mb-10">
+ <div v-if="store.registation.status?.can_invite" class="mt-6 mb-10">
<Registation />
</div>
diff --git a/front-end/src/components/Settings/Bookmarks.vue b/front-end/src/components/Settings/Bookmarks.vue
index a4ab55a..aa4ed31 100644
--- a/front-end/src/components/Settings/Bookmarks.vue
+++ b/front-end/src/components/Settings/Bookmarks.vue
@@ -1,13 +1,14 @@
<script setup lang="ts">
import { apiCall, useWait } from '@vnuge/vnlib.browser';
import { useStore, type DownloadContentType, TabId } from '../../store';
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
-const { bookmarks } = useStore();
+const { bookmarks, websiteLookup } = useStore();
const downloadAnchor = ref();
const { waiting } = useWait()
+const curlSupported = computed(() => websiteLookup.isSupported);
const downloadBookmarks = (contentType: DownloadContentType) => {
apiCall(async () => {
@@ -58,8 +59,10 @@ javascript: (function() {
</div>
<p class="p-0.5 my-auto text-sm flex flex-row">
<span class="">
- <svg class="w-6 h-5 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12l4-4m-4 4 4 4"/>
+ <svg class="w-6 h-5 text-gray-800 dark:text-white" aria-hidden="true"
+ xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
+ d="M5 12h14M5 12l4-4m-4 4 4 4" />
</svg>
</span>
<span>
@@ -72,47 +75,66 @@ javascript: (function() {
<MenuButton :disabled="waiting" class="flex items-center gap-3 btn light">
<div class="hidden lg:inline">Download</div>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none">
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 13V4M7 14H5a1 1 0 0 0-1 1v4c0 .6.4 1 1 1h14c.6 0 1-.4 1-1v-4c0-.6-.4-1-1-1h-2m-1-5-4 5-4-5m9 8h0"/>
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
+ d="M12 13V4M7 14H5a1 1 0 0 0-1 1v4c0 .6.4 1 1 1h14c.6 0 1-.4 1-1v-4c0-.6-.4-1-1-1h-2m-1-5-4 5-4-5m9 8h0" />
</svg>
</MenuButton>
- <transition
- enter-active-class="transition duration-100 ease-out"
- enter-from-class="transform scale-95 opacity-0"
- enter-to-class="transform scale-100 opacity-100"
+ <transition enter-active-class="transition duration-100 ease-out"
+ enter-from-class="transform scale-95 opacity-0" enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-out"
- leave-from-class="transform scale-100 opacity-100"
- leave-to-class="transform scale-95 opacity-0"
- >
- <MenuItems class="absolute z-10 bg-white divide-y divide-gray-100 rounded-b shadow right-2 lg:left-0 min-w-32 lg:end-0 dark:bg-gray-700">
- <ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownDefaultButton">
+ leave-from-class="transform scale-100 opacity-100" leave-to-class="transform scale-95 opacity-0">
+ <MenuItems
+ class="absolute z-10 bg-white divide-y divide-gray-100 rounded-b shadow right-2 lg:left-0 min-w-32 lg:end-0 dark:bg-gray-700">
+ <ul class="py-2 text-sm text-gray-700 dark:text-gray-200"
+ aria-labelledby="dropdownDefaultButton">
<!-- Use the `active` state to conditionally style the active item. -->
<MenuItem as="template" v-slot="{ }">
- <li>
- <button @click="downloadBookmarks('text/html')" class="block w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
- HTML
- </button>
- </li>
+ <li>
+ <button @click="downloadBookmarks('text/html')"
+ class="block w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
+ HTML
+ </button>
+ </li>
</MenuItem>
<MenuItem as="template" v-slot="{ }">
- <li>
- <button @click="downloadBookmarks('text/csv')" class="block w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
- CSV
- </button>
- </li>
+ <li>
+ <button @click="downloadBookmarks('text/csv')"
+ class="block w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
+ CSV
+ </button>
+ </li>
</MenuItem>
- <MenuItem as="template" v-slot="{ }">
- <li>
- <button @click="downloadBookmarks('application/json')" class="block w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
- JSON
- </button>
- </li>
+ <MenuItem as="template" v-slot="{ }">
+ <li>
+ <button @click="downloadBookmarks('application/json')"
+ class="block w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
+ JSON
+ </button>
+ </li>
</MenuItem>
</ul>
</MenuItems>
</transition>
</Menu>
</div>
+ <div class="mt-3">
+ <h4 class="mb-2 font-bold">Features</h4>
+ <p class="text-sm text-gray-500 dark:text-gray-400">
+ Some features for Simple-Bookmark use tools and applications that are already installed on
+ your server such as curl.
+ </p>
+ <div class="flex flex-row gap-2 mt-4">
+ <span class="w-3 h-3 my-auto rounded-full" :class="[curlSupported ? 'bg-green-500' : 'bg-amber-500']"></span>
+ <span class="my-auto font-bold">
+ curl
+ </span>
+ <span class="my-auto text-sm text-gray-500 ms-4 dark:text-gray-400">
+ Curl is used to fetch website details like title, description and tags.
+ {{ curlSupported ? '(supported)' : '(not supported)' }}
+ </span>
+ </div>
+ </div>
</div>
-
+
<a ref="downloadAnchor" class="hidden"></a>
</template> \ No newline at end of file
diff --git a/front-end/src/components/Settings/PkiSettings.vue b/front-end/src/components/Settings/PkiSettings.vue
index dfa4cad..885b2cb 100644
--- a/front-end/src/components/Settings/PkiSettings.vue
+++ b/front-end/src/components/Settings/PkiSettings.vue
@@ -32,6 +32,8 @@ const removeKey = async (key: PkiPublicKey) => {
title: 'Key Removed',
text: `${key.kid} has been successfully removed`
})
+
+ store.mfaRefreshMethods()
})
}
@@ -117,7 +119,8 @@ const onAddKey = async () => {
text: result
})
- hideAddKeyDialog()
+ hideAddKeyDialog();
+ store.mfaRefreshMethods();
})
}
diff --git a/front-end/src/components/Settings/Registation.vue b/front-end/src/components/Settings/Registation.vue
index a0f208e..d0dfaa7 100644
--- a/front-end/src/components/Settings/Registation.vue
+++ b/front-end/src/components/Settings/Registation.vue
@@ -58,7 +58,7 @@ const onCancel = () => {
<div class="">
<div class="flex flex-row justify-between w-full">
- <h3 class="text-xl font-bold">Registation</h3>
+ <h3 class="text-xl font-bold">Invite Links</h3>
<div class="flex flex-row justify-end">
<button class="btn blue" @click="toggleOpen(true)">Invite User</button>
diff --git a/front-end/src/main.ts b/front-end/src/main.ts
index c5be406..2f2ca8e 100644
--- a/front-end/src/main.ts
+++ b/front-end/src/main.ts
@@ -30,6 +30,7 @@ import { mfaSettingsPlugin } from './store/mfaSettingsPlugin'
import { socialMfaPlugin } from './store/socialMfaPlugin'
import { bookmarkPlugin } from './store/bookmarks'
import { registationPlugin } from './store/registation';
+import { siteLookupPlugin } from './store/websiteLookup';
//Setup the vnlib api
configureApi({
@@ -67,9 +68,10 @@ store.use(profilePlugin('/account/profile'))
//Enable mfa with totp settings plugin (optional pki config)
.use(mfaSettingsPlugin('/account/mfa', '/account/pki'))
//Setup social mfa plugin
- .use(socialMfaPlugin())
+ .use(socialMfaPlugin("/account/social/portals"))
//Add the oauth2 apps plugin
.use(bookmarkPlugin('/bookmarks'))
+ .use(siteLookupPlugin('/lookup', 2000))
.use(registationPlugin('/register'))
//Setup oauth apps plugin (disabled for now)
//.use(oauth2AppsPlugin('/oauth/apps', '/oauth/scopes'))
diff --git a/front-end/src/store/bookmarks.ts b/front-end/src/store/bookmarks.ts
index 2af8344..2d12a9a 100644
--- a/front-end/src/store/bookmarks.ts
+++ b/front-end/src/store/bookmarks.ts
@@ -18,7 +18,7 @@ import { MaybeRef, shallowRef, watch, computed, Ref, ref } from 'vue';
import { apiCall, useAxios, WebMessage } from '@vnuge/vnlib.browser';
import { useToggle, get, set, useOffsetPagination, watchDebounced, syncRef } from '@vueuse/core';
import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia'
-import { isArray, join, map, split, sortBy } from 'lodash-es';
+import { isArray, join, map, split, sortBy, filter, isEmpty } from 'lodash-es';
import { useQuery } from './index';
export interface Bookmark{
@@ -189,7 +189,7 @@ const searchQuery = (search: Ref<string | null>, tags: Ref<string[]>) => {
const tagQuery = useQuery('t')
const currentTags = computed({
- get: () => split(tagQuery.value, ' '),
+ get: () => filter(split(tagQuery.value, ' '), p => !isEmpty(p)),
set: (value) => set(tagQuery, join(value, ' '))
})
diff --git a/front-end/src/store/socialMfaPlugin.ts b/front-end/src/store/socialMfaPlugin.ts
index d8d7bb1..e0ec972 100644
--- a/front-end/src/store/socialMfaPlugin.ts
+++ b/front-end/src/store/socialMfaPlugin.ts
@@ -1,26 +1,19 @@
-// Copyright (C) 2024 Vaughn Nugent
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'pinia'
import { MaybeRef } from 'vue';
-import { useSocialOauthLogin, useUser, SocialOAuthPortal, fromPortals, useAxios } from '@vnuge/vnlib.browser'
+import {
+ useUser,
+ useOauthLogin,
+ useSocialDefaultLogout,
+ fetchSocialPortals,
+ fromSocialPortals,
+ fromSocialConnections,
+} from '@vnuge/vnlib.browser'
import { get } from '@vueuse/core';
import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia'
import { defer } from 'lodash-es';
-type SocialMfaPlugin = ReturnType<typeof useSocialOauthLogin>
+type SocialMfaPlugin = ReturnType<typeof useOauthLogin>
declare module 'pinia' {
export interface PiniaCustomProperties {
@@ -35,43 +28,40 @@ export const socialMfaPlugin = (portalEndpoint?: MaybeRef<string>): PiniaPlugin
const { } = storeToRefs(store)
const { logout } = useUser()
- /**
- * Override the logout function to default to a social logout,
- * if the social logout fails, then we will logout the user
- */
- const setLogoutMethod = (socialOauth: SocialMfaPlugin) => {
- const logoutFunc = socialOauth.logout;
+ //Create social login from available portals
+ const defaultSocial = useSocialDefaultLogout(
+ useOauthLogin([]),
+ logout //fallback to default logout
+ );
- (socialOauth as any).logout = async () => {
- if (await logoutFunc() === false) {
- await logout()
- }
- }
- }
-
- const _loadPromise = new Promise<SocialMfaPlugin>((resolve, reject) => {
+ const _loadPromise = new Promise<SocialMfaPlugin>((resolve, _) => {
- if(get(portalEndpoint) == null) {
- const socialOauth = useSocialOauthLogin([])
- setLogoutMethod(socialOauth)
- return resolve(socialOauth)
+ if (get(portalEndpoint) == null) {
+ return resolve(defaultSocial)
}
+ /*
+ Try to load social methods from server, if it fails, then we will
+ fall back to default
+ */
+
defer(async () => {
+
try {
- //Get axios instance
- const axios = useAxios(null)
- //Get all enabled portals
- const { data } = await axios.get<SocialOAuthPortal[]>(get(portalEndpoint)!);
- //Setup social providers from server portals
- const socialOauth = useSocialOauthLogin(fromPortals(data));
- setLogoutMethod(socialOauth);
+ const portals = await fetchSocialPortals(get(portalEndpoint)!);
+ const social = fromSocialPortals(portals);
+ const methods = fromSocialConnections(social);
+
+ //Create social login from available portals
+ const login = useOauthLogin(methods);
+ const socialOauth = useSocialDefaultLogout(login, logout);
resolve(socialOauth)
} catch (error) {
- reject(error)
+ //Let failure fall back to default
+ resolve(defaultSocial)
}
})
})
diff --git a/front-end/src/store/websiteLookup.ts b/front-end/src/store/websiteLookup.ts
new file mode 100644
index 0000000..560d00f
--- /dev/null
+++ b/front-end/src/store/websiteLookup.ts
@@ -0,0 +1,76 @@
+
+import 'pinia'
+import { MaybeRef, Ref, shallowRef, watch } from 'vue';
+import { WebMessage, apiCall, useAxios } from '@vnuge/vnlib.browser'
+import { get, set } from '@vueuse/core';
+import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia'
+import { defer, filter, isEmpty, noop } from 'lodash-es';
+
+export interface WebsiteLookupResult {
+ readonly title: string | undefined,
+ readonly description: string | undefined,
+ keywords: string[] | undefined,
+}
+
+export interface LookupApi{
+ isSupported: Ref<boolean>,
+ timeout: Ref<number>,
+ execLookup(url:string): Promise<WebsiteLookupResult>
+}
+
+declare module 'pinia' {
+ export interface PiniaCustomProperties {
+ websiteLookup:{
+ isSupported: boolean,
+ execLookup(url: string): Promise<WebsiteLookupResult>
+ }
+ }
+}
+
+const urlToBase64UrlEncoded = (url: string) => {
+ return btoa(url)
+ .replace(/-/g, '+')
+ .replace(/_/g, '/')
+ .replace(/\./g, '=') //Fix padding
+}
+
+export const siteLookupPlugin = (lookupEndpoint: MaybeRef<string>, to: number): PiniaPlugin => {
+
+ return ({ store }: PiniaPluginContext) => {
+
+ const { loggedIn } = storeToRefs(store)
+ const axios = useAxios(null)
+
+ const isSupported = shallowRef(false)
+ const timeout = shallowRef(to)
+
+ const checkIsSupported = () => {
+ return apiCall(async () => {
+ //Execute test with the 'support' query parameter
+ const { data } = await axios.get<WebMessage>(`${get(lookupEndpoint)}?support`)
+ set(isSupported, data.success)
+ });
+ }
+
+ const execLookup = async (url:string) => {
+ const base64Url = urlToBase64UrlEncoded(url)
+
+ //Execute test with the 'support' query parameter
+ const { data } = await axios.get<WebMessage<WebsiteLookupResult>>(`${get(lookupEndpoint)}?timeout=${get(timeout)}&url=${base64Url}`)
+ const lookup = data.getResultOrThrow();
+ lookup.keywords = filter(lookup.keywords, (k) => !isEmpty(k))
+ return lookup
+ }
+
+ //If login status changes, recheck support
+ watch([loggedIn], ([li]) => li ? defer(checkIsSupported) : noop(), { immediate: true })
+
+ return {
+ websiteLookup: {
+ isSupported,
+ execLookup,
+ timeout
+ } as LookupApi
+ } as any
+ }
+} \ No newline at end of file