btcpayserver-plugin-builder/PluginBuilder/Views/Plugin/Build.cshtml

293 lines
12 KiB
Plaintext

@using PluginBuilder.ViewModels.Shared
@model BuildViewModel
@{
Layout = "_Layout";
ViewData.SetActivePage(PluginNavPages.Dashboard, $"Build {Model.FullBuildId.BuildId}", Model.FullBuildId.ToString());
var btcpayCompatibilityModalVm = new BTCPayCompatibilityModalViewModel
{
ModalId = "btcpayCompatibilityModal",
ModalLabelId = "btcpayCompatibilityModalLabel",
Title = "Edit BTCPay compatibility",
PostUrl = Url.Action("UpdateVersionBTCPayCompatibility", "Plugin",
new { pluginSlug = Model.FullBuildId.PluginSlug.ToString(), buildId = Model.FullBuildId.BuildId })!,
MinInputId = "btcpayMinVersion",
MaxInputId = "btcpayMaxVersion",
MinInputClass = "compatibility-min-input",
MaxInputClass = "compatibility-max-input",
MinVersion = Model.BTCPayMinVersion,
MaxVersion = Model.BTCPayMaxVersion,
ShowResetToManifest = Model.HasBTCPayCompatibilityOverride
};
}
<div class="d-sm-flex align-items-center justify-content-between">
<div class="d-sm-flex align-items-center">
<h2 class="mb-0">
@ViewData["Title"]
</h2>
</div>
<div class="d-flex gap-3 mt-3 mt-sm-0">
@switch (Model.State)
{
case "running":
<span class="badge bg-info">@Model.State</span>
break;
case "removed":
<span class="badge bg-warning">@Model.State</span>
break;
case "failed":
<a asp-action="CreateBuild" asp-route-pluginSlug="@Model.FullBuildId.PluginSlug" asp-route-copyBuild="@Model.FullBuildId.BuildId"
class="btn btn-secondary">Retry</a>
break;
case "uploaded":
if (Model.Version is null || Model.Version?.PreRelease is true)
{
if (Model.RequireGPGSignatureForRelease && !string.IsNullOrEmpty(Model.ManifestInfoSha256Hash))
{
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#signReleaseModal">
Sign and Release
</button>
}
else
{
<form asp-action="Release" asp-route-pluginSlug="@Model.FullBuildId.PluginSlug" asp-route-version="@(Model.Version?.Version ?? "")"
method="post">
<button type="submit" name="command" value="release" class="btn btn-primary">Release</button>
</form>
}
<a asp-action="CreateBuild" asp-route-pluginSlug="@Model.FullBuildId.PluginSlug" asp-route-copyBuild="@Model.FullBuildId.BuildId"
class="btn btn-secondary">Retry</a>
}
else if (Model.Version?.Published is true)
{
<form asp-action="Release" asp-route-pluginSlug="@Model.FullBuildId.PluginSlug" asp-route-version="@(Model.Version?.Version ?? "")" method="post">
<button type="submit" name="command" value="unrelease" class="btn btn-outline-danger">Revert release</button>
</form>
}
<form asp-action="Release" asp-route-pluginSlug="@Model.FullBuildId.PluginSlug" asp-route-version="@(Model.Version?.Version ?? "")" method="post">
<button type="submit" name="command" value="remove" class="btn btn-outline-danger">Remove version</button>
</form>
if (Model.DownloadLink != null)
{
<a href="@Model.DownloadLink" rel="noreferrer noopener" class="btn btn-outline-info">Download</a>
}
break;
}
</div>
</div>
<div class="modal fade" id="signReleaseModal" tabindex="-1" aria-labelledby="signReleaseModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="signReleaseModalLabel">Sign and Release Plugin Version</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<p>Download the file below, sign it with your GPG key, then upload the detached signature (<code>.asc,.sig</code>).</p>
<input type="hidden" id="manifestHash" value="@Model.ManifestInfoSha256Hash" />
<div class="d-flex align-items-center gap-2 mb-3">
<span class="text-muted small">Manifest SHA-256</span>
<span role="button" style="cursor:pointer" onclick="downloadHashFile()" title="Download manifest.txt file">
<vc:icon symbol="actions-download" />
</span>
</div>
<form asp-action="Release" asp-route-pluginSlug="@Model.FullBuildId.PluginSlug" asp-route-version="@(Model.Version?.Version ?? "")"
method="post" enctype="multipart/form-data">
@Html.AntiForgeryToken()
<input type="hidden" name="command" value="sign_release" />
<div class="mb-3">
<label for="signatureFile" class="form-label">Upload Signature ( <code>.asc,.sig</code> )</label>
<input type="file" class="form-control" id="signatureFile" name="signatureFile" accept=".asc,.sig" required />
</div>
<button type="submit" class="btn btn-primary">
Verify & Release
</button>
</form>
</div>
</div>
</div>
</div>
</div>
@if (Model is { Version: not null, CanEditBTCPayCompatibility: true })
{
<partial name="_BTCPayCompatibilityModal" model="btcpayCompatibilityModalVm" />
}
<table class="table build-summary-table">
@if (Model.Version != null)
{
<tr>
<th class="fw-semibold w-100px">Version</th>
<td>
<vc:plugin-version model="@Model.Version"></vc:plugin-version>
</td>
</tr>
}
<tr>
<th class="fw-semibold">Git version</th>
<td>
@Model.GitRef
@if (Model.RepositoryLink is null)
{
@("@")
<span>@Model.Commit</span>
}
else
{
@("@")
<a href="@Model.RepositoryLink" target="_blank" rel="noreferrer noopener">@Model.Commit</a>
}
</td>
</tr>
<tr>
<th class="fw-semibold">Created at</th>
<td>
@Model.CreatedDate
</td>
</tr>
@if (Model.Version != null && (!string.IsNullOrEmpty(Model.BTCPayMinVersion) || !string.IsNullOrEmpty(Model.BTCPayMaxVersion)))
{
<tr>
<th class="fw-semibold">BTCPay compatibility</th>
<td>
<div class="d-flex flex-wrap align-items-center gap-3">
<span>Min: <code>@Model.BTCPayMinVersion</code></span>
<span>
Max:
@if (!string.IsNullOrEmpty(Model.BTCPayMaxVersion))
{
<code>@Model.BTCPayMaxVersion</code>
}
else
{
<span class="text-muted">None</span>
}
</span>
@if (Model.CanEditBTCPayCompatibility)
{
<button type="button"
class="btn btn-link btn-sm p-0 text-decoration-none d-inline-flex align-items-center justify-content-center compatibility-edit-trigger"
data-bs-toggle="modal"
data-bs-target="#btcpayCompatibilityModal"
title="Edit compatibility"
aria-label="Edit compatibility">
<i class="fa fa-pencil" aria-hidden="true"></i>
<span class="visually-hidden">Edit compatibility</span>
</button>
}
</div>
</td>
</tr>
}
</table>
<div>
<nav id="SectionNav">
<div class="nav gap-4" id="artifacts-tabs" role="tablist">
<button class="nav-link active" id="build-logs-tab" data-bs-toggle="pill" data-bs-target="#build-logs-pane" type="button" role="tab"
aria-controls="build-logs-pane">Logs</button>
<button class="nav-link" id="build-info-tab" data-bs-toggle="pill" data-bs-target="#build-info-pane" type="button" role="tab"
aria-controls="build-info-pane">Build info</button>
<button class="nav-link" id="manifest-info-tab" data-bs-toggle="pill" data-bs-target="#manifest-info-pane" type="button" role="tab"
aria-controls="manifest-info-pane">Plugin manifest</button>
</div>
</nav>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane show active" id="build-logs-pane" role="tabpanel" aria-labelledby="build-logs-tab">
<pre><code class="text hljs" id="Logs">@Model.Logs</code>
@if (string.IsNullOrEmpty(Model.Logs))
{
<div class="d-flex align-items-center gap-2 p-3"><div class="spinner-border spinner-border-sm" role="status"></div> Build running...</div>
}</pre>
</div>
<div class="tab-pane" id="build-info-pane" role="tabpanel" aria-labelledby="manifest-info-tab">
<pre><code class="json hljs" id="BuildInfo">@(string.IsNullOrEmpty(Model.BuildInfo) ? "{}" : Model.BuildInfo)</code></pre>
</div>
<div class="tab-pane" id="manifest-info-pane" role="tabpanel" aria-labelledby="build-info-tab">
<pre><code class="json hljs" id="ManifestInfo">@(string.IsNullOrEmpty(Model.ManifestInfo) ? "{}" : Model.ManifestInfo)</code></pre>
</div>
</div>
</div>
@section HeaderScripts {
<link href="~/vendor/highlight.js/default.min.css" asp-append-version="true" rel="stylesheet" />
<style>
body, html {
overflow-x: hidden;
}
.build-summary-table th,
.build-summary-table td {
vertical-align: middle;
}
.compatibility-max-input {
max-width: 12rem;
}
.compatibility-min-input {
max-width: 12rem;
}
.compatibility-edit-trigger {
color: var(--btcpay-body-text-muted, var(--bs-secondary-color));
}
.compatibility-edit-trigger:hover,
.compatibility-edit-trigger:focus {
color: var(--btcpay-primary);
}
#artifacts-tabs .nav-link {
background: none;
border: 0;
border-bottom: 2px solid transparent;
border-radius: 0;
color: var(--btcpay-nav-link);
font-size: 1.125rem;
padding: 0 0 .75rem;
}
</style>
}
@section FooterScripts {
<script src="~/vendor/highlight.js/highlight.min.js" asp-append-version="true"></script>
<script src="~/vendor/signalr/signalr.min.js" asp-append-version="true"></script>
<script src="~/scripts/Build.js" asp-append-version="true"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
if (@(Model.OpenBTCPayCompatibilityModal ? "true" : "false")) {
const modalElement = document.getElementById("btcpayCompatibilityModal");
if (modalElement) {
bootstrap.Modal.getOrCreateInstance(modalElement).show();
}
}
});
function downloadHashFile() {
const hashInput = document.getElementById('manifestHash');
const hash = hashInput?.value?.trim();
const blob = new Blob([hash], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `manifest-${hash.substring(0, 7)}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
</script>
}