637be701ea
- SeedData/Index: added prominent danger banner when running in Production (environment include="Production") so operators are clearly warned before writing to the live database; page remains accessible since seeding is occasionally valid in prod for new company onboarding - StorageMigration/Index: added warning banner in Production explaining the tool is not needed now that Azure Blob migration is complete - PlatformAdminController: hide Storage Migration hub card in Production via ShowStorageMigration flag (same WEBSITE_SITE_NAME pattern as ShowRawLogFiles); Seed Data card remains visible so prod onboarding stays reachable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
412 lines
21 KiB
Plaintext
412 lines
21 KiB
Plaintext
@model List<PowderCoating.Core.Entities.Company>
|
|
@{
|
|
ViewData["Title"] = "Seed Data Management";
|
|
ViewData["PageIcon"] = "bi-database-fill-gear";
|
|
}
|
|
|
|
@section Styles {
|
|
<style>
|
|
/* Dark mode: modal header with bg-warning */
|
|
[data-bs-theme="dark"] .modal-header.bg-warning {
|
|
background-color: #664d03 !important;
|
|
color: #fff !important;
|
|
}
|
|
[data-bs-theme="dark"] .modal-header.bg-warning .btn-close {
|
|
filter: invert(1);
|
|
}
|
|
</style>
|
|
}
|
|
|
|
<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-danger alert-permanent d-flex gap-3 align-items-start mb-4">
|
|
<i class="bi bi-exclamation-octagon-fill fs-4 flex-shrink-0 mt-1 text-danger"></i>
|
|
<div>
|
|
<div class="fw-semibold">You are running in Production</div>
|
|
<div class="small mt-1">Seed operations write data directly to the live database. Only proceed if you have a specific, intentional reason — for example, onboarding a new company. Do not seed the default demo company in production.</div>
|
|
</div>
|
|
</div>
|
|
</environment>
|
|
|
|
@if (TempData["SuccessMessage"] != null)
|
|
{
|
|
<div class="alert alert-success alert-dismissible alert-permanent fade show" role="alert">
|
|
<i class="bi bi-check-circle-fill me-2"></i>
|
|
<strong>Success!</strong> @TempData["SuccessMessage"]
|
|
@if (TempData["SeedDetails"] != null)
|
|
{
|
|
var details = TempData["SeedDetails"].ToString()?.Split('|');
|
|
if (details != null && details.Length > 0)
|
|
{
|
|
<div class="mt-2">
|
|
@foreach (var detail in details)
|
|
{
|
|
<div class="small">@detail</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
@if (TempData["ItemsSeeded"] != null)
|
|
{
|
|
<div class="mt-2">
|
|
<span class="badge bg-success">@TempData["ItemsSeeded"] items seeded</span>
|
|
</div>
|
|
}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
}
|
|
|
|
@if (TempData["WarningMessage"] != null)
|
|
{
|
|
<div class="alert alert-warning alert-dismissible alert-permanent fade show" role="alert">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
<strong>Warning!</strong> @TempData["WarningMessage"]
|
|
@if (TempData["SeedWarnings"] != null)
|
|
{
|
|
var warnings = TempData["SeedWarnings"].ToString()?.Split('|');
|
|
if (warnings != null && warnings.Length > 0)
|
|
{
|
|
<div class="mt-2">
|
|
<details>
|
|
<summary class="small text-decoration-underline" style="cursor: pointer;">View skipped items</summary>
|
|
<div class="mt-2">
|
|
@foreach (var warning in warnings)
|
|
{
|
|
<div class="small">@warning</div>
|
|
}
|
|
</div>
|
|
</details>
|
|
</div>
|
|
}
|
|
}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
}
|
|
|
|
@if (TempData["ErrorMessage"] != null)
|
|
{
|
|
<div class="alert alert-danger alert-dismissible alert-permanent fade show" role="alert">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
<strong>Error!</strong> @TempData["ErrorMessage"]
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
}
|
|
|
|
<!-- System Data Card -->
|
|
<div class="card mb-4 border-primary">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5 class="mb-0"><i class="bi bi-gear-fill me-2"></i>System Data</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="card-text">
|
|
Seed system-level data including:
|
|
</p>
|
|
<ul class="mb-3">
|
|
<li><strong>Roles:</strong> SuperAdmin, Administrator, Manager, Employee, ShopFloor, ReadOnly</li>
|
|
<li><strong>Default Company:</strong> Demo Company (DEMO)</li>
|
|
<li><strong>SuperAdmin User:</strong> artemis@("@")powdercoatinglogix.com</li>
|
|
</ul>
|
|
<div class="alert alert-info alert-permanent">
|
|
<i class="bi bi-info-circle-fill me-2"></i>
|
|
<strong>Note:</strong> This operation is idempotent. If data already exists, it will be skipped.
|
|
</div>
|
|
<form asp-action="SeedSystem" method="post" onsubmit="return confirm('Are you sure you want to seed system data? This will create default roles, companies, and SuperAdmin users.');">
|
|
@Html.AntiForgeryToken()
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-database-fill-add me-2"></i>Seed System Data
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Company Data Card -->
|
|
<div class="card">
|
|
<div class="card-header bg-success text-white">
|
|
<h5 class="mb-0"><i class="bi bi-building me-2"></i>Company Data</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="card-text">
|
|
Seed company-specific data including:
|
|
</p>
|
|
<ul class="mb-3">
|
|
<li><strong>Inventory Items:</strong> 8 powder coating colors + 2 consumables</li>
|
|
<li><strong>Operating Costs:</strong> Default labor, equipment, and overhead rates</li>
|
|
<li><strong>Demo Users:</strong> Company Admin and Manager (only for Demo Company)</li>
|
|
</ul>
|
|
<div class="alert alert-info alert-permanent mb-3">
|
|
<i class="bi bi-info-circle-fill me-2"></i>
|
|
<strong>Note:</strong> Select a company below to seed demo data. Existing data will not be overwritten.
|
|
</div>
|
|
|
|
@if (Model == null || !Model.Any())
|
|
{
|
|
<div class="alert alert-warning alert-permanent">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
No companies found. Please seed system data first to create the default company.
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<!-- Desktop table -->
|
|
<div class="table-responsive d-none d-lg-block">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Company Name</th>
|
|
<th>Code</th>
|
|
<th>Status</th>
|
|
<th>Subscription Plan</th>
|
|
<th class="text-center">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var company in Model)
|
|
{
|
|
<tr>
|
|
<td>
|
|
<strong>@company.CompanyName</strong>
|
|
@if (company.CompanyCode == "DEMO")
|
|
{
|
|
<span class="badge bg-info ms-2">Default</span>
|
|
}
|
|
</td>
|
|
<td><code>@company.CompanyCode</code></td>
|
|
<td>
|
|
@if (company.IsActive)
|
|
{
|
|
<span class="badge bg-success">Active</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Inactive</span>
|
|
}
|
|
</td>
|
|
<td>@company.SubscriptionPlan</td>
|
|
<td class="text-center">
|
|
<div class="d-flex gap-2 justify-content-center">
|
|
<form asp-action="SeedCompany" method="post" style="display: inline;"
|
|
onsubmit="return confirm('Are you sure you want to seed data for @company.CompanyName? Existing data will not be overwritten.');">
|
|
@Html.AntiForgeryToken()
|
|
<input type="hidden" name="companyId" value="@company.Id" />
|
|
<button type="submit" class="btn btn-success btn-sm">
|
|
<i class="bi bi-database-fill-add me-1"></i>Seed Data
|
|
</button>
|
|
</form>
|
|
<button type="button" class="btn btn-warning btn-sm"
|
|
onclick="openUnseedModal(@company.Id, '@Html.Raw(Html.Encode(company.CompanyName))')">
|
|
<i class="bi bi-database-fill-dash me-1"></i>Unseed Data
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Mobile card view — shown on screens < 992px -->
|
|
<div class="mobile-card-view d-lg-none">
|
|
<div class="mobile-card-list">
|
|
@foreach (var company in Model)
|
|
{
|
|
<div class="mobile-data-card">
|
|
<div class="mobile-card-header">
|
|
<div class="mobile-card-icon bg-success"><i class="bi bi-building"></i></div>
|
|
<div class="mobile-card-title">
|
|
<h6>
|
|
@company.CompanyName
|
|
@if (company.CompanyCode == "DEMO")
|
|
{
|
|
<span class="badge bg-info ms-1">Default</span>
|
|
}
|
|
</h6>
|
|
<small><code>@company.CompanyCode</code></small>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-card-body">
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Status</span>
|
|
<span class="mobile-card-value">
|
|
@if (company.IsActive)
|
|
{
|
|
<span class="badge bg-success">Active</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Inactive</span>
|
|
}
|
|
</span>
|
|
</div>
|
|
<div class="mobile-card-row">
|
|
<span class="mobile-card-label">Plan</span>
|
|
<span class="mobile-card-value">@company.SubscriptionPlan</span>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-card-footer">
|
|
<form asp-action="SeedCompany" method="post" style="display: inline;"
|
|
onsubmit="return confirm('Are you sure you want to seed data for @company.CompanyName? Existing data will not be overwritten.');">
|
|
@Html.AntiForgeryToken()
|
|
<input type="hidden" name="companyId" value="@company.Id" />
|
|
<button type="submit" class="btn btn-success btn-sm me-1">
|
|
<i class="bi bi-database-fill-add me-1"></i>Seed
|
|
</button>
|
|
</form>
|
|
<button type="button" class="btn btn-warning btn-sm"
|
|
onclick="openUnseedModal(@company.Id, '@Html.Raw(Html.Encode(company.CompanyName))')">
|
|
<i class="bi bi-database-fill-dash me-1"></i>Unseed
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<environment exclude="Production">
|
|
<!-- Documentation Card -->
|
|
<div class="card mt-4 border-info">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0"><i class="bi bi-book me-2"></i>Default Credentials</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6 class="fw-bold">SuperAdmin (Platform Access)</h6>
|
|
<ul class="list-unstyled">
|
|
<li class="mb-2">
|
|
<strong>SuperAdmin:</strong><br />
|
|
Email: <code>artemis@("@")powdercoatinglogix.com</code><br />
|
|
Password: <code>SuperAdmin123!</code>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6 class="fw-bold">Demo Company Users</h6>
|
|
<ul class="list-unstyled">
|
|
<li class="mb-2">
|
|
<strong>Company Admin:</strong><br />
|
|
Email: <code>demo@("@")powdercoatinglogix.com</code><br />
|
|
Password: <code>CompanyAdmin123!</code>
|
|
</li>
|
|
<li>
|
|
<strong>Manager:</strong><br />
|
|
Email: <code>manager@("@")demo.com</code><br />
|
|
Password: <code>Manager123!</code>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</environment>
|
|
</div>
|
|
|
|
<!-- Unseed Data Modal -->
|
|
<div class="modal fade" id="unseedModal" tabindex="-1" aria-labelledby="unseedModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-warning text-dark">
|
|
<h5 class="modal-title" id="unseedModalLabel">
|
|
<i class="bi bi-database-fill-dash me-2"></i>Unseed Data
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form asp-action="RemoveSeedData" method="post" id="unseedForm">
|
|
@Html.AntiForgeryToken()
|
|
<input type="hidden" name="companyId" id="unseedCompanyId" />
|
|
<div class="modal-body">
|
|
<p class="mb-1">Select the data types to remove for <strong id="unseedCompanyName"></strong>.</p>
|
|
<p class="text-muted small mb-3">Only records that match the original seed values will be removed. Any data you have added or modified will not be affected.</p>
|
|
|
|
<div class="list-group">
|
|
<label class="list-group-item list-group-item-action d-flex gap-3">
|
|
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.Customers" value="true" id="chkCustomers" />
|
|
<span>
|
|
<strong>Customers</strong>
|
|
<span class="d-block text-muted small">Also removes associated jobs, quotes, and their line items (25 seeded customers)</span>
|
|
</span>
|
|
</label>
|
|
<label class="list-group-item list-group-item-action d-flex gap-3">
|
|
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.InventoryItems" value="true" id="chkInventory" />
|
|
<span>
|
|
<strong>Inventory Items</strong>
|
|
<span class="d-block text-muted small">10 seeded items (8 powder colors, cleaner, masking tape) and their transactions</span>
|
|
</span>
|
|
</label>
|
|
<label class="list-group-item list-group-item-action d-flex gap-3">
|
|
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.Equipment" value="true" id="chkEquipment" />
|
|
<span>
|
|
<strong>Equipment</strong>
|
|
<span class="d-block text-muted small">10 seeded equipment records and their maintenance history</span>
|
|
</span>
|
|
</label>
|
|
<label class="list-group-item list-group-item-action d-flex gap-3">
|
|
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.Catalog" value="true" id="chkCatalog" />
|
|
<span>
|
|
<strong>Catalog Items & Categories</strong>
|
|
<span class="d-block text-muted small">7 categories and all their catalog items</span>
|
|
</span>
|
|
</label>
|
|
<label class="list-group-item list-group-item-action d-flex gap-3">
|
|
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.PricingTiers" value="true" id="chkPricingTiers" />
|
|
<span>
|
|
<strong>Pricing Tiers</strong>
|
|
<span class="d-block text-muted small">Standard, Silver, Gold, Platinum tiers</span>
|
|
</span>
|
|
</label>
|
|
<label class="list-group-item list-group-item-action d-flex gap-3">
|
|
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="options.OperatingCosts" value="true" id="chkOperatingCosts" />
|
|
<span>
|
|
<strong>Operating Costs</strong>
|
|
<span class="d-block text-muted small">Default labor, equipment, and overhead rates</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="alert alert-warning alert-permanent mt-3 mb-0">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
<strong>This action permanently deletes records and cannot be undone.</strong>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-warning" id="unseedSubmitBtn" disabled>
|
|
<i class="bi bi-database-fill-dash me-1"></i>Remove Selected Data
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
function openUnseedModal(companyId, companyName) {
|
|
document.getElementById('unseedCompanyId').value = companyId;
|
|
document.getElementById('unseedCompanyName').textContent = companyName;
|
|
|
|
// Reset checkboxes
|
|
document.querySelectorAll('#unseedForm input[type="checkbox"]').forEach(cb => cb.checked = false);
|
|
document.getElementById('unseedSubmitBtn').disabled = true;
|
|
|
|
new bootstrap.Modal(document.getElementById('unseedModal')).show();
|
|
}
|
|
|
|
// Enable submit only when at least one checkbox is checked
|
|
document.querySelectorAll('#unseedForm input[type="checkbox"]').forEach(cb => {
|
|
cb.addEventListener('change', function () {
|
|
const anyChecked = [...document.querySelectorAll('#unseedForm input[type="checkbox"]')].some(c => c.checked);
|
|
document.getElementById('unseedSubmitBtn').disabled = !anyChecked;
|
|
});
|
|
});
|
|
</script>
|
|
}
|