Configure push notifications for internal-only access
- Changed VAPID subject from public URL to mailto format - Updated docker-compose.yml to use mailto:admin@littleshop.local - Removed dependency on thebankofdebbie.giize.com public domain - All push notifications now work through VPN (admin.dark.side) only - Added update-push-internal.sh helper script for deployment - Improved security by keeping all admin traffic internal Push notifications will continue working normally through FCM, but all configuration and management stays on the internal network. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,145 +1,145 @@
|
||||
@model IEnumerable<LittleShop.DTOs.ProductDto>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Products";
|
||||
}
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1><i class="fas fa-box"></i> Products</h1>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
<a href="@Url.Action("Blazor")" class="btn btn-success">
|
||||
<i class="fas fa-rocket"></i> <span class="d-none d-sm-inline">New</span> Blazor UI
|
||||
</a>
|
||||
<a href="@Url.Action("Create")" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> <span class="d-none d-sm-inline">Add Product</span>
|
||||
</a>
|
||||
<a href="@Url.Action("Import")" class="btn btn-outline-success">
|
||||
<i class="fas fa-upload"></i> <span class="d-none d-sm-inline">Import</span>
|
||||
</a>
|
||||
<a href="@Url.Action("Export")" class="btn btn-outline-info">
|
||||
<i class="fas fa-download"></i> <span class="d-none d-sm-inline">Export</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Price</th>
|
||||
<th>Variations</th>
|
||||
<th>Stock</th>
|
||||
<th>Weight</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var product in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
@if (product.Photos.Any())
|
||||
{
|
||||
<img src="@product.Photos.First().FilePath" alt="@product.Photos.First().AltText" class="img-thumbnail" style="width: 50px; height: 50px; object-fit: cover;">
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="bg-light d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
<i class="fas fa-image text-muted"></i>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<strong>@product.Name</strong>
|
||||
<br><small class="text-muted">@product.Description.Substring(0, Math.Min(50, product.Description.Length))@(product.Description.Length > 50 ? "..." : "")</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">@product.CategoryName</span>
|
||||
</td>
|
||||
<td>
|
||||
<strong>£@product.Price</strong>
|
||||
</td>
|
||||
<td>
|
||||
@if (product.MultiBuys.Any())
|
||||
{
|
||||
<span class="badge bg-info">@product.MultiBuys.Count() multi-buys</span>
|
||||
}
|
||||
@if (product.Variants.Any())
|
||||
{
|
||||
<span class="badge bg-success">@product.Variants.Count() variants</span>
|
||||
}
|
||||
@if (!product.MultiBuys.Any() && !product.Variants.Any())
|
||||
{
|
||||
<span class="text-muted">None</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (product.StockQuantity > 0)
|
||||
{
|
||||
<span class="badge bg-success">@product.StockQuantity in stock</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-warning text-dark">Out of stock</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@product.Weight @product.WeightUnit.ToString().ToLower()
|
||||
</td>
|
||||
<td>
|
||||
@if (product.IsActive)
|
||||
{
|
||||
<span class="badge bg-success">Active</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger">Inactive</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="@Url.Action("Edit", new { id = product.Id })" class="btn btn-outline-primary" title="Edit Product">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="@Url.Action("Variations", new { id = product.Id })" class="btn btn-outline-info" title="Manage Multi-Buys">
|
||||
<i class="fas fa-tags"></i>
|
||||
</a>
|
||||
<a href="@Url.Action("Variants", new { id = product.Id })" class="btn btn-outline-success" title="Manage Variants">
|
||||
<i class="fas fa-palette"></i>
|
||||
</a>
|
||||
<form method="post" action="@Url.Action("Delete", new { id = product.Id })" class="d-inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this product?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-outline-danger" title="Delete Product">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-box fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">No products found. <a href="@Url.Action("Create")">Create your first product</a>.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@model IEnumerable<LittleShop.DTOs.ProductDto>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Products";
|
||||
}
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1><i class="fas fa-box"></i> Products</h1>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
<a href="@Url.Action("Blazor")" class="btn btn-success">
|
||||
<i class="fas fa-rocket"></i> <span class="d-none d-sm-inline">New</span> Blazor UI
|
||||
</a>
|
||||
<a href="@Url.Action("Create")" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> <span class="d-none d-sm-inline">Add Product</span>
|
||||
</a>
|
||||
<a href="@Url.Action("Import")" class="btn btn-outline-success">
|
||||
<i class="fas fa-upload"></i> <span class="d-none d-sm-inline">Import</span>
|
||||
</a>
|
||||
<a href="@Url.Action("Export")" class="btn btn-outline-info">
|
||||
<i class="fas fa-download"></i> <span class="d-none d-sm-inline">Export</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Price</th>
|
||||
<th>Variations</th>
|
||||
<th>Stock</th>
|
||||
<th>Weight</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var product in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
@if (product.Photos.Any())
|
||||
{
|
||||
<img src="@product.Photos.First().FilePath" alt="@product.Photos.First().AltText" class="img-thumbnail" style="width: 50px; height: 50px; object-fit: cover;">
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="bg-light d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
<i class="fas fa-image text-muted"></i>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<strong>@product.Name</strong>
|
||||
<br><small class="text-muted">@product.Description.Substring(0, Math.Min(50, product.Description.Length))@(product.Description.Length > 50 ? "..." : "")</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">@product.CategoryName</span>
|
||||
</td>
|
||||
<td>
|
||||
<strong>£@product.Price</strong>
|
||||
</td>
|
||||
<td>
|
||||
@if (product.MultiBuys.Any())
|
||||
{
|
||||
<span class="badge bg-info">@product.MultiBuys.Count() multi-buys</span>
|
||||
}
|
||||
@if (product.Variants.Any())
|
||||
{
|
||||
<span class="badge bg-success">@product.Variants.Count() variants</span>
|
||||
}
|
||||
@if (!product.MultiBuys.Any() && !product.Variants.Any())
|
||||
{
|
||||
<span class="text-muted">None</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (product.StockQuantity > 0)
|
||||
{
|
||||
<span class="badge bg-success">@product.StockQuantity in stock</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-warning text-dark">Out of stock</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@product.Weight @product.WeightUnit.ToString().ToLower()
|
||||
</td>
|
||||
<td>
|
||||
@if (product.IsActive)
|
||||
{
|
||||
<span class="badge bg-success">Active</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger">Inactive</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="@Url.Action("Edit", new { id = product.Id })" class="btn btn-outline-primary" title="Edit Product">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="@Url.Action("Variations", new { id = product.Id })" class="btn btn-outline-info" title="Manage Multi-Buys">
|
||||
<i class="fas fa-tags"></i>
|
||||
</a>
|
||||
<a href="@Url.Action("Variants", new { id = product.Id })" class="btn btn-outline-success" title="Manage Variants">
|
||||
<i class="fas fa-palette"></i>
|
||||
</a>
|
||||
<form method="post" action="@Url.Action("Delete", new { id = product.Id })" class="d-inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this product?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-outline-danger" title="Delete Product">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-box fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">No products found. <a href="@Url.Action("Create")">Create your first product</a>.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user