using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Mime; using Jellyfin.Api.Attributes; using Jellyfin.Api.Models; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; namespace Jellyfin.Api.Controllers { /// /// The dashboard controller. /// [Route("")] public class DashboardController : BaseJellyfinApiController { private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; /// /// Initializes a new instance of the class. /// /// Instance of interface. /// Instance of interface. public DashboardController( ILogger logger, IServerApplicationHost appHost) { _logger = logger; _appHost = appHost; } /// /// Gets the configuration pages. /// /// Whether to enable in the main menu. /// The . /// ConfigurationPages returned. /// Server still loading. /// An with infos about the plugins. [HttpGet("web/ConfigurationPages")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetConfigurationPages( [FromQuery] bool? enableInMainMenu, [FromQuery] ConfigurationPageType? pageType) { const string unavailableMessage = "The server is still loading. Please try again momentarily."; var pages = _appHost.GetExports().ToList(); if (pages == null) { return NotFound(unavailableMessage); } // Don't allow a failing plugin to fail them all var configPages = pages.Select(p => { try { return new ConfigurationPageInfo(p); } catch (Exception ex) { _logger.LogError(ex, "Error getting plugin information from {Plugin}", p.GetType().Name); return null; } }) .Where(i => i != null) .ToList(); configPages.AddRange(_appHost.Plugins.SelectMany(GetConfigPages)); if (pageType.HasValue) { configPages = configPages.Where(p => p!.ConfigurationPageType == pageType).ToList(); } if (enableInMainMenu.HasValue) { configPages = configPages.Where(p => p!.EnableInMainMenu == enableInMainMenu.Value).ToList(); } return configPages; } /// /// Gets a dashboard configuration page. /// /// The name of the page. /// ConfigurationPage returned. /// Plugin configuration page not found. /// The configuration page. [HttpGet("web/ConfigurationPage")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesFile(MediaTypeNames.Text.Html, "application/x-javascript")] public ActionResult GetDashboardConfigurationPage([FromQuery] string? name) { IPlugin? plugin = null; Stream? stream = null; var isJs = false; var isTemplate = false; var page = _appHost.GetExports().FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); if (page != null) { plugin = page.Plugin; stream = page.GetHtmlStream(); } if (plugin == null) { var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase)); if (altPage != null) { plugin = altPage.Item2; stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath); isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal); } } if (plugin != null && stream != null) { if (isJs) { return File(stream, MimeTypes.GetMimeType("page.js")); } if (isTemplate) { return File(stream, MimeTypes.GetMimeType("page.html")); } return File(stream, MimeTypes.GetMimeType("page.html")); } return NotFound(); } private IEnumerable GetConfigPages(IPlugin plugin) { return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1)); } private IEnumerable> GetPluginPages(IPlugin plugin) { if (!(plugin is IHasWebPages hasWebPages)) { return new List>(); } return hasWebPages.GetPages().Select(i => new Tuple(i, plugin)); } private IEnumerable> GetPluginPages() { return _appHost.Plugins.SelectMany(GetPluginPages); } } }