move into sonardesk

This commit is contained in:
Mark Gregory
2026-10-12 14:50:13 +01:00
parent 5f0d2ceb72
commit 38b4a7406f
879 changed files with 24021 additions and 1 deletions
@@ -0,0 +1,3 @@
@extends('licensing::admin.layout')
@section('title', 'Licensing Overview')
@section('content')<h1>SonarDesk licensing</h1><div class="cards"><div class="card"><div class="muted">Total licences</div><div class="metric">{{ number_format($licenseCount) }}</div></div><div class="card"><div class="muted">Usable licences</div><div class="metric">{{ number_format($activeCount) }}</div></div><div class="card"><div class="muted">Products</div><div class="metric">{{ number_format($productCount) }}</div></div></div><h2>Recent licences</h2><div class="table-wrap"><table><thead><tr><th>Customer</th><th>Product</th><th>Edition</th><th>Status</th><th>Created</th></tr></thead><tbody>@forelse($recentLicenses as $license)<tr><td><a href="{{ route('administration.sonardesk.licenses.show', $license) }}">{{ $license->customer_email ?: 'Unknown customer' }}</a></td><td>{{ $license->product?->name ?? '—' }}</td><td>{{ $license->edition }}</td><td>{{ $license->is_usable ? 'Usable' : 'Inactive' }}</td><td>{{ $license->created_at?->format('d M Y') }}</td></tr>@empty<tr><td colspan="5">No licences found.</td></tr>@endforelse</tbody></table></div><p><a href="{{ route('administration.sonardesk.licenses') }}">View all licences →</a></p>@endsection
@@ -0,0 +1,37 @@
{{-- Shared ABSDev administration shell; no separate SonarDesk layout or navigation. --}}
<x-layouts.app :title="trim($__env->yieldContent('title', 'SonarDesk')) . ' · SonarDesk'">
<div class="space-y-6">
<div class="flex flex-wrap items-center justify-between gap-4 border-b border-zinc-200 pb-5 dark:border-zinc-700">
<div>
<p class="text-xs font-semibold uppercase tracking-widest text-amber-600 dark:text-amber-400">ABSDev / Products</p>
<h1 class="mt-1 text-3xl font-bold tracking-tight text-zinc-900 dark:text-white">SonarDesk</h1>
<p class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">Licensing administration · Read only</p>
</div>
<div class="flex flex-wrap gap-2 text-sm">
<a class="rounded-lg border border-zinc-300 px-4 py-2 hover:bg-zinc-100 dark:border-zinc-600 dark:hover:bg-zinc-800" href="{{ route('administration.sonardesk.index') }}">Overview</a>
<a class="rounded-lg border border-zinc-300 px-4 py-2 hover:bg-zinc-100 dark:border-zinc-600 dark:hover:bg-zinc-800" href="{{ route('administration.sonardesk.licenses') }}">Licences</a>
</div>
</div>
<div class="sonardesk-admin space-y-6">@yield('content')</div>
</div>
<style>
.sonardesk-admin h1{font-size:1.65rem;font-weight:700;margin-bottom:1.25rem}
.sonardesk-admin h2{font-size:1.125rem;font-weight:650;margin:1.5rem 0 .85rem}
.sonardesk-admin a{color:#b7791f;text-decoration:underline;text-underline-offset:3px}
.sonardesk-admin .cards,.sonardesk-admin .detail{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:1rem;margin-bottom:1.5rem}
.sonardesk-admin .card{border:1px solid #d4d4d8;border-radius:.85rem;padding:1.4rem;background:rgba(127,127,127,.035)}
.dark .sonardesk-admin .card{border-color:#52525b}
.sonardesk-admin .metric{font-size:2rem;font-weight:750;color:#b7791f}
.sonardesk-admin .muted,.sonardesk-admin dt{color:#71717a}
.sonardesk-admin dd{margin:.25rem 0 .8rem;overflow-wrap:anywhere}
.sonardesk-admin .table-wrap{width:100%;overflow-x:auto;border:1px solid #d4d4d8;border-radius:.85rem}
.dark .sonardesk-admin .table-wrap{border-color:#52525b}
.sonardesk-admin table{width:100%;border-collapse:collapse;text-align:left;font-size:.875rem}
.sonardesk-admin th,.sonardesk-admin td{padding:.85rem 1rem;border-bottom:1px solid #d4d4d8;vertical-align:top}
.dark .sonardesk-admin th,.dark .sonardesk-admin td{border-color:#52525b}
.sonardesk-admin th{font-size:.75rem;letter-spacing:.04em;text-transform:uppercase}
.sonardesk-admin .pill{border-radius:999px;padding:.25rem .65rem;background:#e4e4e7}
.dark .sonardesk-admin .pill{background:#3f3f46}
.sonardesk-admin .pager{margin-top:1.25rem}
</style>
</x-layouts.app>
@@ -0,0 +1,3 @@
@extends('licensing::admin.layout')
@section('title', 'Licences')
@section('content')<h1>Licences</h1><div class="table-wrap"><table><thead><tr><th>Customer</th><th>Product</th><th>Edition</th><th>Provider</th><th>Status</th><th>Created</th></tr></thead><tbody>@forelse($licenses as $license)<tr><td><a href="{{ route('administration.sonardesk.licenses.show', $license) }}">{{ $license->customer_email ?: 'Unknown customer' }}</a></td><td>{{ $license->product?->name ?? '—' }}</td><td>{{ $license->edition }}</td><td>{{ $license->payment_provider ?? '—' }}</td><td>{{ $license->is_usable ? 'Usable' : 'Inactive' }}</td><td>{{ $license->created_at?->format('d M Y') }}</td></tr>@empty<tr><td colspan="6">No licences found.</td></tr>@endforelse</tbody></table></div><div class="pager">{{ $licenses->links() }}</div>@endsection
@@ -0,0 +1,3 @@
@extends('licensing::admin.layout')
@section('title', 'Licence details')
@section('content')<h1>Licence details</h1><div class="detail"><section class="card"><h2>Customer and licence</h2><dl><dt>Customer</dt><dd>{{ $license->customer_name ?? '—' }}</dd><dt>Email</dt><dd>{{ $license->customer_email ?? '—' }}</dd><dt>Product</dt><dd>{{ $license->product?->name ?? '—' }}</dd><dt>Edition</dt><dd>{{ $license->edition }}</dd><dt>Key (last four only)</dt><dd>•••• {{ $license->license_key_last4 }}</dd><dt>Status</dt><dd>{{ $license->is_usable ? 'Usable' : 'Inactive' }}</dd></dl></section><section class="card"><h2>Subscription</h2><dl><dt>Provider</dt><dd>{{ $license->payment_provider ?? '—' }}</dd><dt>Subscription status</dt><dd>{{ $license->subscription_status ?? '—' }}</dd><dt>Period ends</dt><dd>{{ $license->current_period_ends_at?->format('d M Y') ?? '—' }}</dd><dt>Expires</dt><dd>{{ $license->expires_at?->format('d M Y') ?? '—' }}</dd><dt>Activation limit</dt><dd>{{ $license->activation_limit }}</dd></dl></section></div><h2>Device activations</h2><div class="table-wrap"><table><thead><tr><th>Platform</th><th>Version</th><th>Activated</th><th>Last seen</th><th>Status</th></tr></thead><tbody>@forelse($license->activations as $activation)<tr><td>{{ $activation->platform ?? '—' }}</td><td>{{ $activation->app_version ?? '—' }}</td><td>{{ $activation->activated_at?->format('d M Y H:i') ?? '—' }}</td><td>{{ $activation->last_seen_at?->format('d M Y H:i') ?? '—' }}</td><td>{{ $activation->deactivated_at ? 'Deactivated' : 'Active' }}</td></tr>@empty<tr><td colspan="5">No device activations.</td></tr>@endforelse</tbody></table></div>@endsection
@@ -0,0 +1,25 @@
@extends('licensing::admin.layout')
@section('title', 'SonarDesk Subscriptions')
@section('content')
<h1>Subscriptions</h1>
<p class="muted">Read-only subscription records stored in ABSDev. This is not a live Paddle reconciliation.</p>
<div class="cards">
<div class="card"><div class="muted">Subscription-linked licences</div><div class="metric">{{ number_format($total) }}</div></div>
<div class="card"><div class="muted">Active / trialing</div><div class="metric">{{ number_format($active) }}</div></div>
<div class="card"><div class="muted">Cancellation scheduled</div><div class="metric">{{ number_format($cancelScheduled) }}</div></div>
</div>
<div class="table-wrap"><table><thead><tr><th>Customer</th><th>Product</th><th>Provider</th><th>Status</th><th>Period ends</th><th>Cancellation</th><th>Licence</th></tr></thead><tbody>
@forelse($subscriptions as $subscription)
<tr>
<td>{{ $subscription->customer_email ?: '—' }}</td>
<td>{{ $subscription->product?->name ?? '—' }}</td>
<td>{{ $subscription->payment_provider ?: '—' }}</td>
<td><span class="pill">{{ $subscription->subscription_status ?: 'Unknown' }}</span></td>
<td>{{ $subscription->current_period_ends_at?->format('d M Y') ?? '—' }}</td>
<td>{{ $subscription->cancel_scheduled_at?->format('d M Y') ?? '—' }}</td>
<td><a href="{{ route('administration.sonardesk.licenses.show', $subscription) }}">View licence</a></td>
</tr>
@empty<tr><td colspan="7">No subscription-linked licences in the local database.</td></tr>@endforelse
</tbody></table></div>
<div class="pager">{{ $subscriptions->links() }}</div>
@endsection