Merge pull request #111 from sbwalker/master

Added ability to install modules and skins at run-time directly from Nuget
This commit is contained in:
Shaun Walker
2019-10-04 16:21:38 -04:00
committed by GitHub
12 changed files with 341 additions and 19 deletions

View File

@ -3,6 +3,7 @@
@inject NavigationManager NavigationManager
@inject IFileService FileService
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
<table class="table table-borderless">
<tr>
@ -14,31 +15,66 @@
</td>
</tr>
</table>
<button type="button" class="btn btn-primary" @onclick="UploadFile">Upload Module</button>
@if (packages != null)
{
<hr />
<div class="mx-auto text-center"><h2>Available Modules</h2></div>
<Pager Items="@packages">
<Header>
<th>Name</th>
<th>Version</th>
<th></th>
</Header>
<Row>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadModule(context.PackageId, context.Version))>Download Module</button>
</td>
</Row>
</Pager>
}
@if (uploaded)
{
<button type="button" class="btn btn-success" @onclick="InstallFile">Install</button>
}
else
{
<button type="button" class="btn btn-success" @onclick="UploadFile">Upload</button>
<button type="button" class="btn btn-success" @onclick="InstallModules">Install</button>
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
@code {
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
bool uploaded = false;
List<Package> packages;
protected override async Task OnInitializedAsync()
{
packages = await PackageService.GetPackagesAsync("module");
}
private async Task UploadFile()
{
await FileService.UploadFilesAsync("Modules");
ModuleInstance.AddModuleMessage("Module Uploaded Successfully. Click Install To Complete Installation.", MessageType.Success);
uploaded = true;
StateHasChanged();
}
private async Task InstallFile()
private async Task InstallModules()
{
await ModuleDefinitionService.InstallModulesAsync();
NavigationManager.NavigateTo(NavigateUrl(Reload.Application));
}
private async Task DownloadModule(string moduledefinitionname, string version)
{
await PackageService.DownloadPackageAsync(moduledefinitionname, version, "Modules");
ModuleInstance.AddModuleMessage("Module Downloaded Successfully. Click Install To Complete Installation.", MessageType.Success);
uploaded = true;
StateHasChanged();
}
}

View File

@ -3,6 +3,7 @@
@inject NavigationManager NavigationManager
@inject IFileService FileService
@inject IThemeService ThemeService
@inject IPackageService PackageService
<table class="table table-borderless">
<tr>
@ -14,13 +15,32 @@
</td>
</tr>
</table>
<button type="button" class="btn btn-primary" @onclick="UploadTheme">Upload Theme</button>
@if (packages != null)
{
<hr />
<div class="mx-auto text-center"><h2>Available Themes</h2></div>
<Pager Items="@packages">
<Header>
<th>Name</th>
<th>Version</th>
<th></th>
</Header>
<Row>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadTheme(context.PackageId, context.Version))>Download Theme</button>
</td>
</Row>
</Pager>
}
@if (uploaded)
{
<button type="button" class="btn btn-success" @onclick="InstallFile">Install</button>
}
else
{
<button type="button" class="btn btn-success" @onclick="UploadFile">Upload</button>
<button type="button" class="btn btn-success" @onclick="InstallThemes">Install</button>
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
@ -28,17 +48,32 @@ else
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
bool uploaded = false;
List<Package> packages;
private async Task UploadFile()
protected override async Task OnInitializedAsync()
{
packages = await PackageService.GetPackagesAsync("theme");
}
private async Task UploadTheme()
{
await FileService.UploadFilesAsync("Themes");
ModuleInstance.AddModuleMessage("Theme Uploaded Successfully. Click Install To Complete Installation.", MessageType.Success);
uploaded = true;
StateHasChanged();
}
private async Task InstallFile()
private async Task InstallThemes()
{
await ThemeService.InstallThemesAsync();
NavigationManager.NavigateTo(NavigateUrl(Reload.Application));
}
private async Task DownloadTheme(string packageid, string version)
{
await PackageService.DownloadPackageAsync(packageid, version, "Themes");
ModuleInstance.AddModuleMessage("Theme Downloaded Successfully. Click Install To Complete Installation.", MessageType.Success);
uploaded = true;
StateHasChanged();
}
}

View File

@ -43,7 +43,7 @@
</p>
@code {
int Pages;
int Pages = 0;
int Page;
int MaxItems;
int MaxPages;
@ -87,8 +87,11 @@
}
Page = 1;
ItemList = Items.Skip((Page - 1) * MaxItems).Take(MaxItems);
Pages = (int)Math.Ceiling(Items.Count() / (decimal)MaxItems);
if (Items != null)
{
ItemList = Items.Skip((Page - 1) * MaxItems).Take(MaxItems);
Pages = (int)Math.Ceiling(Items.Count() / (decimal)MaxItems);
}
SetPagerSize("forward");
}

View File

@ -9,5 +9,5 @@ namespace Oqtane.Services
Task<List<ModuleDefinition>> GetModuleDefinitionsAsync(int SiteId);
Task UpdateModuleDefinitionAsync(ModuleDefinition ModuleDefinition);
Task InstallModulesAsync();
}
}
}

View File

@ -0,0 +1,12 @@
using Oqtane.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Oqtane.Services
{
public interface IPackageService
{
Task<List<Package>> GetPackagesAsync(string Tag);
Task DownloadPackageAsync(string PackageId, string Version, string Folder);
}
}

View File

@ -66,7 +66,7 @@ namespace Oqtane.Services
public async Task UpdateModuleDefinitionAsync(ModuleDefinition ModuleDefinition)
{
await http.PutJsonAsync<Page>(apiurl + "/" + ModuleDefinition.ModuleDefinitionId.ToString(), ModuleDefinition);
await http.PutJsonAsync(apiurl + "/" + ModuleDefinition.ModuleDefinitionId.ToString(), ModuleDefinition);
}
public async Task InstallModulesAsync()

View File

@ -0,0 +1,40 @@
using Oqtane.Models;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Oqtane.Shared;
using System.Linq;
namespace Oqtane.Services
{
public class PackageService : ServiceBase, IPackageService
{
private readonly HttpClient http;
private readonly SiteState sitestate;
private readonly NavigationManager NavigationManager;
public PackageService(HttpClient http, SiteState sitestate, NavigationManager NavigationManager)
{
this.http = http;
this.sitestate = sitestate;
this.NavigationManager = NavigationManager;
}
private string apiurl
{
get { return CreateApiUrl(sitestate.Alias, NavigationManager.Uri, "Package"); }
}
public async Task<List<Package>> GetPackagesAsync(string Tag)
{
List<Package> packages = await http.GetJsonAsync<List<Package>>(apiurl + "?tag=" + Tag);
return packages.OrderByDescending(item => item.Downloads).ToList();
}
public async Task DownloadPackageAsync(string PackageId, string Version, string Folder)
{
await http.PostJsonAsync(apiurl + "?packageid=" + PackageId + "&version=" + Version + "&folder=" + Folder, null);
}
}
}

View File

@ -52,6 +52,7 @@ namespace Oqtane.Client
services.AddScoped<IUserRoleService, UserRoleService>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<IPackageService, PackageService>();
// dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

View File

@ -0,0 +1,171 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Models;
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
namespace Oqtane.Controllers
{
[Route("{site}/api/[controller]")]
public class PackageController : Controller
{
private readonly IWebHostEnvironment environment;
public PackageController(IWebHostEnvironment environment)
{
this.environment = environment;
}
// GET: api/<controller>?tag=x
[HttpGet]
public async Task<IEnumerable<Package>> Get(string tag)
{
List<Package> packages = new List<Package>();
using (var httpClient = new HttpClient())
{
CancellationToken token;
var searchResult = await GetJson<SearchResult>(httpClient, "https://azuresearch-usnc.nuget.org/query?q=tags:oqtane", token);
foreach(Data data in searchResult.Data)
{
if (data.Tags.Contains(tag))
{
Package package = new Package();
package.PackageId = data.Id;
package.Name = data.Title;
package.Description = data.Description;
package.Owner = data.Authors[0];
package.Version = data.Version;
package.Downloads = data.TotalDownloads;
packages.Add(package);
}
}
}
return packages;
}
[HttpPost]
public async Task Post(string packageid, string version, string folder)
{
using (var httpClient = new HttpClient())
{
CancellationToken token;
folder = Path.Combine(environment.WebRootPath, folder);
var response = await httpClient.GetAsync("https://www.nuget.org/api/v2/package/" + packageid.ToLower() + "/" + version, token).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
string filename = packageid + "." + version + ".nupkg";
using (var fileStream = new FileStream(Path.Combine(folder, filename), FileMode.Create, FileAccess.Write, FileShare.None))
{
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
}
}
}
private async Task<T> GetJson<T>(HttpClient httpClient, string url, CancellationToken token)
{
Uri uri = new Uri(url);
var response = await httpClient.GetAsync(uri, token).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
using (var streamReader = new StreamReader(stream))
{
using (var jsonTextReader = new JsonTextReader(streamReader))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<T>(jsonTextReader);
}
}
}
}
public partial class SearchResult
{
[JsonProperty("@context")]
public Context Context { get; set; }
[JsonProperty("totalHits")]
public long TotalHits { get; set; }
[JsonProperty("data")]
public Data[] Data { get; set; }
}
public partial class Context
{
[JsonProperty("@vocab")]
public Uri Vocab { get; set; }
[JsonProperty("@base")]
public Uri Base { get; set; }
}
public partial class Data
{
[JsonProperty("@id")]
public Uri Url { get; set; }
[JsonProperty("@type")]
public string Type { get; set; }
[JsonProperty("registration")]
public Uri Registration { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("summary")]
public string Summary { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("iconUrl")]
public Uri IconUrl { get; set; }
[JsonProperty("licenseUrl")]
public Uri LicenseUrl { get; set; }
[JsonProperty("projectUrl")]
public Uri ProjectUrl { get; set; }
[JsonProperty("tags")]
public string[] Tags { get; set; }
[JsonProperty("authors")]
public string[] Authors { get; set; }
[JsonProperty("totalDownloads")]
public long TotalDownloads { get; set; }
[JsonProperty("verified")]
public bool Verified { get; set; }
[JsonProperty("versions")]
public Version[] Versions { get; set; }
}
public partial class Version
{
[JsonProperty("version")]
public string Number { get; set; }
[JsonProperty("downloads")]
public long Downloads { get; set; }
[JsonProperty("@id")]
public Uri Url { get; set; }
}
}

View File

@ -5,6 +5,8 @@ using Oqtane.Repository;
using Oqtane.Models;
using Oqtane.Shared;
using System.Linq;
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace Oqtane.Controllers
{
@ -12,10 +14,14 @@ namespace Oqtane.Controllers
public class SiteController : Controller
{
private readonly ISiteRepository Sites;
private readonly ITenantResolver Tenants;
private readonly IWebHostEnvironment environment;
public SiteController(ISiteRepository Sites)
public SiteController(ISiteRepository Sites, ITenantResolver Tenants, IWebHostEnvironment environment)
{
this.Sites = Sites;
this.Tenants = Tenants;
this.environment = environment;
}
// GET: api/<controller>
@ -50,6 +56,11 @@ namespace Oqtane.Controllers
if (authorized)
{
Site = Sites.AddSite(Site);
string folder = environment.WebRootPath + "\\Tenants\\" + Tenants.GetTenant().TenantId.ToString() + "\\Sites\\" + Site.SiteId.ToString();
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
}
}
return Site;

View File

@ -98,6 +98,7 @@ namespace Oqtane.Server
services.AddScoped<IUserRoleService, UserRoleService>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<IPackageService, PackageService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

View File

@ -0,0 +1,12 @@
namespace Oqtane.Models
{
public class Package
{
public string PackageId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Owner { get; set; }
public string Version { get; set; }
public long Downloads { get; set; }
}
}