Skip to content

Commit

Permalink
Merge pull request #8 from btcpayserver/kukks
Browse files Browse the repository at this point in the history
* Add a homepage that shows last 50 builds from all your plugins
* Allow removing a version (build gets marked as removed, file is deleted from blob)
* Allow toggling between release / pre-release
* Only allow 5 concurrent builds, the rest are scheduled
* Mark builds that were in some active state as failed on startup
  • Loading branch information
NicolasDorier authored Dec 21, 2023
2 parents 9ca35f2 + 643ca38 commit 0e46da3
Show file tree
Hide file tree
Showing 24 changed files with 418 additions and 250 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext
context.Fail();
return;
}
using var conn = await ConnectionFactory.Open();

await using var conn = await ConnectionFactory.Open();
var userId = UserManager.GetUserId(context.User)!;
if (await conn.UserOwnsPlugin(userId, slug))
{
Expand Down
30 changes: 30 additions & 0 deletions PluginBuilder/BuildStates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace PluginBuilder
{
public enum BuildStates
{
Queued,
Uploaded,
Uploading,
Removed,
Running,
Failed,
WaitingUpload
}
public static class BuildStatesExtensions
{
public static string ToEventName(this BuildStates buildState)
{
return buildState switch
{
BuildStates.Queued => "queued",
BuildStates.Removed => "removed",
BuildStates.Running => "running",
BuildStates.Failed => "failed",
BuildStates.Uploaded => "uploaded",
BuildStates.Uploading => "uploading",
BuildStates.WaitingUpload => "waiting-upload",
_ => throw new ArgumentOutOfRangeException(nameof(buildState), buildState, null)
};
}
}
}
16 changes: 3 additions & 13 deletions PluginBuilder/Components/PluginSelector/Default.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,9 @@
@model PluginBuilder.Components.PluginSelector.PluginSelectorViewModel


@if (Model.PluginSlug == null)
{
<a href="~/" class="navbar-brand py-2 js-scroll-trigger">
<svg xmlns="http://www.w3.org/2000/svg" role="img" alt="BTCPay Server" class="logo"><use href="/img/logo.svg#small" class="logo-small" /><use href="/img/logo.svg#large" class="logo-large" /></svg>
</a>
}
else
{
<a asp-controller="Home" asp-action="Dashboard" asp-route-pluginSlug="@Model.PluginSlug" class="navbar-brand py-2 js-scroll-trigger">
<svg xmlns="http://www.w3.org/2000/svg" role="img" alt="BTCPay Server" class="logo"><use href="/img/logo.svg#small" class="logo-small" /><use href="/img/logo.svg#large" class="logo-large" /></svg>
</a>
}

<a asp-controller="Home" asp-action="HomePage" class="navbar-brand py-2 js-scroll-trigger">
<svg xmlns="http://www.w3.org/2000/svg" role="img" alt="BTCPay Server" class="logo"><use href="/img/logo.svg#small" class="logo-small" /><use href="/img/logo.svg#large" class="logo-large" /></svg>
</a>
<div id="StoreSelector">
@if (Model.Options.Count > 0)
{
Expand Down
2 changes: 1 addition & 1 deletion PluginBuilder/Components/PluginSelector/PluginSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public PluginSelector(

public async Task<IViewComponentResult> InvokeAsync()
{
using var connection = await ConnectionFactory.Open();
await using var connection = await ConnectionFactory.Open();
var userId = _userManager.GetUserId(UserClaimsPrincipal)!;
var plugins = await connection.GetPluginsByUserId(userId);

Expand Down
2 changes: 1 addition & 1 deletion PluginBuilder/Components/PluginVersion/Default.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="d-flex align-items-center gap-1">
@if (Model.Published)
{
<div><a asp-action="Version" asp-controller="Plugin" asp-route-pluginSlug="@Context.GetRouteValue("pluginSlug")" asp-route-version="@Model.Version">@Model.Version</a></div>
<div><a asp-action="Version" asp-controller="Plugin" asp-route-pluginSlug="@Model.PluginSlug" asp-route-version="@Model.Version">@Model.Version</a></div>
}
else
{
Expand Down
10 changes: 7 additions & 3 deletions PluginBuilder/Components/PluginVersion/PluginVersionViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@ namespace PluginBuilder.Components.PluginVersion
{
public class PluginVersionViewModel
{
public static PluginVersionViewModel CreateOrNull(string version, bool published, bool pre_release)
public static PluginVersionViewModel CreateOrNull(string version, bool published, bool pre_release, string state, string pluginSlug)
{
if (version is null)
return null;
return new PluginVersionViewModel()
return new PluginVersionViewModel
{
Version = version,
Published = published,
PreRelease = pre_release
PreRelease = pre_release,
PluginSlug = pluginSlug,
Removed = state == BuildStates.Removed.ToEventName()
};
}
public string Version { get; set; }
public string PluginSlug { get; set; }
public bool Published { get; set; }
public bool PreRelease { get; set; }
public bool Removed { get; set; }
public bool HidePublishBadge { get; set; }
}
}
6 changes: 6 additions & 0 deletions PluginBuilder/Components/StatusMessage/Default.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
<span style="white-space: pre-wrap;">@TempData[TempDataConstant.SuccessMessage]</span>
</div>
}
@if (TempData[TempDataConstant.WarningMessage] != null)
{
<div class="alert alert-warning text-break" role="alert">
<span style="white-space: pre-wrap;">@TempData[TempDataConstant.WarningMessage]</span>
</div>
}
41 changes: 33 additions & 8 deletions PluginBuilder/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Dapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -33,17 +34,40 @@ public HomeController(
}

[HttpGet("/")]

public async Task<IActionResult> HomePage()
{
if (HttpContext.Request.Cookies.TryGetValue(Cookies.PluginSlug, out var s) && s is not null && PluginSlug.TryParse(s, out var p))
await using var conn = await ConnectionFactory.Open();
var rows = await conn.QueryAsync<(long id, string state, string? manifest_info, string? build_info, DateTimeOffset created_at, bool published, bool pre_release, string slug, string? identifier)>
(@"SELECT id, state, manifest_info, build_info, created_at, v.ver IS NOT NULL, v.pre_release, p.slug, p.identifier
FROM builds b
LEFT JOIN versions v ON b.plugin_slug=v.plugin_slug AND b.id=v.build_id
JOIN plugins p ON p.slug = b.plugin_slug
JOIN users_plugins up ON up.plugin_slug = b.plugin_slug
WHERE up.user_id = @userId
ORDER BY created_at DESC
LIMIT 50", new { userId = UserManager.GetUserId(User) });
var vm = new BuildListViewModel();
foreach (var row in rows)
{
var auth = await AuthorizationService.AuthorizeAsync(User, p, new OwnPluginRequirement());
if (auth.Succeeded)
return RedirectToAction(nameof(PluginController.Dashboard), "Plugin", new { pluginSlug = p.ToString() });
else
HttpContext.Response.Cookies.Delete(Cookies.PluginSlug);
var b = new BuildListViewModel.BuildViewModel();
var buildInfo = row.build_info is null ? null : BuildInfo.Parse(row.build_info);
var manifest = row.manifest_info is null ? null : PluginManifest.Parse(row.manifest_info);
vm.Builds.Add(b);
b.BuildId = row.id;
b.State = row.state;
b.Commit = buildInfo?.GitCommit?.Substring(0, 8);
b.Repository = buildInfo?.GitRepository;
b.GitRef = buildInfo?.GitRef;
b.Version = Components.PluginVersion.PluginVersionViewModel.CreateOrNull(manifest?.Version?.ToString(), row.published, row.pre_release, row.state, row.slug);
b.Date = (DateTimeOffset.UtcNow - row.created_at).ToTimeAgo();
b.RepositoryLink = PluginController.GetUrl(buildInfo);
b.DownloadLink = buildInfo?.Url;
b.Error = buildInfo?.Error;
b.PluginSlug = row.slug;
b.PluginIdentifier = row.identifier ?? row.slug;
}
return View();
return View("Views/Plugin/Dashboard",vm);
}

[HttpGet("/logout")]
Expand Down Expand Up @@ -139,7 +163,8 @@ public async Task<IActionResult> CreatePlugin(CreatePluginViewModel model)
ModelState.AddModelError(nameof(model.PluginSlug), "Invalid plug slug, it should only contains latin letter in lowercase or numbers or '-' (example: my-awesome-plugin)");
return View(model);
}
using var conn = await ConnectionFactory.Open();

await using var conn = await ConnectionFactory.Open();
if (!await conn.NewPlugin(pluginSlug))
{
ModelState.AddModelError(nameof(model.PluginSlug), "This slug already exists");
Expand Down
Loading

0 comments on commit 0e46da3

Please sign in to comment.