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:
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -9,5 +9,5 @@ namespace Oqtane.Services
|
||||
Task<List<ModuleDefinition>> GetModuleDefinitionsAsync(int SiteId);
|
||||
Task UpdateModuleDefinitionAsync(ModuleDefinition ModuleDefinition);
|
||||
Task InstallModulesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
Oqtane.Client/Services/Interfaces/IPackageService.cs
Normal file
12
Oqtane.Client/Services/Interfaces/IPackageService.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
40
Oqtane.Client/Services/PackageService.cs
Normal file
40
Oqtane.Client/Services/PackageService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
171
Oqtane.Server/Controllers/PackageController.cs
Normal file
171
Oqtane.Server/Controllers/PackageController.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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>();
|
||||
|
||||
|
12
Oqtane.Shared/Models/Package.cs
Normal file
12
Oqtane.Shared/Models/Package.cs
Normal 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; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user