4ec55e7290
The HTML entity sweep script had a bug where it wrote empty files for any
view that contained no target Unicode characters, zeroing out 215 view files.
All views restored from the pre-sweep commit (cefdf3e).
Bulk gift certificate feature:
- BulkCreateGiftCertificateDto with Quantity (1-500), Amount, Reason, Expiry, Notes
- GenerateBulkGiftCertificatePdfAsync on IPdfService / PdfService: one Letter page
per cert, reusing the same purple/gold branded ComposeGiftCertificateContent helper
- GiftCertificatesController: BulkCreate GET/POST, BulkResult GET, BulkDownloadPdf POST
- Views: BulkCreate.cshtml (form with live total preview), BulkResult.cshtml (table +
Download All PDF button that POSTs cert IDs to avoid URL length limits)
- gift-certificate-bulk.js: live preview + spinner/disable on submit
- Index.cshtml: Bulk Create button added alongside New Certificate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
190 lines
9.2 KiB
Plaintext
190 lines
9.2 KiB
Plaintext
@{
|
|
ViewData["Title"] = "Storage Migration";
|
|
ViewData["PageIcon"] = "bi-cloud-upload";
|
|
bool mediaExists = ViewBag.MediaExists;
|
|
int localFileCount = ViewBag.LocalFileCount;
|
|
}
|
|
|
|
<div class="container-fluid">
|
|
<div class="mb-2">
|
|
<a asp-controller="PlatformAdmin" asp-action="Maintenance" class="text-muted small text-decoration-none">
|
|
<i class="bi bi-arrow-left me-1"></i>Maintenance
|
|
</a>
|
|
</div>
|
|
|
|
<environment include="Production">
|
|
<div class="alert alert-warning alert-permanent d-flex gap-3 align-items-start mb-4">
|
|
<i class="bi bi-exclamation-triangle-fill fs-4 flex-shrink-0 mt-1"></i>
|
|
<div>
|
|
<div class="fw-semibold">This tool is not needed in Production</div>
|
|
<div class="small mt-1">The platform has already migrated to Azure Blob Storage. Running this migration again in production is unnecessary and may cause unintended side effects. If you believe there is a specific reason to proceed, verify with the team first.</div>
|
|
</div>
|
|
</div>
|
|
</environment>
|
|
|
|
<div class="row g-4">
|
|
<!-- Status Card -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0"><i class="bi bi-hdd me-2"></i>Local Filesystem Status</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
@if (mediaExists)
|
|
{
|
|
<div class="d-flex align-items-center gap-3 mb-3">
|
|
<div class="rounded-circle bg-warning bg-opacity-10 p-3">
|
|
<i class="bi bi-folder2-open fs-4 text-warning"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold">@localFileCount file@(localFileCount != 1 ? "s" : "") found locally</div>
|
|
<small class="text-muted">@ViewBag.MediaPath</small>
|
|
</div>
|
|
</div>
|
|
<div class="alert alert-info alert-permanent mb-0">
|
|
<i class="bi bi-info-circle me-1"></i>
|
|
These files will be uploaded to Azure Blob Storage. Files already present in Azure will be skipped automatically.
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="d-flex align-items-center gap-3">
|
|
<div class="rounded-circle bg-success bg-opacity-10 p-3">
|
|
<i class="bi bi-check-circle fs-4 text-success"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold">No local media files found</div>
|
|
<small class="text-muted">@ViewBag.MediaPath</small>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Azure Targets Card -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0"><i class="bi bi-cloud me-2"></i>Azure Container Mapping</h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<!-- Desktop table -->
|
|
<table class="table table-sm mb-0 d-none d-lg-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="ps-3">File Type</th>
|
|
<th>Container</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="ps-3"><i class="bi bi-person-circle me-1 text-primary"></i>Profile photos</td>
|
|
<td><code>profileimages</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="ps-3"><i class="bi bi-camera me-1 text-success"></i>Job photos</td>
|
|
<td><code>jobimages</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="ps-3"><i class="bi bi-building me-1 text-info"></i>Company logos</td>
|
|
<td><code>companylogos</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="ps-3"><i class="bi bi-file-earmark-pdf me-1 text-danger"></i>Equipment manuals</td>
|
|
<td><code>manuals</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Mobile card view — shown on screens < 992px -->
|
|
<div class="mobile-card-view d-lg-none p-3">
|
|
<div class="mobile-card-list">
|
|
<div class="mobile-data-card">
|
|
<div class="mobile-card-header">
|
|
<div class="mobile-card-icon bg-primary"><i class="bi bi-person-circle"></i></div>
|
|
<div class="mobile-card-title">
|
|
<h6>Profile photos</h6>
|
|
<small><code>profileimages</code></small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-data-card">
|
|
<div class="mobile-card-header">
|
|
<div class="mobile-card-icon bg-success"><i class="bi bi-camera"></i></div>
|
|
<div class="mobile-card-title">
|
|
<h6>Job photos</h6>
|
|
<small><code>jobimages</code></small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-data-card">
|
|
<div class="mobile-card-header">
|
|
<div class="mobile-card-icon bg-info"><i class="bi bi-building"></i></div>
|
|
<div class="mobile-card-title">
|
|
<h6>Company logos</h6>
|
|
<small><code>companylogos</code></small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-data-card">
|
|
<div class="mobile-card-header">
|
|
<div class="mobile-card-icon bg-danger"><i class="bi bi-file-earmark-pdf"></i></div>
|
|
<div class="mobile-card-title">
|
|
<h6>Equipment manuals</h6>
|
|
<small><code>manuals</code></small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (mediaExists && localFileCount > 0)
|
|
{
|
|
<!-- Migration Form -->
|
|
<div class="card mt-4">
|
|
<div class="card-header">
|
|
<h6 class="mb-0"><i class="bi bi-play-circle me-2"></i>Run Migration</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<form asp-action="Migrate" method="post" id="migrationForm">
|
|
@Html.AntiForgeryToken()
|
|
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="deleteAfterMigration" value="true" id="deleteAfterMigration">
|
|
<label class="form-check-label" for="deleteAfterMigration">
|
|
Delete local files after successful migration
|
|
</label>
|
|
</div>
|
|
<div class="form-text text-warning">
|
|
<i class="bi bi-exclamation-triangle me-1"></i>
|
|
Leave unchecked on the first run. Files already in Azure are always skipped — re-running is safe.
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary" id="migrateBtn">
|
|
<i class="bi bi-cloud-upload me-2"></i>Start Migration
|
|
</button>
|
|
<span class="ms-3 text-muted d-none" id="migrationSpinner">
|
|
<span class="spinner-border spinner-border-sm me-1"></span>Migrating @localFileCount files, please wait…
|
|
</span>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
document.getElementById('migrationForm')?.addEventListener('submit', function () {
|
|
document.getElementById('migrateBtn').disabled = true;
|
|
document.getElementById('migrationSpinner').classList.remove('d-none');
|
|
});
|
|
</script>
|
|
}
|