littleshop/LittleShop/Areas/Admin/Views/PushSubscriptions/Index.cshtml
SysAdmin a2247d7c02
Some checks failed
Build and Deploy LittleShop / Build TeleBot Docker Image (push) Failing after 11s
Build and Deploy LittleShop / Build LittleShop Docker Image (push) Failing after 15s
Build and Deploy LittleShop / Deploy to Production VPS (Manual Only) (push) Has been skipped
Build and Deploy LittleShop / Deploy to Pre-Production (CT109) (push) Has been skipped
feat: Add customer management, payments, and push notifications with security enhancements
Major Feature Additions:
- Customer management: Full CRUD with data export and privacy compliance
- Payment management: Centralized payment tracking and administration
- Push notification subscriptions: Manage and track web push subscriptions

Security Enhancements:
- IP whitelist middleware for administrative endpoints
- Data retention service with configurable policies
- Enhanced push notification security documentation
- Security fixes progress tracking (2025-11-14)

UI/UX Improvements:
- Enhanced navigation with improved mobile responsiveness
- Updated admin dashboard with order status counts
- Improved product CRUD forms
- New customer and payment management interfaces

Backend Improvements:
- Extended customer service with data export capabilities
- Enhanced order service with status count queries
- Improved crypto payment service with better error handling
- Updated validators and configuration

Documentation:
- DEPLOYMENT_NGINX_GUIDE.md: Nginx deployment instructions
- IP_STORAGE_ANALYSIS.md: IP storage security analysis
- PUSH_NOTIFICATION_SECURITY.md: Push notification security guide
- UI_UX_IMPROVEMENT_PLAN.md: Planned UI/UX enhancements
- UI_UX_IMPROVEMENTS_COMPLETED.md: Completed improvements

Cleanup:
- Removed temporary database WAL files
- Removed stale commit message file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 19:33:02 +00:00

261 lines
13 KiB
Plaintext

@model IEnumerable<LittleShop.Models.PushSubscription>
@{
ViewData["Title"] = "Push Subscriptions";
}
<div class="row mb-3">
<div class="col">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="@Url.Action("Index", "Dashboard")">Dashboard</a></li>
<li class="breadcrumb-item active">Push Subscriptions</li>
</ol>
</nav>
</div>
</div>
<div class="row mb-3">
<div class="col-md-8">
<h1><i class="fas fa-bell"></i> Push Subscriptions</h1>
<p class="text-muted mb-0">Manage browser push notification subscriptions for admins and customers</p>
</div>
<div class="col-md-4 text-end">
<form method="post" action="@Url.Action("CleanupExpired")" class="d-inline">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-warning"
onclick="return confirm('Remove all inactive and expired subscriptions?')">
<i class="fas fa-broom"></i> Cleanup Expired
</button>
</form>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card border-primary">
<div class="card-body">
<h6 class="text-muted mb-2">Total Subscriptions</h6>
<h3 class="mb-0">@Model.Count()</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-success">
<div class="card-body">
<h6 class="text-muted mb-2">Active</h6>
<h3 class="mb-0 text-success">@Model.Count(s => s.IsActive)</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-info">
<div class="card-body">
<h6 class="text-muted mb-2">Admin Users</h6>
<h3 class="mb-0">@Model.Count(s => s.UserId.HasValue)</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-warning">
<div class="card-body">
<h6 class="text-muted mb-2">Customers</h6>
<h3 class="mb-0">@Model.Count(s => s.CustomerId.HasValue)</h3>
</div>
</div>
</div>
</div>
<!-- Subscriptions Table -->
@if (Model.Any())
{
<div class="card">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-list"></i> Subscription List</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th style="width: 5%;">ID</th>
<th style="width: 15%;">Type</th>
<th style="width: 20%;">Endpoint</th>
<th style="width: 15%;">Subscribed</th>
<th style="width: 15%;">Last Used</th>
<th style="width: 15%;">Browser/Device</th>
<th style="width: 8%;">Status</th>
<th class="text-center" style="width: 7%;">Actions</th>
</tr>
</thead>
<tbody>
@foreach (var subscription in Model)
{
var daysInactive = subscription.LastUsedAt.HasValue
? (DateTime.UtcNow - subscription.LastUsedAt.Value).Days
: (DateTime.UtcNow - subscription.SubscribedAt).Days;
var statusClass = subscription.IsActive
? (daysInactive > 90 ? "warning" : "success")
: "danger";
<tr>
<td><code>@subscription.Id</code></td>
<td>
@if (subscription.UserId.HasValue)
{
<span class="badge bg-info">
<i class="fas fa-user-shield"></i> Admin User
</span>
@if (subscription.User != null)
{
<br><small class="text-muted">@subscription.User.Username</small>
}
}
else if (subscription.CustomerId.HasValue)
{
<span class="badge bg-warning">
<i class="fas fa-user"></i> Customer
</span>
@if (subscription.Customer != null)
{
<br><small class="text-muted">@subscription.Customer.TelegramDisplayName</small>
}
}
else
{
<span class="badge bg-secondary">
<i class="fas fa-question"></i> Unknown
</span>
}
</td>
<td>
<small class="font-monospace text-break"
data-bs-toggle="tooltip"
title="@subscription.Endpoint">
@(subscription.Endpoint.Length > 40 ? subscription.Endpoint.Substring(0, 40) + "..." : subscription.Endpoint)
</small>
@if (!string.IsNullOrEmpty(subscription.IpAddress))
{
<br><span class="badge bg-secondary"><i class="fas fa-network-wired"></i> @subscription.IpAddress</span>
}
</td>
<td>
@subscription.SubscribedAt.ToString("MMM dd, yyyy")
<br><small class="text-muted">@subscription.SubscribedAt.ToString("HH:mm")</small>
</td>
<td>
@if (subscription.LastUsedAt.HasValue)
{
@subscription.LastUsedAt.Value.ToString("MMM dd, yyyy")
<br><small class="text-muted">@subscription.LastUsedAt.Value.ToString("HH:mm")</small>
@if (daysInactive > 0)
{
<br><span class="badge bg-@(daysInactive > 90 ? "danger" : daysInactive > 30 ? "warning" : "info")">
@daysInactive days ago
</span>
}
}
else
{
<span class="text-muted">Never</span>
}
</td>
<td>
@if (!string.IsNullOrEmpty(subscription.UserAgent))
{
var ua = subscription.UserAgent;
var browser = ua.Contains("Chrome") ? "Chrome" :
ua.Contains("Firefox") ? "Firefox" :
ua.Contains("Safari") ? "Safari" :
ua.Contains("Edge") ? "Edge" : "Unknown";
var os = ua.Contains("Windows") ? "Windows" :
ua.Contains("Mac") ? "macOS" :
ua.Contains("Linux") ? "Linux" :
ua.Contains("Android") ? "Android" :
ua.Contains("iOS") ? "iOS" : "Unknown";
<span class="badge bg-secondary">@browser</span>
<span class="badge bg-dark">@os</span>
<br><small class="text-muted"
data-bs-toggle="tooltip"
title="@subscription.UserAgent">
@(ua.Length > 30 ? ua.Substring(0, 30) + "..." : ua)
</small>
}
else
{
<span class="text-muted">Not available</span>
}
</td>
<td class="text-center">
<span class="badge bg-@statusClass">
@if (subscription.IsActive)
{
<i class="fas fa-check-circle"></i> <text>Active</text>
}
else
{
<i class="fas fa-times-circle"></i> <text>Inactive</text>
}
</span>
</td>
<td class="text-center">
<form method="post" action="@Url.Action("Delete", new { id = subscription.Id })" class="d-inline">
@Html.AntiForgeryToken()
<button type="submit"
class="btn btn-sm btn-danger"
onclick="return confirm('Are you sure you want to delete this push subscription?')"
title="Delete Subscription">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
else
{
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>No push subscriptions yet</strong>
<p class="mb-0">Push notification subscriptions will appear here when users enable browser notifications.</p>
</div>
}
<!-- Information Card -->
<div class="row mt-4">
<div class="col">
<div class="card border-info">
<div class="card-header bg-info text-white">
<h6 class="mb-0"><i class="fas fa-info-circle"></i> About Push Subscriptions</h6>
</div>
<div class="card-body">
<ul class="mb-0">
<li><strong>Active Status:</strong> Subscriptions marked as active can receive push notifications</li>
<li><strong>IP Address Storage:</strong> IP addresses are stored for security and duplicate detection purposes</li>
<li><strong>Cleanup:</strong> Expired subscriptions (inactive for >90 days) can be removed using the cleanup button</li>
<li><strong>User Agent:</strong> Browser and device information helps identify subscription sources</li>
<li><strong>Privacy:</strong> Subscription data contains encryption keys required for Web Push API</li>
</ul>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
// Initialize Bootstrap tooltips
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
</script>
}