diff --git a/zero.Backoffice/BackofficeAssetFileSystem.cs b/zero.Backoffice/BackofficeAssetFileSystem.cs
new file mode 100644
index 00000000..c95279f4
--- /dev/null
+++ b/zero.Backoffice/BackofficeAssetFileSystem.cs
@@ -0,0 +1,11 @@
+namespace zero.Backoffice;
+
+public class BackofficeAssetFileSystem : PhysicalFileSystem, IBackofficeAssetFileSystem
+{
+ public BackofficeAssetFileSystem(string root) : base(root) { }
+}
+
+
+public interface IBackofficeAssetFileSystem : IFileSystem
+{
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Configuration/BackofficeOptions.cs b/zero.Backoffice/Configuration/BackofficeOptions.cs
index ed5d84b0..5eafc93c 100644
--- a/zero.Backoffice/Configuration/BackofficeOptions.cs
+++ b/zero.Backoffice/Configuration/BackofficeOptions.cs
@@ -2,6 +2,11 @@
public class BackofficeOptions
{
+ ///
+ /// Public path to zero assets
+ ///
+ public string AssetPath { get; set; }
+
///
/// Paths in the backoffice which are not handled by zero
///
diff --git a/zero.Backoffice/Endpoints/DebugController.cs b/zero.Backoffice/Endpoints/DebugController.cs
new file mode 100644
index 00000000..b9822db8
--- /dev/null
+++ b/zero.Backoffice/Endpoints/DebugController.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using zero.Api.Filters;
+using zero.Backoffice.Services;
+using zero.Identity.Models;
+
+namespace zero.Backoffice;
+
+[ZeroSystemApi]
+[ZeroAuthorize(false)]
+public class DebugController : ZeroBackofficeController
+{
+ [HttpGet("icons")]
+ public async Task GetIcons([FromServices] IIconRepository icons)
+ {
+ return Ok(await icons.GetSets());
+ }
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Plugin.cs b/zero.Backoffice/Plugin.cs
index ec438e67..a9e2667a 100644
--- a/zero.Backoffice/Plugin.cs
+++ b/zero.Backoffice/Plugin.cs
@@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using System.IO;
using zero.Backoffice.Endpoints.Account;
+using zero.Backoffice.Services;
namespace zero.Backoffice;
@@ -29,6 +30,15 @@ public class ZeroBackofficePlugin : ZeroPlugin
services.AddTransient();
services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton(svc =>
+ {
+ IOptions options = svc.GetRequiredService>();
+ IWebHostEnvironment env = svc.GetRequiredService();
+ return new(Path.Combine(env.WebRootPath, options.Value.AssetPath));
+ });
+
services.AddZeroBackofficeUIComposition();
//services.AddTransient();
@@ -43,13 +53,16 @@ public class ZeroBackofficePlugin : ZeroPlugin
protected void ConfigureOptions(BackofficeOptions options, IWebHostEnvironment env)
{
+ options.ExcludedPaths.Add("resources");
+ options.AssetPath = "zero/resources";
+
options.DevServer.WorkingDirectory = Path.Combine(env.ContentRootPath, "..", "Zero.Web.UI", "App");
options.IconSets.Add(new BackofficeIconSet()
{
Alias = "feather",
Name = "Feather",
- SpritePath = "/assets/icons/feather.svg",
+ SpritePath = "icons/feather.svg",
Prefix = "fth"
});
diff --git a/zero.Backoffice/Resources/index.tpl.html b/zero.Backoffice/Resources/backoffice.tpl.html
similarity index 100%
rename from zero.Backoffice/Resources/index.tpl.html
rename to zero.Backoffice/Resources/backoffice.tpl.html
diff --git a/zero.Backoffice/Resources/login.tpl.html b/zero.Backoffice/Resources/login.tpl.html
new file mode 100644
index 00000000..8a9e63ec
--- /dev/null
+++ b/zero.Backoffice/Resources/login.tpl.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {css}
+ welcome to zero
+
+
+
+
+
+ {js}
+
+
+
\ No newline at end of file
diff --git a/zero.Backoffice/Services/Icons/BackofficeIconSetPresentation.cs b/zero.Backoffice/Services/Icons/BackofficeIconSetPresentation.cs
new file mode 100644
index 00000000..9cb0efb0
--- /dev/null
+++ b/zero.Backoffice/Services/Icons/BackofficeIconSetPresentation.cs
@@ -0,0 +1,12 @@
+namespace zero.Backoffice.Services;
+
+public class BackofficeIconSetPresentation
+{
+ public string Alias { get; set; }
+
+ public string Name { get; set; }
+
+ public string Prefix { get; set; }
+
+ public IEnumerable Icons { get; set; }
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Services/Icons/IconRepository.cs b/zero.Backoffice/Services/Icons/IconRepository.cs
new file mode 100644
index 00000000..1f72cfd1
--- /dev/null
+++ b/zero.Backoffice/Services/Icons/IconRepository.cs
@@ -0,0 +1,113 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Text;
+using System.Xml.Linq;
+
+namespace zero.Backoffice.Services;
+
+public class IconRepository : IIconRepository
+{
+ protected IOptions Options { get; set; }
+
+ protected IBackofficeAssetFileSystem FileSystem { get; set; }
+
+ protected ILogger Logger { get; set; }
+
+ protected ConcurrentBag CachedSets { get; private set; } = new();
+
+ protected string CachedSvg { get; set; }
+
+ protected bool IsLoaded { get; set; }
+
+
+ public IconRepository(IOptions options, IBackofficeAssetFileSystem fileSystem, ILogger logger)
+ {
+ Options = options;
+ FileSystem = fileSystem;
+ Logger = logger;
+ }
+
+
+ ///
+ public async Task> GetSets()
+ {
+ await EnsureIconsAreLoaded();
+ return CachedSets;
+ }
+
+
+ ///
+ public async Task GetCompiledSvg()
+ {
+ await EnsureIconsAreLoaded();
+ return CachedSvg;
+ }
+
+
+ async Task EnsureIconsAreLoaded()
+ {
+ if (IsLoaded)
+ {
+ return;
+ }
+
+ StringBuilder svg = new();
+
+ foreach (BackofficeIconSet set in Options.Value.IconSets)
+ {
+ if (!await FileSystem.Exists(set.SpritePath))
+ {
+ Logger.LogWarning("Could not load icon set {alias} from path {path}", set.Alias, FileSystem.Map(set.SpritePath));
+ continue;
+ }
+
+ using Stream stream = await FileSystem.StreamFile(set.SpritePath);
+
+ XDocument xml = XDocument.Load(stream);
+ IEnumerable symbols = xml.Descendants().Where(x => x.Name.LocalName == "symbol");
+
+ if (!symbols.Any())
+ {
+ Logger.LogWarning("Icon set {alias} does not contain any ", set.Alias);
+ continue;
+ }
+
+ HashSet icons = new();
+
+ foreach (XElement symbol in symbols)
+ {
+ string symbolAlias = set.Prefix + "-" + symbol.Attribute("id").Value.ToString();
+ symbol.SetAttributeValue("id", symbolAlias);
+ svg.Append(symbol.ToString().RemoveNewLines());
+ icons.Add(symbolAlias);
+ }
+
+ CachedSets.Add(new()
+ {
+ Alias = set.Alias,
+ Name = set.Name,
+ Prefix = set.Prefix,
+ Icons = icons
+ });
+ }
+
+ CachedSvg = svg.ToString();
+ IsLoaded = true;
+ }
+}
+
+
+public interface IIconRepository
+{
+ ///
+ /// Get all registered icon sets with all their containing icons
+ ///
+ Task> GetSets();
+
+ ///
+ /// Get compiled SVG string for alle registered icon sets
+ ///
+ Task GetCompiledSvg();
+}
\ No newline at end of file
diff --git a/zero.Backoffice/ZeroIndexController.cs b/zero.Backoffice/ZeroIndexController.cs
index 8d95b919..b285bff8 100644
--- a/zero.Backoffice/ZeroIndexController.cs
+++ b/zero.Backoffice/ZeroIndexController.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using System.IO;
using zero.Api.Controllers;
+using zero.Backoffice.Services;
namespace zero.Backoffice.Controllers;
@@ -10,13 +11,17 @@ public class ZeroIndexController : Controller
{
IZeroVue ZeroVue { get; set; }
IZeroOptions Options { get; set; }
+ IAuthenticationService AuthService { get; set; }
+ IIconRepository IconRepository { get; set; }
string[] JsDevModulePaths = new[] { "/vite/client", "/app/app.js" };
- public ZeroIndexController(IZeroVue zeroVue, IZeroOptions options)
+ public ZeroIndexController(IZeroVue zeroVue, IZeroOptions options, IAuthenticationService authService, IIconRepository iconRepository)
{
ZeroVue = zeroVue;
Options = options;
+ AuthService = authService;
+ IconRepository = iconRepository;
}
@@ -28,21 +33,18 @@ public class ZeroIndexController : Controller
}
string config = await ZeroVue.ConfigAsJson();
-
+ bool isLoggedIn = AuthService.IsLoggedIn();
int port = Options.For().DevServer.Port;
- string resourceName = $"zero.Backoffice.Resources.index.tpl.html";
+ string resourceName = $"zero.Backoffice.Resources.backoffice.tpl.html";
- using Stream stream = GetType().Assembly.GetManifestResourceStream(resourceName);
- using StreamReader reader = new(stream);
-
- string template = await reader.ReadToEndAsync();
+ string template = await LoadTemplate(resourceName);
string content = TokenReplacement.Apply(template, new()
{
{ "css", String.Empty },
{ "js", String.Join(String.Empty, GetJsModules("http://localhost:" + port)) },
- { "svg", ZeroVue.GetIconSvg() },
+ { "svg", await IconRepository.GetCompiledSvg() },
{ "config", config }
});
@@ -50,10 +52,36 @@ public class ZeroIndexController : Controller
}
+ //async Task Login()
+ //{
+ // string content = TokenReplacement.Apply(await LoadTemplate("zero.Backoffice.Resources.backoffice.tpl.html"), new()
+ // {
+ // { "css", String.Empty },
+ // { "js", String.Join(String.Empty, GetJsModules("http://localhost:" + port)) },
+ // { "svg", ZeroVue.GetIconSvg() },
+ // { "config", config }
+ // });
+ //}
+
+
+ //async Task Backoffice()
+ //{
+
+ //}
+
+
IEnumerable GetJsModules(string domain)
{
return JsDevModulePaths.Select(path => $"");
}
+
+
+ async Task LoadTemplate(string resourceName)
+ {
+ using Stream stream = GetType().Assembly.GetManifestResourceStream(resourceName);
+ using StreamReader reader = new(stream);
+ return await reader.ReadToEndAsync();
+ }
}
public class ZeroBackofficeModel
diff --git a/zero.Backoffice/ZeroVue.cs b/zero.Backoffice/ZeroVue.cs
index 78979f7b..3506e90a 100644
--- a/zero.Backoffice/ZeroVue.cs
+++ b/zero.Backoffice/ZeroVue.cs
@@ -63,7 +63,6 @@ public class ZeroVue : IZeroVue
config.Alias = CreateAliases();
config.AppId = Context.AppId;
//config.SharedAppId = Constants.Database.SharedAppId; // TODO appx
- config.Icons = CreateIconSets();
config.MultiApps = Options.For().EnableMultiple;
ZeroUser user = await AuthenticationApi.GetUser();
@@ -349,57 +348,6 @@ public class ZeroVue : IZeroVue
}
- IList CreateIconSets()
- {
- List result = new();
- IEnumerable sets = Options.For().IconSets;
-
- StringBuilder svg = new();
-
- foreach (BackofficeIconSet set in sets)
- {
- string path = Paths.Map(set.SpritePath.TrimStart('/'));
-
- if (!File.Exists(path))
- {
- Logger.LogWarning("Could not load icon set {alias} from path {path}", set.Alias, path);
- continue;
- }
-
- string svgContent = File.ReadAllText(path, Encoding.UTF8);
- XDocument xml = XDocument.Parse(svgContent);
- IEnumerable symbols = xml.Descendants().Where(x => x.Name.LocalName == "symbol"); // ("symbol");
-
- if (!symbols.Any())
- {
- Logger.LogWarning("Icon set {alias} does not contain any ", set.Alias);
- continue;
- }
-
- ZeroVueIconSet iconSet = new()
- {
- Alias = set.Alias,
- Name = set.Name,
- Prefix = set.Prefix
- };
-
- foreach (XElement symbol in symbols)
- {
- string symbolAlias = set.Prefix + "-" + symbol.Attribute("id").Value.ToString();
- symbol.SetAttributeValue("id", symbolAlias);
- svg.Append(symbol.ToString().RemoveNewLines());
- iconSet.Icons.Add(symbolAlias);
- }
-
- result.Add(iconSet);
- }
-
- IconSymbolsSvg = svg.ToString();
-
- return result;
- }
-
-
List CreateBlueprints()
{
List items = new();
@@ -489,8 +437,6 @@ public class ZeroVueConfig
public Dictionary Overrides { get; set; } = new Dictionary();
- public IList Icons { get; set; } = new List();
-
public ZeroVueServices Services { get; set; } = new();
public IList Blueprints { get; set; } = new List();
@@ -558,17 +504,6 @@ public class ZeroVueApplication
public string Image { get; set; }
}
-public class ZeroVueIconSet
-{
- public string Alias { get; set; }
-
- public string Name { get; set; }
-
- public string Prefix { get; set; }
-
- public HashSet Icons { get; set; } = new();
-}
-
public class ZeroVueServices
{
public string YouTubeApiKey { get; set; }
diff --git a/zero.Backoffice/zero.Backoffice.csproj b/zero.Backoffice/zero.Backoffice.csproj
index 2e4abc41..deb7587a 100644
--- a/zero.Backoffice/zero.Backoffice.csproj
+++ b/zero.Backoffice/zero.Backoffice.csproj
@@ -18,7 +18,8 @@
-
+
+
diff --git a/zero.Core/FileStorage/FileStorageModule.cs b/zero.Core/FileStorage/FileStorageModule.cs
index 4ebca033..9e9fa4a9 100644
--- a/zero.Core/FileStorage/FileStorageModule.cs
+++ b/zero.Core/FileStorage/FileStorageModule.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
namespace zero.FileStorage;
diff --git a/zero.Core/FileStorage/IFileSystem.cs b/zero.Core/FileStorage/IFileSystem.cs
index b2a28a47..bd8c5238 100644
--- a/zero.Core/FileStorage/IFileSystem.cs
+++ b/zero.Core/FileStorage/IFileSystem.cs
@@ -4,6 +4,16 @@ namespace zero.FileStorage;
public interface IFileSystem
{
+ ///
+ /// Map a relative path to an absolute internal path
+ ///
+ string Map(string path);
+
+ ///
+ /// Map an internal path to a public accessible path
+ ///
+ string MapToPublicPath(string path);
+
///
/// Determines whether a file or directory at the given path already exists
///
diff --git a/zero.Core/FileStorage/PhysicalFileSystem.cs b/zero.Core/FileStorage/PhysicalFileSystem.cs
index a633fc5c..c5b3cf33 100644
--- a/zero.Core/FileStorage/PhysicalFileSystem.cs
+++ b/zero.Core/FileStorage/PhysicalFileSystem.cs
@@ -14,6 +14,20 @@ public class PhysicalFileSystem : IFileSystem
}
+ ///
+ public virtual string Map(string path)
+ {
+ return ResolvePath(path);
+ }
+
+
+ ///
+ public virtual string MapToPublicPath(string path)
+ {
+ return path.EnsureStartsWith('/');
+ }
+
+
///
public virtual Task Exists(string path, CancellationToken cancellationToken = default)
{
@@ -268,10 +282,11 @@ public class PhysicalFileSystem : IFileSystem
path = path.Replace('\\', '/').FullTrim().Trim('/');
string physicalPath = Path.Combine(_root, path);
+ string rootPath = Path.GetFullPath(_root);
// do not allow paths which are outside this file system
// the boundaries are set be the basePath which is assigned in the file system constructor
- if (!Path.GetFullPath(physicalPath).StartsWith(_root, StringComparison.InvariantCultureIgnoreCase))
+ if (!Path.GetFullPath(physicalPath).StartsWith(rootPath, StringComparison.InvariantCultureIgnoreCase))
{
throw new FileSystemException($"The path '{path}' is outside the file system");
}
diff --git a/zero.Core/Media/MediaFileSystem.cs b/zero.Core/Media/MediaFileSystem.cs
index 3b381485..05fdecb4 100644
--- a/zero.Core/Media/MediaFileSystem.cs
+++ b/zero.Core/Media/MediaFileSystem.cs
@@ -12,17 +12,11 @@ public class MediaFileSystem : PhysicalFileSystem, IMediaFileSystem
///
- public virtual string MapToPublicPath(string path)
+ public override string MapToPublicPath(string path)
{
return PublicPathPrefix + path.TrimStart('/');
}
}
-public interface IMediaFileSystem : IFileSystem
-{
- ///
- /// Map an internal path to a public accessible path
- ///
- string MapToPublicPath(string path);
-}
\ No newline at end of file
+public interface IMediaFileSystem : IFileSystem { }
\ No newline at end of file