using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; using System.Xml.Linq; using zero.Core; using zero.Core.Api; using zero.Core.Blueprints; using zero.Core.Database; using zero.Core.Entities; using zero.Core.Extensions; using zero.Core.Identity; using zero.Core.Options; using zero.Core.Plugins; using zero.Web.Models; namespace zero.Web { public class ZeroVue : IZeroVue { protected IZeroOptions Options { get; private set; } protected IPaths Paths { get; private set; } protected IApplicationsApi ApplicationsApi { get; private set; } protected IAuthenticationApi AuthenticationApi { get; private set; } protected IEnumerable Plugins { get; private set; } protected IZeroContext Context { get; private set; } protected ILogger Logger { get; private set; } protected IZeroStore Store { get; private set; } string IconSymbolsSvg { get; set; } public ZeroVue(IZeroOptions options, IPaths paths, IApplicationsApi applicationsApi, IAuthenticationApi authenticationApi, IEnumerable plugins, IZeroContext context, ILogger logger, IZeroStore store) { Paths = paths; Options = options; ApplicationsApi = applicationsApi; AuthenticationApi = authenticationApi; Plugins = plugins; Context = context; Logger = logger; Store = store; } /// public async Task Config() { ZeroVueConfig config = new ZeroVueConfig(); config.Path = Options.BackofficePath.EnsureEndsWith('/'); config.ApiPath = config.Path + "api/"; config.PluginPath = "@/Plugins"; config.Version = Options.ZeroVersion; config.PluginCount = Plugins.Count(); config.ErrorFieldNone = Constants.ErrorFieldNone; config.Alias = CreateAliases(); config.AppId = Context.AppId; //config.SharedAppId = Constants.Database.SharedAppId; // TODO appx config.Icons = CreateIconSets(); config.MultiApps = Options.Applications.EnableMultiple; BackofficeUser user = await AuthenticationApi.GetUser(); config.Translations = CreateTranslations(user?.LanguageId); if (user != null) { config.User = new UserEditModel() { Id = user.Id, AvatarId = user.AvatarId, Email = user.Email, IsSuper = user.IsSuper, Name = user.Name, Roles = user.RoleIds }; config.Sections = CreateSections(); config.Applications = await CreateApplications(); config.SettingsAreas = CreateSettingsAreas(); config.Plugins = Plugins.Select(x => new ZeroVuePlugin() { Name = x.Options.Name, Description = x.Options.Description, PluginPath = x.Options.PluginPath }).ToList(); config.Services = new() { YouTubeApiKey = Options.Services.YouTubeApiKey }; config.Blueprints = CreateBlueprints(); } return config; } /// public async Task ConfigAsJson() { return JsonSerializer.Serialize(await Config(), new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } /// public string GetIconSvg() { return IconSymbolsSvg; } /// /// Creates the sections /// IList CreateSections() { bool isSuperUser = AuthenticationApi.IsSuper(); IList permissions = AuthenticationApi.GetPermissions(Permissions.Sections.PREFIX); List sections = new List(); foreach (ISection section in Options.Sections.GetAllItems()) { if (!isSuperUser && !Permission.CanReadKey(permissions, section.Alias, true)) { continue; } bool isExternal = !(section is IInternalSection); string url = Safenames.Alias(section.Alias).EnsureStartsWith('/'); if (section.Alias == Constants.Sections.Dashboard) { url = "/"; } ZeroVueSection vueSection = new ZeroVueSection() { Alias = section.Alias, Name = section.Name, Icon = section.Icon, Color = section.Color, Url = url, Children = new List(), IsExternal = isExternal }; foreach (ISection child in section.Children) { vueSection.Children.Add(new ZeroVueSection() { Alias = child.Alias, Name = child.Name, Url = vueSection.Url.EnsureEndsWith('/') + Safenames.Alias(child.Alias), IsExternal = isExternal }); } sections.Add(vueSection); } return sections; } /// /// Create aliases /// Dictionary> CreateAliases() { Dictionary> aliases = new Dictionary>(); Dictionary sections = new Dictionary(); sections.Add("dashboard", Constants.Sections.Dashboard); sections.Add("pages", Constants.Sections.Pages); sections.Add("spaces", Constants.Sections.Spaces); sections.Add("media", Constants.Sections.Media); sections.Add("settings", Constants.Sections.Settings); Dictionary settings = new Dictionary(); settings.Add("applications", Constants.Settings.Applications); settings.Add("countries", Constants.Settings.Countries); settings.Add("logging", Constants.Settings.Logging); settings.Add("languages", Constants.Settings.Languages); settings.Add("translations", Constants.Settings.Translations); settings.Add("mails", Constants.Settings.Mails); settings.Add("updates", Constants.Settings.Updates); settings.Add("users", Constants.Settings.Users); settings.Add("integrations", Constants.Settings.Integrations); aliases.Add("sections", sections); aliases.Add("settings", settings); aliases.Add("pages", new Dictionary() { { "folder", Constants.Pages.FolderAlias } }); return aliases; } /// /// Creates the areas in the settings section /// IList CreateSettingsAreas() { bool isSuperUser = AuthenticationApi.IsSuper(); IList permissions = AuthenticationApi.GetPermissions(Permissions.Settings.PREFIX); List groups = new List(); bool hasIntegrations = Options.Integrations.GetAllItems().Any(); foreach (SettingsGroup group in Options.Settings.GetAllItems()) { List areas = new List(); foreach (SettingsArea area in group.Items) { if (!isSuperUser && !Permission.CanReadKey(permissions, area.Alias, true)) { continue; } if (area.Alias == Constants.Settings.Integrations && !hasIntegrations) { continue; } bool isPlugin = !(area is InternalSettingsArea); ZeroVueSettingsArea vueArea = new ZeroVueSettingsArea() { Alias = area.Alias, Name = area.Name, Description = area.Description, Icon = area.Icon, Url = Constants.Sections.Settings.EnsureStartsWith('/') + Safenames.Alias(area.Alias).EnsureStartsWith('/'), IsPlugin = isPlugin }; areas.Add(vueArea); } if (areas.Count > 0) { groups.Add(new ZeroVueSettingsGroup() { Name = group.Name, Items = areas }); } } return groups; } /// /// Get all visible applications /// async Task> CreateApplications() { IList applications = await ApplicationsApi.GetAll(); string[] mediaIds = applications.Select(x => x.IconId).Where(x => x != null).ToArray(); Dictionary media = await Store.Session().LoadAsync(mediaIds); return applications.OrderBy(app => app.Sort).Select(app => new ZeroVueApplication() { Id = app.Id, Name = app.Name, Image = app.IconId.IsNullOrEmpty() ? null : media.GetValueOrDefault(app.IconId)?.ThumbnailSource }).ToList(); } /// /// Creates all translations for the project /// Dictionary CreateTranslations(string culture) { var zeroTranslations = CreateTranslationsForFile("O:/zero/zero.Web/Resources/Localization/zero.en-us.json", culture); // TODO foreach (IZeroPlugin plugin in Plugins) { if (plugin.Options.LocalizationPaths.Count > 0) { foreach (string path in plugin.Options.LocalizationPaths) { Dictionary translations = CreateTranslationsForFile(path, culture); foreach (var translation in translations) { zeroTranslations.Add(translation.Key, translation.Value); } } } } return zeroTranslations; } Dictionary CreateTranslationsForFile(string path, string culture) { Dictionary items = new(); culture = culture?.ToLower(); if (!culture.IsNullOrEmpty() && culture != "en-us") { items = CreateTranslationsForFile(path, "en-us"); path = path.Replace("en-us", culture); } if (!File.Exists(path)) { return items; } string text = File.ReadAllText(path, Encoding.GetEncoding("ISO-8859-1")); JObject json = JObject.Parse(text); IEnumerable tokens = json.Descendants().Where(p => p.Count() == 0); Dictionary translationItems = tokens.Aggregate(new Dictionary(), (properties, token) => { properties.Add(token.Path.ToLowerInvariant(), token.ToString()); return properties; }); foreach (var translation in translationItems) { items[translation.Key] = translation.Value; } return items; } IList CreateIconSets() { List result = new(); IReadOnlyCollection sets = Options.Icons.GetAllItems(); StringBuilder svg = new(); foreach (IconSet 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(); if (!Options.Blueprints.Enabled) { return items; } foreach (Blueprint blueprint in Options.Blueprints.GetAllItems()) { string[] unlocked = blueprint.GetUnlockedFieldNames().ToArray(); items.Add(new() { Alias = blueprint.Alias, Enabled = true, Unlocked = unlocked }); } return items; } } public interface IZeroVue { /// /// Creates the zero configuration for vue /// Task Config(); ///// ///// Creates the zero configuration for vue ///// //Task> Translations(); ///// ///// Creates the zero configuration for vue ///// //Task IconSets(); /// /// Creates the zero configuration for vue /// Task ConfigAsJson(); /// /// Get SVG for icon sets /// string GetIconSvg(); } public class ZeroVueConfig { public string Path { get; set; } public string ApiPath { get; set; } public string PluginPath { get; set; } public IList Plugins { get; set; } = new List(); public string Version { get; set; } public int PluginCount { get; set; } public string ErrorFieldNone { get; set; } public string AppId { get; set; } public bool MultiApps { get; set; } public UserEditModel User { get; set; } public IList Sections { get; set; } = new List(); public IList Applications { get; set; } = new List(); public IList SettingsAreas { get; set; } = new List(); public Dictionary> Alias { get; set; } = new Dictionary>(); public Dictionary Translations { get; set; } = new Dictionary(); 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(); } public class ZeroVueSection { public string Alias { get; set; } public string Name { get; set; } public string Icon { get; set; } public string Color { get; set; } public string Url { get; set; } public bool IsExternal { get; set; } public IList Children { get; set; } } public class ZeroVuePlugin { public string Name { get; set; } public string Description { get; set; } public string PluginPath { get; set; } } public class ZeroVueSettingsGroup { public string Name { get; set; } public IList Items { get; set; } } public class ZeroVueSettingsArea { public string Alias { get; set; } public string Name { get; set; } public string Description { get; set; } public string Icon { get; set; } public string Url { get; set; } public bool IsPlugin { get; set; } } public class ZeroVueApplication { public string Id { get; set; } public string Name { get; set; } 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; } } public class ZeroVueBlueprint { public string Alias { get; set; } public bool Enabled { get; set; } public string[] Unlocked { get; set; } = Array.Empty(); } }